From e08b633c8891510a40e869dab9b6f8075906fc28 Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Sun, 13 Oct 2019 13:14:22 +0200
Subject: [PATCH 01/16] add bluetooth plugin

---
 pwnagotchi/defaults.yml                 |   5 +
 pwnagotchi/plugins/default/bt-tether.py | 408 ++++++++++++++++++++++++
 2 files changed, 413 insertions(+)
 create mode 100644 pwnagotchi/plugins/default/bt-tether.py

diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml
index a9e4c82..b529160 100644
--- a/pwnagotchi/defaults.yml
+++ b/pwnagotchi/defaults.yml
@@ -66,6 +66,11 @@ main:
         wordlist_folder: /opt/wordlists/
       AircrackOnly:
         enabled: false
+      bt-tether:
+        enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0
+        mac: ~ # mac of your bluetooth device
+        ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable
+        netmask: 24
     # monitor interface to use
     iface: mon0
     # command to run to bring the mon interface up in case it's not up already
diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py
new file mode 100644
index 0000000..0305ba9
--- /dev/null
+++ b/pwnagotchi/plugins/default/bt-tether.py
@@ -0,0 +1,408 @@
+__author__ = '33197631+dadav@users.noreply.github.com'
+__version__ = '1.0.0'
+__name__ = 'bt-tether'
+__license__ = 'GPL3'
+__description__ = 'This makes the display reachable over bluetooth'
+
+import os
+import time
+import re
+import logging
+import subprocess
+import dbus
+from pwnagotchi.ui.components import LabeledValue
+from pwnagotchi.ui.view import BLACK
+import pwnagotchi.ui.fonts as fonts
+from pwnagotchi.utils import StatusFile
+
+READY = False
+INTERVAL = StatusFile('/root/.bt-tether')
+OPTIONS = dict()
+
+
+class BTError(Exception):
+    """
+    Custom bluetooth exception
+    """
+    pass
+
+class BTNap:
+    """
+    This class creates a bluetooth connection to the specified bt-mac
+
+    see https://github.com/bablokb/pi-btnap/blob/master/files/usr/local/sbin/btnap.service.py
+    """
+
+    IFACE_BASE = 'org.bluez'
+    IFACE_DEV = 'org.bluez.Device1'
+    IFACE_ADAPTER = 'org.bluez.Adapter1'
+    IFACE_PROPS = 'org.freedesktop.DBus.Properties'
+
+    def __init__(self, mac):
+        self._mac = mac
+
+
+    @staticmethod
+    def get_bus():
+        """
+        Get systembus obj
+        """
+        bus = getattr(BTNap.get_bus, 'cached_obj', None)
+        if not bus:
+            bus = BTNap.get_bus.cached_obj = dbus.SystemBus()
+        return bus
+
+    @staticmethod
+    def get_manager():
+        """
+        Get manager obj
+        """
+        manager = getattr(BTNap.get_manager, 'cached_obj', None)
+        if not manager:
+                manager = BTNap.get_manager.cached_obj = dbus.Interface(
+                        BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'),
+                        'org.freedesktop.DBus.ObjectManager' )
+        return manager
+
+    @staticmethod
+    def prop_get(obj, k, iface=None):
+        """
+        Get a property of the obj
+        """
+        if iface is None:
+            iface = obj.dbus_interface
+        return obj.Get(iface, k, dbus_interface=BTNap.IFACE_PROPS)
+
+    @staticmethod
+    def prop_set(obj, k, v, iface=None):
+        """
+        Set a property of the obj
+        """
+        if iface is None:
+            iface = obj.dbus_interface
+        return obj.Set(iface, k, v, dbus_interface=BTNap.IFACE_PROPS)
+
+
+    @staticmethod
+    def find_adapter(pattern=None):
+        """
+        Find the bt adapter
+        """
+
+        return BTNap.find_adapter_in_objects(BTNap.get_manager().GetManagedObjects(), pattern)
+
+    @staticmethod
+    def find_adapter_in_objects(objects, pattern=None):
+        """
+        Finds the obj with a pattern
+        """
+        bus, obj = BTNap.get_bus(), None
+        for path, ifaces in objects.items():
+                adapter = ifaces.get(BTNap.IFACE_ADAPTER)
+                if adapter is None:
+                    continue
+                if not pattern or pattern == adapter['Address'] or path.endswith(pattern):
+                        obj = bus.get_object(BTNap.IFACE_BASE, path)
+                        yield dbus.Interface(obj, BTNap.IFACE_ADAPTER)
+        if obj is None:
+                raise BTError('Bluetooth adapter not found')
+
+    @staticmethod
+    def find_device(device_address, adapter_pattern=None):
+        """
+        Finds the device
+        """
+        return BTNap.find_device_in_objects(BTNap.get_manager().GetManagedObjects(),
+                                            device_address, adapter_pattern)
+
+    @staticmethod
+    def find_device_in_objects(objects, device_address, adapter_pattern=None):
+        """
+        Finds the device in objects
+        """
+        bus = BTNap.get_bus()
+        path_prefix = ''
+        if adapter_pattern:
+            if not isinstance(adapter_pattern, str):
+                adapter = adapter_pattern
+            else:
+                adapter = BTNap.find_adapter_in_objects(objects, adapter_pattern)
+            path_prefix = adapter.object_path
+        for path, ifaces in objects.items():
+            device = ifaces.get(BTNap.IFACE_DEV)
+            if device is None:
+                continue
+            if device['Address'] == device_address and path.startswith(path_prefix):
+                obj = bus.get_object(BTNap.IFACE_BASE, path)
+                return dbus.Interface(obj, BTNap.IFACE_DEV)
+        raise BTError('Bluetooth device not found')
+
+    def power(self, on=True):
+        """
+        Set power of devices to on/off
+        """
+
+        devs = list(BTNap.find_adapter())
+        devs = dict((BTNap.prop_get(dev, 'Address'), dev) for dev in devs)
+
+        for dev_addr, dev in devs.items():
+            BTNap.prop_set(dev, 'Powered', on)
+            logging.debug('Set power of %s (addr %s) to %s', dev.object_path, dev_addr, str(on))
+
+        if devs:
+            return list(devs.values())[0]
+
+        return None
+
+    def wait_for_device(self, timeout=30):
+        """
+        Wait for device
+
+        returns device if found None if not
+        """
+        bt_dev = self.power(True)
+
+        if not bt_dev:
+            return None
+
+        # could be set to 0, so check if > -1
+        while timeout > -1:
+            try:
+                dev_remote = BTNap.find_device(self._mac, bt_dev)
+                logging.debug('Using remote device (addr: %s): %s',
+                    BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path )
+                return dev_remote
+            except BTError:
+                pass
+
+            time.sleep(1)
+            timeout -= 1
+
+        # Device not found :(
+        return None
+
+
+    def connect(self, reconnect=False):
+        """
+        Connect to device
+
+        return True if connected; False if failed
+        """
+
+        # power up devices
+        bt_dev = self.power(True)
+        if not bt_dev:
+            return False
+
+        # check if device is close
+        dev_remote = self.wait_for_device()
+
+        if not dev_remote:
+            return False
+
+        #wait_iter = lambda: time.sleep(3600)
+        # signal.signal(signal.SIGTERM, lambda sig,frm: sys.exit(0))
+
+        try:
+            dev_remote.ConnectProfile('nap')
+        except Exception:
+            pass
+
+        net = dbus.Interface(dev_remote, 'org.bluez.Network1')
+
+        try:
+            net.Connect('nap')
+        except dbus.exceptions.DBusException as err:
+            if err.get_dbus_name() != 'org.bluez.Error.Failed':
+                raise
+
+            connected = BTNap.prop_get(net, 'Connected')
+
+            if not connected:
+                return False
+
+            if reconnect:
+                net.Disconnect()
+                return self.connect(reconnect=False)
+
+            return True
+
+
+#################################################
+#################################################
+#################################################
+
+class SystemdUnitWrapper:
+    """
+    systemd wrapper
+    """
+
+    def __init__(self, unit):
+        self.unit = unit
+
+    @staticmethod
+    def _action_on_unit(action, unit):
+        process = subprocess.Popen(f"systemctl {action} {unit}", shell=True, stdin=None,
+                                  stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
+        process.wait()
+        if process.returncode > 0:
+            return False
+        return True
+
+    @staticmethod
+    def daemon_reload():
+        """
+        Calls systemctl daemon-reload
+        """
+        process = subprocess.Popen("systemctl daemon-reload", shell=True, stdin=None,
+                                  stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
+        process.wait()
+        if process.returncode > 0:
+            return False
+        return True
+
+    def is_active(self):
+        """
+        Checks if unit is active
+        """
+        return SystemdUnitWrapper._action_on_unit('is-active', self.unit)
+
+    def is_enabled(self):
+        """
+        Checks if unit is enabled
+        """
+        return SystemdUnitWrapper._action_on_unit('is-enabled', self.unit)
+
+    def is_failed(self):
+        """
+        Checks if unit is failed
+        """
+        return SystemdUnitWrapper._action_on_unit('is-failed', self.unit)
+
+    def enable(self):
+        """
+        Enables the unit
+        """
+        return SystemdUnitWrapper._action_on_unit('enable', self.unit)
+
+    def disable(self):
+        """
+        Disables the unit
+        """
+        return SystemdUnitWrapper._action_on_unit('disable', self.unit)
+
+    def start(self):
+        """
+        Starts the unit
+        """
+        return SystemdUnitWrapper._action_on_unit('start', self.unit)
+
+    def stop(self):
+        """
+        Stops the unit
+        """
+        return SystemdUnitWrapper._action_on_unit('stop', self.unit)
+
+    def restart(self):
+        """
+        Restarts the unit
+        """
+        return SystemdUnitWrapper._action_on_unit('restart', self.unit)
+
+
+class IfaceWrapper:
+    """
+    Small wrapper to check and manage ifaces
+
+    see: https://github.com/rlisagor/pynetlinux/blob/master/pynetlinux/ifconfig.py
+    """
+
+    def __init__(self, iface):
+        self.iface = iface
+        self.path = f"/sys/class/net/{iface}"
+
+    def exists(self):
+        """
+        Checks if iface exists
+        """
+        return os.path.exists(self.path)
+
+    def is_up(self):
+        """
+        Checks if iface is ip
+        """
+        return open(f"{self.path}/operstate", 'r').read().rsplit('\n') == 'up'
+
+
+    def set_addr(self, addr):
+        """
+        Set the netmask
+        """
+        process = subprocess.Popen(f"ip addr add {addr} dev {self.iface}", shell=True, stdin=None,
+                                  stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
+        process.wait()
+
+        if process.returncode == 2 or process.returncode == 0: # 2 = already set
+            return True
+
+        return False
+
+
+def on_loaded():
+    """
+    Gets called when the plugin gets loaded
+    """
+    global READY
+    global INTERVAL
+
+    for opt in ['mac', 'ip', 'netmask', 'interval']:
+        if opt not in OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None):
+            logging.error("BT-TET: Pleace specify the %s in your config.yml.", opt)
+            return
+
+    # ensure bluetooth is running
+    bt_unit = SystemdUnitWrapper('bluetooth.service')
+    if not bt_unit.is_active():
+        if not bt_unit.start():
+            logging.error("BT-TET: Can't start bluetooth.service")
+            return
+
+    INTERVAL.update()
+    READY = True
+
+
+def on_ui_update(ui):
+    """
+    Try to connect to device
+    """
+
+    if READY:
+        global INTERVAL
+        if INTERVAL.newer_then_minutes(OPTIONS['interval']):
+            return
+
+        INTERVAL.update()
+
+        bt = BTNap(OPTIONS['mac'])
+        if bt.connect():
+            btnap_iface = IfaceWrapper('bnep0')
+
+            if btnap_iface.exists():
+                # check ip
+                addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}"
+
+                if not btnap_iface.set_addr(addr):
+                    ui.set('bluetooth', 'ERR1')
+                    logging.error("Could not set ip of bnep0 to %s", addr)
+                    return
+
+                ui.set('bluetooth', 'CON')
+            else:
+                ui.set('bluetooth', 'ERR2')
+        else:
+            ui.set('bluetooth', 'NF')
+
+
+def on_ui_setup(ui):
+    ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 30, 0),
+                                       label_font=fonts.Bold, text_font=fonts.Medium))

