diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep
index fc00e9f0b1a24b4efc95e5ffac8a7dcb822573e3..3f6df3479e472910d88029f86e018f0243236cd9 100644
--- a/drivers/Makefile.dep
+++ b/drivers/Makefile.dep
@@ -394,6 +394,10 @@ ifneq (,$(filter sdcard_spi,$(USEMODULE)))
   USEMODULE += xtimer
 endif
 
+ifneq (,$(filter sds011,$(USEMODULE)))
+  FEATURES_REQUIRED += periph_uart
+endif
+
 ifneq (,$(filter servo,$(USEMODULE)))
   FEATURES_REQUIRED += periph_pwm
 endif
diff --git a/drivers/Makefile.include b/drivers/Makefile.include
index d4ab792e104fecc4b30ff98eb0a87bf37e49ba7b..936ca29a31e50ed24d615bed677abdf238cd9a3f 100644
--- a/drivers/Makefile.include
+++ b/drivers/Makefile.include
@@ -285,3 +285,7 @@ endif
 ifneq (,$(filter xbee,$(USEMODULE)))
   USEMODULE_INCLUDES += $(RIOTBASE)/drivers/xbee/include
 endif
+
+ifneq (,$(filter sds011,$(USEMODULE)))
+  USEMODULE_INCLUDES += $(RIOTBASE)/drivers/sds011/include
+endif
diff --git a/drivers/include/saul.h b/drivers/include/saul.h
index 7acb21046a56d11211293aedb2e3d97ca473e5ca..17da75990ec16e9f32338f309804db0822619cfe 100644
--- a/drivers/include/saul.h
+++ b/drivers/include/saul.h
@@ -101,6 +101,7 @@ enum {
     SAUL_SENSE_RSSI     = 0x93,     /**< sensor: RSSI */
     SAUL_SENSE_CHARGE   = 0x94,     /**< sensor: coulomb counter */
     SAUL_SENSE_CURRENT  = 0x95,     /**< sensor: ammeter */
+    SAUL_SENSE_PM       = 0x96,     /**< sensor: particulate matter */
     SAUL_CLASS_ANY      = 0xff      /**< any device - wildcard */
     /* extend this list as needed... */
 };
