diff --git a/Dockerfile b/Dockerfile
index fb67fb7..b125740 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,22 +6,26 @@ ARG repo_ref
USER root
RUN apt-get update && \
- DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
+ DEBIAN_FRONTEND=noninteractive \
+ apt-get install -y --no-install-recommends \
iperf3 \
gcc \
git \
pulseaudio-utils \
python3 \
python3-dev \
- python3-matplotlib \
python3-pip \
- python3-scipy \
python3-setuptools \
- python3-wheel \
python3-wxgtk4.0 \
- wireless-tools && \
- pip3 install \
- iperf3
+ wireless-tools \
+ && rm -rf /var/lib/apt/lists/*
+
+RUN pip3 install iperf3 matplotlib scipy wheel
+
+# Install libnl from DL6ER's fork because the python3.6+
+# compatibility fixes weren't included in the upstream
+# project in 12/2020
+RUN pip3 install --upgrade --user git+https://github.com/DL6ER/libnl
COPY . /app
diff --git a/README.rst b/README.rst
index f6944cf..2f2c4bc 100644
--- a/README.rst
+++ b/README.rst
@@ -20,7 +20,7 @@ Quick start
Check out the **Running In Docker** steps below to get single-line commands that run without the need to install *anything* on your computer (thanks to using `docker`).
Creating a heatmap using the software consists of the following three essential steps:
-1. Start an `iperf3` server on any machine in your local network. This server is used for bandwidth measurements to be independent of your Internet connection.
+1. Start an `iperf3` server on any machine in your local network. This server is used for bandwidth measurements to be independent of your Internet connection. When omitting the `--server` option, this may be skipped, however, be aware that the performance heatmaps tpyically are the icing on the cake of your measurement and are very useful in determining the *real* performance of your WiFi.
2. Use the `wifi-survey` tool to record a measurement. You can load a floorplan and click on your current location ot record signal strength and determine the achievable bandwidth.
3. Once done with all the measurements, use the `wifi-heatmap` tool to compute a high-resolution heatmap from your recorded data. In case your data turns out to be too coarse, you can always go back to step 2 and delete or move old and also add new measurements at any time.
@@ -32,7 +32,7 @@ Installation and Dependencies
* The Python `iperf3 `_ package, which needs `iperf3 `_ installed on your system.
* The Python `libiw `_ package.
* `wxPython Phoenix `_, which unfortunately must be installed using OS packages or built from source.
-* An iperf3 server running on another system on the LAN, as described below.
+* An iperf3 server running on another system on the LAN, as described below is recommended but optional.
Recommended installation is via ``python setup.py develop`` in a virtualenv setup with ``--system-site-packages`` (for the above dependencies).
@@ -43,15 +43,15 @@ Data Collection
At each survey location, data collection should take 45-60 seconds. The data collected is currently:
-* 10-second iperf3 measurement, TCP, client (this app) sending to server, default iperf3 options
-* 10-second iperf3 measurement, TCP, server sending to client, default iperf3 options
-* 10-second iperf3 measurement, UDP, client (this app) sending to server, default iperf3 options
+* 10-second iperf3 measurement, TCP, client (this app) sending to server, default iperf3 options [optional, enable with `--server`]
+* 10-second iperf3 measurement, TCP, server sending to client, default iperf3 options [optional, enable with `--server`]
+* 10-second iperf3 measurement, UDP, client (this app) sending to server, default iperf3 options [optional, enable with `--server`]
* Recording of various WiFi details such as advertised channel bandwidth, bitrate, or signal strength
-* Scan of all visible access points in the vicinity
+* Scan of all visible access points in the vicinity [optional, enable with `--scan`]
Hints:
- The duration of the bandwidth measurement can be changed using the `--duration` argument of `wifi-survey`. This has great influence on the actual length of the individual data collections.
-- Scanning for other network takes rather long. As this isn't required in most cases, you can skip this using `wifi-survey --no-scan`
+- Scanning for other network takes rather long. As this isn't required in most cases, it is not enabled by default
Usage
-----
@@ -68,20 +68,19 @@ Performing a Survey
The survey tool (``wifi-survey``) must be run as root or via ``sudo`` in order to use iwconfig/iwlist.
-First connect to the network that you want to survey. Then, run ``sudo wifi-survey INTERFACE SERVER PNG Title`` where:
+First connect to the network that you want to survey. Then, run ``sudo wifi-survey`` where:
-* ``INTERFACE`` is the name of your Wireless interface (e.g. ``wlp3s0``)
-* ``SERVER`` is the IP address or hostname of the iperf3 server
-* ``PNG`` is the path to a floorplan PNG file to use as the background for the map; see `examples/example_floorplan.png `_ for an example. In order to compare multiple surveys it may be helpful to pre-mark your measurement points on the floorplan, like `examples/example_with_marks.png `_ for an example. In order to compare multiple surveys it may be helpful to pre-mark your measurement points on the floorplan, like `examples/example_with_marks.png 0 else 0
+ for i in range(0,N,interval):
+ newcolors[i] = rgba
+ print(newcolors)
+ return ListedColormap(newcolors)
+ else:
+ return pp.get_cmap(cname)
+
def load_data(self):
a = defaultdict(list)
- for row in self._data:
+ for row in self._data['survey_points']:
a['x'].append(row['x'])
a['y'].append(row['y'])
a['channel'].append(row['result']['channel'])
- a['tcp_upload_Mbps'].append(
- row['result']['tcp']['received_Mbps']
- )
- a['tcp_download_Mbps'].append(
- row['result']['tcp-reverse']['received_Mbps']
- )
- a['udp_download_Mbps'].append(row['result']['udp']['Mbps'])
- a['udp_upload_Mbps'].append(row['result']['udp-reverse']['Mbps'])
- a['jitter'].append(row['result']['udp']['jitter_ms'])
+ if 'tcp' in row['result']:
+ a['tcp_upload_Mbps'].append(
+ row['result']['tcp']['received_Mbps']
+ )
+ if 'tcp-reverse' in row['result']:
+ a['tcp_download_Mbps'].append(
+ row['result']['tcp-reverse']['received_Mbps']
+ )
+ if 'udp' in row['result']:
+ a['udp_download_Mbps'].append(row['result']['udp']['Mbps'])
+ a['jitter_download'].append(row['result']['udp']['jitter_ms'])
+ if 'udp-reverse' in row['result']:
+ a['udp_upload_Mbps'].append(
+ row['result']['udp-reverse']['Mbps'])
+ a['jitter_upload'].append(
+ row['result']['udp-reverse']['jitter_ms'])
a['tx_power'].append(row['result']['tx_power'])
- a['frequency'].append(row['result']['frequency'])
- a['channel_bitrate'].append(row['result']['bitrate'])
+ a['frequency'].append(row['result']['frequency']*1e-3)
+ if 'bitrate' in row['result']:
+ a['channel_bitrate'].append(row['result']['bitrate'])
a['signal_quality'].append(row['result']['signal_mbm']+130)
ap = self._ap_names.get(
row['result']['ssid'].upper(),
row['result']['ssid']
)
- if row['result']['frequency'] < 5000:
- a['ap'].append(ap + '_2.4GHz')
- else:
- a['ap'].append(ap + '_5GHz')
+ a['ap'].append(ap + ' ({0:.1f} GHz)'.format(1e-3*int(row['result']['frequency'])))
return a
def _load_image(self):
@@ -239,9 +275,13 @@ def generate(self):
gx, gy = np.meshgrid(x, y)
gx, gy = gx.flatten(), gy.flatten()
for k, ptitle in self.graphs.items():
- self._plot(
- a, k, '%s - %s' % (self._title, ptitle), gx, gy, num_x, num_y
- )
+ try:
+ self._plot(
+ a, k, '%s - %s' % (self._title, ptitle), gx, gy, num_x, num_y
+ )
+ except:
+ logger.warning('Cannot create {} plot: '
+ 'insufficient data'.format(k))
def _channel_to_signal(self):
"""
@@ -251,7 +291,7 @@ def _channel_to_signal(self):
"""
# build a dict of frequency (GHz) to list of quality values
channels = defaultdict(list)
- for row in self._data:
+ for row in self._data['survey_points']:
for scan in row['result']['scan_results']:
ssid = row['result']['scan_results'][scan]['ssid']
if ssid in self._ignore_ssids:
@@ -338,11 +378,18 @@ def _add_inner_title(self, ax, title, loc, size=None, **kwargs):
return at
def _plot(self, a, key, title, gx, gy, num_x, num_y):
+ if key not in a:
+ logger.info("Skipping {} due to insufficient data".format(key))
+ return
+ if not len(a['x']) == len(a['y']) == len(a[key]):
+ logger.info("Skipping {} because data has holes".format(key))
+ return
logger.debug('Plotting: %s', key)
pp.rcParams['figure.figsize'] = (
self._image_width / 300, self._image_height / 300
)
- pp.title(title)
+ fig, ax = pp.subplots()
+ ax.set_title(title)
if 'min' in self.thresholds.get(key, {}):
vmin = self.thresholds[key]['min']
logger.debug('Using min threshold from thresholds: %s', vmin)
@@ -368,36 +415,44 @@ def _plot(self, a, key, title, gx, gy, num_x, num_y):
# (avoids interpolation artifacts)
z = numpy.ones((num_y, num_x))*vmin
# Render the interpolated data to the plot
- pp.axis('off')
+ ax.axis('off')
# begin color mapping
-
- cmap = pp.get_cmap(self._cname)
norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax, clip=True)
- mapper = cm.ScalarMappable(norm=norm, cmap=cmap)
+ mapper = cm.ScalarMappable(norm=norm, cmap=self._cmap)
# end color mapping
- image = pp.imshow(
+ image = ax.imshow(
z,
extent=(0, self._image_width, self._image_height, 0),
alpha=0.5, zorder=100,
- cmap=cmap, vmin=vmin, vmax=vmax
+ cmap=self._cmap, vmin=vmin, vmax=vmax
)
- cbar = pp.colorbar(image)
+
+ # Draw contours if requested and meaningful in this plot
+ if self._contours is not None and vmin != vmax:
+ CS = ax.contour(z, colors='k', linewidths=1, levels=self._contours,
+ extent=(0, self._image_width, self._image_height, 0),
+ alpha=0.3, zorder=150, origin='upper')
+ ax.clabel(CS, inline=1, fontsize=6)
+ cbar = fig.colorbar(image)
+
# Print only one ytick label when there is only one value to be shown
if vmin == vmax:
cbar.set_ticks([vmin])
- pp.imshow(self._layout, interpolation='bicubic', zorder=1, alpha=1)
+
+ # Draw floorplan itself to the lowest layer with full opacity
+ ax.imshow(self._layout, interpolation='bicubic', zorder=1, alpha=1)
labelsize = FontManager.get_default_size() * 0.4
if(self._showpoints):
# begin plotting points
for idx in range(0, len(a['x'])):
if (a['x'][idx], a['y'][idx]) in self._corners:
continue
- pp.plot(
- a['x'][idx], a['y'][idx],
+ ax.plot(
+ a['x'][idx], a['y'][idx], zorder=200,
marker='o', markeredgecolor='black', markeredgewidth=1,
markerfacecolor=mapper.to_rgba(a[key][idx]), markersize=6
)
- pp.text(
+ ax.text(
a['x'][idx], a['y'][idx] - 30,
a['ap'][idx], fontsize=labelsize,
horizontalalignment='center'
@@ -429,10 +484,14 @@ def parse_args(argv):
'a string to label each measurement with, showing '
'which AP it was connected to. Useful when doing '
'multi-AP surveys.')
- p.add_argument('-c', '--cmap-name', type=str, dest='cname', action='store',
+ p.add_argument('-c', '--cmap', type=str, dest='CNAME', action='store',
default="RdYlBu_r",
help='If specified, a valid matplotlib colormap name.')
- p.add_argument('IMAGE', type=str, help='Path to background image')
+ p.add_argument('-n', '--contours', type=int, dest='N', action='store',
+ default=None,
+ help='If specified, N contour lines will be added to the graphs')
+ p.add_argument('-p', '--picture', dest='IMAGE', type=str, action='store',
+ default=None, help='Path to background image')
p.add_argument(
'TITLE', type=str, help='Title for survey (and data filename)'
)
@@ -483,7 +542,7 @@ def main():
showpoints = True if args.showpoints > 0 else False
HeatMapGenerator(
- args.IMAGE, args.TITLE, showpoints, args.cname,
+ args.IMAGE, args.TITLE, showpoints, args.CNAME, args.N,
ignore_ssids=args.ignore, aps=args.aps, thresholds=args.thresholds
).generate()
diff --git a/wifi_survey_heatmap/libnl.py b/wifi_survey_heatmap/libnl.py
index 5d42d62..355c435 100644
--- a/wifi_survey_heatmap/libnl.py
+++ b/wifi_survey_heatmap/libnl.py
@@ -73,13 +73,13 @@
class Scanner(object):
- def __init__(self, interface_name, scan=True):
+ def __init__(self, interface_name=None, scan=True):
super().__init__()
logger.debug(
'Initializing Scanner for interface: %s',
interface_name
)
- self._interface_name = interface_name
+ self.interface_name = interface_name
self._scan = scan
self.iface_data = {}
@@ -87,19 +87,26 @@ def __init__(self, interface_name, scan=True):
# Get all interfaces of this machine
self.if_idx = None
- self.update_iface_details(nl80211.NL80211_CMD_GET_INTERFACE)
- names = []
+ self.iface_names = self.list_all_interfaces()
+
+ def set_interface(self, interface_name):
for idx in self.iface_data:
- if 'name' in self.iface_data[idx]:
- names.append(self.iface_data[idx]['name'])
- if self.iface_data[idx]['name'] == interface_name:
- self.if_idx = idx
- break
+ if self.iface_data[idx]['name'] == interface_name:
+ self.if_idx = idx
+ break
if self.if_idx == None:
logger.error("Device {0} is not a valid interface, use"
- " one of {1}".format(interface_name, names))
+ " one of {1}".format(interface_name, self.iface_names))
exit(1)
+ def list_all_interfaces(self):
+ self.update_iface_details(nl80211.NL80211_CMD_GET_INTERFACE)
+ iface_names = []
+ for idx in self.iface_data:
+ if 'name' in self.iface_data[idx]:
+ iface_names.append(self.iface_data[idx]['name'])
+ return iface_names
+
def _error_handler(self, _, err, arg):
"""Update the mutable integer `arg` with the error code."""
arg.value = err.error
@@ -289,7 +296,7 @@ def scan_all_access_points(self):
# Scan for access points within reach
# First get the wireless interface index.
- pack = struct.pack('16sI', self._interface_name.encode('ascii'), 0)
+ pack = struct.pack('16sI', self.interface_name.encode('ascii'), 0)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
info = struct.unpack('16sI', fcntl.ioctl(
@@ -297,7 +304,7 @@ def scan_all_access_points(self):
except OSError:
return logger.warning(
'Wireless interface {0}\
- does not exist.'.format(self._interface_name))
+ does not exist.'.format(self.interface_name))
finally:
sock.close()
if_index = int(info[1])
diff --git a/wifi_survey_heatmap/ui.py b/wifi_survey_heatmap/ui.py
index dc6d69a..5023e2b 100644
--- a/wifi_survey_heatmap/ui.py
+++ b/wifi_survey_heatmap/ui.py
@@ -44,6 +44,7 @@
import subprocess
from wifi_survey_heatmap.collector import Collector
+from wifi_survey_heatmap.libnl import Scanner
FORMAT = "[%(asctime)s %(levelname)s] %(message)s"
logging.basicConfig(level=logging.WARNING, format=FORMAT)
@@ -119,6 +120,7 @@ def set_progress(self, value, total):
def set_is_finished(self):
self.is_finished = True
+ self.is_failed = False
self.progress = 100
def draw(self, dc, color=None):
@@ -192,14 +194,17 @@ def __init__(self, parent):
self._load_file(self.data_filename)
self._duration = self.parent.duration
self.collector = Collector(
- self.parent.interface, self.parent.server, self._duration)
+ self.parent.server, self._duration, self.parent.scanner)
self.parent.SetStatusText("Ready.")
def _load_file(self, fpath):
with open(fpath, 'r') as fh:
raw = fh.read()
data = json.loads(raw)
- for point in data:
+ if 'survey_points' not in data:
+ logger.error('Trying to load incompatible JSON file')
+ exit(1)
+ for point in data['survey_points']:
p = SurveyPoint(self, point['x'], point['y'])
p.set_result(point['result'])
p.set_is_finished()
@@ -282,7 +287,7 @@ def onLeftDown(self, event):
self._moving_point = point
self._moving_x = point.x
self._moving_y = point.y
- point.draw(wx.ClientDC(self), color='blue')
+ point.draw(wx.ClientDC(self), color='lightblue')
def onLeftUp(self, event):
x, y = pos = self.get_xy(event)
@@ -293,9 +298,9 @@ def onLeftUp(self, event):
oldy = self._moving_point.y
self._moving_point.x = x
self._moving_point.y = y
- self._moving_point.draw(wx.ClientDC(self), color='red')
+ self._moving_point.draw(wx.ClientDC(self), color='lightblue')
res = self.YesNo(
- f'Move point from blue ({oldx}, {oldy}) to red ({x}, {y})?'
+ f'Move point from ({oldx}, {oldy}) to ({x}, {y})?'
)
if not res:
self._moving_point.x = self._moving_x
@@ -314,7 +319,7 @@ def onMotion(self, event):
self._moving_point.erase(dc)
self._moving_point.x = x
self._moving_point.y = y
- self._moving_point.draw(dc, color='red')
+ self._moving_point.draw(dc, color='lightblue')
def _check_bssid(self):
# Return early if BSSID is not to be verified
@@ -334,9 +339,17 @@ def _check_bssid(self):
self.warn(msg)
return False
+ def _abort(self, reason):
+ self.survey_points[-1].set_is_failed()
+ self.parent.SetStatusText('Aborted: {}'.format(reason))
+ self.Refresh()
+
def _do_measurement(self, pos):
- self.parent.SetStatusText('Got click at: %s' % pos)
+ self.parent.SetStatusText('Starting survey...')
+ # Add new survey point
self.survey_points.append(SurveyPoint(self, pos[0], pos[1]))
+ # Delete failed survey points
+ self.survey_points = [p for p in self.survey_points if not p.is_failed]
self.Refresh()
res = {}
count = 0
@@ -345,13 +358,15 @@ def _do_measurement(self, pos):
# Check if we are connected to an AP, all the
# rest doesn't any sense otherwise
if not self.collector.check_associated():
+ self._abort("Not connected to an access point")
return
# Check BSSID
if not self._check_bssid():
+ self._abort("BSSID check failed")
return
# Skip iperf test if empty server string was given
- if len(self.collector._iperf_server) > 0:
+ if self.collector._iperf_server is not None:
for protoname, udp in {'tcp': False, 'udp': True}.items():
for suffix, reverse in {'': False, '-reverse': True}.items():
# Update progress mark
@@ -360,15 +375,14 @@ def _do_measurement(self, pos):
# Check if we're still connected to the same AP
if not self._check_bssid():
+ self._abort("BSSID check failed")
return
# Start iperf test
tmp = self.run_iperf(count, udp, reverse)
if tmp is None:
# bail out; abort this survey point
- del self.survey_points[-1]
- self.parent.SetStatusText('Aborted; ready to retry...')
- self.Refresh()
+ self._abort("iperf test failed")
return
# else success
res['%s%s' % (protoname, suffix)] = {
@@ -377,7 +391,7 @@ def _do_measurement(self, pos):
# Check if we're still connected to the same AP
if not self._check_bssid():
- del self.survey_points[-1]
+ self._abort("BSSID check failed")
return
# Get all signal metrics from nl
@@ -413,8 +427,11 @@ def _ding(self):
subprocess.call([self.parent.ding_command, self.parent.ding_path])
def _write_json(self):
+ # Only store finished survey points
+ survey_points = [p.as_dict for p in self.survey_points if p.is_finished]
+
res = json.dumps(
- [x.as_dict for x in self.survey_points],
+ {'img_path': self.img_path, 'survey_points': survey_points},
cls=SafeEncoder, indent=2
)
with open(self.data_filename, 'w') as fh:
@@ -454,7 +471,8 @@ def run_iperf(self, count, udp, reverse):
# else this is an error
if tmp.error.startswith('unable to connect to server'):
self.warn(
- 'ERROR: Unable to connect to iperf server. Aborting.'
+ 'ERROR: Unable to connect to iperf server at {}. Aborting.'.
+ format(self.collector._iperf_server)
)
return None
if self.YesNo('iperf error: %s. Retry?' % tmp.error):
@@ -472,12 +490,11 @@ def on_paint(self, event=None):
class MainFrame(wx.Frame):
def __init__(
- self, img_path, interface, server, survey_title, scan, bssid, ding,
- ding_command, duration, *args, **kw
+ self, img_path, server, survey_title, scan, bssid, ding,
+ ding_command, duration, scanner, *args, **kw
):
super(MainFrame, self).__init__(*args, **kw)
self.img_path = img_path
- self.interface = interface
self.server = server
self.scan = scan
self.survey_title = survey_title
@@ -488,6 +505,7 @@ def __init__(
self.ding_command = ding_command
self.duration = duration
self.CreateStatusBar()
+ self.scanner = scanner
self.pnl = FloorplanPanel(self)
self.makeMenuBar()
@@ -515,9 +533,14 @@ def parse_args(argv):
p = argparse.ArgumentParser(description='wifi survey data collection UI')
p.add_argument('-v', '--verbose', dest='verbose', action='count', default=0,
help='verbose output. specify twice for debug-level output.')
- p.add_argument('-S', '--no-scan', dest='scan', action='store_false',
- default=True, help='skip access point scan')
- p.add_argument('-b', '--bssid', dest='bssid', action='store', type=str,
+ p.add_argument('-S', '--scan', dest='scan', action='store_true',
+ default=False, help='Scan for access points in the vicinity')
+ p.add_argument('-s', '--server', dest='IPERF3_SERVER', action='store', type=str,
+ default=None, help='iperf3 server IP or hostname')
+ p.add_argument('-d', '--duration', dest='IPERF3_DURATION', action='store',
+ type=int, default=10,
+ help='Duration of each individual ipref3 test run')
+ p.add_argument('-b', '--bssid', dest='BSSID', action='store', type=str,
default=None, help='Restrict survey to this BSSID')
p.add_argument('--ding', dest='ding', action='store', type=str,
default=None,
@@ -525,15 +548,14 @@ def parse_args(argv):
p.add_argument('--ding-command', dest='ding_command', action='store',
type=str, default='/usr/bin/paplay',
help='Path to ding command')
- p.add_argument('-d', '--duration', dest='duration', action='store',
- type=int, default=10,
- help='Duration of each individual ipref test run')
- p.add_argument('INTERFACE', type=str, help='Wireless interface name')
- p.add_argument('SERVER', type=str, help='iperf3 server IP or hostname')
- p.add_argument('IMAGE', type=str, help='Path to background image')
- p.add_argument(
- 'TITLE', type=str, help='Title for survey (and data filename)'
- )
+ p.add_argument('-i', '--interface', dest='INTERFACE', action='store',
+ type=str, default=None,
+ help='Wireless interface name')
+ p.add_argument('-p', '--picture', dest='IMAGE', type=str,
+ default=None, help='Path to background image')
+ p.add_argument('-t', '--title', dest='TITLE', type=str,
+ default=None, help='Title for survey (and data filename)'
+ )
args = p.parse_args(argv)
return args
@@ -567,6 +589,58 @@ def set_log_level_format(level, format):
logger.setLevel(level)
+def ask_for_wifi_iface(app, scanner):
+ frame = wx.Frame(None)
+ title = 'Wireless interface'
+ description = 'Please specify the wireless interface\nto be used for your survey'
+ dlg = wx.SingleChoiceDialog(frame, description, title, scanner.iface_names)
+ if dlg.ShowModal() == wx.ID_OK:
+ resu = dlg.GetStringSelection()
+ else:
+ # User clicked [Cancel]
+ exit()
+ dlg.Destroy()
+ frame.Destroy()
+
+ return resu
+
+
+def ask_for_title(app):
+ frame = wx.Frame(None)
+ title = 'Title of your measurement'
+ description = 'Please specify a title for your measurement. This title will be used to store the results and to distinguish the generated plots'
+ default = 'Example'
+ dlg = wx.TextEntryDialog(frame, description, title)
+ dlg.SetValue(default)
+ if dlg.ShowModal() == wx.ID_OK:
+ resu = dlg.GetValue()
+ else:
+ # User clicked [Cancel]
+ exit()
+ dlg.Destroy()
+ frame.Destroy()
+
+ return resu
+
+
+def ask_for_floorplan(app):
+ frame = wx.Frame(None)
+ title = 'Select floorplan for your measurement'
+ dlg = wx.FileDialog(frame, title,
+ wildcard='Compatible image files (*.png, *.jpg,*.tiff, *.bmp)|*.png;*.jpg;*.tiff;*.bmp;*:PNG;*.JPG;*.TIFF;*.BMP;*.jpeg;*.JPEG',
+ style=wx.FD_FILE_MUST_EXIST)
+ if dlg.ShowModal() == wx.ID_OK:
+ resu = dlg.GetPath()
+ print(resu)
+ else:
+ # User clicked [Cancel]
+ exit()
+ dlg.Destroy()
+ frame.Destroy()
+
+ return resu
+
+
def main():
if os.getuid() != 0:
logger.warning("You should run this script as root"
@@ -582,13 +656,37 @@ def main():
set_log_info()
app = wx.App()
+
+ scanner = Scanner(scan=args.scan)
+
+ # Ask for possibly missing fields
+ # Wireless interface
+ if args.INTERFACE is None:
+ INTERFACE = ask_for_wifi_iface(app, scanner)
+ else:
+ INTERFACE = args.INTERFACE
+
+ # Definitely set interface at this point
+ scanner.set_interface(INTERFACE)
+
+ # Floorplan image
+ if args.IMAGE is None:
+ IMAGE = ask_for_floorplan(app)
+ else:
+ IMAGE = args.IMAGE
+
+ # Title
+ if args.TITLE is None:
+ TITLE = ask_for_title(app)
+ else:
+ TITLE = args.TITLE
+
frm = MainFrame(
- args.IMAGE, args.INTERFACE, args.SERVER, args.TITLE, args.scan,
- args.bssid, args.ding, args.ding_command, args.duration, None,
- title='wifi-survey: %s' % args.TITLE,
+ IMAGE, args.IPERF3_SERVER, TITLE, args.scan,
+ args.BSSID, args.ding, args.ding_command, args.IPERF3_DURATION,
+ scanner, None, title='wifi-survey: %s' % args.TITLE,
)
frm.Show()
- frm.Maximize(True)
frm.SetStatusText('%s' % frm.pnl.GetSize())
app.MainLoop()