From 1691896531401fc66c46d0fa19d4b63b3df0bb81 Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Sun, 13 Oct 2019 13:23:38 +0200
Subject: [PATCH 02/16] add config param

---
 pwnagotchi/defaults.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml
index b529160..65bb5c6 100644
--- a/pwnagotchi/defaults.yml
+++ b/pwnagotchi/defaults.yml
@@ -71,6 +71,7 @@ main:
         mac: ~ # mac of your bluetooth device
         ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable
         netmask: 24
+        interval: 1 # check every x minutes for device
     # monitor interface to use
     iface: mon0
     # command to run to bring the mon interface up in case it's not up already

From 5d2855760859030b2cd36b27e42a36f2b95877f0 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 17:24:47 +0200
Subject: [PATCH 03/16] new: using pwngrid for mesh advertising (we got rid of
 scapy loading times)

---
 bin/pwnagotchi                     |   6 +-
 builder/pwnagotchi.yml             |   6 +-
 pwnagotchi/agent.py                |  32 +-----
 pwnagotchi/grid.py                 |  97 ++++++++++++++++
 pwnagotchi/mesh/__init__.py        |   4 -
 pwnagotchi/mesh/advertise.py       | 176 -----------------------------
 pwnagotchi/mesh/peer.py            |  30 +++--
 pwnagotchi/mesh/utils.py           |  96 +++++++++++-----
 pwnagotchi/mesh/wifi.py            |  37 ------
 pwnagotchi/plugins/default/grid.py |  79 ++-----------
 10 files changed, 197 insertions(+), 366 deletions(-)
 create mode 100644 pwnagotchi/grid.py
 delete mode 100644 pwnagotchi/mesh/advertise.py
 delete mode 100644 pwnagotchi/mesh/wifi.py

