diff --git a/cpu/esp_common/esp-now/esp_now_gnrc.c b/cpu/esp_common/esp-now/esp_now_gnrc.c
new file mode 100644
index 0000000000000000000000000000000000000000..2680a2316e41a12c166936420cf4435b36b7e69e
--- /dev/null
+++ b/cpu/esp_common/esp-now/esp_now_gnrc.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2018 Timo Rothenpieler
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     cpu_esp_common_esp_now
+ * @{
+ *
+ * @file
+ * @brief       Netif interface for the ESP-NOW WiFi P2P protocol
+ *
+ * @author Timo Rothenpieler <timo.rothenpieler@uni-bremen.de>
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <sys/uio.h>
+
+#include "net/netdev.h"
+#include "net/gnrc.h"
+#include "esp_now_params.h"
+#include "esp_now_netdev.h"
+#include "esp_now_gnrc.h"
+#include "net/gnrc/netif.h"
+
+#define ENABLE_DEBUG    (0)
+#include "debug.h"
+
+static int _send(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt)
+{
+    uint8_t mac[ESP_NOW_ADDR_LEN];
+    esp_now_pkt_hdr_t esp_hdr;
+    netdev_t *dev = netif->dev;
+
+    assert(pkt != NULL);
+
+    if (pkt->type != GNRC_NETTYPE_NETIF) {
+        DEBUG("gnrc_esp_now: First header was not generic netif header\n");
+        gnrc_pktbuf_release(pkt);
+        return -EBADMSG;
+    }
+
+    gnrc_netif_hdr_t *netif_hdr = (gnrc_netif_hdr_t*)pkt->data;
+    gnrc_pktsnip_t *payload = pkt->next;
+
+    if (netif_hdr->flags & (GNRC_NETIF_HDR_FLAGS_BROADCAST | GNRC_NETIF_HDR_FLAGS_MULTICAST)) {
+        /* ESP-NOW does not support multicast, always broadcast */
+        memset(mac, 0xff, ESP_NOW_ADDR_LEN);
+    } else if (netif_hdr->dst_l2addr_len == ESP_NOW_ADDR_LEN) {
+        memcpy(mac, gnrc_netif_hdr_get_dst_addr(netif_hdr), ESP_NOW_ADDR_LEN);
+    } else {
+        DEBUG("gnrc_esp_now: destination address had unexpected format"
+              "(flags=%d, dst_l2addr_len=%d)\n", netif_hdr->flags, netif_hdr->dst_l2addr_len);
+        gnrc_pktbuf_release(pkt);
+        return -EBADMSG;
+    }
+
+    iolist_t esp_hdr_iolist = {
+        .iol_base = &esp_hdr,
+        .iol_len = sizeof(esp_hdr),
+        .iol_next = (iolist_t*)payload
+    };
+
+    iolist_t iolist = {
+        .iol_base = mac,
+        .iol_len = sizeof(mac),
+        .iol_next = &esp_hdr_iolist
+    };
+
+    switch (payload->type) {
+#ifdef MODULE_GNRC_SIXLOWPAN
+        case GNRC_NETTYPE_SIXLOWPAN:
+            esp_hdr.flags = ESP_NOW_PKT_HDR_FLAG_SIXLO;
+            break;
+#endif
+        default:
+            esp_hdr.flags = 0;
+    }
+
+    DEBUG("gnrc_esp_now: sending packet to %02x:%02x:%02x:%02x:%02x:%02x with size %u\n",
+          mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], (unsigned)payload->size);
+
+    int res = dev->driver->send(dev, &iolist);
+
+    gnrc_pktbuf_release(pkt);
+
+    return res;
+}
+
+static gnrc_pktsnip_t *_recv(gnrc_netif_t *netif)
+{
+    netdev_t *dev = netif->dev;
+    esp_now_netdev_t *esp_now = (esp_now_netdev_t*)dev;
+
+    int bytes_expected = dev->driver->recv(dev, NULL, 0, NULL);
+    if (bytes_expected <= 0) {
+        DEBUG("gnrc_esp_now: failed receiving packet: %d\n", bytes_expected);
+        return NULL;
+    }
+
+    gnrc_pktsnip_t *pkt;
+    pkt = gnrc_pktbuf_add(NULL, NULL,
+                          bytes_expected,
+                          GNRC_NETTYPE_UNDEF);
+    if (!pkt) {
+        DEBUG("gnrc_esp_now: cannot allocate pktsnip.\n");
+
+        /* drop the packet */
+        dev->driver->recv(dev, NULL, bytes_expected, NULL);
+
+        return NULL;
+    }
+
+    int nread = dev->driver->recv(dev, pkt->data, bytes_expected, NULL);
+    if (nread <= 0) {
+        DEBUG("gnrc_esp_now: read error %d\n", nread);
+        goto err;
+    }
+
+    if (nread < bytes_expected) {
+        DEBUG("gnrc_esp_now: reallocating.\n");
+        gnrc_pktbuf_realloc_data(pkt, nread);
+    }
+
+    gnrc_pktsnip_t *mac_hdr;
+    mac_hdr = gnrc_pktbuf_mark(pkt, ESP_NOW_ADDR_LEN, GNRC_NETTYPE_UNDEF);
+    if (!mac_hdr) {
+        DEBUG("gnrc_esp_now: no space left in packet buffer\n");
+        goto err;
+    }
+
+    gnrc_pktsnip_t *esp_hdr;
+    esp_hdr = gnrc_pktbuf_mark(pkt, sizeof(esp_now_pkt_hdr_t), GNRC_NETTYPE_UNDEF);
+    if (!esp_hdr) {
+        DEBUG("gnrc_esp_now: no space left in packet buffer\n");
+        pkt = mac_hdr;
+        goto err;
+    }
+    esp_now_pkt_hdr_t *hdr = (esp_now_pkt_hdr_t*)esp_hdr->data;
+
+#ifdef MODULE_L2FILTER
+    if (!l2filter_pass(dev->filter, mac_hdr->data, ESP_NOW_ADDR_LEN)) {
+        DEBUG("gnrc_esp_now: incoming packet filtered by l2filter\n");
+        pkt = mac_hdr;
+        goto err;
+    }
+#endif
+
+    pkt->type = GNRC_NETTYPE_UNDEF;
+
+#ifdef MODULE_GNRC_SIXLOWPAN
+    if (hdr->flags & ESP_NOW_PKT_HDR_FLAG_SIXLO) {
+        pkt->type = GNRC_NETTYPE_SIXLOWPAN;
+    }
+#endif
+
+    gnrc_pktsnip_t *netif_hdr = gnrc_netif_hdr_build(mac_hdr->data, ESP_NOW_ADDR_LEN,
+                                                     esp_now->addr, ESP_NOW_ADDR_LEN);
+    if (!netif_hdr) {
+        DEBUG("gnrc_esp_now: no space left in packet buffer\n");
+        pkt = mac_hdr;
+        goto err;
+    }
+
+    ((gnrc_netif_hdr_t *)netif_hdr->data)->if_pid = netif->pid;
+
+    uint8_t *mac = mac_hdr->data;
+    DEBUG("gnrc_esp_now: received packet from %02x:%02x:%02x:%02x:%02x:%02x of length %u\n",
+          mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], pkt->size);
+
+    gnrc_pktbuf_remove_snip(pkt, mac_hdr);
+    gnrc_pktbuf_remove_snip(pkt, esp_hdr);
+    LL_APPEND(pkt, netif_hdr);
+
+    return pkt;
+
+err:
+    gnrc_pktbuf_release(pkt);
+    return NULL;
+}
+
+static const gnrc_netif_ops_t _esp_now_ops = {
+    .send = _send,
+    .recv = _recv,
+    .get = gnrc_netif_get_from_netdev,
+    .set = gnrc_netif_set_from_netdev,
+};
+
+gnrc_netif_t *gnrc_netif_esp_now_create(char *stack, int stacksize, char priority,
+                                        char *name, netdev_t *dev)
+{
+    return gnrc_netif_create(stack, stacksize, priority, name, dev, &_esp_now_ops);
+}
+
+/* device thread stack */
+static char _esp_now_stack[ESP_NOW_STACKSIZE];
+
+void auto_init_esp_now(void)
+{
+    LOG_TAG_INFO("esp_now", "initializing ESP-NOW device\n");
+
+    esp_now_netdev_t *esp_now_dev = netdev_esp_now_setup();
+    if (!esp_now_dev) {
+        LOG_ERROR("[auto_init_netif] error initializing esp_now\n");
+    } else {
+        gnrc_netif_esp_now_create(_esp_now_stack, sizeof(_esp_now_stack),
+                                  ESP_NOW_PRIO,
+                                  "net-esp-now",
+                                  &esp_now_dev->netdev);
+    }
+}
+
+/** @} */
diff --git a/cpu/esp_common/esp-now/esp_now_gnrc.h b/cpu/esp_common/esp-now/esp_now_gnrc.h
new file mode 100644
index 0000000000000000000000000000000000000000..9d451d5cc1c9736eafeb06f359a25c91c3220881
--- /dev/null
+++ b/cpu/esp_common/esp-now/esp_now_gnrc.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 Timo Rothenpieler
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     cpu_esp_common_esp_now
+ * @{
+ *
+ * @file
+ * @brief       ESP-NOW adaption for @ref net_gnrc_netif
+ *
+ * @author      Timo Rothenpieler <timo.rothenpieler@uni-bremen.de>
+ */
+#ifndef ESP_NOW_GNRC_H
+#define ESP_NOW_GNRC_H
+
+#include "net/gnrc/netif.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   Creates the ESP-NOW network interface
+ * @see     gnrc_netif_create
+ * @param   [in]    stack       The stack for the network interface's thread.
+ * @param   [in]    stacksize   Size of stack.
+ * @param   [in]    priority    Priority for the network interface's thread.
+ * @param   [in]    name        Name for the network interface. May be NULL.
+ * @param   [in]    dev         Device for the interface.
+ */
+gnrc_netif_t *gnrc_netif_esp_now_create(char *stack, int stacksize, char priority,
+                                        char *name, netdev_t *dev);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ESP_NOW_GNRC_H */
+/** @} */
diff --git a/cpu/esp_common/esp-now/esp_now_netdev.c b/cpu/esp_common/esp-now/esp_now_netdev.c
index 67cfad24486c5e5f47de1e43d3c89ba83dae8071..5b48dbe34240e455added3e7680d1b177d4d5906 100644
--- a/cpu/esp_common/esp-now/esp_now_netdev.c
+++ b/cpu/esp_common/esp-now/esp_now_netdev.c
@@ -14,10 +14,9 @@
  * @brief       Netdev interface for the ESP-NOW WiFi P2P protocol
  *
  * @author      Gunar Schorcht <gunar@schorcht.net>
+ * @author      Timo Rothenpieler <timo.rothenpieler@uni-bremen.de>
  */
 
-#define ENABLE_DEBUG (0)
-#include "debug.h"
 #include "log.h"
 #include "tools.h"
 
@@ -25,7 +24,6 @@
 #include <assert.h>
 #include <errno.h>
 
-#include "net/gnrc/netif/raw.h"
 #include "net/gnrc.h"
 #include "xtimer.h"
 
@@ -36,19 +34,20 @@
 #include "esp_system.h"
 #include "esp_wifi.h"
 #include "irq_arch.h"
+#include "od.h"
 
 #include "nvs_flash/include/nvs_flash.h"
 
 #include "esp_now_params.h"
 #include "esp_now_netdev.h"
 
-#include "net/ipv6/hdr.h"
-#include "net/gnrc/ipv6/nib.h"
+#define ENABLE_DEBUG             (0)
+#include "debug.h"
 
-#define ESP_NOW_UNICAST          1
+#define ESP_NOW_UNICAST          (1)
 
-#define ESP_NOW_WIFI_STA         1
-#define ESP_NOW_WIFI_SOFTAP      2
+#define ESP_NOW_WIFI_STA         (1)
+#define ESP_NOW_WIFI_SOFTAP      (2)
 #define ESP_NOW_WIFI_STA_SOFTAP  (ESP_NOW_WIFI_STA + ESP_NOW_WIFI_SOFTAP)
 
 #define ESP_NOW_AP_PREFIX        "RIOT_ESP_"
@@ -60,34 +59,10 @@
  * not provide an argument that could be used as pointer to the ESP-NOW
  * device which triggers the interrupt.
  */
-static esp_now_netdev_t _esp_now_dev;
+static esp_now_netdev_t _esp_now_dev = { 0 };
 static const netdev_driver_t _esp_now_driver;
 
-/* device thread stack */
-static char _esp_now_stack[ESP_NOW_STACKSIZE];
-
-static inline int _get_mac_from_iid(uint8_t *iid, uint8_t *mac)
-{
-    CHECK_PARAM_RET (iid != NULL, -EINVAL);
-    CHECK_PARAM_RET (mac != NULL, -EINVAL);
-
-    /* interface id according to */
-    /* https://tools.ietf.org/html/rfc4291#section-2.5.1 */
-    mac[0] = iid[0] ^ 0x02; /* invert bit1 */
-    mac[1] = iid[1];
-    mac[2] = iid[2];
-    mac[3] = iid[5];
-    mac[4] = iid[6];
-    mac[5] = iid[7];
-
-    return 0;
-}
-
-#if ESP_NOW_UNICAST
-static xtimer_t _esp_now_scan_peers_timer;
-static bool _esp_now_scan_peers_done = false;
-
-static bool _esp_now_add_peer(uint8_t* bssid, uint8_t channel, uint8_t* key)
+static bool _esp_now_add_peer(const uint8_t* bssid, uint8_t channel, uint8_t* key)
 {
     if (esp_now_is_peer_exist(bssid)) {
         return false;
@@ -111,11 +86,26 @@ static bool _esp_now_add_peer(uint8_t* bssid, uint8_t channel, uint8_t* key)
     return (ret == ESP_OK);
 }
 
+#if ESP_NOW_UNICAST
+
+static xtimer_t _esp_now_scan_peers_timer;
+static bool _esp_now_scan_peers_done = false;
+
 #define ESP_NOW_APS_BLOCK_SIZE 8 /* has to be power of two */
 
 static wifi_ap_record_t* aps = NULL;
 static uint32_t aps_size = 0;
 
+static const wifi_scan_config_t scan_cfg = {
+        .ssid = NULL,
+        .bssid = NULL,
+        .channel = ESP_NOW_CHANNEL,
+        .show_hidden = true,
+        .scan_type = WIFI_SCAN_TYPE_ACTIVE,
+        .scan_time.active.min = 0,
+        .scan_time.active.max = 120 /* TODO tune value */
+};
+
 static void IRAM_ATTR esp_now_scan_peers_done(void)
 {
     mutex_lock(&_esp_now_dev.dev_lock);
@@ -152,9 +142,8 @@ static void IRAM_ATTR esp_now_scan_peers_done(void)
         /* iterate over APs records */
         for (uint16_t i = 0; i < ap_num; i++) {
 
-            /* check whether the AP is an ESP_NOW node which is not already a peer */
-            if (strncmp((char*)aps[i].ssid, ESP_NOW_AP_PREFIX, ESP_NOW_AP_PREFIX_LEN) == 0 &&
-                !esp_now_is_peer_exist(aps[i].bssid)) {
+            /* check whether the AP is an ESP_NOW node */
+            if (strncmp((char*)aps[i].ssid, ESP_NOW_AP_PREFIX, ESP_NOW_AP_PREFIX_LEN) == 0) {
                 /* add the AP as peer */
                 _esp_now_add_peer(aps[i].bssid, aps[i].primary, esp_now_params.key);
             }
@@ -181,44 +170,19 @@ static void esp_now_scan_peers_start(void)
 {
     DEBUG("%s\n", __func__);
 
-    wifi_scan_config_t scan_cfg = {
-        .ssid = NULL,
-        .bssid = NULL,
-        .channel = esp_now_params.channel,
-        .show_hidden = true,
-        .scan_type = WIFI_SCAN_TYPE_ACTIVE,
-        .scan_time.active.min = 0,
-        .scan_time.active.max = 120 /* TODO tune value */
-    };
-
     esp_wifi_scan_start(&scan_cfg, false);
 }
 
-#define ESP_NOW_EVENT_SCAN_PEERS 1
-
-static kernel_pid_t esp_now_event_handler_pid;
-char esp_now_event_handler_stack [THREAD_STACKSIZE_DEFAULT];
-
-static void *esp_now_event_handler(void *arg)
-{
-    msg_t event;
-    while (1) {
-        msg_receive(&event);
-        switch (event.content.value) {
-            case ESP_NOW_EVENT_SCAN_PEERS:
-                esp_now_scan_peers_start();
-                break;
-        }
-    }
-    return NULL;
-}
-
 static void IRAM_ATTR esp_now_scan_peers_timer_cb(void* arg)
 {
     DEBUG("%s\n", __func__);
 
-    static msg_t event = { .content = { .value = ESP_NOW_EVENT_SCAN_PEERS } };
-    msg_send(&event, esp_now_event_handler_pid);
+    esp_now_netdev_t* dev = (esp_now_netdev_t*)arg;
+
+    if (dev->netdev.event_callback) {
+        dev->scan_event = true;
+        dev->netdev.event_callback((netdev_t*)dev, NETDEV_EVENT_ISR);
+    }
 }
 
 #else
@@ -236,33 +200,43 @@ static IRAM_ATTR void esp_now_recv_cb(const uint8_t *mac, const uint8_t *data, i
     }
 #endif
 
-    if (_esp_now_dev.rx_len) {
-        /* there is already a packet in receive buffer, we drop the new one */
+    mutex_lock(&_esp_now_dev.rx_lock);
+    critical_enter();
+
+    /*
+     * The ring buffer uses a single byte for the pkt length, followed by the mac address,
+     * followed by the actual packet data. The MTU for ESP-NOW is 250 bytes, so len will never
+     * exceed the limits of a byte as the mac address length is not included.
+     */
+    if ((int)ringbuffer_get_free(&_esp_now_dev.rx_buf) < 1 + ESP_NOW_ADDR_LEN + len) {
+        critical_exit();
+        mutex_unlock(&_esp_now_dev.rx_lock);
+        DEBUG("%s: buffer full, dropping incoming packet of %d bytes\n", __func__, len);
         return;
     }
 
-    critical_enter();
-
 #if 0 /* don't printf anything in ISR */
-    printf ("%s\n", __func__);
-    printf ("%s: received %d byte from %02x:%02x:%02x:%02x:%02x:%02x\n",
-            __func__, len,
-            mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
-    esp_hexdump (data, len, 'b', 16);
+    printf("%s\n", __func__);
+    printf("%s: received %d byte from %02x:%02x:%02x:%02x:%02x:%02x\n",
+           __func__, len,
+           mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+    od_hex_dump(data, len, OD_WIDTH_DEFAULT);
 #endif
 
-    _esp_now_dev.rx_len = len;
-    memcpy(_esp_now_dev.rx_buf, data, len);
-    memcpy(_esp_now_dev.rx_mac, mac, ESP_NOW_ADDR_LEN);
+    ringbuffer_add_one(&_esp_now_dev.rx_buf, len);
+    ringbuffer_add(&_esp_now_dev.rx_buf, (char*)mac, ESP_NOW_ADDR_LEN);
+    ringbuffer_add(&_esp_now_dev.rx_buf, (char*)data, len);
 
     if (_esp_now_dev.netdev.event_callback) {
+        _esp_now_dev.recv_event = true;
         _esp_now_dev.netdev.event_callback((netdev_t*)&_esp_now_dev, NETDEV_EVENT_ISR);
     }
 
     critical_exit();
+    mutex_unlock(&_esp_now_dev.rx_lock);
 }
 
-static int _esp_now_sending = 0;
+static volatile int _esp_now_sending = 0;
 
 static void IRAM_ATTR esp_now_send_cb(const uint8_t *mac, esp_now_send_status_t status)
 {
@@ -286,7 +260,9 @@ static esp_err_t IRAM_ATTR _esp_system_event_handler(void *ctx, system_event_t *
             break;
         case SYSTEM_EVENT_SCAN_DONE:
             DEBUG("%s WiFi scan done\n", __func__);
+#if ESP_NOW_UNICAST
             esp_now_scan_peers_done();
+#endif
             break;
         default:
             break;
@@ -322,10 +298,17 @@ static esp_err_t IRAM_ATTR _esp_system_event_handler(void *ctx, system_event_t *
 extern esp_err_t esp_system_event_add_handler(system_event_cb_t handler,
                                               void *arg);
 
-static void esp_now_setup(esp_now_netdev_t* dev)
+esp_now_netdev_t *netdev_esp_now_setup(void)
 {
+    esp_now_netdev_t* dev = &_esp_now_dev;
+
     DEBUG("%s: %p\n", __func__, dev);
 
+    if (dev->netdev.driver) {
+        DEBUG("%s: early returning previously initialized device\n", __func__);
+        return dev;
+    }
+
     /*
      * Init the WiFi driver. TODO It is not only required before ESP_NOW is
      * initialized but also before other WiFi functions are used. Once other
@@ -334,6 +317,8 @@ static void esp_now_setup(esp_now_netdev_t* dev)
     extern portMUX_TYPE g_intr_lock_mux;
     mutex_init(&g_intr_lock_mux);
 
+    ringbuffer_init(&dev->rx_buf, (char*)dev->rx_mem, sizeof(dev->rx_mem));
+
     esp_system_event_add_handler(_esp_system_event_handler, NULL);
 
     esp_err_t result;
@@ -342,7 +327,7 @@ static void esp_now_setup(esp_now_netdev_t* dev)
     if (result != ESP_OK) {
         LOG_TAG_ERROR("esp_now",
                       "nfs_flash_init failed with return value %d\n", result);
-        return;
+        return NULL;
     }
 #endif
 
@@ -351,7 +336,7 @@ static void esp_now_setup(esp_now_netdev_t* dev)
     if (result != ESP_OK) {
         LOG_TAG_ERROR("esp_now",
                       "esp_wifi_init failed with return value %d\n", result);
-        return;
+        return NULL;
     }
 
 #ifdef CONFIG_WIFI_COUNTRY
@@ -396,7 +381,7 @@ static void esp_now_setup(esp_now_netdev_t* dev)
         LOG_TAG_ERROR("esp_now",
                       "esp_wifi_set_mode failed with return value %d\n",
                       result);
-        return;
+        return NULL;
     }
 
     /* set the Station and SoftAP configuration */
@@ -404,14 +389,14 @@ static void esp_now_setup(esp_now_netdev_t* dev)
     if (result != ESP_OK) {
         LOG_TAG_ERROR("esp_now", "esp_wifi_set_config station failed with "
                       "return value %d\n", result);
-        return;
+        return NULL;
     }
     result = esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config_ap);
     if (result != ESP_OK) {
         LOG_TAG_ERROR("esp_now",
                       "esp_wifi_set_mode softap failed with return value %d\n",
                       result);
-        return;
+        return NULL;
     }
 
     /* start the WiFi driver */
@@ -419,57 +404,50 @@ static void esp_now_setup(esp_now_netdev_t* dev)
     if (result != ESP_OK) {
         LOG_TAG_ERROR("esp_now",
                       "esp_wifi_start failed with return value %d\n", result);
-        return;
+        return NULL;
     }
 
-#if ESP_NOW_UNICAST==0 /* TODO */
+#if !ESP_NOW_UNICAST
     /* all ESP-NOW nodes get the shared mac address on their station interface */
-    wifi_set_macaddr(STATION_IF, (uint8_t*)_esp_now_mac);
+    esp_wifi_set_mac(ESP_IF_WIFI_STA, (uint8_t*)_esp_now_mac);
 #endif
 
     /* set the netdev driver */
     dev->netdev.driver = &_esp_now_driver;
 
     /* initialize netdev data structure */
-    dev->peers_all = 0;
-    dev->peers_enc = 0;
+    dev->recv_event = false;
+    dev->scan_event = false;
+
     mutex_init(&dev->dev_lock);
+    mutex_init(&dev->rx_lock);
 
     /* initialize ESP-NOW and register callback functions */
     result = esp_now_init();
     if (result != ESP_OK) {
         LOG_TAG_ERROR("esp_now", "esp_now_init failed with return value %d\n",
                       result);
-        return;
+        return NULL;
     }
     esp_now_register_send_cb(esp_now_send_cb);
     esp_now_register_recv_cb(esp_now_recv_cb);
 
 #if ESP_NOW_UNICAST
-    /* create the ESP_NOW event handler thread */
-    esp_now_event_handler_pid = thread_create(esp_now_event_handler_stack,
-                                             sizeof(esp_now_event_handler_stack),
-                                             ESP_NOW_PRIO + 1,
-                                             THREAD_CREATE_WOUT_YIELD |
-                                             THREAD_CREATE_STACKTEST,
-                                             esp_now_event_handler,
-                                             NULL, "net-esp-now-event");
-
     /* timer for peer scan initialization */
     _esp_now_scan_peers_done = false;
     _esp_now_scan_peers_timer.callback = &esp_now_scan_peers_timer_cb;
     _esp_now_scan_peers_timer.arg = dev;
 
     /* execute the first scan */
-    esp_now_scan_peers_done();
+    esp_now_scan_peers_start();
 
 #else /* ESP_NOW_UNICAST */
-#if 0
-    int res = esp_now_add_peer((uint8_t*)_esp_now_mac, ESP_NOW_ROLE_COMBO,
-                               esp_now_params.channel, NULL, 0);
-    DEBUG("%s: multicast node added %d\n", __func__, res);
-#endif
+    bool res = _esp_now_add_peer(_esp_now_mac, esp_now_params.channel,
+                                               esp_now_params.key);
+    DEBUG("%s: multicast node add %s\n", __func__, res ? "success" : "error");
 #endif /* ESP_NOW_UNICAST */
+
+    return dev;
 }
 
 static int _init(netdev_t *netdev)
@@ -494,162 +472,75 @@ static int _send(netdev_t *netdev, const iolist_t *iolist)
     DEBUG("%s: %p %p\n", __func__, netdev, iolist);
 
     CHECK_PARAM_RET(netdev != NULL, -ENODEV);
-    CHECK_PARAM_RET(iolist != NULL, -EINVAL);
+    CHECK_PARAM_RET(iolist != NULL && iolist->iol_len == ESP_NOW_ADDR_LEN, -EINVAL);
+    CHECK_PARAM_RET(iolist->iol_next != NULL, -EINVAL);
 
-    esp_now_netdev_t* dev = (esp_now_netdev_t*)netdev;
+    esp_now_netdev_t *dev = (esp_now_netdev_t*)netdev;
 
     mutex_lock(&dev->dev_lock);
-    dev->tx_len = 0;
 
-    /* load packet data into TX buffer */
-    for (const iolist_t *iol = iolist; iol; iol = iol->iol_next) {
-        if (dev->tx_len + iol->iol_len > ESP_NOW_MAX_SIZE) {
-            mutex_unlock(&dev->dev_lock);
-            return -EOVERFLOW;
+#if ESP_NOW_UNICAST
+    uint8_t* _esp_now_dst = NULL;
+
+    for (uint8_t i = 0; i < ESP_NOW_ADDR_LEN; i++) {
+        if (((uint8_t*)iolist->iol_base)[i] != 0xff) {
+            _esp_now_dst = iolist->iol_base;
+            break;
         }
-        memcpy (dev->tx_buf + dev->tx_len, iol->iol_base, iol->iol_len);
-        dev->tx_len += iol->iol_len;
     }
-
-#if ENABLE_DEBUG
-    printf("%s: send %d byte\n", __func__, dev->tx_len);
-    /* esp_hexdump(dev->tx_buf, dev->tx_len, 'b', 16); */
+#else
+   const uint8_t* _esp_now_dst = _esp_now_mac;
 #endif
+    iolist = iolist->iol_next;
 
-    _esp_now_sending = 1;
-
-    uint8_t* _esp_now_dst = 0;
-
-    #if ESP_NOW_UNICAST
-    ipv6_hdr_t* ipv6_hdr = (ipv6_hdr_t*)dev->tx_buf;
-    uint8_t  _esp_now_dst_from_iid[6];
+    uint8_t *data_pos = dev->tx_mem;
+    uint8_t data_len = 0;
 
-    if (ipv6_hdr->dst.u8[0] == 0xff) {
-        /* packets to multicast prefix ff::/8 are sent to all peers */
-        DEBUG("multicast to all peers\n");
-        _esp_now_dst = 0;
-        _esp_now_sending = dev->peers_all;
-
-        #ifdef MODULE_NETSTATS_L2
-        netdev->stats.tx_mcast_count++;
-        #endif
-    }
-
-    else if ((byteorder_ntohs(ipv6_hdr->dst.u16[0]) & 0xffc0) == 0xfe80) {
-        /* for link local addresses fe80::/10, the MAC address is derived from dst address */
-        _get_mac_from_iid(&ipv6_hdr->dst.u8[8], _esp_now_dst_from_iid);
-        DEBUG("link local to %02x:%02x:%02x:%02x:%02x:%02x\n",
-              _esp_now_dst_from_iid[0], _esp_now_dst_from_iid[1],
-              _esp_now_dst_from_iid[2], _esp_now_dst_from_iid[3],
-              _esp_now_dst_from_iid[4], _esp_now_dst_from_iid[5]);
-        _esp_now_dst = _esp_now_dst_from_iid;
-        _esp_now_sending = 1;
-    }
-
-    else {
-        #ifdef MODULE_GNRC_IPV6_NIB
-        /* for other addresses, try to find an entry in NIB cache */
-        gnrc_ipv6_nib_nc_t nce;
-        int ret = gnrc_ipv6_nib_get_next_hop_l2addr (&ipv6_hdr->dst, dev->netif,
-                                                     NULL, &nce);
-        if (ret == 0) {
-            /* entry was found in NIB, use MAC adress from the NIB cache entry */
-            DEBUG("global, next hop to neighbor %02x:%02x:%02x:%02x:%02x:%02x\n",
-                  nce.l2addr[0], nce.l2addr[1], nce.l2addr[2],
-                  nce.l2addr[3], nce.l2addr[4], nce.l2addr[5]);
-            _esp_now_dst = nce.l2addr;
-            _esp_now_sending = 1;
-        }
-        else {
-        #endif
-            /* entry was not found in NIB, send to all peers */
-            DEBUG("global, no neibhbor found, multicast to all peers\n");
-            _esp_now_dst = 0;
-            _esp_now_sending = dev->peers_all;
-
-            #ifdef MODULE_NETSTATS_L2
-            netdev->stats.tx_mcast_count++;
-            #endif
-
-        #ifdef MODULE_GNRC_IPV6_NIB
+    while (iolist) {
+        if (((int)data_len + iolist->iol_len) > ESP_NOW_MAX_SIZE_RAW) {
+            DEBUG("%s: payload length exceeds maximum(%u>%u)\n", __func__,
+                  data_len + iolist->iol_len, ESP_NOW_MAX_SIZE_RAW);
+            return -EBADMSG;
         }
-        #endif
-    }
-
-    #else /* ESP_NOW_UNICAST */
 
-    ipv6_hdr_t* ipv6_hdr = (ipv6_hdr_t*)dev->tx_buf;
-    uint8_t  _esp_now_dst_from_iid[6];
+        memcpy(data_pos, iolist->iol_base, iolist->iol_len);
+        data_pos += iolist->iol_len;
+        data_len += iolist->iol_len;
 
-    _esp_now_dst = (uint8_t*)_esp_now_mac;
-    _esp_now_sending = 1;
-
-    if (ipv6_hdr->dst.u8[0] == 0xff) {
-        /* packets to multicast prefix ff::/8 are sent to all peers */
-        DEBUG("multicast to all peers\n");
-
-        #ifdef MODULE_NETSTATS_L2
-        netdev->stats.tx_mcast_count++;
-        #endif
+        iolist = iolist->iol_next;
     }
 
-    else if ((byteorder_ntohs(ipv6_hdr->dst.u16[0]) & 0xffc0) == 0xfe80) {
-        /* for link local addresses fe80::/10, the MAC address is derived from dst address */
-        _get_mac_from_iid(&ipv6_hdr->dst.u8[8], _esp_now_dst_from_iid);
-        DEBUG("link local to %02x:%02x:%02x:%02x:%02x:%02x\n",
-              _esp_now_dst_from_iid[0], _esp_now_dst_from_iid[1],
-              _esp_now_dst_from_iid[2], _esp_now_dst_from_iid[3],
-              _esp_now_dst_from_iid[4], _esp_now_dst_from_iid[5]);
-        if (esp_now_is_peer_exist(_esp_now_dst_from_iid) > 0) {
-            _esp_now_dst = _esp_now_dst_from_iid;
-        }
-    }
-
-    else
-    {
-        /* for other addresses, try to find an entry in NIB cache */
-          gnrc_ipv6_nib_nc_t nce;
-        int ret = gnrc_ipv6_nib_get_next_hop_l2addr (&ipv6_hdr->dst, dev->netif,
-                                                     NULL, &nce);
-        if (ret == 0 && esp_now_is_peer_exist(nce.l2addr) > 0) {
-            /* entry was found in NIB, use MAC adress from the NIB cache entry */
-            DEBUG("global, next hop to neighbor %02x:%02x:%02x:%02x:%02x:%02x\n",
-                  nce.l2addr[0], nce.l2addr[1], nce.l2addr[2],
-                  nce.l2addr[3], nce.l2addr[4], nce.l2addr[5]);
-            _esp_now_dst = nce.l2addr;
-        }
-        else {
-            /* entry was not found in NIB, send to all peers */
-            DEBUG("global, no neibhbor found, multicast to all peers\n");
-
-            #ifdef MODULE_NETSTATS_L2
-            netdev->stats.tx_mcast_count++;
-            #endif
-        }
-    }
+    DEBUG("%s: send %u byte\n", __func__, (unsigned)data_len);
+#if defined(MODULE_OD) && ENABLE_DEBUG
+    od_hex_dump(dev->tx_mem, data_len, OD_WIDTH_DEFAULT);
+#endif
 
-    #endif /* ESP_NOW_UNICAST */
     if (_esp_now_dst) {
         DEBUG("%s: send to esp_now addr %02x:%02x:%02x:%02x:%02x:%02x\n", __func__,
               _esp_now_dst[0], _esp_now_dst[1], _esp_now_dst[2],
               _esp_now_dst[3], _esp_now_dst[4], _esp_now_dst[5]);
+    } else {
+        DEBUG("%s: send esp_now broadcast\n", __func__);
     }
 
-    /* send the the packet to the peer(s) mac address */
-    if (esp_now_send (_esp_now_dst, dev->tx_buf, dev->tx_len) == 0) {
+    _esp_now_sending = 1;
+
+    /* send the packet to the peer(s) mac address */
+    if (esp_now_send(_esp_now_dst, dev->tx_mem, data_len) == ESP_OK) {
         while (_esp_now_sending > 0) {
             thread_yield_higher();
         }
 
 #ifdef MODULE_NETSTATS_L2
-        netdev->stats.tx_bytes += dev->tx_len;
+        netdev->stats.tx_bytes += data_len;
         netdev->event_callback(netdev, NETDEV_EVENT_TX_COMPLETE);
 #endif
 
         mutex_unlock(&dev->dev_lock);
-        return dev->tx_len;
-    }
-    else {
+        return data_len;
+    } else {
+        _esp_now_sending = 0;
+
 #ifdef MODULE_NETSTATS_L2
         netdev->stats.tx_failed++;
 #endif
@@ -667,55 +558,74 @@ static int _recv(netdev_t *netdev, void *buf, size_t len, void *info)
 
     esp_now_netdev_t* dev = (esp_now_netdev_t*)netdev;
 
-    mutex_lock(&dev->dev_lock);
+    mutex_lock(&dev->rx_lock);
+
+    uint16_t size = ringbuffer_empty(&dev->rx_buf)
+        ? 0
+        : (ringbuffer_peek_one(&dev->rx_buf) + ESP_NOW_ADDR_LEN);
 
-    uint8_t size = dev->rx_len;
+    if (size && dev->rx_buf.avail < size) {
+        /* this should never happen unless this very function messes up */
+        mutex_unlock(&dev->rx_lock);
+        return -EIO;
+    }
 
     if (!buf && !len) {
         /* return the size without dropping received data */
-        mutex_unlock(&dev->dev_lock);
+        mutex_unlock(&dev->rx_lock);
         return size;
     }
 
     if (!buf && len) {
         /* return the size and drop received data */
-        mutex_unlock(&dev->dev_lock);
-        dev->rx_len = 0;
+        if (size) {
+            ringbuffer_remove(&dev->rx_buf, 1 + size);
+        }
+        mutex_unlock(&dev->rx_lock);
         return size;
     }
 
-    if (buf && len && dev->rx_len) {
-        if (dev->rx_len > len) {
+    if (buf && len && !size) {
+        mutex_unlock(&dev->rx_lock);
+        return 0;
+    }
+
+    if (buf && len && size) {
+        if (size > len) {
             DEBUG("[esp_now] No space in receive buffers\n");
-            mutex_unlock(&dev->dev_lock);
+            mutex_unlock(&dev->rx_lock);
             return -ENOBUFS;
         }
 
-#if ENABLE_DEBUG
-        printf ("%s: received %d byte from %02x:%02x:%02x:%02x:%02x:%02x\n",
-                __func__, dev->rx_len,
-                dev->rx_mac[0], dev->rx_mac[1], dev->rx_mac[2],
-                dev->rx_mac[3], dev->rx_mac[4], dev->rx_mac[5]);
-        /* esp_hexdump (dev->rx_buf, dev->rx_len, 'b', 16); */
+        /* remove already peeked size byte */
+        ringbuffer_remove(&dev->rx_buf, 1);
+        ringbuffer_get(&dev->rx_buf, buf, size);
+
+        uint8_t *mac = buf;
+
+        DEBUG("%s: received %d byte from %02x:%02x:%02x:%02x:%02x:%02x\n",
+              __func__, size - ESP_NOW_ADDR_LEN,
+              mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+#if defined(MODULE_OD) && ENABLE_DEBUG
+        od_hex_dump(buf + ESP_NOW_ADDR_LEN, size - ESP_NOW_ADDR_LEN, OD_WIDTH_DEFAULT);
 #endif
 
-        if (esp_now_is_peer_exist(dev->rx_mac) <= 0) {
-            _esp_now_add_peer(dev->rx_mac, esp_now_params.channel, esp_now_params.key);
+#if ESP_NOW_UNICAST
+        if (esp_now_is_peer_exist(mac) <= 0) {
+            _esp_now_add_peer(mac, esp_now_params.channel, esp_now_params.key);
         }
-
-        memcpy(buf, dev->rx_buf, dev->rx_len);
-        dev->rx_len = 0;
+#endif
 
 #ifdef MODULE_NETSTATS_L2
         netdev->stats.rx_count++;
         netdev->stats.rx_bytes += size;
 #endif
 
-        mutex_unlock(&dev->dev_lock);
+        mutex_unlock(&dev->rx_lock);
         return size;
     }
 
-    mutex_unlock(&dev->dev_lock);
+    mutex_unlock(&dev->rx_lock);
     return -EINVAL;
 }
 
@@ -841,7 +751,15 @@ static void _isr(netdev_t *netdev)
     CHECK_PARAM(netdev != NULL);
 
     esp_now_netdev_t *dev = (esp_now_netdev_t*)netdev;
-    dev->netdev.event_callback(netdev, NETDEV_EVENT_RX_COMPLETE);
+
+    if (dev->recv_event) {
+        dev->recv_event = false;
+        dev->netdev.event_callback(netdev, NETDEV_EVENT_RX_COMPLETE);
+    }
+    else if (dev->scan_event) {
+        dev->scan_event = false;
+        esp_now_scan_peers_start();
+    }
     return;
 }
 
@@ -855,15 +773,4 @@ static const netdev_driver_t _esp_now_driver =
     .set = _set,
 };
 
-void auto_init_esp_now (void)
-{
-    LOG_TAG_INFO("esp_now", "initializing ESP-NOW device\n");
-
-    esp_now_setup(&_esp_now_dev);
-    _esp_now_dev.netif = gnrc_netif_raw_create(_esp_now_stack,
-                                              ESP_NOW_STACKSIZE, ESP_NOW_PRIO,
-                                              "net-esp-now",
-                                              (netdev_t *)&_esp_now_dev);
-}
-
 /** @} */
diff --git a/cpu/esp_common/esp-now/esp_now_netdev.h b/cpu/esp_common/esp-now/esp_now_netdev.h
index abeca8b33058aa678840728add6fcf5257a5e61f..2e0a41884bc1a539d4b401cf6c5fd7e50359bb13 100644
--- a/cpu/esp_common/esp-now/esp_now_netdev.h
+++ b/cpu/esp_common/esp-now/esp_now_netdev.h
@@ -14,32 +14,67 @@
  * @brief       Netdev interface for the ESP-NOW WiFi P2P protocol
  *
  * @author      Gunar Schorcht <gunar@schorcht.net>
+ * @author      Timo Rothenpieler <timo.rothenpieler@uni-bremen.de>
  */
 
 #ifndef ESP_NOW_NETDEV_H
 #define ESP_NOW_NETDEV_H
 
 #include "net/netdev.h"
+#include "ringbuffer.h"
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /**
- * @brief   Maximum packet size that can be used with ESP-NOW
+ * @brief   Maximum raw packet size that can be used with ESP-NOW (including headers)
  */
-#define ESP_NOW_MAX_SIZE (250)
+#define ESP_NOW_MAX_SIZE_RAW (250)
 
 /**
  * @brief   Length of ESP-NOW addresses
  */
 #define ESP_NOW_ADDR_LEN ETHERNET_ADDR_LEN
 
+/**
+ * @brief   Size of non-data header elements in ESP-NOW packet
+ */
+#define ESP_NOW_HEADER_LENGTH (sizeof(esp_now_pkt_hdr_t))
+
+/**
+ * @brief   Maximum packet size that can be used with ESP-NOW
+ */
+#define ESP_NOW_MAX_SIZE (ESP_NOW_MAX_SIZE_RAW - ESP_NOW_HEADER_LENGTH)
+
+/**
+ * @brief   buffer size used for RX buffering
+ *
+ * Reduce this value if your expected traffic does not include full IPv6 MTU
+ * sized packets.
+ */
+#ifndef ESP_NOW_BUFSIZE
+#define ESP_NOW_BUFSIZE (1500)
+#endif
+
 /**
  * @brief   Reference to the netdev device driver struct
  */
 extern const netdev_driver_t esp_now_driver;
 
+/**
+ * @brief   Header with neccesary flags for ESP-NOW packets
+ */
+typedef struct __attribute__((packed))
+{
+    uint8_t flags; /**< Flags */
+} esp_now_pkt_hdr_t;
+
+/**
+ * @brief   Packet is carrying 6Lo data
+ */
+#define ESP_NOW_PKT_HDR_FLAG_SIXLO (1)
+
 /**
  * @brief   Device descriptor for ESP-NOW devices
  */
@@ -47,28 +82,32 @@ typedef struct
 {
     netdev_t netdev;                 /**< netdev parent struct */
 
-    uint8_t addr[ESP_NOW_ADDR_LEN];   /**< device addr (MAC address) */
-
-    uint8_t rx_len;                  /**< number of bytes received */
-    uint8_t rx_buf[ESP_NOW_MAX_SIZE]; /**< receive buffer */
-    uint8_t rx_mac[ESP_NOW_ADDR_LEN]; /**< source address */
+    uint8_t addr[ESP_NOW_ADDR_LEN];  /**< device addr (MAC address) */
 
-    uint8_t tx_len;                  /**< number of bytes in transmit buffer */
-    uint8_t tx_buf[ESP_NOW_MAX_SIZE]; /**< transmit buffer */
+    uint8_t rx_mem[ESP_NOW_BUFSIZE]; /**< memory holding incoming packages */
+    ringbuffer_t rx_buf;             /**< ringbuffer for incoming packages */
 
-    gnrc_netif_t* netif;             /**< reference to the corresponding netif */
+    uint8_t tx_mem[ESP_NOW_MAX_SIZE_RAW]; /**< memory holding outgoing package */
 
 #ifdef MODULE_GNRC
     gnrc_nettype_t proto;            /**< protocol for upper layer */
 #endif
 
-    uint8_t peers_all;               /**< number of peers reachable */
-    uint8_t peers_enc;               /**< number of encrypted peers */
-
     mutex_t dev_lock;                /**< device is already in use */
+    mutex_t rx_lock;                 /**< rx_buf handling in progress */
+
+    bool recv_event;                 /**< ESP-NOW frame received */
+    bool scan_event;                 /**< ESP-NOW peers have to be scannged */
 
 } esp_now_netdev_t;
 
+/**
+ * @brief netdev <-> esp_npw glue code initialization function
+ *
+ * @return          NULL on error, pointer to esp_now_netdev on success
+ */
+esp_now_netdev_t *netdev_esp_now_setup(void);
+
 #ifdef __cplusplus
 }
 #endif