mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Add tracker for OralB toothbrushes
The OralB toothbrushes expose some of their information in their bluetooth advertisement data.
This data lets us see the state (idle, running), brush mode (daily clean, tongue, whitening, etc.), pressure and some other bits of data.
This component lets you expose that data with config as follows:
```
esp32_ble_tracker:
sensor:
  - platform: oralb_brush
    mac_address: 00:00:00:00:00:00
    state:
      name: "Toothbrush State"
```
Checkout https://github.com/zewelor/bt-mqtt-gateway/blob/master/workers/toothbrush_homeassistant.py and https://esphome.io/components/esp32_ble_tracker.html for more information.
			
			
This commit is contained in:
		| @@ -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' | ||||
|   | ||||
							
								
								
									
										18
									
								
								esphome/components/oralb_ble/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/oralb_ble/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
							
								
								
									
										48
									
								
								esphome/components/oralb_ble/oralb_ble.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								esphome/components/oralb_ble/oralb_ble.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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<OralbParseResult> 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 | ||||
							
								
								
									
										27
									
								
								esphome/components/oralb_ble/oralb_ble.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/oralb_ble/oralb_ble.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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<uint8_t> state; | ||||
| }; | ||||
|  | ||||
| bool parse_oralb_data_byte(uint8_t data_type, const uint8_t *data, uint8_t data_length, OralbParseResult &result); | ||||
|  | ||||
| optional<OralbParseResult> 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 | ||||
							
								
								
									
										0
									
								
								esphome/components/oralb_brush/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/oralb_brush/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										19
									
								
								esphome/components/oralb_brush/oralb_brush.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								esphome/components/oralb_brush/oralb_brush.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										43
									
								
								esphome/components/oralb_brush/oralb_brush.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								esphome/components/oralb_brush/oralb_brush.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										36
									
								
								esphome/components/oralb_brush/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/oralb_brush/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
		Reference in New Issue
	
	Block a user