diff --git a/drivers/include/sds011.h b/drivers/include/sds011.h
new file mode 100644
index 0000000000000000000000000000000000000000..2aca1a57a8bfec8a68638b1a63e2359b9ced5569
--- /dev/null
+++ b/drivers/include/sds011.h
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2018 HAW-Hamburg
+ *
+ * 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.
+ */
+
+/**
+ * @defgroup    drivers_sds011 SDS011 Laser Dust Sensor
+ * @ingroup     drivers_sensors
+ * @brief       Driver SDS011 Laser Dust Sensor
+ * @{
+ *
+ * @file
+ * @brief       Interface for controlling SDS011 Laser Dust Sensor
+ *
+ * @author      Michel Rottleuthner <michel.rottleuthner@haw-hamburg.de>
+ */
+
+#ifndef SDS011_H
+#define SDS011_H
+
+#include <stdbool.h>
+
+#include "periph/gpio.h"
+#include "periph/uart.h"
+#include "mutex.h"
+#include "sds011_internal.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief SDS011 wildcard address to address all devices
+ */
+#define SDS011_DEVID_WILDCARD            (0xFFFF)
+
+/**
+ * @brief   Named return values
+ */
+enum {
+    SDS011_OK,               /**< all good */
+    SDS011_INVALID_RESPONSE, /**< invalid response */
+    SDS011_INVALID_CHKSUM,   /**< invalid checksum */
+    SDS011_ERROR,            /**< internal error */
+};
+
+/**
+ * @brief   Report mode of the SDS011 sensor
+ */
+typedef enum sds011_reporting_mode {
+    SDS011_RMODE_ACTIVE = 0, /**< continuously reporting values */
+    SDS011_RMODE_QUERY  = 1, /**< sensor needs to be queried */
+} sds011_reporting_mode_t;
+
+/**
+ * @brief   Work/sleep mode of the SDS011 sensor
+ */
+typedef enum sds011_working_mode {
+    SDS011_WMODE_SLEEP = 0, /**< laser & fan are disabled */
+    SDS011_WMODE_WORK  = 1, /**< laser & fan are enabled */
+} sds011_working_mode_t;
+
+/**
+ * @brief   Configuration parameters for SDS011 Laser Dust Sensor
+ */
+typedef struct {
+    uart_t   uart;    /**< UART device the sensor is connected to */
+    gpio_t   pwr_pin; /**< GPIO pin for disabling supply voltage for the sensor */
+    uint16_t dev_id;  /**< Unique sensor device ID */
+    bool     pwr_ah;  /**< Logic level of the power pin (true for active high) */
+} sds011_params_t;
+
+/**
+ * @brief   Data type for storing SDS011 sensor readings
+ */
+typedef struct {
+    uint16_t pm_2_5;  /**< Particulate Matter 2.5 concentration [0.1µg/m^3] */
+    uint16_t pm_10;   /**< Particulate Matter 10 concentration [0.1µg/m^3] */
+} sds011_data_t;
+
+/**
+ * @brief   callback for measurements actively reported by the SDS011 sensor
+ */
+typedef void (*sds011_callback_t)(sds011_data_t *data, void *ctx);
+
+/**
+ * @brief   Device descriptor definition for SDS011 Laser Dust Sensor
+ */
+typedef struct {
+    sds011_params_t   params;        /**< parameters for SDS011 device */
+    mutex_t           dev_lock;      /**< mutex to synchronize device access */
+    mutex_t           cb_lock;       /**< mutex to synchronize callbacks */
+    sds011_callback_t cb;            /**< callback deliver values async */
+    void              *cbctx;        /**< user context for the callback */
+    uint16_t          checksum;      /**< iteratively calculated checksum */
+    uint8_t           rx_mem[SDS011_FRAME_RECV_LEN]; /**< receive buffer */
+    uint8_t           pos;           /**< receive buffer position counter */
+} sds011_t;
+
+/**
+ * @brief   Initialize SDS011 Laser Dust Sensor
+ *
+ * @param[out] dev      device descriptor
+ * @param[in]  params   device configuration
+ *
+ * @pre     @p dev != NULL
+ * @pre     @p params != NULL
+ *
+ * @return              SDS011_OK on success
+ * @return              SDS011_ERROR on error
+ */
+int sds011_init(sds011_t *dev, const sds011_params_t *params);
+
+/**
+ * @brief   Enable power supply of SDS011 laser dust sensor
+ *
+ * @param[in] dev       device descriptor
+ *
+ * @pre     @p dev != NULL
+ */
+void sds011_power_on(const sds011_t *dev);
+
+/**
+ * @brief   Disable power supply of SDS011 laser dust sensor
+ *
+ * @param[in] dev       device descriptor
+ *
+ * @pre     @p dev != NULL
+ */
+void sds011_power_off(const sds011_t *dev);
+
+/**
+ * @brief   Read measurement values from SDS011 laser dust sensor
+ *
+ * @param[in]  dev       device descriptor
+ * @param[out] data      pointer for storing the values
+ *
+ * @return  SDS011_OK on success
+ * @return  SDS011_INVALID_RESPONSE when response doesn't match the request
+ * @return  SDS011_INVALID_CHKSUM when response checksum is invalid
+ * @return  SDS011_ERROR when other error occured
+ *
+ * @pre     @p dev != NULL
+ * @pre     @p data != NULL
+ */
+int sds011_read(sds011_t *dev, sds011_data_t *data);
+
+/**
+ * @brief   Register measurement callback
+ *
+ *          The registered callback is executed when new measurements were
+ *          received by the sensor. This function should be used together with
+ *          active reporting mode of the sensor that automatically sends new
+ *          measurements periodically (factory default setting of the sensor).
+ *
+ * @param[in]  dev       device descriptor
+ * @param[in]  cb        function to be called for new values (NULL for disable)
+ * @param[in]  ctx       context pointer that will be handed to the callback
+ *
+ * @return  SDS011_OK on success
+ * @return  SDS011_ERROR when error occured
+ *
+ * @pre     @p dev != NULL
+ */
+int sds011_register_callback(sds011_t *dev, sds011_callback_t cb, void *ctx);
+
+/**
+ * @brief   Get the current reporting mode of the sensor
+ *
+ * @param[in]   dev       device descriptor
+ * @param[out]  mode      SDS011_RMODE_ACTIVE: continuously report new values
+ *                        SDS011_RMODE_QUERY: new values need to be requested
+ *
+ * @return  SDS011_OK on success
+ * @return  SDS011_INVALID_RESPONSE when response doesn't match the request
+ * @return  SDS011_INVALID_CHKSUM when response checksum is invalid
+ * @return  SDS011_ERROR when other error occured
+ *
+ * @pre     @p dev != NULL
+ */
+int sds011_get_reporting_mode(sds011_t *dev, sds011_reporting_mode_t *mode);
+
+/**
+ * @brief   Set the reporting mode of the sensor
+ *
+ * @param[in]   dev       device descriptor
+ * @param[in]   mode      SDS011_RMODE_ACTIVE: continuously report new values
+ *                        SDS011_RMODE_QUERY: new values need to be requested
+ *
+ * @note    This setting is persistent even after a full power-cycle!
+ *          Factory default is SDS011_RMODE_ACTIVE
+ *
+ * @return  SDS011_OK on success
+ * @return  SDS011_INVALID_RESPONSE when response doesn't match the request
+ * @return  SDS011_INVALID_CHKSUM when response checksum is invalid
+ * @return  SDS011_ERROR when other error occured
+ *
+ * @pre     @p dev != NULL
+ */
+int sds011_set_reporting_mode(sds011_t *dev, sds011_reporting_mode_t mode);
+
+/**
+ * @brief   Get current working mode of the sensor
+ *
+ * @param[in]   dev       device descriptor
+ * @param[out]  mode      SDS011_WMODE_SLEEP: sensor is in sleep mode (~3 mA)
+ *                        SDS011_WMODE_WORK: sensor is in working mode (~65 mA)
+ *
+ * @return  SDS011_OK on success
+ * @return  SDS011_INVALID_RESPONSE when response doesn't match the request
+ * @return  SDS011_INVALID_CHKSUM when response checksum is invalid
+ * @return  SDS011_ERROR when other error occured
+ *
+ * @pre     @p dev != NULL
+ */
+int sds011_get_working_mode(sds011_t *dev, sds011_working_mode_t *mode);
+
+/**
+ * @brief   Set working mode of the sensor
+ *
+ * @param[in]   dev       device descriptor
+ * @param[in]   mode      SDS011_WMODE_SLEEP: put to sleep mode (~3 mA)
+ *                        SDS011_WMODE_WORK: put to working mode (~65 mA)
+ *
+ * @return  SDS011_OK on success
+ * @return  SDS011_INVALID_RESPONSE when response doesn't match the request
+ * @return  SDS011_INVALID_CHKSUM when response checksum is invalid
+ * @return  SDS011_ERROR when other error occured
+ *
+ * @pre     @p dev != NULL
+ */
+int sds011_set_working_mode(sds011_t *dev, sds011_working_mode_t mode);
+
+/**
+ * @brief   Get current working period of the sensor
+ *
+ * @param[in]   dev       device descriptor
+ * @param[out]  minutes   working period of the sensor in minutes
+ *
+ * @return  SDS011_OK on success
+ * @return  SDS011_INVALID_RESPONSE when response doesn't match the request
+ * @return  SDS011_INVALID_CHKSUM when response checksum is invalid
+ * @return  SDS011_ERROR when other error occured
+ *
+ * @pre     @p dev != NULL
+ */
+int sds011_get_working_period(sds011_t *dev, uint8_t *minutes);
+
+/**
+ * @brief   Set working period of the sensor
+ *
+ * @param[in]   dev       device descriptor
+ * @param[in]   minutes   0 - 30 new working period of the sensor in minutes
+ *                        0 for coninuous reporting mode
+ *                        1 - 30 for a period of @p minutes
+ *
+ * @note    For values greater than 0, the active duration (fan, laser enabled)
+ *          is always fixed to 30 seconds, while the sleep duration is adjusted
+ *          to give an overall period of @p minutes.
+ *
+ * @return  SDS011_OK on success
+ * @return  SDS011_INVALID_RESPONSE when response doesn't match the request
+ * @return  SDS011_INVALID_CHKSUM when response checksum is invalid
+ * @return  SDS011_ERROR when other error occured
+ *
+ * @pre     @p dev != NULL
+ */
+int sds011_set_working_period(sds011_t *dev, uint8_t minutes);
+
+/**
+ * @brief   Get firmware version of the sensor
+ *
+ * @param[in]   dev       device descriptor
+ * @param[out]  year      year of the firmware version
+ * @param[out]  mon       month of the firmware version
+ * @param[out]  day       day of the firmware version
+ *
+ * @return  SDS011_OK on success
+ * @return  SDS011_INVALID_RESPONSE when response doesn't match the request
+ * @return  SDS011_INVALID_CHKSUM when response checksum is invalid
+ * @return  SDS011_ERROR when other error occured
+ *
+ * @pre     @p dev != NULL
+ * @pre     @p year != NULL
+ * @pre     @p mon != NULL
+ * @pre     @p day != NULL
+ */
+int sds011_get_fw_version(sds011_t *dev, uint8_t *year, uint8_t *mon, uint8_t *day);
+
+/**
+ * @brief   Set device ID of the sensor
+ *
+ * @param[in]   dev            device descriptor
+ * @param[in]   sens_dev_id   ID as one number (ID byte 1 MSB, ID byte 2 LSB)
+ *
+ * @note    This setting is persistent even after a full power-cycle!
+ *          Factory default is an individual ID which is printed next to the
+ *          serial number barcode. For the number xxxx-abab the ID is 0xabab.
+ *
+ * @return  SDS011_OK on success
+ * @return  SDS011_INVALID_RESPONSE when response doesn't match the request
+ * @return  SDS011_INVALID_CHKSUM when response checksum is invalid
+ * @return  SDS011_ERROR when other error occured
+ *
+ * @pre     @p dev != NULL
+ */
+int sds011_set_dev_id(sds011_t *dev, uint16_t sens_dev_id);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SDS011_H */
+/** @} */
diff --git a/drivers/saul/saul_str.c b/drivers/saul/saul_str.c
index c8e1e85b46d310a76df863273251a0a3e9552035..dbbb487985092b7e193750bd120e28f445b05bae 100644
--- a/drivers/saul/saul_str.c
+++ b/drivers/saul/saul_str.c
@@ -57,8 +57,9 @@ const char *saul_class_to_str(const uint8_t class_id)
         case SAUL_SENSE_RSSI:      return "SENSE_RSSI";
         case SAUL_SENSE_CHARGE:    return "SENSE_CHARGE";
         case SAUL_SENSE_CURRENT:   return "SENSE_CURRENT";
