/* * wavemon - a wireless network monitoring aplication * * Copyright (c) 2001-2002 Jan Morgenstern * * wavemon 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; either version 2, or (at your option) any later * version. * * wavemon 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 wavemon; see the file COPYING. If not, write to the Free Software * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "iw_if.h" #include "iw_nl80211.h" /* Number of lines in the key window at the bottom */ #define KEY_WIN_HEIGHT 3 /* Total number of lines in the histogram window */ #define HIST_WIN_HEIGHT (WAV_HEIGHT - KEY_WIN_HEIGHT) /* * Analogous to MAXYLEN, the following sets both the * - highest y/line index and the * - total count of lines inside the histogram window. */ #define HIST_MAXYLEN (HIST_WIN_HEIGHT - 1) /* Position (relative to right border) and maximum length of dBm level tags. */ #define LEVEL_TAG_POS 5 /* GLOBALS */ static WINDOW *w_lhist, *w_key; /* * Keeping track of global minima/maxima */ static struct iw_extrema { bool initialised; float min; float max; } e_signal, e_noise, e_snr; static void init_extrema(struct iw_extrema *ie) { memset(ie, 0, sizeof(*ie)); } static void track_extrema(const float new_sample, struct iw_extrema *ie) { if (! ie->initialised) { ie->initialised = true; ie->min = ie->max = new_sample; } else if (new_sample < ie->min) { ie->min = new_sample; } else if (new_sample > ie->max) { ie->max = new_sample; } } static char *fmt_extrema(const struct iw_extrema *ie, const char *unit) { static char range[256]; if (! ie->initialised) snprintf(range, sizeof(range), "unknown"); else if (ie->min == ie->max) snprintf(range, sizeof(range), "%+.0f %s", ie->min, unit); else snprintf(range, sizeof(range), "%+.0f..%+.0f %s", ie->min, ie->max, unit); return range; } /* * Simple array-based circular FIFO buffer * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Insertion works from lower to higher indices. * Access works from higher down to lower indices. * * Cases & assumptions: * ~~~~~~~~~~~~~~~~~~~~ * - principle: unsigned counter + hash function to handle array wrap-around; * - buffer is empty if count == 0; * - else count indicates the next place to insert(modulo %IW_STACKSIZE). */ #define IW_STACKSIZE 1024 static struct iw_levelstat iw_stats_cache[IW_STACKSIZE]; static uint32_t count; #define COUNTMAX (typeof(count))-1 static void iw_cache_insert(const struct iw_levelstat new) { iw_stats_cache[count % IW_STACKSIZE] = new; /* * Handle counter overflow by mapping into a smaller index which is * identical (modulo %IW_STACKSIZE) to the old value. (The datatype * of 'count' must be able to express at least 2 * IW_STACKSIZE.) */ if (++count == COUNTMAX) count = IW_STACKSIZE + (COUNTMAX % IW_STACKSIZE); } static struct iw_levelstat iw_cache_get(const uint32_t index) { struct iw_levelstat zero = IW_LSTAT_INIT; if (index > IW_STACKSIZE || index > count) return zero; return iw_stats_cache[(count - index) % IW_STACKSIZE]; } void iw_cache_update(struct iw_nl80211_linkstat *ls) { static struct iw_levelstat prev, avg = IW_LSTAT_INIT; static int slot; int sig_level = ls->signal; /* * Prefer signal level over average signal level. * One card in particular (Intel 9260NGW) reported inconsistent * station and beacon average signals. * See https://github.com/uoaerg/wavemon/issues/47 */ if (sig_level == 0) sig_level = ls->signal_avg; /* * If hardware does not support dBm signal level, it will not * be filled in, and show up as 0. Try to fall back to the BSS * probe where again a 0 dBm value reflects 'not initialized'. */ if (sig_level == 0) sig_level = ls->bss_signal; if (sig_level == 0) { avg.flags |= IW_QUAL_LEVEL_INVALID; } else { avg.flags &= ~IW_QUAL_LEVEL_INVALID; avg.signal += (float)sig_level / conf.slotsize; track_extrema(sig_level, &e_signal); } if (iw_nl80211_have_survey_data(ls)) { avg.flags &= ~IW_QUAL_NOISE_INVALID; avg.noise += (float)ls->survey.noise / conf.slotsize; track_extrema(ls->survey.noise, &e_noise); if (! (avg.flags & IW_QUAL_LEVEL_INVALID)) track_extrema(sig_level - ls->survey.noise, &e_snr); } if (++slot >= conf.slotsize) { iw_cache_insert(avg); if (conf.lthreshold_action && prev.signal < conf.lthreshold && avg.signal >= conf.lthreshold) threshold_action(conf.lthreshold); else if (conf.hthreshold_action && prev.signal > conf.hthreshold && avg.signal <= conf.hthreshold) threshold_action(conf.hthreshold); prev = avg; avg.signal = avg.noise = slot = 0; avg.flags = IW_QUAL_LEVEL_INVALID | IW_QUAL_NOISE_INVALID; } } /* * Histogram-specific display functions */ static double hist_level(double val, int min, int max) { return map_range(val, min, max, 1, HIST_MAXYLEN); } static double hist_level_inverse(int y_level, int min, int max) { return map_range(y_level, 1, HIST_MAXYLEN, min, max); } /* Order needs to be reversed as y-coordinates grow downwards */ static int hist_y(int yval) { return reverse_range(yval, 1, HIST_MAXYLEN); } /* Values come in from the right, so 'x' also needs to be reversed */ static int hist_x(int xval) { return reverse_range(xval, 1, MAXXLEN); } /* plot single values, without clamping to min/max */ static void hist_plot(double yval, int xval, enum colour_pair plot_colour) { double level, fraction; chtype ch; fraction = modf(yval, &level); if (in_range(level, 1, HIST_MAXYLEN)) { /* * The 5 different scanline chars provide a pretty good accuracy. * ncurses will fall back to standard ASCII chars anyway if they * are not available. */ if (fraction < 0.2) ch = ACS_S9; else if (fraction < 0.4) ch = ACS_S7; else if (fraction < 0.6) ch = ACS_HLINE; else if (fraction < 0.8) ch = ACS_S3; else ch = ACS_S1; wattrset(w_lhist, COLOR_PAIR(plot_colour) | A_BOLD); mvwaddch(w_lhist, hist_y(level), hist_x(xval), ch); } } static void display_lhist(void) { struct iw_levelstat iwl; double snr_level, noise_level, sig_level; enum colour_pair plot_colour; int x, y; for (x = 1; x <= MAXXLEN; x++) { iwl = iw_cache_get(x); /* Clear screen and set up horizontal grid lines */ wattrset(w_lhist, COLOR_PAIR(CP_STATBKG)); for (y = 1; y <= HIST_MAXYLEN; y++) mvwaddch(w_lhist, hist_y(y), hist_x(x), (y % 5) ? ' ' : '-'); /* * SNR comes first, as it determines the background. If either * noise or signal is invalid, set level below minimum value to * indicate that no background is present. */ if (iwl.flags & (IW_QUAL_NOISE_INVALID | IW_QUAL_LEVEL_INVALID)) { snr_level = 0; } else { snr_level = hist_level(iwl.signal - iwl.noise, conf.sig_min - conf.noise_max, conf.sig_max - conf.noise_min); wattrset(w_lhist, COLOR_PAIR(CP_STATSNR)); for (y = 1; y <= clamp(snr_level, 1, HIST_MAXYLEN); y++) mvwaddch(w_lhist, hist_y(y), hist_x(x), ' '); } if (! (iwl.flags & IW_QUAL_NOISE_INVALID)) { noise_level = hist_level(iwl.noise, conf.noise_min, conf.noise_max); plot_colour = noise_level > snr_level ? CP_STATNOISE : CP_STATNOISE_S; hist_plot(noise_level, x, plot_colour); } else if (x == LEVEL_TAG_POS && ! (iwl.flags & IW_QUAL_LEVEL_INVALID)) { char tmp[LEVEL_TAG_POS + 1]; int len; /* * Tag the horizontal grid lines with dBm levels. * This is only supported for signal levels, when the screen is not * shared by several graphs (each having a different scale). */ wattrset(w_lhist, COLOR_PAIR(CP_STATSIG)); for (y = 1; y <= HIST_MAXYLEN; y++) { if (y != 1 && (y % 5) && y != HIST_MAXYLEN) continue; len = snprintf(tmp, sizeof(tmp), "%.0f", hist_level_inverse(y, conf.sig_min, conf.sig_max)); mvwaddstr(w_lhist, hist_y(y), hist_x(len), tmp); } } if (! (iwl.flags & IW_QUAL_LEVEL_INVALID)) { sig_level = hist_level(iwl.signal, conf.sig_min, conf.sig_max); plot_colour = sig_level > snr_level ? CP_STATSIG : CP_STATSIG_S; hist_plot(sig_level, x, plot_colour); } } wrefresh(w_lhist); } static void display_key(WINDOW *w_key) { /* Clear the (one-line) screen) */ wmove(w_key, 1, 1); wclrtoborder(w_key); wattrset(w_key, COLOR_PAIR(CP_STANDARD)); waddch(w_key, '['); wattrset(w_key, COLOR_PAIR(CP_STATSIG)); waddch(w_key, ACS_HLINE); wattrset(w_key, COLOR_PAIR(CP_STANDARD)); wprintw(w_key, "] sig lvl (%s) [", fmt_extrema(&e_signal, "dBm")); wattrset(w_key, COLOR_PAIR(CP_STATNOISE)); waddch(w_key, ACS_HLINE); wattrset(w_key, COLOR_PAIR(CP_STANDARD)); wprintw(w_key, "] ns lvl (%s) [", fmt_extrema(&e_noise, "dBm")); wattrset(w_key, COLOR_PAIR(CP_STATSNR)); waddch(w_key, ' '); wattrset(w_key, COLOR_PAIR(CP_STANDARD)); wprintw(w_key, "] S-N ratio (%s)", fmt_extrema(&e_snr, "dB")); wrefresh(w_key); } void scr_lhist_init(void) { w_lhist = newwin_title(0, HIST_WIN_HEIGHT, "Level histogram", true); w_key = newwin_title(HIST_MAXYLEN + 1, KEY_WIN_HEIGHT, "Key", false); init_extrema(&e_signal); init_extrema(&e_noise); init_extrema(&e_snr); sampling_init(); display_key(w_key); } int scr_lhist_loop(WINDOW *w_menu) { static int vcount = 1; if (!--vcount) { vcount = conf.slotsize; display_lhist(); display_key(w_key); } return wgetch(w_menu); } void scr_lhist_fini(void) { sampling_stop(); delwin(w_lhist); delwin(w_key); }