diff mbox

[2/2] wpa_gui-qt4: tray icon based signal strength meter

Message ID 20150214174024.051bdbfa@sambook
State Changes Requested
Headers show

Commit Message

Arkadiusz (Arkq) Bokowy Feb. 14, 2015, 4:40 p.m. UTC
System tray icon can be set to 5 different pictographs according to the
connection status. One for disconnected state (not associated with the
network, or not connected with the wpa_supplicant service), and four for
connected status (showing the signal strength on the receiver). Actually,
signal strength meter is available for Linux platforms only. For other
systems, connected state is displayed as maximum strength level. Signal
strength value is obtained via the iwlib library and read action is pooled
every 3.5 seconds (this value can be adjusted to provide best look & feel
experience).

Four level based signal strength meter might be not the best idea, though.
Currently, due to inconsistent output among wireless drivers, there is
no simple way to determine relative signal strength (0 -> 100 %). In this
approach, returned values are converted to the dBm units (if needed), which
then are mapped into the corresponding pictographs (range-based).

Status icon names are based on various Gnome icon packs (e.g. Faba). When
icon can not be found, default one is shown (wpa_gui logo).

Signed-off-by: Arkadiusz Bokowy <arkadiusz.bokowy@gmail.com>
---
 wpa_supplicant/wpa_gui-qt4/wpa_gui.pro |   3 +-
 wpa_supplicant/wpa_gui-qt4/wpagui.cpp  | 168 ++++++++++++++++++++++++++++++++-
 wpa_supplicant/wpa_gui-qt4/wpagui.h    |  31 ++++++
 3 files changed, 197 insertions(+), 5 deletions(-)

Comments

Jouni Malinen Feb. 21, 2015, 11:43 a.m. UTC | #1
On Sat, Feb 14, 2015 at 05:40:24PM +0100, Arkadiusz (Arkq) Bokowy wrote:
> System tray icon can be set to 5 different pictographs according to the
> connection status. One for disconnected state (not associated with the
> network, or not connected with the wpa_supplicant service), and four for
> connected status (showing the signal strength on the receiver). Actually,
> signal strength meter is available for Linux platforms only. For other
> systems, connected state is displayed as maximum strength level. Signal
> strength value is obtained via the iwlib library and read action is pooled
> every 3.5 seconds (this value can be adjusted to provide best look & feel
> experience).

The changes themselves may be otherwise fine, but I cannot accept use of
iwlib either from licensing view point or from the view point of it
using the wireless extensions that has been deprecated for years.

This functionality should fetch signal strength information through
wpa_supplicant (not directly from the driver) to make this more
portable. For example, see the SIGNAL_POLL control interface command.
Arkadiusz (Arkq) Bokowy Feb. 21, 2015, 2:06 p.m. UTC | #2
> This functionality should fetch signal strength information through
> wpa_supplicant (not directly from the driver) to make this more
> portable. For example, see the SIGNAL_POLL control interface command.

Thanks for the replay, I will definitely use this SIGNAL_POLL command -
I wasn't aware of the licence incompatibility with iwlib and that the
interface has been deprecated.

Anyway, I've tracked a little bit this command in the wpa_suplicant
drivers code, however I was unable to determine if the returned value
for RSSI is unified - e.g. it is guaranteed, that the value is in range
from -100 to 0 (or any arbitrary range) or if it is a raw value from
the chipset. Because according to the Wiki (and part of the iwlib)
various chipsets might operate on different scales (e.g. DBM, RCPI).

Best regards,
Arek
Jouni Malinen Feb. 21, 2015, 2:53 p.m. UTC | #3
On Sat, Feb 21, 2015 at 03:06:49PM +0100, Arkadiusz (Arkq) Bokowy wrote:
> Thanks for the replay, I will definitely use this SIGNAL_POLL command -
> I wasn't aware of the licence incompatibility with iwlib and that the
> interface has been deprecated.
> 
> Anyway, I've tracked a little bit this command in the wpa_suplicant
> drivers code, however I was unable to determine if the returned value
> for RSSI is unified - e.g. it is guaranteed, that the value is in range
> from -100 to 0 (or any arbitrary range) or if it is a raw value from
> the chipset. Because according to the Wiki (and part of the iwlib)
> various chipsets might operate on different scales (e.g. DBM, RCPI).