-        case SAUL_CLASS_ANY:       return "CLASS_ANY";
         case SAUL_SENSE_OCCUP:     return "SENSE_OCCUP";
+        case SAUL_SENSE_PM:        return "SENSE_PM";
+        case SAUL_CLASS_ANY:       return "CLASS_ANY";
         default:                   return "CLASS_UNKNOWN";
     }
 }
diff --git a/drivers/sds011/Makefile b/drivers/sds011/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2
--- /dev/null
+++ b/drivers/sds011/Makefile
@@ -0,0 +1 @@
+include $(RIOTBASE)/Makefile.base
diff --git a/drivers/sds011/include/sds011_internal.h b/drivers/sds011/include/sds011_internal.h
new file mode 100644
index 0000000000000000000000000000000000000000..e41fca0f1fa978ac04080e3bbafbf83e2720016c
--- /dev/null
+++ b/drivers/sds011/include/sds011_internal.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018 HAW-Hamburg
+ *
+ * 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     drivers_sds011
+  * @{
+  *
+  * @file
+  * @brief       Internal constants etc. for the SDS011 laser dust sensor
+  *
+  * @author      Michel Rottleuthner <michel.rottleuthner@haw-hamburg.de>
+  * @}
+  */
+#ifndef SDS011_INTERNAL_H
+#define SDS011_INTERNAL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief SDS011 baud rate
+ */
+#define SDS011_UART_BAUDRATE             (9600U)
+
+ /**
+  * @name SDS011 frame lengths
+  * @{
+  */
+#define SDS011_FRAME_SEND_LEN            (19U)
+#define SDS011_FRAME_RECV_LEN            (10U)
+/** @} */
+
+/**
+ * @name SDS011 command values
+ * @{
+ */
+#define SDS011_CMD_DB1_SET_DR_MODE       (2U)
+#define SDS011_CMD_DB1_QUERY_DATA        (4U)
+#define SDS011_CMD_DB1_SET_DEV_ID        (5U)
+#define SDS011_CMD_DB1_SET_SLEEP_WORK    (6U)
+#define SDS011_CMD_DB1_CHECK_FIRMWARE    (7U)
+#define SDS011_CMD_DB1_SET_WORK_PERIOD   (8U)
+/** @} */
+
+/**
+ * @name SDS011 command option values
+ * @{
+ */
+#define SDS011_CMD_OPT_QUERY             (0U)
+#define SDS011_CMD_OPT_SET               (1U)
+#define SDS011_CMD_OPT_REPORT_ACTIVE     (0U)
+#define SDS011_CMD_OPT_REPORT_QUERY      (1U)
+#define SDS011_CMD_OPT_SLEEP             (0U)
+#define SDS011_CMD_OPT_WORK              (1U)
+/** @} */
+
+/**
+ * @name SDS011 frame constants
+ * @{
+ */
+#define SDS011_CMDID_QUERY               (0xB4)
+#define SDS011_RCMDID_REPLY              (0xC5)
+#define SDS011_RCMDID_DATA               (0xC0)
+#define SDS011_FRAME_TAIL                (0xAB)
+#define SDS011_FRAME_HEAD                (0xAA)
+#define SDS011_FRAME_CSUM_MSK            (0xFF)
+/** @} */
+
+/**
+ * @name SDS011 frame value indexes
+ * @{
+ */
+#define SDS011_FRAME_HEAD_IDX            (0U)
+#define SDS011_CMDID_IDX                 (1U)
+#define SDS011_DB1_IDX                   (2U)
+#define SDS011_DB2_IDX                   (3U)
+#define SDS011_DB3_IDX                   (4U)
+#define SDS011_DB4_IDX                   (5U)
+#define SDS011_DB5_IDX                   (6U)
+#define SDS011_DB6_IDX                   (7U)
+#define SDS011_DEVID1_IDX                (15U)
+#define SDS011_DEVID2_IDX                (16U)
+#define SDS011_FRAME_SEND_TAIL_IDX       (SDS011_FRAME_SEND_LEN - 1)
+#define SDS011_FRAME_RECV_TAIL_IDX       (SDS011_FRAME_RECV_LEN - 1)
+#define SDS011_FRAME_SEND_CSUM_IDX       (SDS011_FRAME_SEND_LEN - 2)
+#define SDS011_FRAME_RECV_CSUM_IDX       (SDS011_FRAME_RECV_LEN - 2)
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SDS011_INTERNAL_H */
+/** @} */
diff --git a/drivers/sds011/include/sds011_params.h b/drivers/sds011/include/sds011_params.h
new file mode 100644
index 0000000000000000000000000000000000000000..c8ea97a5e7fb537714908ad3665d558934308aff
--- /dev/null
+++ b/drivers/sds011/include/sds011_params.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 HAW-Hamburg
+ *
+ * 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     drivers_sds011
+ * @{
+ *
+ * @file
+ * @brief       SDS011 sensor specific configuration
+ *
+ * @author      Michel Rottleuthner <michel.rottleuthner@haw-hamburg.de>
+ */
+
+#ifndef SDS011_PARAMS_H
+#define SDS011_PARAMS_H
+
+#include "board.h"
+#include "periph/uart.h"
+#include "saul_reg.h"
+#include "sds011.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @name   Set default configuration parameters for the SDS011 driver
+ * @{
+ */
+#ifndef SDS011_PARAM_UART_DEV
+#define SDS011_PARAM_UART_DEV       (UART_DEV(1))
+#endif
+#ifndef SDS011_PARAM_PWR_PIN
+#define SDS011_PARAM_PWR_PIN        (GPIO_PIN(0, 0))
+#endif
+#ifndef SDS011_PARAM_PWR_PIN_AH
+#define SDS011_PARAM_PWR_PIN_AH     (true)
+#endif
+
+#ifndef SDS011_PARAMS
+#define SDS011_PARAMS               { .uart    = SDS011_PARAM_UART_DEV, \
+                                      .pwr_pin = SDS011_PARAM_PWR_PIN, \
+                                      .pwr_ah  = SDS011_PARAM_PWR_PIN_AH, \
+                                      .dev_id  = SDS011_DEVID_WILDCARD }
+#endif
+
+#ifndef SDS011_SAUL_INFO
+#define SDS011_SAUL_INFO            { .name = "SDS011" }
+#endif
+/** @} */
+
+/**
+ * @brief   SDS011 configuration
+ */
+static const  sds011_params_t sds011_params[] =
+{
+    SDS011_PARAMS
+};
+
+/**
+ * @brief   Allocate and configure entries to the SAUL registry
+ */
+saul_reg_info_t sds011_saul_info[] =
+{
+    SDS011_SAUL_INFO
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SDS011_PARAMS_H */
+/** @} */
diff --git a/drivers/sds011/sds011.c b/drivers/sds011/sds011.c
new file mode 100644
index 0000000000000000000000000000000000000000..06da9d6f6c316abbc8199332aee0616ac4222574
--- /dev/null
+++ b/drivers/sds011/sds011.c
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2018 HAW-Hamburg
+ *
+ * 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     drivers_sds011
+ * @{
+ *
+ * @file
+ * @brief       SDS011 Laser Dust Sensor driver implementation
+ *
+ * @author      Michel Rottleuthner <michel.rottleuthner@haw-hamburg.de>
+ *
+ * @}
+ */
+
+#include <string.h>
+
+#include "assert.h"
+#include "sds011.h"
+#include "periph/uart.h"
+
+/**
+ * @brief             UART receive callback
+ *
+ * @param[in]   arg   Context value previously handed to the uart_init call
+ * @param[in]   data  single byte received over UART
+ */
+static void _rx_cb(void *arg, uint8_t data)
+{
+    sds011_t *dev = (sds011_t*)arg;
+
+    /* frame MUST start with HEAD byte and the buffer must be cleared
+       before writing to it again */
+    if (((dev->pos == 0) && (data != SDS011_FRAME_HEAD)) ||
+         (dev->pos == SDS011_FRAME_RECV_LEN)) {
+        return;
+    }
+
+    dev->rx_mem[dev->pos] = data;
+
+    if ((dev->pos >= SDS011_DB1_IDX) &&
+        (dev->pos < SDS011_FRAME_RECV_CSUM_IDX)) {
+        dev->checksum += data;
+    }
+    else if ((dev->pos == SDS011_FRAME_RECV_LEN - 1) &&
+             (dev->rx_mem[SDS011_FRAME_HEAD_IDX] == SDS011_FRAME_HEAD) &&
+             (dev->rx_mem[SDS011_FRAME_RECV_TAIL_IDX] == SDS011_FRAME_TAIL)) {
+
+        dev->checksum &= SDS011_FRAME_CSUM_MSK;
+        if (dev->rx_mem[SDS011_FRAME_RECV_CSUM_IDX] == dev->checksum) {
+
+            if ((dev->cb != NULL) &&
+                (dev->rx_mem[SDS011_CMDID_IDX] == SDS011_RCMDID_DATA)) {
+                    sds011_data_t measure;
+                    measure.pm_2_5 = dev->rx_mem[SDS011_DB1_IDX] |
+                                    (dev->rx_mem[SDS011_DB2_IDX] << 8);
+                    measure.pm_10  = dev->rx_mem[SDS011_DB3_IDX] |
+                                    (dev->rx_mem[SDS011_DB4_IDX] << 8);
+                    dev->cb(&measure, dev->cbctx);
+                    dev->pos = -1;
+            }
+        }
+        else {
+            dev->pos = -1;
+        }
+
+        dev->checksum = 0;
+
+        /* unlock the mutex for the calling function */
+        mutex_unlock(&dev->cb_lock);
+    }
+
+    dev->pos++;
+}
+
+/**
+ * @brief             send command and wait for first replied message
+ *
+ * @param[in]   dev         SDS011 device the command is sent to
+ * @param[in]   data_bytes  data bytes to send within the command
+ * @param[in]   len         number of data bytes
+ * @param[out]  recv_frm    pointer where the received frame will be stored
+ *                          must at least provide SDS011_FRAME_RECV_LEN bytes
+ */
+int _send_recv_cmd(sds011_t *dev, uint8_t *data_bytes, size_t len, uint8_t *recv_frm)
+{
+    uint8_t cmd[SDS011_FRAME_SEND_LEN] = {0};
+    int checksum = 0;
+    int res = SDS011_ERROR;
+
+    cmd[SDS011_FRAME_HEAD_IDX]  = SDS011_FRAME_HEAD;
+    cmd[SDS011_CMDID_IDX] = SDS011_CMDID_QUERY;
+
+    for (unsigned i = 0; i < len; i++) {
+        cmd[SDS011_DB1_IDX + i] = data_bytes[i];
+        checksum += data_bytes[i];
+    }
+
+    cmd[SDS011_DEVID1_IDX] = (dev->params.dev_id >> 8) & 0xFF;
+    checksum += cmd[SDS011_DEVID1_IDX];
+    cmd[SDS011_DEVID2_IDX] = dev->params.dev_id & 0xFF;
+    checksum += cmd[SDS011_DEVID2_IDX];
+
+    cmd[SDS011_FRAME_SEND_LEN - 2] = checksum & SDS011_FRAME_CSUM_MSK;
+    cmd[SDS011_FRAME_SEND_TAIL_IDX] = SDS011_FRAME_TAIL;
+
+    mutex_lock(&dev->dev_lock);
+
+    dev->pos = 0;
+    dev->checksum = 0;
+
+    mutex_lock(&dev->cb_lock);
+
+    /* if no active reporting callback is registered, UART must be enabled first */
+    if((dev->cb == NULL) &&
+       (uart_init(dev->params.uart, SDS011_UART_BAUDRATE, _rx_cb, dev) != 0)) {
+        mutex_unlock(&dev->cb_lock);
+        mutex_unlock(&dev->dev_lock);
+        return SDS011_ERROR;
+    }
+
+    uart_write(dev->params.uart, cmd, SDS011_FRAME_SEND_LEN);
+
+    /* wait for the isr callback to unlock the mutex */
+    mutex_lock(&dev->cb_lock);
+
+    /* only copy data when checksum was valid */
+    if (dev->pos != 0) {
+        memcpy(recv_frm, dev->rx_mem, SDS011_FRAME_RECV_LEN);
+        /* mark the recv buffer as free */
+        dev->pos = 0;
+        dev->checksum = 0;
+
+        /* check if we received a valid response for the cmd sent*/
+        if(((recv_frm[SDS011_CMDID_IDX]  == SDS011_RCMDID_REPLY) &&
+            (cmd[SDS011_DB1_IDX] == recv_frm[SDS011_DB1_IDX]))
+           || ((recv_frm[SDS011_CMDID_IDX] == SDS011_RCMDID_DATA)
+            && (cmd[SDS011_DB1_IDX] == SDS011_CMD_DB1_QUERY_DATA))) {
+            res = SDS011_OK;
+        }
+        else {
+            res = SDS011_INVALID_RESPONSE;
+        }
+    }
+    else {
+        res = SDS011_INVALID_CHKSUM;
+    }
+
+    /* reset mutex state */
+    mutex_unlock(&dev->cb_lock);
+
+    /* if no active reporting callback is registered, UART can be disabled */
+    if((dev->cb == NULL) &&
+       (uart_init(dev->params.uart, SDS011_UART_BAUDRATE, NULL, NULL) != 0)) {
+        res = SDS011_ERROR;
+    }
+
+    /* release device */
+    mutex_unlock(&dev->dev_lock);
+
+    return res;
+}
+
+/**
+ * @brief       shorthand to get a single byte property with _send_recv_cmd
+ *
+ * @param[in]   dev         SDS011 device the command is sent to
+ * @param[in]   data_bytes  data bytes to send within the command
+ * @param[in]   len         number of data bytes
+ * @param[out]  p           pointer for storing single data byte of the reply
+ * @param[out]  p_idx       index of data byte we want to read
+ */
+static int _get_property(sds011_t *dev, uint8_t *data_bytes, size_t len,
+                         uint8_t *p, uint8_t p_idx)
+{
+    uint8_t recv[SDS011_FRAME_RECV_LEN];
+    int res = _send_recv_cmd(dev, data_bytes, len, recv);
+
+    if (res == SDS011_OK) {
+        *p = recv[p_idx];
+    }
+
+    return res;
+}
+
+int sds011_init(sds011_t *dev, const sds011_params_t *params)
+{
+    assert((dev != NULL) && (params != NULL) && (params->uart < UART_NUMOF));
+
+    if ((params->pwr_pin != GPIO_UNDEF) &&
+        (gpio_init(params->pwr_pin, GPIO_OUT) != 0)) {
+        return SDS011_ERROR;
+    }
+
+    memcpy(&dev->params, params, sizeof(sds011_params_t));
+
+    mutex_init(&dev->dev_lock);
+    mutex_init(&dev->cb_lock);
+
+    dev->cb = NULL;
+
+    sds011_power_on(dev);
+
+    return SDS011_OK;
+}
+
+int sds011_register_callback(sds011_t *dev, sds011_callback_t cb, void *ctx)
+{
+    assert(dev != NULL);
+    mutex_lock(&dev->dev_lock);
+    dev->cbctx = ctx;
+    dev->cb = cb;
+
+    /* either register un unregister the uart callback */
+    if (uart_init(dev->params.uart, SDS011_UART_BAUDRATE,
+                  cb == NULL ? NULL : _rx_cb,
+                  cb == NULL ? NULL : dev) != 0) {
+        mutex_unlock(&dev->dev_lock);
+        return SDS011_ERROR;
+    }
+    mutex_unlock(&dev->dev_lock);
+    return SDS011_OK;
+}
+
+void sds011_power_on(const sds011_t *dev)
+{
+    assert(dev != NULL);
+    if(dev->params.pwr_pin != GPIO_UNDEF) {
+        gpio_write(dev->params.pwr_pin, dev->params.pwr_ah);
+    }
+}
+
+void sds011_power_off(const sds011_t *dev)
+{
+    assert(dev != NULL);
+    if(dev->params.pwr_pin != GPIO_UNDEF) {
+        gpio_write(dev->params.pwr_pin, !dev->params.pwr_ah);
+    }
+}
+
+int sds011_get_reporting_mode(sds011_t *dev, sds011_reporting_mode_t *mode)
+{
+    assert(dev != NULL);
+    uint8_t cmd[] = {SDS011_CMD_DB1_SET_DR_MODE, SDS011_CMD_OPT_QUERY};
+    uint8_t prop = 0;
+    int res = _get_property(dev, cmd, sizeof(cmd), &prop, SDS011_DB3_IDX);
+    *mode = ((prop == 0) ? SDS011_RMODE_ACTIVE : SDS011_RMODE_QUERY);
+    return res;
+}
+
+int sds011_set_reporting_mode(sds011_t *dev, sds011_reporting_mode_t mode)
+{
+    assert(dev != NULL);
+    uint8_t cmd[] = {SDS011_CMD_DB1_SET_DR_MODE, SDS011_CMD_OPT_SET, mode};
+
+    uint8_t recv[SDS011_FRAME_RECV_LEN];
+    int res = _send_recv_cmd(dev, cmd, sizeof(cmd), recv);
+
+    if (res == SDS011_OK) {
+        if ((recv[SDS011_DB2_IDX] == SDS011_CMD_OPT_SET) &&
+            (recv[SDS011_DB3_IDX] == mode)) {
+            return SDS011_OK;
+        }
+
+        return SDS011_ERROR;
+    }
+
+    return res;
+}
+
+int sds011_read(sds011_t *dev, sds011_data_t *data)
+{
+    assert((dev != NULL) && (data != NULL));
+    uint8_t cmd[] = {SDS011_CMD_DB1_QUERY_DATA};
+
+    uint8_t recv[SDS011_FRAME_RECV_LEN];
+    int res = _send_recv_cmd(dev, cmd, sizeof(cmd), recv);
+
+    if (res == SDS011_OK) {
+        data->pm_2_5 = recv[SDS011_DB1_IDX] | (recv[SDS011_DB2_IDX] << 8);
+        data->pm_10  = recv[SDS011_DB3_IDX] | (recv[SDS011_DB4_IDX] << 8);
+    }
+
+    return res;
+}
+
+int sds011_set_dev_id(sds011_t *dev, uint16_t sens_dev_id)
+{
+    assert(dev != NULL);
+    uint8_t cmd[13] = {0};
+    cmd[0]  = SDS011_CMD_DB1_SET_DEV_ID;
+    cmd[11] = (sens_dev_id >> 8) & 0xFF;
+    cmd[12] = sens_dev_id & 0xFF;
+
+    uint8_t recv[SDS011_FRAME_RECV_LEN];
+    int res = _send_recv_cmd(dev, cmd, sizeof(cmd), recv);
+
+    if ((res == SDS011_OK) &&
+        (recv[SDS011_DB5_IDX] == cmd[11]) &&
+        (recv[SDS011_DB6_IDX] == cmd[12])) {
+        return SDS011_OK;
+    }
+
+    return SDS011_ERROR;
+}
+
+int sds011_get_working_mode(sds011_t *dev, sds011_working_mode_t *mode)
+{
+    assert(dev != NULL);
+    uint8_t cmd[] = {SDS011_CMD_DB1_SET_SLEEP_WORK, SDS011_CMD_OPT_QUERY};
+    uint8_t prop = 0;
+    int res = _get_property(dev, cmd, sizeof(cmd), &prop, SDS011_DB3_IDX);
+    *mode = ((prop == 0) ? SDS011_WMODE_SLEEP : SDS011_WMODE_WORK);
+    return res;
+}
+
+int sds011_set_working_mode(sds011_t *dev, sds011_working_mode_t mode)
+{
+    assert(dev != NULL);
+    uint8_t cmd[] = {SDS011_CMD_DB1_SET_SLEEP_WORK, SDS011_CMD_OPT_SET, mode};
+
+    uint8_t recv[SDS011_FRAME_RECV_LEN];
+    int res = _send_recv_cmd(dev, cmd, sizeof(cmd), recv);
+
+    if (res == SDS011_OK) {
+        if ((recv[SDS011_DB2_IDX] == SDS011_CMD_OPT_SET) &&
+            (recv[SDS011_DB3_IDX] == mode)) {
+            return SDS011_OK;
+        }
+
+        return SDS011_ERROR;
+    }
+
+    return res;
+}
+
+int sds011_get_working_period(sds011_t *dev, uint8_t *minutes)
+{
+    assert(dev != NULL);
+    uint8_t cmd[] = {SDS011_CMD_DB1_SET_WORK_PERIOD, SDS011_CMD_OPT_QUERY};
+    return _get_property(dev, cmd, sizeof(cmd), minutes, SDS011_DB3_IDX);
+}
+
+int sds011_set_working_period(sds011_t *dev, uint8_t minutes)
+{
+    assert(dev != NULL);
+    uint8_t cmd[] = {SDS011_CMD_DB1_SET_WORK_PERIOD, SDS011_CMD_OPT_SET, minutes};
+
+    uint8_t recv[SDS011_FRAME_RECV_LEN];
+    int res = _send_recv_cmd(dev, cmd, sizeof(cmd), recv);
+
+    if (res == SDS011_OK) {
+        if ((recv[SDS011_DB2_IDX] == SDS011_CMD_OPT_SET) &&
+            (recv[SDS011_DB3_IDX] == minutes)) {
+            return SDS011_OK;
+        }
+
+        return SDS011_ERROR;
+    }
+
+    return res;
+}
+
+int sds011_get_fw_version(sds011_t *dev, uint8_t *year, uint8_t *mon, uint8_t *day)
+{
+    assert((dev != NULL) && (year != NULL) && (mon != NULL) && (day != NULL));
+    uint8_t cmd[] = {SDS011_CMD_DB1_CHECK_FIRMWARE};
+
+    uint8_t recv[SDS011_FRAME_RECV_LEN];
+    int res = _send_recv_cmd(dev, cmd, sizeof(cmd), recv);
+
+    if (res == SDS011_OK) {
+        *year  = recv[SDS011_DB2_IDX];
+        *mon = recv[SDS011_DB3_IDX];
+        *day = recv[SDS011_DB4_IDX];
+    }
+
+    return res;
+}
diff --git a/drivers/sds011/sds011_saul.c b/drivers/sds011/sds011_saul.c
new file mode 100644
index 0000000000000000000000000000000000000000..d57e125f9e4c415b1d64e9911f6f630381210745
--- /dev/null
+++ b/drivers/sds011/sds011_saul.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 HAW-Hamburg
+ *
+ * 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     drivers_sds011
+ * @{
+ *
+ * @file
+ * @brief       SAUL adaption for SDS011 sensor
+ *
+ * @author      Michel Rottleuthner <michel.rottleuthner@haw-hamburg.de>
+ *
+ * @}
+ */
+
+#include <string.h>
+
+#include "saul.h"
+#include "sds011.h"
+#include "xtimer.h"
+
+static int _read(const void *dev, phydat_t *res)
+{
+    sds011_data_t data;
+
+    if (sds011_read((sds011_t *)dev, &data) == SDS011_OK) {
+       res->val[0] = data.pm_2_5;
+       res->val[1] = data.pm_10;
+       res->unit = UNIT_GPM3;
+       res->scale = -7;
+       return 2;
+    }
+    return ECANCELED;
+}
+
+const saul_driver_t sds011_saul_driver = {
+    .read = _read,
+    .write = saul_notsup,
+    .type = SAUL_SENSE_PM
+};
diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c
index a5c45badece582ba03cec0bcd87e6e1c1f0a01bc..b4e389d95d8dc741338b79459e41fa062a4044ac 100644
--- a/sys/auto_init/auto_init.c
+++ b/sys/auto_init/auto_init.c
@@ -449,6 +449,10 @@ void auto_init(void)
     extern void auto_init_sht3x(void);
     auto_init_sht3x();
 #endif
+#ifdef MODULE_SDS011
+    extern void auto_init_sds011(void);
+    auto_init_sds011();
+#endif
 #ifdef MODULE_SI114X
     extern void auto_init_si114x(void);
     auto_init_si114x();
diff --git a/sys/auto_init/saul/auto_init_sds011.c b/sys/auto_init/saul/auto_init_sds011.c
new file mode 100644
index 0000000000000000000000000000000000000000..f5874b6f491b2c9058fd6692f8a3405eded7da2e
--- /dev/null
+++ b/sys/auto_init/saul/auto_init_sds011.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 HAW-Hamburg
+ *
+ * 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     sys_auto_init_saul
+ * @{
+ *
+ * @file
+ * @brief       Auto initialization for SDS011 particulate matter sensor
+ *
+ * @author      Michel Rottleuthner <michel.rottleuthner@haw-hamburg.de>
+ *
+ * @}
+ */
+
+#ifdef MODULE_SDS011
+
+#include "assert.h"
+#include "log.h"
+#include "saul_reg.h"
+#include "sds011.h"
+#include "sds011_params.h"
+
+/**
+ * @brief   Define the number of configured sensors
+ */
+#define SDS011_NUM     (sizeof(sds011_params) / sizeof(sds011_params[0]))
+
+/**
+ * @brief   Allocate memory for the device descriptors
+ */
+static sds011_t sds011_devs[SDS011_NUM];
+
+/**
+ * @brief   Memory for the SAUL registry entries
+ */
+static saul_reg_t saul_entries[SDS011_NUM];
+
+/**
+ * @brief   Define the number of saul info
+ */
+#define SDS011_INFO_NUM (sizeof(sds011_saul_info) / sizeof(sds011_saul_info[0]))
+
+/**
+ * @name    Import SAUL endpoint
+ * @{
+ */
+extern const saul_driver_t sds011_saul_driver;
+/** @} */
+
+void auto_init_sds011(void)
+{
+    assert(SDS011_INFO_NUM == SDS011_NUM);
+
+    for (unsigned int i = 0; i < SDS011_NUM; i++) {
+        LOG_DEBUG("[auto_init_saul] initializing sds011 #%u\n", i);
+
+        if (sds011_init(&sds011_devs[i], &sds011_params[i]) != SDS011_OK) {
+            LOG_ERROR("[auto_init_saul] error initializing sds011 #%u\n", i);
+            continue;
+        }
+
+        int retries = 0;
+
+        /* sensor must be set to query mode for manual reading by saul */
+        while (sds011_set_reporting_mode(&sds011_devs[i], SDS011_RMODE_QUERY) != SDS011_OK) {
+            if (retries++ >= 3) {
+                LOG_ERROR("[auto_init_saul] error setting sds011 to query mode #%u\n", i);
+                continue;
+            }
+        }
+
+        saul_entries[i].dev = &(sds011_devs[i]);
+        saul_entries[i].name = sds011_saul_info[i].name;
+        saul_entries[i].driver = &sds011_saul_driver;
+        saul_reg_add(&saul_entries[i]);
+    }
+}
+
+#else
+typedef int dont_be_pedantic;
+#endif /* MODULE_SDS011 */
diff --git a/sys/include/phydat.h b/sys/include/phydat.h
index 8a4e161f180d49e6c53f8bdde37683975ffe8711..e57a517c5436596b62e0d7a8e42242d1855282f6 100644
--- a/sys/include/phydat.h
+++ b/sys/include/phydat.h
@@ -110,7 +110,9 @@ enum {
     UNIT_PPB,       /**< part per billion */
     /* aggregate values */
     UNIT_TIME,      /**< the three dimensions contain sec, min, and hours */
-    UNIT_DATE       /**< the 3 dimensions contain days, months and years */
+    UNIT_DATE,      /**< the 3 dimensions contain days, months and years */
+    /* mass concentration */
+    UNIT_GPM3       /**< grams per cubic meters */
     /* extend this list as needed */
 };
 
diff --git a/sys/phydat/phydat_str.c b/sys/phydat/phydat_str.c
index 27916b31bea712c47591fc1bf23e60fba1d28bbd..f450c9aa65938d9944ee41c23b2d2938073cb3e2 100644
--- a/sys/phydat/phydat_str.c
+++ b/sys/phydat/phydat_str.c
@@ -102,6 +102,7 @@ const char *phydat_unit_to_str(uint8_t unit)
         case UNIT_PERCENT:  return "%";
         case UNIT_CTS:      return "cts";
         case UNIT_COULOMB:  return "C";
+        case UNIT_GPM3:     return "g/m^3";
         default:            return "";
     }
 }