diff --git a/bin/pwnagotchi b/bin/pwnagotchi
index 297bce6..a7d3c97 100755
--- a/bin/pwnagotchi
+++ b/bin/pwnagotchi
@@ -2,10 +2,10 @@
 if __name__ == '__main__':
     import argparse
     import time
-    import os
     import logging
 
     import pwnagotchi
+    import pwnagtochi.grid as grid
     import pwnagotchi.utils as utils
     import pwnagotchi.plugins as plugins
 
@@ -64,7 +64,7 @@ if __name__ == '__main__':
             display.on_manual_mode(agent.last_session)
             time.sleep(1)
 
-            if Agent.is_connected():
+            if grid.is_connected():
                 plugins.on('internet_available', agent)
 
     else:
@@ -102,7 +102,7 @@ if __name__ == '__main__':
                 # affect ours ... neat ^_^
                 agent.next_epoch()
 
-                if Agent.is_connected():
+                if grid.is_connected():
                     plugins.on('internet_available', agent)
 
             except Exception as e:
diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml
index 219948e..8079d60 100644
--- a/builder/pwnagotchi.yml
+++ b/builder/pwnagotchi.yml
@@ -34,7 +34,7 @@
         url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip"
         ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
       pwngrid:
-        url: "https://github.com/evilsocket/pwngrid/releases/download/v1.6.3/pwngrid_linux_armv6l_v1.6.3.zip"
+        url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.1/pwngrid_linux_armv6l_1.7.1.zip"
       apt:
         hold:
           - firmware-atheros
@@ -497,12 +497,12 @@
         Description=pwngrid peer service.
         Documentation=https://pwnagotchi.ai
         Wants=network.target
-        After=network.target
+        After=bettercap.service
 
         [Service]
         Type=simple
         PermissionsStartOnly=true
-        ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -wait -log /var/log/pwngrid-peer.log
+        ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -wait -log /var/log/pwngrid-peer.log -iface mon0
         Restart=always
         RestartSec=30
 
diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py
index b5c876f..41680df 100644
--- a/pwnagotchi/agent.py
+++ b/pwnagotchi/agent.py
@@ -2,8 +2,6 @@ import time
 import json
 import os
 import re
-import socket
-from datetime import datetime
 import logging
 import _thread
 
@@ -42,15 +40,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
         if not os.path.exists(config['bettercap']['handshakes']):
             os.makedirs(config['bettercap']['handshakes'])
 
-    @staticmethod
-    def is_connected():
-        try:
-            socket.create_connection(("www.google.com", 80))
-            return True
-        except OSError:
-            pass
-        return False
-
     def config(self):
         return self._config
 
@@ -193,7 +182,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
     def set_access_points(self, aps):
         self._access_points = aps
         plugins.on('wifi_update', self, aps)
-        self._epoch.observe(aps, self._advertiser.peers() if self._advertiser is not None else ())
+        self._epoch.observe(aps, list(self._peers.values()))
         return self._access_points
 
     def get_access_points(self):
@@ -274,19 +263,8 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
         if new_shakes > 0:
             self._view.on_handshakes(new_shakes)
 
-    def _update_advertisement(self, s):
-        run_handshakes = len(self._handshakes)
-        tot_handshakes = utils.total_unique_handshakes(self._config['bettercap']['handshakes'])
-        self._advertiser.update({
-            'pwnd_run': run_handshakes,
-            'pwnd_tot': tot_handshakes,
-            'uptime': pwnagotchi.uptime(),
-            'epoch': self._epoch.epoch})
-
     def _update_peers(self):
-        peer = self._advertiser.closest_peer()
-        tot = self._advertiser.num_peers()
-        self._view.set_closest_peer(peer, tot)
+        self._view.set_closest_peer(self._closest_peer, len(self._peers))
 
     def _save_recovery_data(self):
         logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE)
@@ -331,10 +309,8 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
             s = self.session()
             self._update_uptime(s)
 
-            if self._advertiser is not None:
-                self._update_advertisement(s)
-                self._update_peers()
-
+            self._update_advertisement(s)
+            self._update_peers()
             self._update_counters()
 
             try:
