From 465cd3d1f94ee6bd77a28c1a34be69178de7cdcc Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 14 Jul 2020 18:47:17 +0200 Subject: [PATCH] Add exposure notifications (#1135) --- .../esp32_ble_tracker/esp32_ble_tracker.h | 3 ++ .../exposure_notifications/__init__.py | 28 +++++++++++ .../exposure_notifications.cpp | 49 +++++++++++++++++++ .../exposure_notifications.h | 29 +++++++++++ 4 files changed, 109 insertions(+) create mode 100644 esphome/components/exposure_notifications/__init__.py create mode 100644 esphome/components/exposure_notifications/exposure_notifications.cpp create mode 100644 esphome/components/exposure_notifications/exposure_notifications.h diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 8d011abfe3..eef7930b78 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -28,6 +28,7 @@ class ESPBTUUID { bool contains(uint8_t data1, uint8_t data2) const; bool operator==(const ESPBTUUID &uuid) const; + bool operator!=(const ESPBTUUID &uuid) const { return !(*this == uuid); } esp_bt_uuid_t get_uuid(); @@ -74,6 +75,8 @@ class ESPBTDevice { uint64_t address_uint64() const; + const uint8_t *address() const { return address_; } + esp_ble_addr_type_t get_address_type() const { return this->address_type_; } int get_rssi() const { return rssi_; } const std::string &get_name() const { return this->name_; } diff --git a/esphome/components/exposure_notifications/__init__.py b/esphome/components/exposure_notifications/__init__.py new file mode 100644 index 0000000000..de9c3a58df --- /dev/null +++ b/esphome/components/exposure_notifications/__init__.py @@ -0,0 +1,28 @@ +import esphome.codegen as cg +from esphome import automation +import esphome.config_validation as cv +from esphome.components import esp32_ble_tracker +from esphome.const import CONF_TRIGGER_ID + +DEPENDENCIES = ['esp32_ble_tracker'] + +exposure_notifications_ns = cg.esphome_ns.namespace('exposure_notifications') +ExposureNotification = exposure_notifications_ns.struct('ExposureNotification') +ExposureNotificationTrigger = exposure_notifications_ns.class_( + 'ExposureNotificationTrigger', esp32_ble_tracker.ESPBTDeviceListener, + automation.Trigger.template(ExposureNotification)) + +CONF_ON_EXPOSURE_NOTIFICATION = 'on_exposure_notification' + +CONFIG_SCHEMA = cv.Schema({ + cv.Required(CONF_ON_EXPOSURE_NOTIFICATION): automation.validate_automation(cv.Schema({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ExposureNotificationTrigger), + }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)), +}) + + +def to_code(config): + for conf in config.get(CONF_ON_EXPOSURE_NOTIFICATION, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + yield automation.build_automation(trigger, [(ExposureNotification, 'x')], conf) + yield esp32_ble_tracker.register_ble_device(trigger, conf) diff --git a/esphome/components/exposure_notifications/exposure_notifications.cpp b/esphome/components/exposure_notifications/exposure_notifications.cpp new file mode 100644 index 0000000000..1d8cf83a0e --- /dev/null +++ b/esphome/components/exposure_notifications/exposure_notifications.cpp @@ -0,0 +1,49 @@ +#include "exposure_notifications.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace exposure_notifications { + +using namespace esp32_ble_tracker; + +static const char *TAG = "exposure_notifications"; + +bool ExposureNotificationTrigger::parse_device(const ESPBTDevice &device) { + // See also https://blog.google/documents/70/Exposure_Notification_-_Bluetooth_Specification_v1.2.2.pdf + if (device.get_service_uuids().size() != 1) + return false; + + // Exposure notifications have Service UUID FD 6F + ESPBTUUID uuid = device.get_service_uuids()[0]; + // constant service identifier + const ESPBTUUID expected_uuid = ESPBTUUID::from_uint16(0xFD6F); + if (uuid != expected_uuid) + return false; + if (device.get_service_datas().size() != 1) + return false; + + // The service data should be 20 bytes + // First 16 bytes are the rolling proximity identifier (RPI) + // Then 4 bytes of encrypted metadata follow which can be used to get the transmit power level. + ServiceData service_data = device.get_service_datas()[0]; + if (service_data.uuid != expected_uuid) + return false; + auto data = service_data.data; + if (data.size() != 20) + return false; + ExposureNotification notification{}; + memcpy(¬ification.address[0], device.address(), 6); + memcpy(¬ification.rolling_proximity_identifier[0], &data[0], 16); + memcpy(¬ification.associated_encrypted_metadata[0], &data[16], 4); + notification.rssi = device.get_rssi(); + this->trigger(notification); + return true; +} + +} // namespace exposure_notifications +} // namespace esphome + +#endif diff --git a/esphome/components/exposure_notifications/exposure_notifications.h b/esphome/components/exposure_notifications/exposure_notifications.h new file mode 100644 index 0000000000..6b9f61b2a0 --- /dev/null +++ b/esphome/components/exposure_notifications/exposure_notifications.h @@ -0,0 +1,29 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace exposure_notifications { + +struct ExposureNotification { + std::array address; + int rssi; + std::array rolling_proximity_identifier; + std::array associated_encrypted_metadata; +}; + +class ExposureNotificationTrigger : public Trigger, + public esp32_ble_tracker::ESPBTDeviceListener { + public: + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; +}; + +} // namespace exposure_notifications +} // namespace esphome + +#endif