diff --git a/tests/driver_sds011/Makefile b/tests/driver_sds011/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..03a1d7827e662dc19d3a1006cbd6ff3cfcfe891e
--- /dev/null
+++ b/tests/driver_sds011/Makefile
@@ -0,0 +1,6 @@
+include ../Makefile.tests_common
+
+USEMODULE += sds011
+USEMODULE += xtimer
+
+include $(RIOTBASE)/Makefile.include
diff --git a/tests/driver_sds011/main.c b/tests/driver_sds011/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..7d5b83c12373cc736ab77aaa0b56d77600a08c04
--- /dev/null
+++ b/tests/driver_sds011/main.c
@@ -0,0 +1,236 @@
+/**
+ * Copyright (C) 2018 HAW-Hamburg
+ *
+ * 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     tests
+ * @{
+ *
+ * @file
+ * @brief       Test application for the SDS011 Laser Dust Sensor driver
+ *
+ * @author      Michel Rottleuthner <michel.rottleuthner@haw-hamburg.de>
+ *
+ * @}
+ */
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#include "xtimer.h"
+
+#include "sds011.h"
+#include "sds011_params.h"
+#include "msg.h"
+#include "thread.h"
+
+#define ACTIVE_REPORTING_TEST_CNT                (20U)
+#define PUT_TO_QUERY_MODE_RETRIES                (3U)
+#define PUT_TO_QUERY_MODE_RETRY_TIMEOUT_MS       (100U)
+#define MANUAL_QUERY_CNT                         (10U)
+#define WORKING_PERIOD                           (0U)
+
+/**
+ * @brief   Allocate the device descriptor
+ */
+static sds011_t dev;
+
+static char* _rmode_str(sds011_reporting_mode_t rmode)
+{
+    switch (rmode) {
+        case SDS011_RMODE_ACTIVE:
+            return "ACTIVE";
+        case SDS011_RMODE_QUERY:
+            return "QUERY";
+        default:
+            return "INVALID";
+    }
+}
+
+static char* _wmode_str(sds011_working_mode_t wmode)
+{
+    switch (wmode) {
+        case SDS011_WMODE_WORK:
+            return "WORK";
+        case SDS011_WMODE_SLEEP:
+            return "SLEEP";
+        default:
+            return "INVALID";
+    }
+}
+
+static void _print_measurement(sds011_data_t *data)
+{
+    uint16_t pm10_ug_int = data->pm_10 / 10;
+    uint16_t pm10_ug_dec = data->pm_10 - 10 * pm10_ug_int;
+    uint16_t pm2_5_ug_int = data->pm_2_5 / 10;
+    uint16_t pm2_5_ug_dec = data->pm_2_5 - 10 * pm2_5_ug_int;
+    printf("==> PM2.5: %d.%0d ug/m^3 | PM10: %d.%0d ug/m^3\n",
+           pm2_5_ug_int, pm2_5_ug_dec, pm10_ug_int, pm10_ug_dec);
+}
+
+void measure_cb(sds011_data_t *data, void *ctx)
+{
+    msg_t msg = { .content.value = (((uint32_t)data->pm_10) << 16 | data->pm_2_5) };
+    kernel_pid_t target_pid = (int)ctx;
+    msg_send(&msg, target_pid);
+}
+
+int main(void)
+{
+    unsigned retry_cnt = 0;
+    uint8_t year;
+    uint8_t month;
+    uint8_t day;
+    sds011_reporting_mode_t rmode;
+    sds011_working_mode_t wmode;
+    uint8_t minutes;
+    sds011_data_t data;
+
+    puts("SDS011 test application");
+
+    /* initialize the driver */
+    if (sds011_init(&dev, &sds011_params[0]) == SDS011_OK) {
+        puts("init [OK]");
+    }
+    else {
+        puts("initalization [ERROR]");
+        return -1;
+    }
+
+    printf("setting reporting mode to '%s'...\n", _rmode_str(SDS011_RMODE_QUERY));
+
+    /* set the sensor to query mode to disable active reporting messages
+       -> to work correctly, this step may need to be repeated if the automatic
+          output is incoming while the reply is expected */
+    while (sds011_set_reporting_mode(&dev, SDS011_RMODE_QUERY) != SDS011_OK) {
+        if (retry_cnt++ >= PUT_TO_QUERY_MODE_RETRIES) {
+            puts("[ERROR]");
+            return -1;
+        }
+        xtimer_usleep(PUT_TO_QUERY_MODE_RETRY_TIMEOUT_MS * 1000);
+        puts("[RETRY]");
+    }
+
+    puts("[OK]");
+    puts("getting reporting mode from device...");
+
+    if (sds011_get_reporting_mode(&dev, &rmode) == SDS011_OK) {
+        printf("[OK] => mode: %s\n", _rmode_str(rmode));
+        if (rmode != SDS011_RMODE_QUERY) {
+            puts("mismatch! [ERROR]");
+            return -1;
+        }
+    }
+    else {
+        puts("[ERROR]");
+        return -1;
+    }
+
+    puts("getting firmware version...");
+
+    if (sds011_get_fw_version(&dev, &year, &month, &day) == SDS011_OK) {
+        printf("[OK] => %d.%d.%d\n", year, month, day);
+    }
+    else {
+        puts("[ERROR]");
+        return -1;
+    }
+
+    printf("setting working mode to '%s'...\n", _wmode_str(SDS011_WMODE_WORK));
+
+    if (sds011_set_working_mode(&dev, SDS011_WMODE_WORK) == SDS011_OK) {
+        puts("[OK]");
+    }
+    else {
+        puts("[ERROR]");
+        return -1;
+    }
+
+    puts("getting working mode from device...");
+
+    if (sds011_get_working_mode(&dev, &wmode) == SDS011_OK) {
+        printf("[OK] => mode: %s\n", _wmode_str(wmode));
+        if (wmode != SDS011_WMODE_WORK) {
+            puts("mismatch! [ERROR]");
+            return -1;
+        }
+    }
+    else {
+        puts("[ERROR]");
+        return -1;
+    }
+
+    printf("setting working period to %u...\n", WORKING_PERIOD);
+
+    if (sds011_set_working_period(&dev, WORKING_PERIOD) == SDS011_OK) {
+        puts("[OK]");
+    }
+    else {
+        puts("[ERROR]");
+        return 1;
+    }
+
+    if (sds011_get_working_period(&dev, &minutes) == SDS011_OK) {
+        printf("[OK] => working period: %u\n", minutes);
+        if (minutes != WORKING_PERIOD) {
+            puts("mismatch! [ERROR]");
+            return -1;
+        }
+    }
+    else {
+        puts("[ERROR]");
+        return -1;
+    }
+
+    for (unsigned i = 0; i < MANUAL_QUERY_CNT; i++) {
+        if (sds011_read(&dev, &data) == SDS011_OK) {
+            printf("manual query %2u/%u [OK]: ", i + 1, MANUAL_QUERY_CNT);
+            _print_measurement(&data);
+        }
+    }
+
+    sds011_register_callback(&dev, measure_cb, (void*)(int)thread_getpid());
+
+    printf("switching to active reporting mode for %u measurements...\n",
+            ACTIVE_REPORTING_TEST_CNT);
+
+    if (sds011_set_reporting_mode(&dev, SDS011_RMODE_ACTIVE) == SDS011_OK) {
+        puts("[OK]");
+    }
+    else {
+        puts("[ERROR]");
+        return -1;
+    }
+
+    /* wait a little bit so the callback gets executed a few times */
+    msg_t msg;
+    for(unsigned msg_cnt = 0; msg_cnt < ACTIVE_REPORTING_TEST_CNT; msg_cnt++){
+        msg_receive(&msg);
+        sds011_data_t data;
+        data.pm_10 = msg.content.value >> 16;
+        data.pm_2_5 = msg.content.value & 0xFFFF;
+        printf("msg from callback: ");
+        _print_measurement(&data);
+    }
+
+    /* unregister callback */
+    sds011_register_callback(&dev, NULL, NULL);
+
+    puts("switching to sleep mode...");
+
+    if (sds011_set_working_mode(&dev, SDS011_WMODE_SLEEP) == SDS011_OK) {
+        puts("[OK]");
+    }
+    else {
+        puts("[ERROR]");
+        return -1;
+    }
+
+    puts("[SUCCESS]");
+    return 0;
+}