diff --git a/pwnagotchi/grid.py b/pwnagotchi/grid.py
new file mode 100644
index 0000000..93bc08a
--- /dev/null
+++ b/pwnagotchi/grid.py
@@ -0,0 +1,97 @@
+import subprocess
+import socket
+import requests
+import json
+import logging
+
+import pwnagotchi
+
+# pwngrid-peer is running on port 8666
+API_ADDRESS = "http://127.0.0.1:8666/api/v1"
+
+
+def is_connected():
+    try:
+        socket.create_connection(("www.google.com", 80))
+        return True
+    except OSError:
+        pass
+    return False
+
+
+def call(path, obj=None):
+    url = '%s%s' % (API_ADDRESS, path)
+    if obj is None:
+        r = requests.get(url, headers=None)
+    else:
+        r = requests.post(url, headers=None, json=obj)
+
+    if r.status_code != 200:
+        raise Exception("(status %d) %s" % (r.status_code, r.text))
+    return r.json()
+
+
+def advertise(enabled=True):
+    return call("/mesh/%s" % 'true' if enabled else 'false')
+
+
+def set_advertisement_data(data):
+    return call("/mesh/data", obj=data)
+
+
+def peers():
+    return call("/mesh/peers")
+
+
+def closest_peer():
+    all = peers()
+    return all[0] if len(all) else None
+
+
+def update_data(last_session):
+    brain = {}
+    try:
+        with open('/root/brain.json') as fp:
+            brain = json.load(fp)
+    except:
+        pass
+
+    data = {
+        'session': {
+            'duration': last_session.duration,
+            'epochs': last_session.epochs,
+            'train_epochs': last_session.train_epochs,
+            'avg_reward': last_session.avg_reward,
+            'min_reward': last_session.min_reward,
+            'max_reward': last_session.max_reward,
+            'deauthed': last_session.deauthed,
+            'associated': last_session.associated,
+            'handshakes': last_session.handshakes,
+            'peers': last_session.peers,
+        },
+        'uname': subprocess.getoutput("uname -a"),
+        'brain': brain,
+        'version': pwnagotchi.version
+    }
+
+    logging.debug("updating grid data: %s" % data)
+
+    call("/data", data)
+
+
+def report_ap(essid, bssid):
+    try:
+        call("/report/ap", {
+            'essid': essid,
+            'bssid': bssid,
+        })
+        return True
+    except Exception as e:
+        logging.exception("error while reporting ap %s(%s)" % (essid, bssid))
+
+    return False
+
+
+def inbox(page=1, with_pager=False):
+    obj = call("/inbox?p=%d" % page)
+    return obj["messages"] if not with_pager else obj
diff --git a/pwnagotchi/mesh/__init__.py b/pwnagotchi/mesh/__init__.py
index e1c033f..e69de29 100644
--- a/pwnagotchi/mesh/__init__.py
+++ b/pwnagotchi/mesh/__init__.py
@@ -1,4 +0,0 @@
-import os
-
-def new_session_id():
-    return ':'.join(['%02x' % b for b in os.urandom(6)])
diff --git a/pwnagotchi/mesh/advertise.py b/pwnagotchi/mesh/advertise.py
deleted file mode 100644
index a4f88b7..0000000
--- a/pwnagotchi/mesh/advertise.py
+++ /dev/null
@@ -1,176 +0,0 @@
-import time
-import json
-import _thread
-import threading
-import logging
-from scapy.all import Dot11, Dot11Elt, RadioTap, sendp, sniff
-
-import pwnagotchi.ui.faces as faces
-
-import pwnagotchi.mesh.wifi as wifi
-from pwnagotchi.mesh import new_session_id
-from pwnagotchi.mesh.peer import Peer
-
-
-def _dummy_peer_cb(peer):
-    pass
-
-
-class Advertiser(object):
-    MAX_STALE_TIME = 300
-
-    def __init__(self, iface, name, version, identity, period=0.3, data={}):
-        self._iface = iface
-        self._period = period
-        self._running = False
-        self._stopped = threading.Event()
-        self._peers_lock = threading.Lock()
-        self._adv_lock = threading.Lock()
-        self._new_peer_cb = _dummy_peer_cb
-        self._lost_peer_cb = _dummy_peer_cb
-        self._peers = {}
-        self._frame = None
-        self._me = Peer(new_session_id(), 0, 0, {
-            'name': name,
-            'version': version,
-            'identity': identity,
-            'face': faces.FRIEND,
-            'pwnd_run': 0,
-            'pwnd_tot': 0,
-            'uptime': 0,
-            'epoch': 0,
-            'data': data
-        })
-        self.update()
-
-    def update(self, values={}):
-        with self._adv_lock:
-            for field, value in values.items():
-                self._me.adv[field] = value
-            self._frame = wifi.encapsulate(payload=json.dumps(self._me.adv), addr_from=self._me.session_id)
-
-    def on_peer(self, new_cb, lost_cb):
-        self._new_peer_cb = new_cb
-        self._lost_peer_cb = lost_cb
-
-    def on_face_change(self, old, new):
-        self.update({'face': new})
-
-    def start(self):
-        self._running = True
-        _thread.start_new_thread(self._sender, ())
-        _thread.start_new_thread(self._listener, ())
-        _thread.start_new_thread(self._pruner, ())
-
-    def num_peers(self):
-        with self._peers_lock:
-            return len(self._peers)
-
-    def peers(self):
-        with self._peers_lock:
-            return list(self._peers.values())
-
-    def closest_peer(self):
-        closest = None
-        with self._peers_lock:
-            for ident, peer in self._peers.items():
-                if closest is None or peer.is_closer(closest):
-                    closest = peer
-        return closest
-
-    def stop(self):
-        self._running = False
-        self._stopped.set()
-
-    def _sender(self):
-        logging.info("started advertiser thread (period:%s sid:%s) ..." % (str(self._period), self._me.session_id))
-        while self._running:
-            try:
-                sendp(self._frame, iface=self._iface, verbose=False, count=1, inter=self._period)
-            except OSError as ose:
-                logging.warning("non critical issue while sending advertising packet: %s" % ose)
-            except Exception as e:
-                logging.exception("error")
-            time.sleep(self._period)
-
-    def _on_advertisement(self, src_session_id, channel, rssi, adv):
-        ident = adv['identity']
-        with self._peers_lock:
-            if ident not in self._peers:
-                peer = Peer(src_session_id, channel, rssi, adv)
-                logging.info("detected unit %s (v%s) on channel %d (%s dBm) [sid:%s pwnd_tot:%d uptime:%d]" % ( \
-                    peer.full_name(),
-                    peer.version(),
-                    channel,
-                    rssi,
-                    src_session_id,
-                    peer.pwnd_total(),
-                    peer.uptime()))
-
-                self._peers[ident] = peer
-                self._new_peer_cb(peer)
-            else:
-                self._peers[ident].update(src_session_id, channel, rssi, adv)
-
-    def _parse_identity(self, radio, dot11, dot11elt):
-        payload = b''
-        while dot11elt:
-            payload += dot11elt.info
-            dot11elt = dot11elt.payload.getlayer(Dot11Elt)
-
-        if payload != b'':
-            adv = json.loads(payload)
-            self._on_advertisement( \
-                dot11.addr3,
-                wifi.freq_to_channel(radio.Channel),
-                radio.dBm_AntSignal,
-                adv)
-
-    def _is_broadcasted_advertisement(self, dot11):
-        # dst bcast + protocol signature + not ours
-        return dot11 is not None and \
-               dot11.addr1 == wifi.BroadcastAddress and \
-               dot11.addr2 == wifi.SignatureAddress and \
-               dot11.addr3 != self._me.session_id
-
-    def _is_frame_for_us(self, dot11):
-        # dst is us + protocol signature + not ours (why would we send a frame to ourself anyway?)
-        return dot11 is not None and \
-               dot11.addr1 == self._me.session_id and \
-               dot11.addr2 == wifi.SignatureAddress and \
-               dot11.addr3 != self._me.session_id
-
-    def _on_packet(self, p):
-        dot11 = p.getlayer(Dot11)
-
-        if self._is_broadcasted_advertisement(dot11):
-            try:
-                dot11elt = p.getlayer(Dot11Elt)
-                if dot11elt.ID == wifi.Dot11ElemID_Whisper:
-                    self._parse_identity(p[RadioTap], dot11, dot11elt)
-
-                else:
-                    raise Exception("unknown frame id %d" % dot11elt.ID)
-
-            except Exception as e:
-                logging.exception("error decoding packet from %s" % dot11.addr3)
-
-    def _listener(self):
-        # logging.info("started advertisements listener ...")
-        expr = "type mgt subtype beacon and ether src %s" % wifi.SignatureAddress
-        sniff(iface=self._iface, filter=expr, prn=self._on_packet, store=0, stop_filter=lambda x: self._stopped.isSet())
-
-    def _pruner(self):
-        while self._running:
-            time.sleep(10)
-            with self._peers_lock:
-                stale = []
-                for ident, peer in self._peers.items():
-                    inactive_for = peer.inactive_for()
-                    if inactive_for >= Advertiser.MAX_STALE_TIME:
-                        logging.info("peer %s lost (inactive for %ds)" % (peer.full_name(), inactive_for))
-                        self._lost_peer_cb(peer)
-                        stale.append(ident)
-
-                for ident in stale:
-                    del self._peers[ident]
diff --git a/pwnagotchi/mesh/peer.py b/pwnagotchi/mesh/peer.py
index 0d3b061..14f0057 100644
--- a/pwnagotchi/mesh/peer.py
+++ b/pwnagotchi/mesh/peer.py
@@ -1,32 +1,28 @@
 import time
 import logging
 
