diff --git a/docs/source/backends.rst b/docs/source/backends.rst
index 1d83b38d0..eb26189dc 100644
--- a/docs/source/backends.rst
+++ b/docs/source/backends.rst
@@ -14,6 +14,8 @@ Backends
     platypush/backend/bluetooth.rst
     platypush/backend/bluetooth.fileserver.rst
     platypush/backend/bluetooth.pushserver.rst
+    platypush/backend/bluetooth.scanner.rst
+    platypush/backend/bluetooth.scanner.ble.rst
     platypush/backend/button.flic.rst
     platypush/backend/camera.pi.rst
     platypush/backend/chat.telegram.rst
diff --git a/docs/source/platypush/backend/bluetooth.scanner.ble.rst b/docs/source/platypush/backend/bluetooth.scanner.ble.rst
new file mode 100644
index 000000000..f75c64138
--- /dev/null
+++ b/docs/source/platypush/backend/bluetooth.scanner.ble.rst
@@ -0,0 +1,5 @@
+``platypush.backend.bluetooth.scanner.ble``
+===========================================
+
+.. automodule:: platypush.backend.bluetooth.scanner.ble
+    :members:
diff --git a/docs/source/platypush/backend/bluetooth.scanner.rst b/docs/source/platypush/backend/bluetooth.scanner.rst
new file mode 100644
index 000000000..ef00ee42a
--- /dev/null
+++ b/docs/source/platypush/backend/bluetooth.scanner.rst
@@ -0,0 +1,5 @@
+``platypush.backend.bluetooth.scanner``
+=======================================
+
+.. automodule:: platypush.backend.bluetooth.scanner
+    :members:
diff --git a/platypush/backend/bluetooth/scanner/__init__.py b/platypush/backend/bluetooth/scanner/__init__.py
new file mode 100644
index 000000000..77821abbb
--- /dev/null
+++ b/platypush/backend/bluetooth/scanner/__init__.py
@@ -0,0 +1,47 @@
+from typing import Dict, Optional
+
+from platypush.backend.sensor import SensorBackend
+from platypush.message.event.bluetooth import BluetoothDeviceFoundEvent, BluetoothDeviceLostEvent
+
+
+class BluetoothScannerBackend(SensorBackend):
+    """
+    This backend periodically scans for available bluetooth devices and returns events when a devices enter or exits
+    the range.
+
+    Triggers:
+
+        * :class:`platypush.message.event.bluetooth.BluetoothDeviceFoundEvent` when a new bluetooth device is found.
+        * :class:`platypush.message.event.bluetooth.BluetoothDeviceLostEvent` when a bluetooth device is lost.
+
+    Requires:
+
+        * The :class:`platypush.plugins.bluetooth.BluetoothPlugin` plugin working.
+
+    """
+
+    def __init__(self, device_id: Optional[int] = None, scan_interval: int = 10, **kwargs):
+        """
+        :param device_id: Bluetooth adapter ID to use (default configured on the ``bluetooth`` plugin if None).
+        :param scan_interval:  How long the scan should run (default: 10 seconds).
+        """
+        super().__init__(plugin='bluetooth', plugin_args={
+            'device_id': device_id,
+            'duration': scan_interval,
+        }, **kwargs)
+
+        self._last_seen_devices = {}
+
+    def process_data(self, data: Dict[str, dict], new_data: Dict[str, dict]):
+        for addr, dev in data.items():
+            if addr not in self._last_seen_devices:
+                self.bus.post(BluetoothDeviceFoundEvent(address=dev.pop('addr'), **dev))
+            self._last_seen_devices[addr] = {'addr': addr, **dev}
+
+        for addr, dev in self._last_seen_devices.copy().items():
+            if addr not in data:
+                self.bus.post(BluetoothDeviceLostEvent(address=dev.pop('addr'), **dev))
+                del self._last_seen_devices[addr]
+
+
+# vim:sw=4:ts=4:et:
diff --git a/platypush/backend/bluetooth/scanner/ble.py b/platypush/backend/bluetooth/scanner/ble.py
new file mode 100644
index 000000000..3a66e41fd
--- /dev/null
+++ b/platypush/backend/bluetooth/scanner/ble.py
@@ -0,0 +1,33 @@
+from typing import Optional
+
+from platypush.backend.bluetooth.scanner import BluetoothScannerBackend
+
+
+class BluetoothBleScannerBackend(BluetoothScannerBackend):
+    """
+    This backend periodically scans for available bluetooth low-energy devices and returns events when a devices enter
+    or exits the range.
+
+    Triggers:
+
+        * :class:`platypush.message.event.bluetooth.BluetoothDeviceFoundEvent` when a new bluetooth device is found.
+        * :class:`platypush.message.event.bluetooth.BluetoothDeviceLostEvent` when a bluetooth device is lost.
+
+    Requires:
+
+        * The :class:`platypush.plugins.bluetooth.BluetoothBlePlugin` plugin working.
+
+    """
+
+    def __init__(self, interface: Optional[int] = None, scan_interval: int = 10, **kwargs):
+        """
+        :param interface: Bluetooth adapter name to use (default configured on the ``bluetooth.ble`` plugin if None).
+        :param scan_interval:  How long the scan should run (default: 10 seconds).
+        """
+        super().__init__(plugin='bluetooth.ble', plugin_args={
+            'interface': interface,
+            'duration': scan_interval,
+        }, **kwargs)
+
+
+# vim:sw=4:ts=4:et:
diff --git a/platypush/backend/sensor/__init__.py b/platypush/backend/sensor/__init__.py
index 0a2bf887e..966176bdc 100644
--- a/platypush/backend/sensor/__init__.py
+++ b/platypush/backend/sensor/__init__.py
@@ -158,7 +158,9 @@ class SensorBackend(Backend):
                     except (TypeError, ValueError):
                         pass
 