This depends a bit on the user driver interface. With nl80211,
SIGNAL_PLL uses NL80211_STA_INFO_SIGNAL which is defined to use dBm.
With WEXT, I would not really trust any value without separately
checking with each driver..
diff mbox

Patch

diff --git a/wpa_supplicant/wpa_gui-qt4/wpa_gui.pro b/wpa_supplicant/wpa_gui-qt4/wpa_gui.pro
index 69bc0f6..4a8695e 100644
--- a/wpa_supplicant/wpa_gui-qt4/wpa_gui.pro
+++ b/wpa_supplicant/wpa_gui-qt4/wpa_gui.pro
@@ -24,7 +24,8 @@  win32 {
   SOURCES += ../../src/utils/os_win32.c
   RESOURCES += icons_png.qrc
 } else {
-  DEFINES += CONFIG_CTRL_IFACE_UNIX
+  LIBS += -liw
+  DEFINES += CONFIG_CTRL_IFACE_UNIX CONFIG_SIGNAL_METER
   SOURCES += ../../src/utils/os_unix.c
 }
 
diff --git a/wpa_supplicant/wpa_gui-qt4/wpagui.cpp b/wpa_supplicant/wpa_gui-qt4/wpagui.cpp
index bc6fa7f..17164ad 100644
--- a/wpa_supplicant/wpa_gui-qt4/wpagui.cpp
+++ b/wpa_supplicant/wpa_gui-qt4/wpagui.cpp
@@ -135,6 +135,10 @@  WpaGui::WpaGui(QApplication *_app, QWidget *parent, const char *, Qt::WFlags)
 	monitor_conn = NULL;
 	msgNotifier = NULL;
 	ctrl_iface_dir = strdup("/var/run/wpa_supplicant");
+#ifdef CONFIG_SIGNAL_METER
+	signal_skfd = -1;
+	signal_avg_level = -55;
+#endif
 
 	parse_argv();
 
@@ -161,6 +165,12 @@  WpaGui::WpaGui(QApplication *_app, QWidget *parent, const char *, Qt::WFlags)
 	timer->setSingleShot(FALSE);
 	timer->start(1000);
 
+#ifdef CONFIG_SIGNAL_METER
+	signalMeterTimer = new QTimer(this);
+	signalMeterTimer->setInterval(3500);
+	connect(signalMeterTimer, SIGNAL(timeout()), SLOT(signalMeterUpdate()));
+#endif
+
 	if (openCtrlConnection(ctrl_iface) < 0) {
 		debug("Failed to open control connection to "
 		      "wpa_supplicant.");
@@ -216,6 +226,11 @@  WpaGui::~WpaGui()
 		udr = NULL;
 	}
 
+#ifdef CONFIG_SIGNAL_METER
+	if (signal_skfd)
+		iw_sockets_close(signal_skfd);
+#endif
+
 	free(ctrl_iface);
 	ctrl_iface = NULL;
 
@@ -345,6 +360,10 @@  int WpaGui::openCtrlConnection(const char *ifname)
 		return -1;
 	}
 
+#ifdef CONFIG_SIGNAL_METER
+	signalMeterInitialization(ctrl_iface);
+#endif
+
 #ifdef CONFIG_CTRL_IFACE_UNIX
 	flen = strlen(ctrl_iface_dir) + strlen(ctrl_iface) + 2;
 	cfile = (char *) malloc(flen);
@@ -496,6 +515,10 @@  void WpaGui::updateStatus()
 		textBssid->clear();
 		textIpAddress->clear();
 		updateTrayToolTip(tr("no status information"));
+		updateTrayIcon(TrayIconOffline);
+#ifdef CONFIG_SIGNAL_METER
+		signalMeterTimer->stop();
+#endif
 
 #ifdef CONFIG_NATIVE_WINDOWS
 		static bool first = true;
@@ -544,6 +567,9 @@  void WpaGui::updateStatus()
 				ssid_updated = true;
 				textSsid->setText(pos);
 				updateTrayToolTip(pos + tr(" (associated)"));
+#ifndef CONFIG_SIGNAL_METER
+				updateTrayIcon(TrayIconSignalExcellent);
+#endif
 			} else if (strcmp(start, "ip_address") == 0) {
 				ipaddr_updated = true;
 				textIpAddress->setText(pos);
@@ -587,12 +613,27 @@  void WpaGui::updateStatus()
 	} else
 		textEncryption->clear();
 