-import pwnagotchi.mesh.wifi as wifi
 import pwnagotchi.ui.faces as faces
 
 
 class Peer(object):
-    def __init__(self, sid, channel, rssi, adv):
+    def __init__(self, obj):
         self.first_seen = time.time()
         self.last_seen = self.first_seen
-        self.session_id = sid
-        self.last_channel = channel
-        self.presence = [0] * wifi.NumChannels
-        self.adv = adv
-        self.rssi = rssi
-        self.presence[channel - 1] = 1
+        self.session_id = obj['session_id']
+        self.last_channel = obj['channel']
+        self.rssi = obj['rssi']
+        self.adv = obj['advertisement']
 
-    def update(self, sid, channel, rssi, adv):
-        if self.name() != adv['name']:
-            logging.info("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), adv['name']))
+    def update(self, new):
+        if self.name() != new.name():
+            logging.info("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), new.name()))
 
-        if self.session_id != sid:
-            logging.info("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, sid))
+        if self.session_id != new.session_id:
+            logging.info("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, new.session_id))
 
-        self.presence[channel - 1] += 1
-        self.adv = adv
-        self.rssi = rssi
-        self.session_id = sid
+        self.adv = new.adv
+        self.rssi = new.rssi
+        self.session_id = new.session_id
         self.last_seen = time.time()
 
     def inactive_for(self):
diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py
index 3a67ed9..d28f04d 100644
--- a/pwnagotchi/mesh/utils.py
+++ b/pwnagotchi/mesh/utils.py
@@ -2,7 +2,11 @@ import _thread
 import logging
 
 import pwnagotchi
+import pwnagotchi.utils as utils
+import pwnagotchi.ui.faces as faces
 import pwnagotchi.plugins as plugins
+import pwnagotchi.grid as grid
+from pwnagotchi.mesh.peer import Peer
 
 
 class AsyncAdvertiser(object):
@@ -10,38 +14,76 @@ class AsyncAdvertiser(object):
         self._config = config
         self._view = view
         self._keypair = keypair
-        self._advertiser = None
+        self._advertisement = {
+            'name': pwnagotchi.name(),
+            'version': pwnagotchi.version,
+            'identity': self._keypair.fingerprint,
+            'face': faces.FRIEND,
+            'pwnd_run': 0,
+            'pwnd_tot': 0,
+            'uptime': 0,
+            'epoch': 0,
+            'policy': self._config['personality']
+        }
+        self._peers = {}
+        self._closest_peer = None
 
-    def keypair(self):
-        return self._keypair
+        _thread.start_new_thread(self._adv_poller, ())
+
+    def _update_advertisement(self, s):
+        self._advertisement['pwnd_run'] = len(self._handshakes)
+        self._advertisement['pwnd_tot'] = utils.total_unique_handshakes(self._config['bettercap']['handshakes'])
+        self._advertisement['uptime'] = pwnagotchi.uptime()
+        self._advertisement['epoch'] = self._epoch.epoch
+        grid.set_advertisement_data(self._advertisement)
 
     def start_advertising(self):
-        _thread.start_new_thread(self._adv_worker, ())
-
-    def _adv_worker(self):
-        # this will take some time due to scapy being slow to be imported ...
-        from pwnagotchi.mesh.advertise import Advertiser
-
-        self._advertiser = Advertiser(
-            self._config['main']['iface'],
-            pwnagotchi.name(),
-            pwnagotchi.version,
-            self._keypair.fingerprint,
-            period=0.3,
-            data=self._config['personality'])
-
-        self._advertiser.on_peer(self._on_new_unit, self._on_lost_unit)
-
         if self._config['personality']['advertise']:
-            self._advertiser.start()
-            self._view.on_state_change('face', self._advertiser.on_face_change)
+            grid.set_advertisement_data(self._advertisement)
+            grid.advertise(True)
+            self._view.on_state_change('face', self._on_face_change)
         else:
             logging.warning("advertising is disabled")
 
-    def _on_new_unit(self, peer):
-        self._view.on_new_peer(peer)
-        plugins.on('peer_detected', self, peer)
+    def _on_face_change(self, old, new):
+        self._advertisement['face'] = new
+        grid.set_advertisement_data(self._advertisement)
 
-    def _on_lost_unit(self, peer):
-        self._view.on_lost_peer(peer)
-        plugins.on('peer_lost', self, peer)
+    def _adv_poller(self):
+        while True:
+            logging.debug("polling pwngrid-peer for peers ...")
+
+            try:
+                grid_peers = grid.peers()
+                new_peers = {}
+
+                self._closest_peer = None
+                for obj in grid_peers:
+                    peer = Peer(obj)
+                    new_peers[peer.identity()] = peer
+                    if self._closest_peer is None:
+                        self._closest_peer = peer
+
+                # check who's gone
+                to_delete = []
+                for ident, peer in self._peers.items():
+                    if ident not in new_peers:
+                        self._view.on_lost_peer(peer)
+                        plugins.on('peer_lost', self, peer)
+                        to_delete.append(ident)
+
+                for ident in to_delete:
+                    del self._peers[ident]
+
+                for ident, peer in new_peers:
+                    # check who's new
+                    if ident not in self._peers:
+                        self._peers[ident] = peer
+                        self._view.on_new_peer(peer)
+                        plugins.on('peer_detected', self, peer)
+                    # update the rest
+                    else:
+                        self._peers[ident].update(peer)
+
+            except Exception as e:
+                logging.error("error while polling pwngrid-peer: %s" % e)
diff --git a/pwnagotchi/mesh/wifi.py b/pwnagotchi/mesh/wifi.py
deleted file mode 100644
index 6fe231e..0000000
--- a/pwnagotchi/mesh/wifi.py
+++ /dev/null
@@ -1,37 +0,0 @@
-SignatureAddress = 'de:ad:be:ef:de:ad'
-BroadcastAddress = 'ff:ff:ff:ff:ff:ff'
-Dot11ElemID_Whisper = 222
-NumChannels = 140
-
-def freq_to_channel(freq):
-    if freq <= 2472:
-        return int(((freq - 2412) / 5) + 1)
-    elif freq == 2484:
-        return int(14)
-    elif 5035 <= freq <= 5865:
-        return int(((freq - 5035) / 5) + 7)
-    else:
-        return 0
-
-
-def encapsulate(payload, addr_from, addr_to=BroadcastAddress):
-    from scapy.all import Dot11, Dot11Beacon, Dot11Elt, RadioTap
-
-    radio = RadioTap()
-    dot11 = Dot11(type=0, subtype=8, addr1=addr_to, addr2=SignatureAddress, addr3=addr_from)
-    beacon = Dot11Beacon(cap='ESS')
-    frame = radio / dot11 / beacon
-
-    data_size = len(payload)
-    data_left = data_size
-    data_off = 0
-    chunk_size = 255
-
-    while data_left > 0:
-        sz = min(chunk_size, data_left)
-        chunk = payload[data_off: data_off + sz]
-        frame /= Dot11Elt(ID=Dot11ElemID_Whisper, info=chunk, len=sz)
-        data_off += sz
-        data_left -= sz
-
-    return frame
diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py
index 516a6a2..4b62a55 100644
--- a/pwnagotchi/plugins/default/grid.py
+++ b/pwnagotchi/plugins/default/grid.py
@@ -6,11 +6,9 @@ __description__ = 'This plugin signals the unit cryptographic identity and list
 
 import os
 import logging
-import requests
 import glob
-import json
-import subprocess
-import pwnagotchi
+
+import pwnagotchi.grid as grid
 import pwnagotchi.utils as utils
 from pwnagotchi.ui.components import LabeledValue
 from pwnagotchi.ui.view import BLACK
@@ -65,74 +63,13 @@ def is_excluded(what):
     return False
 
 
-def grid_call(path, obj=None):
-    # pwngrid-peer is running on port 8666
-    api_address = 'http://127.0.0.1:8666/api/v1%s' % path
-    if obj is None:
-        r = requests.get(api_address, headers=None)
-    else:
-        r = requests.post(api_address, headers=None, json=obj)
-
-    if r.status_code != 200:
-        raise Exception("(status %d) %s" % (r.status_code, r.text))
-    return r.json()
-
-
-def grid_update_data(last_session):
-    brain = {}
-    try:
-        with open('/root/brain.json') as fp:
-            brain = json.load(fp)
-    except:
-        pass
-
-    data = {
-        'session': {
-            'duration': last_session.duration,
-            'epochs': last_session.epochs,
-            'train_epochs': last_session.train_epochs,
-            'avg_reward': last_session.avg_reward,
-            'min_reward': last_session.min_reward,
-            'max_reward': last_session.max_reward,
-            'deauthed': last_session.deauthed,
-            'associated': last_session.associated,
-            'handshakes': last_session.handshakes,
-            'peers': last_session.peers,
-        },
-        'uname': subprocess.getoutput("uname -a"),
-        'brain': brain,
-        'version': pwnagotchi.version
-    }
-
-    logging.debug("updating grid data: %s" % data)
-
-    grid_call("/data", data)
-
-
-def grid_report_ap(essid, bssid):
-    try:
-        grid_call("/report/ap", {
-            'essid': essid,
-            'bssid': bssid,
-        })
-        return True
-    except Exception as e:
-        logging.exception("error while reporting ap %s(%s)" % (essid, bssid))
-
-    return False
-
-
-def grid_inbox():
-    return grid_call("/inbox")["messages"]
-
-
 def on_ui_update(ui):
     new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES)
     if not ui.has_element('mailbox') and TOTAL_MESSAGES > 0:
         if ui.is_inky():
-            pos=(80, 0)
+            pos = (80, 0)
         else:
-            pos=(100,0)
+            pos = (100, 0)
         ui.add_element('mailbox',
                        LabeledValue(color=BLACK, label='MSG', value=new_value,
                                     position=pos,
@@ -147,7 +84,7 @@ def on_internet_available(agent):
     logging.debug("internet available")
 
     try:
-        grid_update_data(agent.last_session)
+        grid.update_data(agent.last_session)
     except Exception as e:
         logging.error("error connecting to the pwngrid-peer service: %s" % e)
         return
@@ -155,13 +92,13 @@ def on_internet_available(agent):
     try:
         logging.debug("checking mailbox ...")
 
-        messages = grid_inbox()
+        messages = grid.inbox()
         TOTAL_MESSAGES = len(messages)
         UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None])
 
         if TOTAL_MESSAGES:
             on_ui_update(agent.view())
-            logging.debug( " %d unread messages of %d total" % (UNREAD_MESSAGES, TOTAL_MESSAGES))
+            logging.debug(" %d unread messages of %d total" % (UNREAD_MESSAGES, TOTAL_MESSAGES))
 
         logging.debug("checking pcaps")
 
@@ -189,7 +126,7 @@ def on_internet_available(agent):
                             if is_excluded(essid) or is_excluded(bssid):
                                 logging.debug("not reporting %s due to exclusion filter" % pcap_file)
 
-                            elif grid_report_ap(essid, bssid):
+                            elif grid.report_ap(essid, bssid):
                                 reported.append(net_id)
                                 REPORT.update(data={'reported': reported})
                         else:

From ac5ee1ba7b39b5ead95853cfafdfbbc0e3fa2068 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 17:38:00 +0200
Subject: [PATCH 04/16] misc: small fix or general refactoring i did not bother
 commenting

---
 bin/pwnagotchi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bin/pwnagotchi b/bin/pwnagotchi
index a7d3c97..ce02bbc 100755
--- a/bin/pwnagotchi
+++ b/bin/pwnagotchi
@@ -5,7 +5,7 @@ if __name__ == '__main__':
     import logging
 
     import pwnagotchi
-    import pwnagtochi.grid as grid
+    import pwnagotchi.grid as grid
     import pwnagotchi.utils as utils
     import pwnagotchi.plugins as plugins
 

From 77efeafd6596669050685c06a3ae74e0a92b3aaf Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 17:39:25 +0200
Subject: [PATCH 05/16] misc: small fix or general refactoring i did not bother
 commenting

---
 pwnagotchi/mesh/wifi.py | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 pwnagotchi/mesh/wifi.py

diff --git a/pwnagotchi/mesh/wifi.py b/pwnagotchi/mesh/wifi.py
new file mode 100644
index 0000000..93fe9ab
--- /dev/null
+++ b/pwnagotchi/mesh/wifi.py
@@ -0,0 +1 @@
+NumChannels = 140

From 1808392a1dcdaabc4241241c4f2bb1ad6cd2f7e6 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 17:40:30 +0200
Subject: [PATCH 06/16] misc: small fix or general refactoring i did not bother
 commenting

---
 pwnagotchi/mesh/wifi.py | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/pwnagotchi/mesh/wifi.py b/pwnagotchi/mesh/wifi.py
index 93fe9ab..3bc714b 100644
--- a/pwnagotchi/mesh/wifi.py
+++ b/pwnagotchi/mesh/wifi.py
@@ -1 +1,12 @@
 NumChannels = 140
+
+
+def freq_to_channel(freq):
+    if freq <= 2472:
+        return int(((freq - 2412) / 5) + 1)
+    elif freq == 2484:
+        return int(14)
+    elif 5035 <= freq <= 5865:
+        return int(((freq - 5035) / 5) + 7)
+    else:
+        return 0

From 1215fda45967d64a7b7d4aa9f20a77b7dfd7b589 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 17:45:34 +0200
Subject: [PATCH 07/16] misc: small fix or general refactoring i did not bother
 commenting

---
 pwnagotchi/mesh/utils.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py
index d28f04d..6d17f4e 100644
--- a/pwnagotchi/mesh/utils.py
+++ b/pwnagotchi/mesh/utils.py
@@ -1,5 +1,6 @@
 import _thread
 import logging
+import time
 
 import pwnagotchi
 import pwnagotchi.utils as utils
@@ -87,3 +88,5 @@ class AsyncAdvertiser(object):
 
             except Exception as e:
                 logging.error("error while polling pwngrid-peer: %s" % e)
+
+            time.sleep(1)

From ad2fbdb9ddfea38b1751ef8291af94f4e4b7fc8d Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 17:54:33 +0200
Subject: [PATCH 08/16] misc: small fix or general refactoring i did not bother
 commenting

---
 pwnagotchi/defaults.yml  | 2 +-
 pwnagotchi/mesh/utils.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml
index 65bb5c6..621a4c3 100644
--- a/pwnagotchi/defaults.yml
+++ b/pwnagotchi/defaults.yml
@@ -173,7 +173,7 @@ ui:
         color: 'black'
         video:
             enabled: true
-            address: '10.0.0.2'
+            address: '0.0.0.0'
             port: 8080
 
 
diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py
index 6d17f4e..fb7f523 100644
--- a/pwnagotchi/mesh/utils.py
+++ b/pwnagotchi/mesh/utils.py
@@ -87,6 +87,6 @@ class AsyncAdvertiser(object):
                         self._peers[ident].update(peer)
 
             except Exception as e:
-                logging.error("error while polling pwngrid-peer: %s" % e)
+                logging.exception("error while polling pwngrid-peer")
 
             time.sleep(1)

From 5989d2571c8a3fed0c8e69b2bf089f759f53f2d3 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 17:55:10 +0200
Subject: [PATCH 09/16] misc: small fix or general refactoring i did not bother
 commenting

---
 pwnagotchi/mesh/utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py
index fb7f523..fbe8271 100644
--- a/pwnagotchi/mesh/utils.py
+++ b/pwnagotchi/mesh/utils.py
@@ -76,7 +76,7 @@ class AsyncAdvertiser(object):
                 for ident in to_delete:
                     del self._peers[ident]
 
-                for ident, peer in new_peers:
+                for ident, peer in new_peers.items():
                     # check who's new
                     if ident not in self._peers:
                         self._peers[ident] = peer

From 6793312691044a8aab2990c366d1f0ee2b10a7ca Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 18:02:11 +0200
Subject: [PATCH 10/16] misc: small fix or general refactoring i did not bother
 commenting

---
 pwnagotchi/defaults.yml | 1 +
 scripts/backup.sh       | 1 +
 2 files changed, 2 insertions(+)

diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml
index 621a4c3..c1d03a5 100644
--- a/pwnagotchi/defaults.yml
+++ b/pwnagotchi/defaults.yml
@@ -27,6 +27,7 @@ main:
         files:
           - /root/brain.nn
           - /root/brain.json
+          - /root/.api-report.json
           - /root/handshakes/
           - /etc/pwnagotchi/
           - /etc/hostname
diff --git a/scripts/backup.sh b/scripts/backup.sh
index 213ed94..c943058 100755
--- a/scripts/backup.sh
+++ b/scripts/backup.sh
@@ -10,6 +10,7 @@ TEMP_BACKUP_FOLDER=/tmp/pwnagotchi_backup
 FILES_TO_BACKUP=(
   /root/brain.nn
   /root/brain.json
+  /root/.api-report.json
   /root/handshakes
   /etc/pwnagotchi/
   /etc/hostname

From 8d2cbee8df4c0b739783c8f11a9514d0ff156d76 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 18:09:33 +0200
Subject: [PATCH 11/16] fix: fixed log flooding with whitelisten networks for
 grid report

---
 pwnagotchi/plugins/default/grid.py | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py
index 4b62a55..264de46 100644
--- a/pwnagotchi/plugins/default/grid.py
+++ b/pwnagotchi/plugins/default/grid.py
@@ -78,6 +78,12 @@ def on_ui_update(ui):
     ui.set('mailbox', new_value)
 
 
+def set_reported(reported, net_id):
+    global REPORT
+    reported.append(net_id)
+    REPORT.update(data={'reported': reported})
+
+
 def on_internet_available(agent):
     global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
 
@@ -118,17 +124,19 @@ def on_internet_available(agent):
                     net_id = os.path.basename(pcap_file).replace('.pcap', '')
                     if net_id not in reported:
                         if is_excluded(net_id):
-                            logging.info("skipping %s due to exclusion filter" % pcap_file)
+                            logging.debug("skipping %s due to exclusion filter" % pcap_file)
+                            set_reported(reported, net_id)
                             continue
 
                         essid, bssid = parse_pcap(pcap_file)
                         if bssid:
+                            add_as_reported = False
                             if is_excluded(essid) or is_excluded(bssid):
                                 logging.debug("not reporting %s due to exclusion filter" % pcap_file)
-
-                            elif grid.report_ap(essid, bssid):
-                                reported.append(net_id)
-                                REPORT.update(data={'reported': reported})
+                                set_reported(reported, net_id)
+                            else:
+                                if grid.report_ap(essid, bssid):
+                                    set_reported(reported, net_id)
                         else:
                             logging.warning("no bssid found?!")
             else:

From ba7c0ee4e6443594494c9d2783abf5bcbfdbec19 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 18:29:56 +0200
Subject: [PATCH 12/16] misc: small fix or general refactoring i did not bother
 commenting

---
 pwnagotchi/log.py | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py
index fa3ddc1..560a6ce 100644
--- a/pwnagotchi/log.py
+++ b/pwnagotchi/log.py
@@ -129,10 +129,12 @@ class LastSession(object):
                 if m:
                     name, pubkey, rssi, sid, pwnd_tot, uptime = m[0]
                     if pubkey not in cache:
-                        self.last_peer = Peer(sid, 1, int(rssi),
-                                              {'name': name,
-                                               'identity': pubkey,
-                                               'pwnd_tot': int(pwnd_tot)})
+                        self.last_peer = Peer({
+                            'session_id': sid,
+                            'channel': 1,
+                            'rssi': int(rssi),
+                            'identity': pubkey,
+                            'advertisement':{'pwnd_tot': int(pwnd_tot)}})
                         self.peers += 1
                         cache[pubkey] = self.last_peer
                     else:

From b539a761241a5d99b1a87003904f44bd586d4449 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 18:32:14 +0200
Subject: [PATCH 13/16] misc: small fix or general refactoring i did not bother
 commenting

---
 pwnagotchi/mesh/utils.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py
index fbe8271..6129e82 100644
--- a/pwnagotchi/mesh/utils.py
+++ b/pwnagotchi/mesh/utils.py
@@ -29,8 +29,6 @@ class AsyncAdvertiser(object):
         self._peers = {}
         self._closest_peer = None
 
-        _thread.start_new_thread(self._adv_poller, ())
-
     def _update_advertisement(self, s):
         self._advertisement['pwnd_run'] = len(self._handshakes)
         self._advertisement['pwnd_tot'] = utils.total_unique_handshakes(self._config['bettercap']['handshakes'])
@@ -40,6 +38,8 @@ class AsyncAdvertiser(object):
 
     def start_advertising(self):
         if self._config['personality']['advertise']:
+            _thread.start_new_thread(self._adv_poller, ())
+
             grid.set_advertisement_data(self._advertisement)
             grid.advertise(True)
             self._view.on_state_change('face', self._on_face_change)

From 2430b4a13406e10e803eeb59f171b2544e420f9c Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 18:35:09 +0200
Subject: [PATCH 14/16] misc: small fix or general refactoring i did not bother
 commenting

---
 pwnagotchi/log.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/pwnagotchi/log.py b/pwnagotchi/log.py
index 560a6ce..c81a17c 100644
--- a/pwnagotchi/log.py
+++ b/pwnagotchi/log.py
@@ -134,7 +134,10 @@ class LastSession(object):
                             'channel': 1,
                             'rssi': int(rssi),
                             'identity': pubkey,
-                            'advertisement':{'pwnd_tot': int(pwnd_tot)}})
+                            'advertisement':{
+                                'name': name,
+                                'pwnd_tot': int(pwnd_tot)
+                            }})
                         self.peers += 1
                         cache[pubkey] = self.last_peer
                     else:

From 1ce361a8397cab995704724b13fa7a552003f995 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 18:49:50 +0200
Subject: [PATCH 15/16] misc: small fix or general refactoring i did not bother
 commenting

---
 builder/pwnagotchi.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml
index 8079d60..21a15dd 100644
--- a/builder/pwnagotchi.yml
+++ b/builder/pwnagotchi.yml
@@ -34,7 +34,7 @@
         url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip"
         ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
       pwngrid:
-        url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.1/pwngrid_linux_armv6l_1.7.1.zip"
+        url: "https://github.com/evilsocket/pwngrid/releases/download/v1.7.3/pwngrid_linux_armv6l_1.7.3.zip"
       apt:
         hold:
           - firmware-atheros

From e15d0f33239c4f4b3ec58a813e53fd9f767b345c Mon Sep 17 00:00:00 2001
From: Simone Margaritelli <evilsocket@gmail.com>
Date: Sun, 13 Oct 2019 19:07:29 +0200
Subject: [PATCH 16/16] releasing v1.0.0RC4

---
 pwnagotchi/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py
index a26d337..c75af90 100644
--- a/pwnagotchi/__init__.py
+++ b/pwnagotchi/__init__.py
@@ -4,7 +4,7 @@ import logging
 import time
 import pwnagotchi.ui.view as view
 
-version = '1.0.0RC3'
+version = '1.0.0RC4'
 
 _name = None