From 96c175602f54134946a0f1f7a101fa2d579fb671 Mon Sep 17 00:00:00 2001 From: Felix Seele Date: Sun, 26 Mar 2017 00:16:29 +0100 Subject: [PATCH 01/13] Added eddystone_temperature platform. --- .../sensor/eddystone_temperature.py | 280 ++++++++++++++++++ homeassistant/const.py | 4 + requirements_all.txt | 1 + 3 files changed, 285 insertions(+) create mode 100644 homeassistant/components/sensor/eddystone_temperature.py diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py new file mode 100644 index 00000000000000..bd75245fe16406 --- /dev/null +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -0,0 +1,280 @@ +""" +Read temperature information from the TLM frame broadcasted by Eddystone +beacons. +Your beacons must be configured to transmit UID (for identification) and TLM +(for temperature) frames. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.eddystone_temperature/ + +Original version of this code (for Skybeacons) by anpetrov. +https://github.com/anpetrov/skybeacon +""" +import logging +import threading +import struct +import binascii + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_NAME, TEMP_CELSIUS, STATE_UNKNOWN, EVENT_HOMEASSISTANT_STOP, + CONF_NAMESPACE, CONF_INSTANCE, CONF_BT_DEVICE_ID, CONF_BEACONS) + +REQUIREMENTS = ['pybluez==0.22'] + +_LOGGER = logging.getLogger(__name__) + +BEACON_SCHEMA = vol.Schema({ + vol.Required(CONF_NAMESPACE): cv.string, + vol.Required(CONF_INSTANCE): cv.string, + vol.Optional(CONF_NAME): cv.string +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_BT_DEVICE_ID, default=0): cv.positive_int, + vol.Required(CONF_BEACONS): vol.Schema({cv.string: BEACON_SCHEMA}), +}) + +LE_META_EVENT = 0x3e +OGF_LE_CTL = 0x08 +OCF_LE_SET_SCAN_ENABLE = 0x000C +EVT_LE_ADVERTISING_REPORT = 0x02 + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Validate configuration, create devices and start monitoring thread""" + + _LOGGER.debug("Setting up...") + + bt_device_id = config.get("bt_device_id") + + beacons = config.get("beacons") + devices = [] + + mon = Monitor(hass, bt_device_id) + + for dev_name, properties in beacons.items(): + namespace = convert_hex_string(properties, "namespace", 20) + instance = convert_hex_string(properties, "instance", 12) + name = properties.get(CONF_NAME, dev_name) + + if instance is None or namespace is None: + _LOGGER.error("Skipping %s", dev_name) + continue + else: + devices.append(EddystoneTemp(name, namespace, instance, mon)) + + def monitor_stop(_service_or_event): + """Stop the monitor thread.""" + _LOGGER.info("Stopping scanner for eddystone beacons") + mon.terminate() + + + if len(devices) > 0: + mon.devices = devices + add_devices(devices) + mon.start() + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, monitor_stop) + else: + _LOGGER.warning("No devices were added") + + +def convert_hex_string(config, config_key, length): + """Retrieve value from config, validate its length and + convert to binary string.""" + + string = config.get(config_key) + if len(string) != length: + _LOGGER.error("Error in config parameter \"%s\": Must be exactly %d " + "bytes. Device will not be added.", + config_key, length/2) + return None + else: + return binascii.unhexlify(string) + + +class EddystoneTemp(Entity): + """Representation of a temperature sensor.""" + + def __init__(self, name, namespace, instance, mon): + """Initialize a sensor.""" + self.mon = mon + self._name = name + self.namespace = namespace + self.instance = instance + self.bt_addr = None + self.temperature = STATE_UNKNOWN + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the device.""" + return self.temperature + + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return TEMP_CELSIUS + + +class Monitor(threading.Thread): + """Continously scan for BLE advertisements.""" + + def __init__(self, hass, bt_device_id): + """Construct interface object.""" + threading.Thread.__init__(self) + self.daemon = False + self.hass = hass + self.keep_going = True + + # list of beacons to monitor + self.devices = [] + # number of the bt device (hciX) + self.bt_device_id = bt_device_id + # bt socket + self.socket = None + + + def run(self): + """Continously scan for BLE advertisements.""" + + # pylint: disable=import-error + import bluetooth._bluetooth as bluez + + self.socket = bluez.hci_open_dev(self.bt_device_id) + self.toggle_scan(True) + + try: + filtr = bluez.hci_filter_new() + bluez.hci_filter_all_events(filtr) + bluez.hci_filter_set_ptype(filtr, bluez.HCI_EVENT_PKT) + self.socket.setsockopt(bluez.SOL_HCI, bluez.HCI_FILTER, filtr) + + _LOGGER.debug("Scanner started") + + while self.keep_going: + + pkt = self.socket.recv(255) + event = pkt[1] + subevent = pkt[3] + if event == LE_META_EVENT and subevent == EVT_LE_ADVERTISING_REPORT: + # we have an BLE advertisement + self.process_packet(pkt) + except: + _LOGGER.error("Exception while scanning for beacons", exc_info=True) + raise + finally: + self.toggle_scan(False) + + def toggle_scan(self, enable): + """ Enable and disable BLE scanning """ + # pylint: disable=import-error + import bluetooth._bluetooth as bluez + + if enable: + command = struct.pack(" Date: Sun, 26 Mar 2017 00:27:28 +0100 Subject: [PATCH 02/13] Fixed style issues. --- .../sensor/eddystone_temperature.py | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py index bd75245fe16406..c4cd1d3b434c7b 100644 --- a/homeassistant/components/sensor/eddystone_temperature.py +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -1,6 +1,5 @@ -""" -Read temperature information from the TLM frame broadcasted by Eddystone -beacons. +"""Read temperature information from Eddystone beacons. + Your beacons must be configured to transmit UID (for identification) and TLM (for temperature) frames. @@ -47,8 +46,7 @@ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Validate configuration, create devices and start monitoring thread""" - + """Validate configuration, create devices and start monitoring thread.""" _LOGGER.debug("Setting up...") bt_device_id = config.get("bt_device_id") @@ -74,7 +72,6 @@ def monitor_stop(_service_or_event): _LOGGER.info("Stopping scanner for eddystone beacons") mon.terminate() - if len(devices) > 0: mon.devices = devices add_devices(devices) @@ -85,9 +82,7 @@ def monitor_stop(_service_or_event): def convert_hex_string(config, config_key, length): - """Retrieve value from config, validate its length and - convert to binary string.""" - + """Retrieve value from config, validate its length and convert to binary string.""" string = config.get(config_key) if len(string) != length: _LOGGER.error("Error in config parameter \"%s\": Must be exactly %d " @@ -166,17 +161,19 @@ def run(self): pkt = self.socket.recv(255) event = pkt[1] subevent = pkt[3] - if event == LE_META_EVENT and subevent == EVT_LE_ADVERTISING_REPORT: + if event == LE_META_EVENT \ + and subevent == EVT_LE_ADVERTISING_REPORT: # we have an BLE advertisement self.process_packet(pkt) except: - _LOGGER.error("Exception while scanning for beacons", exc_info=True) + _LOGGER.error("Exception while scanning for beacons", + exc_info=True) raise finally: self.toggle_scan(False) def toggle_scan(self, enable): - """ Enable and disable BLE scanning """ + """Enable and disable BLE scanning.""" # pylint: disable=import-error import bluetooth._bluetooth as bluez @@ -188,7 +185,8 @@ def toggle_scan(self, enable): def process_packet(self, pkt): - """ Processes an BLE advertisement packet. + """Process an BLE advertisement packet. + First, we look for the unique ID which identifies Eddystone beacons. All other packets will be ignored. We then filter for UID and TLM frames. See https://github.com/google/eddystone/ for reference. @@ -204,7 +202,6 @@ def process_packet(self, pkt): belongs to the beacon monitored. If yes, we can finally extract the temperature. """ - bt_addr = pkt[7:13] # strip bluetooth address and start parsing "length-type-value" @@ -212,7 +209,6 @@ def process_packet(self, pkt): pkt = pkt[14:] for type_, data in self.parse_structure(pkt): # type 0x16: service data, 0xaa 0xfe: eddystone UUID - #_LOGGER.debug("_type: %s data: %s", binascii.hexlify(type_), binascii.hexlify(data)) if type_ == 0x16 and data[:2] == b"\xaa\xfe": # found eddystone beacon if data[2] == 0x00: @@ -234,12 +230,14 @@ def process_packet(self, pkt): if device is not None: # TLM frame from target beacon temp = struct.unpack(" Date: Sun, 26 Mar 2017 00:30:39 +0100 Subject: [PATCH 03/13] Fixed style issues #2. --- .../components/sensor/eddystone_temperature.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py index c4cd1d3b434c7b..42f115801f5cf0 100644 --- a/homeassistant/components/sensor/eddystone_temperature.py +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -82,7 +82,7 @@ def monitor_stop(_service_or_event): def convert_hex_string(config, config_key, length): - """Retrieve value from config, validate its length and convert to binary string.""" + """Retrieve value from config and convert to binary string.""" string = config.get(config_key) if len(string) != length: _LOGGER.error("Error in config parameter \"%s\": Must be exactly %d " @@ -138,10 +138,8 @@ def __init__(self, hass, bt_device_id): # bt socket self.socket = None - def run(self): """Continously scan for BLE advertisements.""" - # pylint: disable=import-error import bluetooth._bluetooth as bluez @@ -162,7 +160,7 @@ def run(self): event = pkt[1] subevent = pkt[3] if event == LE_META_EVENT \ - and subevent == EVT_LE_ADVERTISING_REPORT: + and subevent == EVT_LE_ADVERTISING_REPORT: # we have an BLE advertisement self.process_packet(pkt) except: @@ -181,8 +179,8 @@ def toggle_scan(self, enable): command = struct.pack(" Date: Sun, 26 Mar 2017 00:32:54 +0100 Subject: [PATCH 04/13] Fixed style issues #3. --- homeassistant/components/sensor/eddystone_temperature.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py index 42f115801f5cf0..712933363ac002 100644 --- a/homeassistant/components/sensor/eddystone_temperature.py +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -160,7 +160,7 @@ def run(self): event = pkt[1] subevent = pkt[3] if event == LE_META_EVENT \ - and subevent == EVT_LE_ADVERTISING_REPORT: + and subevent == EVT_LE_ADVERTISING_REPORT: # we have an BLE advertisement self.process_packet(pkt) except: @@ -179,7 +179,7 @@ def toggle_scan(self, enable): command = struct.pack(" Date: Sun, 26 Mar 2017 00:54:39 +0100 Subject: [PATCH 05/13] Added new platform to .coveragerc --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 91cb5ef640493f..21938f4a56aae7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -332,6 +332,7 @@ omit = homeassistant/components/sensor/dovado.py homeassistant/components/sensor/dte_energy_bridge.py homeassistant/components/sensor/ebox.py + homeassistant/components/sensor/eddystone_temperature.py homeassistant/components/sensor/eliqonline.py homeassistant/components/sensor/emoncms.py homeassistant/components/sensor/fastdotcom.py From 581ad6f07392523ec2f867ea51c942e3404b05ff Mon Sep 17 00:00:00 2001 From: Felix Seele Date: Mon, 3 Apr 2017 03:37:02 +0200 Subject: [PATCH 06/13] Refactored platform to use the beacontools package. --- .../sensor/eddystone_temperature.py | 202 ++++-------------- requirements_all.txt | 4 +- 2 files changed, 46 insertions(+), 160 deletions(-) diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py index 712933363ac002..870ae1285d4440 100644 --- a/homeassistant/components/sensor/eddystone_temperature.py +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -11,8 +11,6 @@ """ import logging import threading -import struct -import binascii import voluptuous as vol @@ -23,7 +21,7 @@ CONF_NAME, TEMP_CELSIUS, STATE_UNKNOWN, EVENT_HOMEASSISTANT_STOP, CONF_NAMESPACE, CONF_INSTANCE, CONF_BT_DEVICE_ID, CONF_BEACONS) -REQUIREMENTS = ['pybluez==0.22'] +REQUIREMENTS = ['beacontools[scan]==0.1.2'] _LOGGER = logging.getLogger(__name__) @@ -54,26 +52,25 @@ def setup_platform(hass, config, add_devices, discovery_info=None): beacons = config.get("beacons") devices = [] - mon = Monitor(hass, bt_device_id) for dev_name, properties in beacons.items(): - namespace = convert_hex_string(properties, "namespace", 20) - instance = convert_hex_string(properties, "instance", 12) + namespace = get_from_conf(properties, "namespace", 20) + instance = get_from_conf(properties, "instance", 12) name = properties.get(CONF_NAME, dev_name) if instance is None or namespace is None: _LOGGER.error("Skipping %s", dev_name) continue else: - devices.append(EddystoneTemp(name, namespace, instance, mon)) - - def monitor_stop(_service_or_event): - """Stop the monitor thread.""" - _LOGGER.info("Stopping scanner for eddystone beacons") - mon.terminate() + devices.append(EddystoneTemp(name, namespace, instance)) if len(devices) > 0: - mon.devices = devices + mon = Monitor(hass, devices, bt_device_id) + def monitor_stop(_service_or_event): + """Stop the monitor thread.""" + _LOGGER.info("Stopping scanner for eddystone beacons") + mon.stop() + add_devices(devices) mon.start() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, monitor_stop) @@ -81,8 +78,8 @@ def monitor_stop(_service_or_event): _LOGGER.warning("No devices were added") -def convert_hex_string(config, config_key, length): - """Retrieve value from config and convert to binary string.""" +def get_from_conf(config, config_key, length): + """Retrieve value from config and validate length.""" string = config.get(config_key) if len(string) != length: _LOGGER.error("Error in config parameter \"%s\": Must be exactly %d " @@ -90,15 +87,14 @@ def convert_hex_string(config, config_key, length): config_key, length/2) return None else: - return binascii.unhexlify(string) + return string class EddystoneTemp(Entity): """Representation of a temperature sensor.""" - def __init__(self, name, namespace, instance, mon): + def __init__(self, name, namespace, instance): """Initialize a sensor.""" - self.mon = mon self._name = name self.namespace = namespace self.instance = instance @@ -121,160 +117,48 @@ def unit_of_measurement(self): return TEMP_CELSIUS -class Monitor(threading.Thread): +class Monitor(object): """Continously scan for BLE advertisements.""" - def __init__(self, hass, bt_device_id): + def __init__(self, hass, devices, bt_device_id): """Construct interface object.""" - threading.Thread.__init__(self) - self.daemon = False self.hass = hass - self.keep_going = True # list of beacons to monitor - self.devices = [] + self.devices = devices # number of the bt device (hciX) self.bt_device_id = bt_device_id - # bt socket - self.socket = None - def run(self): - """Continously scan for BLE advertisements.""" - # pylint: disable=import-error - import bluetooth._bluetooth as bluez - - self.socket = bluez.hci_open_dev(self.bt_device_id) - self.toggle_scan(True) - - try: - filtr = bluez.hci_filter_new() - bluez.hci_filter_all_events(filtr) - bluez.hci_filter_set_ptype(filtr, bluez.HCI_EVENT_PKT) - self.socket.setsockopt(bluez.SOL_HCI, bluez.HCI_FILTER, filtr) - - _LOGGER.debug("Scanner started") - - while self.keep_going: - - pkt = self.socket.recv(255) - event = pkt[1] - subevent = pkt[3] - if event == LE_META_EVENT \ - and subevent == EVT_LE_ADVERTISING_REPORT: - # we have an BLE advertisement - self.process_packet(pkt) - except: - _LOGGER.error("Exception while scanning for beacons", - exc_info=True) - raise - finally: - self.toggle_scan(False) - - def toggle_scan(self, enable): - """Enable and disable BLE scanning.""" - # pylint: disable=import-error - import bluetooth._bluetooth as bluez - - if enable: - command = struct.pack(": %d", + namespace, instance, temperature) - yield type_, value - data = data[1+length:] + for dev in self.devices: + if dev.namespace == namespace and dev.instance == instance: + dev.temperature = temperature - def terminate(self): + def stop(self): """Signal runner to stop and join thread.""" - self.keep_going = False - self.join() + _LOGGER.debug("Stopping...") + self.scanner.stop() + _LOGGER.debug("Stopped") diff --git a/requirements_all.txt b/requirements_all.txt index b68f1b05a0770c..657bf4e0b8d2ea 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -66,6 +66,9 @@ astral==1.4 # homeassistant.components.sensor.linux_battery batinfo==0.4.2 +# homeassistant.components.sensor.eddystone_temperature +beacontools[scan]==0.1.2 + # homeassistant.components.device_tracker.linksys_ap # homeassistant.components.sensor.scrape beautifulsoup4==4.5.3 @@ -475,7 +478,6 @@ pyatv==0.2.1 pybbox==0.0.5-alpha # homeassistant.components.device_tracker.bluetooth_tracker -# homeassistant.components.sensor.eddystone_temperature # pybluez==0.22 # homeassistant.components.media_player.cast From cd1b0015fe1a89eec2d849ca2795a8709d35212a Mon Sep 17 00:00:00 2001 From: Felix Seele Date: Mon, 3 Apr 2017 03:48:11 +0200 Subject: [PATCH 07/13] Fixed style issues and added beacontools to excluded requirements. --- homeassistant/components/sensor/eddystone_temperature.py | 3 +-- requirements_all.txt | 2 +- script/gen_requirements_all.py | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py index 870ae1285d4440..59c4bd25ec128f 100644 --- a/homeassistant/components/sensor/eddystone_temperature.py +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -10,7 +10,6 @@ https://github.com/anpetrov/skybeacon """ import logging -import threading import voluptuous as vol @@ -52,7 +51,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): beacons = config.get("beacons") devices = [] - for dev_name, properties in beacons.items(): namespace = get_from_conf(properties, "namespace", 20) instance = get_from_conf(properties, "instance", 12) @@ -66,6 +64,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if len(devices) > 0: mon = Monitor(hass, devices, bt_device_id) + def monitor_stop(_service_or_event): """Stop the monitor thread.""" _LOGGER.info("Stopping scanner for eddystone beacons") diff --git a/requirements_all.txt b/requirements_all.txt index 657bf4e0b8d2ea..64eb2d3db644c5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -67,7 +67,7 @@ astral==1.4 batinfo==0.4.2 # homeassistant.components.sensor.eddystone_temperature -beacontools[scan]==0.1.2 +# beacontools[scan]==0.1.2 # homeassistant.components.device_tracker.linksys_ap # homeassistant.components.sensor.scrape diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 16493dc884ecb5..94635a585251b4 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -13,6 +13,7 @@ 'Adafruit_BBIO', 'fritzconnection', 'pybluez', + 'beacontools', 'bluepy', 'python-lirc', 'gattlib', From c77ebf95e87981b4c5c3f51df04646868c816a8b Mon Sep 17 00:00:00 2001 From: Felix Seele Date: Mon, 3 Apr 2017 03:59:53 +0200 Subject: [PATCH 08/13] Removed obsolete constants and added pylint exception. --- homeassistant/components/sensor/eddystone_temperature.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py index 59c4bd25ec128f..f4ae0f4d11fde0 100644 --- a/homeassistant/components/sensor/eddystone_temperature.py +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -35,12 +35,6 @@ vol.Required(CONF_BEACONS): vol.Schema({cv.string: BEACON_SCHEMA}), }) -LE_META_EVENT = 0x3e -OGF_LE_CTL = 0x08 -OCF_LE_SET_SCAN_ENABLE = 0x000C -EVT_LE_ADVERTISING_REPORT = 0x02 - - # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """Validate configuration, create devices and start monitoring thread.""" @@ -134,6 +128,7 @@ def callback(bt_addr, packet, additional_info): additional_info['instance'], packet.temperature) + # pylint: disable=import-error from beacontools import (BeaconScanner, EddystoneFilter, EddystoneTLMFrame) # Create a device filter for each device From 968d4d8ff209c2ef33abe2f414e1cc2e8e147bd7 Mon Sep 17 00:00:00 2001 From: Felix Seele Date: Mon, 3 Apr 2017 04:02:54 +0200 Subject: [PATCH 09/13] Added blank line --- homeassistant/components/sensor/eddystone_temperature.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py index f4ae0f4d11fde0..8ab99b8ca71bcf 100644 --- a/homeassistant/components/sensor/eddystone_temperature.py +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -35,6 +35,7 @@ vol.Required(CONF_BEACONS): vol.Schema({cv.string: BEACON_SCHEMA}), }) + # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """Validate configuration, create devices and start monitoring thread.""" From b457b0aefea059919310c131ff93c7facd8cdf08 Mon Sep 17 00:00:00 2001 From: Felix Seele Date: Mon, 3 Apr 2017 19:57:23 +0200 Subject: [PATCH 10/13] Updated beacontools to version 1.0.0 --- homeassistant/components/sensor/eddystone_temperature.py | 4 ++-- requirements_all.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py index 8ab99b8ca71bcf..9abdfc4ea582d5 100644 --- a/homeassistant/components/sensor/eddystone_temperature.py +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -20,7 +20,7 @@ CONF_NAME, TEMP_CELSIUS, STATE_UNKNOWN, EVENT_HOMEASSISTANT_STOP, CONF_NAMESPACE, CONF_INSTANCE, CONF_BT_DEVICE_ID, CONF_BEACONS) -REQUIREMENTS = ['beacontools[scan]==0.1.2'] +REQUIREMENTS = ['beacontools[scan]==1.0.0'] _LOGGER = logging.getLogger(__name__) @@ -123,7 +123,7 @@ def __init__(self, hass, devices, bt_device_id): # number of the bt device (hciX) self.bt_device_id = bt_device_id - def callback(bt_addr, packet, additional_info): + def callback(bt_addr, _, packet, additional_info): """Callback for new packets.""" self.process_packet(additional_info['namespace'], additional_info['instance'], diff --git a/requirements_all.txt b/requirements_all.txt index 64eb2d3db644c5..eb661d204fa0b5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -67,7 +67,7 @@ astral==1.4 batinfo==0.4.2 # homeassistant.components.sensor.eddystone_temperature -# beacontools[scan]==0.1.2 +# beacontools[scan]==1.0.0 # homeassistant.components.device_tracker.linksys_ap # homeassistant.components.sensor.scrape From b8aea722fbd351a924707a17648915a2fd8545fc Mon Sep 17 00:00:00 2001 From: Felix Seele Date: Mon, 3 Apr 2017 21:51:36 +0200 Subject: [PATCH 11/13] Updated beacontools to version 1.0.1 --- homeassistant/components/sensor/eddystone_temperature.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py index 9abdfc4ea582d5..41b42345cf9d40 100644 --- a/homeassistant/components/sensor/eddystone_temperature.py +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -20,7 +20,7 @@ CONF_NAME, TEMP_CELSIUS, STATE_UNKNOWN, EVENT_HOMEASSISTANT_STOP, CONF_NAMESPACE, CONF_INSTANCE, CONF_BT_DEVICE_ID, CONF_BEACONS) -REQUIREMENTS = ['beacontools[scan]==1.0.0'] +REQUIREMENTS = ['beacontools[scan]==1.0.1'] _LOGGER = logging.getLogger(__name__) From f485e23babd17c63425c5cbccc1bb2fea8a53b7c Mon Sep 17 00:00:00 2001 From: Felix Seele Date: Mon, 3 Apr 2017 21:55:10 +0200 Subject: [PATCH 12/13] Forgot to regenerate requirements_all --- requirements_all.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_all.txt b/requirements_all.txt index eb661d204fa0b5..be7a96b38488de 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -67,7 +67,7 @@ astral==1.4 batinfo==0.4.2 # homeassistant.components.sensor.eddystone_temperature -# beacontools[scan]==1.0.0 +# beacontools[scan]==1.0.1 # homeassistant.components.device_tracker.linksys_ap # homeassistant.components.sensor.scrape From a36bb24b696662c9cfbf7bd0a960d254798d7ffc Mon Sep 17 00:00:00 2001 From: Felix Seele Date: Tue, 4 Apr 2017 19:28:47 +0200 Subject: [PATCH 13/13] Minor changes --- .../sensor/eddystone_temperature.py | 42 ++++++++++++++++--- homeassistant/const.py | 4 -- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py index 41b42345cf9d40..de51ff0d373bd8 100644 --- a/homeassistant/components/sensor/eddystone_temperature.py +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -18,12 +18,18 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_NAME, TEMP_CELSIUS, STATE_UNKNOWN, EVENT_HOMEASSISTANT_STOP, - CONF_NAMESPACE, CONF_INSTANCE, CONF_BT_DEVICE_ID, CONF_BEACONS) + EVENT_HOMEASSISTANT_START) REQUIREMENTS = ['beacontools[scan]==1.0.1'] _LOGGER = logging.getLogger(__name__) +# constants +CONF_BEACONS = 'beacons' +CONF_BT_DEVICE_ID = 'bt_device_id' +CONF_INSTANCE = 'instance' +CONF_NAMESPACE = 'namespace' + BEACON_SCHEMA = vol.Schema({ vol.Required(CONF_NAMESPACE): cv.string, vol.Required(CONF_INSTANCE): cv.string, @@ -65,9 +71,15 @@ def monitor_stop(_service_or_event): _LOGGER.info("Stopping scanner for eddystone beacons") mon.stop() + def monitor_start(_service_or_event): + """Start the monitor thread.""" + _LOGGER.info("Starting scanner for eddystone beacons") + mon.start() + add_devices(devices) mon.start() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, monitor_stop) + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, monitor_start) else: _LOGGER.warning("No devices were added") @@ -110,6 +122,11 @@ def unit_of_measurement(self): """Return the unit the value is expressed in.""" return TEMP_CELSIUS + @property + def should_poll(self): + """Hass should not poll for state.""" + return False + class Monitor(object): """Continously scan for BLE advertisements.""" @@ -138,10 +155,16 @@ def callback(bt_addr, _, packet, additional_info): self.scanner = BeaconScanner(callback, bt_device_id, device_filters, EddystoneTLMFrame) + self.scanning = False def start(self): """Continously scan for BLE advertisements.""" - self.scanner.start() + if not self.scanning: + self.scanner.start() + self.scanning = True + else: + _LOGGER.debug("Warning: start() called, but scanner is already" + " running") def process_packet(self, namespace, instance, temperature): """Assign temperature to hass device.""" @@ -150,10 +173,17 @@ def process_packet(self, namespace, instance, temperature): for dev in self.devices: if dev.namespace == namespace and dev.instance == instance: - dev.temperature = temperature + if dev.temperature != temperature: + dev.temperature = temperature + dev.schedule_update_ha_state() def stop(self): """Signal runner to stop and join thread.""" - _LOGGER.debug("Stopping...") - self.scanner.stop() - _LOGGER.debug("Stopped") + if self.scanning: + _LOGGER.debug("Stopping...") + self.scanner.stop() + _LOGGER.debug("Stopped") + self.scanning = False + else: + _LOGGER.debug("Warning: stop() called but scanner was not" + " running.") diff --git a/homeassistant/const.py b/homeassistant/const.py index 82dabb523655f5..bb882ca4ea92c2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -61,13 +61,11 @@ CONF_API_KEY = 'api_key' CONF_AUTHENTICATION = 'authentication' CONF_BASE = 'base' -CONF_BEACONS = 'beacons' CONF_BEFORE = 'before' CONF_BELOW = 'below' CONF_BINARY_SENSORS = 'binary_sensors' CONF_BLACKLIST = 'blacklist' CONF_BRIGHTNESS = 'brightness' -CONF_BT_DEVICE_ID = 'bt_device_id' CONF_CODE = 'code' CONF_COLOR_TEMP = 'color_temp' CONF_COMMAND = 'command' @@ -106,7 +104,6 @@ CONF_HOSTS = 'hosts' CONF_ICON = 'icon' CONF_INCLUDE = 'include' -CONF_INSTANCE = 'instance' CONF_ID = 'id' CONF_LATITUDE = 'latitude' CONF_LONGITUDE = 'longitude' @@ -117,7 +114,6 @@ CONF_MONITORED_CONDITIONS = 'monitored_conditions' CONF_MONITORED_VARIABLES = 'monitored_variables' CONF_NAME = 'name' -CONF_NAMESPACE = 'namespace' CONF_OFFSET = 'offset' CONF_OPTIMISTIC = 'optimistic' CONF_PACKAGES = 'packages'