-            if tolerance is None or is_nan or abs(v - old_v) >= tolerance:
+                if tolerance is None or abs(v - old_v) >= tolerance:
+                    ret[k] = v
+            elif k not in self.data or self.data[k] != v:
                 ret[k] = v
 
         return ret
@@ -171,6 +173,10 @@ class SensorBackend(Backend):
         if plugin and hasattr(plugin, 'close'):
             plugin.close()
 
+    def process_data(self, data, new_data):
+        if new_data:
+            self.bus.post(SensorDataChangeEvent(data=data, source=self.plugin or self.__class__.__name__))
+
     def run(self):
         super().run()
         self.logger.info('Initialized {} sensor backend'.format(self.__class__.__name__))
@@ -179,9 +185,7 @@ class SensorBackend(Backend):
             try:
                 data = self.get_measurement()
                 new_data = self.get_new_data(data)
-
-                if new_data:
-                    self.bus.post(SensorDataChangeEvent(data=new_data))
+                self.process_data(data, new_data)
 
                 data_below_threshold = {}
                 data_above_threshold = {}
diff --git a/platypush/message/event/bluetooth.py b/platypush/message/event/bluetooth.py
index dd5940782..b70f68489 100644
--- a/platypush/message/event/bluetooth.py
+++ b/platypush/message/event/bluetooth.py
@@ -1,3 +1,5 @@
+from typing import Optional
+
 from platypush.message.event import Event
 
 
@@ -5,6 +7,22 @@ class BluetoothEvent(Event):
     pass
 
 
+class BluetoothDeviceFoundEvent(Event):
+    """
+    Event triggered when a bluetooth device is found during a scan.
+    """
+    def __init__(self, address: str, name: Optional[str] = None, *args, **kwargs):
+        super().__init__(*args, address=address, name=name, **kwargs)
+
+
+class BluetoothDeviceLostEvent(Event):
+    """
+    Event triggered when a bluetooth device previously scanned is lost.
+    """
+    def __init__(self, address: str, name: Optional[str] = None, *args, **kwargs):
+        super().__init__(*args, address=address, name=name, **kwargs)
+
+
 class BluetoothDeviceConnectedEvent(Event):
     """
     Event triggered on bluetooth device connection
diff --git a/platypush/message/event/sensor/__init__.py b/platypush/message/event/sensor/__init__.py
index c145e0750..b10672eee 100644
--- a/platypush/message/event/sensor/__init__.py
+++ b/platypush/message/event/sensor/__init__.py
@@ -1,3 +1,5 @@
+from typing import Optional
+
 from platypush.message.event import Event
 
 
@@ -6,14 +8,14 @@ class SensorDataChangeEvent(Event):
     Event triggered when a sensor has new data
     """
 
