diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index c4cc7260fd..e3aaf29eea 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -9,7 +9,7 @@ from esphome.const import CONF_ID, ESP_PLATFORM_ESP32, CONF_INTERVAL, \ from esphome.core import coroutine ESP_PLATFORMS = [ESP_PLATFORM_ESP32] -AUTO_LOAD = ['xiaomi_ble', 'ruuvi_ble'] +AUTO_LOAD = ['xiaomi_ble', 'ruuvi_ble', 'oralb_ble'] CONF_ESP32_BLE_ID = 'esp32_ble_id' CONF_SCAN_PARAMETERS = 'scan_parameters' diff --git a/esphome/components/oralb_ble/__init__.py b/esphome/components/oralb_ble/__init__.py new file mode 100644 index 0000000000..a54739dae7 --- /dev/null +++ b/esphome/components/oralb_ble/__init__.py @@ -0,0 +1,18 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import esp32_ble_tracker +from esphome.const import CONF_ID + +DEPENDENCIES = ['esp32_ble_tracker'] + +oralb_ble_ns = cg.esphome_ns.namespace('oralb_ble') +OralbListener = oralb_ble_ns.class_('OralbListener', esp32_ble_tracker.ESPBTDeviceListener) + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(OralbListener), +}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield esp32_ble_tracker.register_ble_device(var, config) diff --git a/esphome/components/oralb_ble/oralb_ble.cpp b/esphome/components/oralb_ble/oralb_ble.cpp new file mode 100644 index 0000000000..6b353e1778 --- /dev/null +++ b/esphome/components/oralb_ble/oralb_ble.cpp @@ -0,0 +1,48 @@ +#include "oralb_ble.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace oralb_ble { + +static const char *TAG = "oralb_ble"; + +bool parse_oralb_data_byte(const esp32_ble_tracker::adv_data_t &adv_data, OralbParseResult &result) { + result.state = adv_data[3]; + return true; +} +optional parse_oralb(const esp32_ble_tracker::ESPBTDevice &device) { + bool success = false; + OralbParseResult result{}; + for (auto &it : device.get_manufacturer_datas()) { + bool is_oralb = it.uuid.contains(0xDC, 0x00); + if (!is_oralb) + continue; + + if (parse_oralb_data_byte(it.data, result)) + success = true; + } + if (!success) + return {}; + return result; +} + +bool OralbListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + auto res = parse_oralb(device); + if (!res.has_value()) + return false; + + ESP_LOGD(TAG, "Got OralB (%s):", device.address_str().c_str()); + + if (res->state.has_value()) { + ESP_LOGD(TAG, " State: %d", *res->state); + } + + return true; +} + +} // namespace oralb_ble +} // namespace esphome + +#endif diff --git a/esphome/components/oralb_ble/oralb_ble.h b/esphome/components/oralb_ble/oralb_ble.h new file mode 100644 index 0000000000..648d3edde7 --- /dev/null +++ b/esphome/components/oralb_ble/oralb_ble.h @@ -0,0 +1,27 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace oralb_ble { + +struct OralbParseResult { + optional state; +}; + +bool parse_oralb_data_byte(uint8_t data_type, const uint8_t *data, uint8_t data_length, OralbParseResult &result); + +optional parse_oralb(const esp32_ble_tracker::ESPBTDevice &device); + +class OralbListener : public esp32_ble_tracker::ESPBTDeviceListener { + public: + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; +}; + +} // namespace oralb_ble +} // namespace esphome + +#endif diff --git a/esphome/components/oralb_brush/__init__.py b/esphome/components/oralb_brush/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/oralb_brush/oralb_brush.cpp b/esphome/components/oralb_brush/oralb_brush.cpp new file mode 100644 index 0000000000..dc1262dc83 --- /dev/null +++ b/esphome/components/oralb_brush/oralb_brush.cpp @@ -0,0 +1,19 @@ +#include "oralb_brush.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace oralb_brush { + +static const char *TAG = "oralb_brush"; + +void OralbBrush::dump_config() { + ESP_LOGCONFIG(TAG, "OralbBrush"); + LOG_SENSOR(" ", "State", this->state_); +} + +} // namespace oralb_brush +} // namespace esphome + +#endif diff --git a/esphome/components/oralb_brush/oralb_brush.h b/esphome/components/oralb_brush/oralb_brush.h new file mode 100644 index 0000000000..652a9e476a --- /dev/null +++ b/esphome/components/oralb_brush/oralb_brush.h @@ -0,0 +1,43 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/oralb_ble/oralb_ble.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace oralb_brush { + +class OralbBrush : public Component, public esp32_ble_tracker::ESPBTDeviceListener { + public: + void set_address(uint64_t address) { address_ = address; } + + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { + if (device.address_uint64() != this->address_) + return false; + + auto res = oralb_ble::parse_oralb(device); + if (!res.has_value()) + return false; + + if (res->state.has_value() && this->state_ != nullptr) + this->state_->publish_state(*res->state); + + return true; + } + + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_state(sensor::Sensor *state) { state_ = state; } + + protected: + uint64_t address_; + sensor::Sensor *state_{nullptr}; +}; + +} // namespace oralb_brush +} // namespace esphome + +#endif diff --git a/esphome/components/oralb_brush/sensor.py b/esphome/components/oralb_brush/sensor.py new file mode 100644 index 0000000000..787fb44036 --- /dev/null +++ b/esphome/components/oralb_brush/sensor.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, esp32_ble_tracker +from esphome.const import CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ + CONF_PRESSURE, CONF_ACCELERATION, CONF_ACCELERATION_X, CONF_ACCELERATION_Y, \ + CONF_ACCELERATION_Z, CONF_BATTERY_VOLTAGE, CONF_TX_POWER, \ + CONF_MEASUREMENT_SEQUENCE_NUMBER, CONF_MOVEMENT_COUNTER, UNIT_CELSIUS, \ + ICON_THERMOMETER, UNIT_PERCENT, UNIT_VOLT, UNIT_HECTOPASCAL, UNIT_G, \ + UNIT_DECIBEL_MILLIWATT, UNIT_EMPTY, ICON_WATER_PERCENT, ICON_BATTERY, \ + ICON_GAUGE, ICON_ACCELERATION, ICON_ACCELERATION_X, ICON_ACCELERATION_Y, \ + ICON_ACCELERATION_Z, ICON_SIGNAL, CONF_ID, ICON_EMPTY, CONF_STATE + +DEPENDENCIES = ['esp32_ble_tracker'] +AUTO_LOAD = ['oralb_ble'] + +oralb_brush_ns = cg.esphome_ns.namespace('oralb_brush') +OralbBrush = oralb_brush_ns.class_( + 'OralbBrush', esp32_ble_tracker.ESPBTDeviceListener, cg.Component) + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(OralbBrush), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_STATE): sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 0), +}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield esp32_ble_tracker.register_ble_device(var, config) + + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + + if CONF_STATE in config: + sens = yield sensor.new_sensor(config[CONF_STATE]) + cg.add(var.set_state(sens))