+#ifdef CONFIG_SIGNAL_METER
+	/* Handle signal meter service. When network is not associated,
+	 * deactivate timer, otherwise keep it going. Tray icon has to be
+	 * initialized here, because of our timer interval (initial delay). */
+	if (ssid_updated) {
+		if (!signalMeterTimer->isActive()) {
+			updateTrayIcon(TrayIconConnected);
+			signalMeterTimer->start();
+		}
+	}
+	else
+		signalMeterTimer->stop();
+#endif
+
 	if (!status_updated)
 		textStatus->clear();
 	if (!auth_updated)
 		textAuthentication->clear();
 	if (!ssid_updated) {
 		textSsid->clear();
+		updateTrayIcon(TrayIconOffline);
 		updateTrayToolTip(tr("(not-associated)"));
 	}
 	if (!bssid_updated)
@@ -828,6 +869,75 @@  void WpaGui::ping()
 }
 
 
+#ifdef CONFIG_SIGNAL_METER
+void WpaGui::signalMeterInitialization(const char *ifname)
+{
+	if (signal_skfd == -1)
+		signal_skfd = iw_sockets_open();
+	if (signal_skfd == -1)
+		return;
+	iw_get_range_info(signal_skfd, ifname, &signal_range);
+}
+#endif /* CONFIG_SIGNAL_METER */
+
+
+#ifdef CONFIG_SIGNAL_METER
+void WpaGui::signalMeterUpdate()
+{
+	TrayIconType iconType = TrayIconSignalNone;
+	int dblevel = -110;
+	iwstats stats;
+
+	if (iw_get_stats(signal_skfd, ctrl_iface, &stats, &signal_range, true) == 0) {
+		if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
+			/* state was read and level is valid */
+
+			/* try to convert signal level into the range from -100 to 0 */
+
+			if(stats.qual.updated & IW_QUAL_RCPI) {
+				/* Deal with signal level in RCPI */
+				/* RCPI = int{(Power in dBm + 110) * 2} for 0dbm > Power > -110dBm */
+				dblevel = ((stats.qual.level / 2.0) - 110.0);
+			}
+			else if((stats.qual.updated & IW_QUAL_DBM) ||
+					(stats.qual.level > signal_range.max_qual.level)) {
+				/* Deal with signal level in dBm (absolute power measurement) */
+				dblevel = stats.qual.level;
+				/* Implement a range for dBm [-192; 63] */
+				if(stats.qual.level >= 64)
+					dblevel -= 0x100;
+			}
+			else {
+				/* Deal with signal level as relative value (0 -> max) */
+				dblevel = stats.qual.level * -110.0 / signal_range.max_qual.level;
+			}
+		}
+
+		/* moving average over two spatial points - this will add a little
+		 * bit of latency to our gauge, which will eliminate fluctuations */
+		signal_avg_level = (signal_avg_level + dblevel) / 2;
+
+		debug("Signal level [-110 ; 0]: %d (avg: %d)",
+		      dblevel, signal_avg_level);
+
+		/* calibration based on Intel Centrino Advanced-N 6235 */
+		if (signal_avg_level >= -45)
+			iconType = TrayIconSignalExcellent;
+		else if (signal_avg_level >= -57)
+			iconType = TrayIconSignalGood;
+		else if (signal_avg_level >= -69)
+			iconType = TrayIconSignalOk;
+		else if (signal_avg_level >= -81)
+			iconType = TrayIconSignalWeak;
+		else
+			iconType = TrayIconSignalNone;
+	}
+
+	updateTrayIcon(iconType);
+}
+#endif /* CONFIG_SIGNAL_METER */
+
+
 static int str_match(const char *a, const char *b)
 {
 	return strncmp(a, b, strlen(b)) == 0;
@@ -1278,10 +1388,7 @@  void WpaGui::createTrayIcon(bool trayOnly)
 	QApplication::setQuitOnLastWindowClosed(false);
 
 	tray_icon = new QSystemTrayIcon(this);
-	if (QImageReader::supportedImageFormats().contains(QByteArray("svg")))
-		tray_icon->setIcon(QIcon(":/icons/wpa_gui.svg"));
-	else
-		tray_icon->setIcon(QIcon(":/icons/wpa_gui.png"));
+	updateTrayIcon(TrayIconOffline);
 
 	connect(tray_icon,
 		SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
@@ -1421,6 +1528,59 @@  void WpaGui::updateTrayToolTip(const QString &msg)
 }
 
 
+void WpaGui::updateTrayIcon(TrayIconType type)
+{
+	if (!tray_icon || currentIconType == type)
+		return;
+
+	QIcon icon;
+	QIcon fallback_icon;
+
+	if (QImageReader::supportedImageFormats().contains(QByteArray("svg")))
+		fallback_icon = QIcon(":/icons/wpa_gui.svg");
+	else
+		fallback_icon = QIcon(":/icons/wpa_gui.png");
+
+	switch(type) {
+	case TrayIconOffline:
+		icon = QIcon::fromTheme("network-wireless-offline",
+				fallback_icon);
+		break;
+	case TrayIconAcquiring:
+		icon = QIcon::fromTheme("network-wireless-acquiring",
+				fallback_icon);
+		break;
+	case TrayIconConnected:
+		icon = QIcon::fromTheme("network-wireless-connected",
+				fallback_icon);
+		break;
+	case TrayIconSignalNone:
+		icon = QIcon::fromTheme("network-wireless-signal-none",
+				fallback_icon);
+		break;
+	case TrayIconSignalWeak:
+		icon = QIcon::fromTheme("network-wireless-signal-weak",
+				fallback_icon);
+		break;
+	case TrayIconSignalOk:
+		icon = QIcon::fromTheme("network-wireless-signal-ok",
+				fallback_icon);
+		break;
+	case TrayIconSignalGood:
+		icon = QIcon::fromTheme("network-wireless-signal-good",
+				fallback_icon);
+		break;
+	case TrayIconSignalExcellent:
+		icon = QIcon::fromTheme("network-wireless-signal-excellent",
+				fallback_icon);
+		break;
+	}
+
+	currentIconType = type;
+	tray_icon->setIcon(icon);
+}
+
+
 void WpaGui::closeEvent(QCloseEvent *event)
 {
 	if (eh) {
diff --git a/wpa_supplicant/wpa_gui-qt4/wpagui.h b/wpa_supplicant/wpa_gui-qt4/wpagui.h
index 026eacb..5661967 100644
--- a/wpa_supplicant/wpa_gui-qt4/wpagui.h
+++ b/wpa_supplicant/wpa_gui-qt4/wpagui.h
@@ -14,6 +14,23 @@ 
 #include "ui_wpagui.h"
 #include "addinterface.h"
 
+#ifdef CONFIG_SIGNAL_METER
+#include <iwlib.h>
+#endif
+
+
+enum TrayIconType {
+	TrayIconOffline = 0,
+	TrayIconAcquiring,
+	TrayIconConnected,
+	TrayIconSignalNone,
+	TrayIconSignalWeak,
+	TrayIconSignalOk,
+	TrayIconSignalGood,
+	TrayIconSignalExcellent,
+};
+
+
 class UserDataRequest;
 
 
@@ -49,6 +66,9 @@  public slots:
 	virtual void scan();
 	virtual void eventHistory();
 	virtual void ping();
+#ifdef CONFIG_SIGNAL_METER
+	virtual void signalMeterUpdate();
+#endif
 	virtual void processMsg(char *msg);
 	virtual void processCtrlReq(const char *req);
 	virtual void receiveMsgs();
@@ -70,6 +90,7 @@  public slots:
 	virtual void showTrayMessage(QSystemTrayIcon::MessageIcon type,
 				     int sec, const QString &msg);
 	virtual void showTrayStatus();
+	virtual void updateTrayIcon(TrayIconType type);
 	virtual void updateTrayToolTip(const QString &msg);
 	virtual void wpsDialog();
 	virtual void peersDialog();
@@ -113,6 +134,7 @@  private:
 	QAction *quitAction;
 	QMenu *tray_menu;
 	QSystemTrayIcon *tray_icon;
+	TrayIconType currentIconType;
 	QString wpaStateTranslate(char *state);
 	void createTrayIcon(bool);
 	bool ackTrayIcon;
@@ -127,6 +149,15 @@  private:
 
 	void stopWpsRun(bool success);
 
+#ifdef CONFIG_SIGNAL_METER
+	QTimer *signalMeterTimer;
+	iwrange signal_range;
+	int signal_skfd;
+	int signal_avg_level;
+
+	void signalMeterInitialization(const char *ifname);
+#endif /* CONFIG_SIGNAL_METER */
+
 #ifdef CONFIG_NATIVE_WINDOWS
 	QAction *fileStartServiceAction;
 	QAction *fileStopServiceAction;