-    def __init__(self, data, *args, **kwargs):
+    def __init__(self, data, source: Optional[str] = None, *args, **kwargs):
         """
         :param data: Sensor data
-        :type data: object
         """
 
-        super().__init__(data=data, *args, **kwargs)
+        super().__init__(data=data, source=source, *args, **kwargs)
         self.data = data
+        self.source = source
 
 
 class SensorDataAboveThresholdEvent(Event):
@@ -24,7 +26,6 @@ class SensorDataAboveThresholdEvent(Event):
     def __init__(self, data, *args, **kwargs):
         """
         :param data: Sensor data
-        :type data: object
         """
 
         super().__init__(data=data, *args, **kwargs)
@@ -39,7 +40,6 @@ class SensorDataBelowThresholdEvent(Event):
     def __init__(self, data, *args, **kwargs):
         """
         :param data: Sensor data
-        :type data: object
         """
 
         super().__init__(data=data, *args, **kwargs)
diff --git a/platypush/plugins/bluetooth/__init__.py b/platypush/plugins/bluetooth/__init__.py
index 16495d4cf..1d4e3ba20 100644
--- a/platypush/plugins/bluetooth/__init__.py
+++ b/platypush/plugins/bluetooth/__init__.py
@@ -3,12 +3,16 @@ import os
 import re
 import select
 
-from platypush.plugins import Plugin, action
+from typing import Dict, Optional
+
+from platypush.plugins.sensor import SensorPlugin
+
+from platypush.plugins import action
 from platypush.message.response.bluetooth import BluetoothScanResponse, \
     BluetoothLookupNameResponse, BluetoothLookupServiceResponse, BluetoothResponse
 
 
-class BluetoothPlugin(Plugin):
+class BluetoothPlugin(SensorPlugin):
     """
     Bluetooth plugin
 
@@ -67,7 +71,7 @@ class BluetoothPlugin(Plugin):
         return self.lookup_address(device).output['addr']
 
     @action
-    def scan(self, device_id: int = None, duration: int = 10) -> BluetoothScanResponse:
+    def scan(self, device_id: Optional[int] = None, duration: int = 10) -> BluetoothScanResponse:
         """
         Scan for nearby bluetooth devices
 
@@ -79,7 +83,7 @@ class BluetoothPlugin(Plugin):
         if device_id is None:
             device_id = self.device_id
 
-        self.logger.info('Discovering devices on adapter {}, duration: {} seconds'.format(
+        self.logger.debug('Discovering devices on adapter {}, duration: {} seconds'.format(
             device_id, duration))
 
         devices = discover_devices(duration=duration, lookup_names=True, lookup_class=True, device_id=device_id,
@@ -91,6 +95,19 @@ class BluetoothPlugin(Plugin):
         self._devices_by_name = {dev['name']: dev for dev in self._devices if dev.get('name')}
         return response
 
+    @action
+    def get_measurement(self, device_id: Optional[int] = None, duration: Optional[int] = 10, *args, **kwargs) \
+            -> Dict[str, dict]:
+        """
+        Wrapper for ``scan`` that returns bluetooth devices in a format usable by sensor backends.
+
+        :param device_id: Bluetooth adapter ID to use (default configured if None)
+        :param duration: Scan duration in seconds
+        :return: Device address -> info map.
+        """
+        devices = self.scan(device_id=device_id, duration=duration).output
+        return {device['addr']: device for device in devices}
+
     @action
     def lookup_name(self, addr: str, timeout: int = 10) -> BluetoothLookupNameResponse:
         """
@@ -280,7 +297,7 @@ class BluetoothPlugin(Plugin):
         sock = self._get_sock(device=device, port=port, service_uuid=service_uuid, service_name=service_name)
 
         if not sock:
-            self.logger.info('Close on device {}({}) that is not connected'.format(device, port))
+            self.logger.debug('Close on device {}({}) that is not connected'.format(device, port))
             return
 
         try: