mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 08:28:12 +01:00
Implement text_sensor based on ble_client (#3079)
Co-authored-by: Otto Winter <otto@otto-winter.com>
This commit is contained in:
parent
f137cc10f4
commit
231908fe9f
5 changed files with 355 additions and 0 deletions
121
esphome/components/ble_client/text_sensor/__init__.py
Normal file
121
esphome/components/ble_client/text_sensor/__init__.py
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import text_sensor, ble_client, esp32_ble_tracker
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_TRIGGER_ID,
|
||||||
|
CONF_SERVICE_UUID,
|
||||||
|
)
|
||||||
|
from esphome import automation
|
||||||
|
from .. import ble_client_ns
|
||||||
|
|
||||||
|
DEPENDENCIES = ["ble_client"]
|
||||||
|
|
||||||
|
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
|
||||||
|
CONF_DESCRIPTOR_UUID = "descriptor_uuid"
|
||||||
|
|
||||||
|
CONF_NOTIFY = "notify"
|
||||||
|
CONF_ON_NOTIFY = "on_notify"
|
||||||
|
|
||||||
|
adv_data_t = cg.std_vector.template(cg.uint8)
|
||||||
|
adv_data_t_const_ref = adv_data_t.operator("ref").operator("const")
|
||||||
|
|
||||||
|
BLETextSensor = ble_client_ns.class_(
|
||||||
|
"BLETextSensor",
|
||||||
|
text_sensor.TextSensor,
|
||||||
|
cg.PollingComponent,
|
||||||
|
ble_client.BLEClientNode,
|
||||||
|
)
|
||||||
|
BLETextSensorNotifyTrigger = ble_client_ns.class_(
|
||||||
|
"BLETextSensorNotifyTrigger", automation.Trigger.template(cg.std_string)
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(BLETextSensor),
|
||||||
|
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||||
|
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
|
||||||
|
cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
|
||||||
|
cv.Optional(CONF_NOTIFY, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_ON_NOTIFY): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
|
BLETextSensorNotifyTrigger
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||||
|
cg.add(
|
||||||
|
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||||
|
)
|
||||||
|
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
|
||||||
|
cg.add(
|
||||||
|
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||||
|
)
|
||||||
|
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||||
|
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
|
||||||
|
cg.add(var.set_service_uuid128(uuid128))
|
||||||
|
|
||||||
|
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||||
|
cg.add(
|
||||||
|
var.set_char_uuid16(
|
||||||
|
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||||
|
esp32_ble_tracker.bt_uuid32_format
|
||||||
|
):
|
||||||
|
cg.add(
|
||||||
|
var.set_char_uuid32(
|
||||||
|
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||||
|
esp32_ble_tracker.bt_uuid128_format
|
||||||
|
):
|
||||||
|
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
|
||||||
|
config[CONF_CHARACTERISTIC_UUID]
|
||||||
|
)
|
||||||
|
cg.add(var.set_char_uuid128(uuid128))
|
||||||
|
|
||||||
|
if CONF_DESCRIPTOR_UUID in config:
|
||||||
|
if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||||
|
cg.add(
|
||||||
|
var.set_descr_uuid16(
|
||||||
|
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
|
||||||
|
esp32_ble_tracker.bt_uuid32_format
|
||||||
|
):
|
||||||
|
cg.add(
|
||||||
|
var.set_descr_uuid32(
|
||||||
|
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
|
||||||
|
esp32_ble_tracker.bt_uuid128_format
|
||||||
|
):
|
||||||
|
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
|
||||||
|
config[CONF_DESCRIPTOR_UUID]
|
||||||
|
)
|
||||||
|
cg.add(var.set_descr_uuid128(uuid128))
|
||||||
|
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await ble_client.register_ble_node(var, config)
|
||||||
|
cg.add(var.set_enable_notify(config[CONF_NOTIFY]))
|
||||||
|
await text_sensor.register_text_sensor(var, config)
|
||||||
|
for conf in config.get(CONF_ON_NOTIFY, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await ble_client.register_ble_node(trigger, config)
|
||||||
|
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
38
esphome/components/ble_client/text_sensor/automation.h
Normal file
38
esphome/components/ble_client/text_sensor/automation.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/components/ble_client/text_sensor/ble_text_sensor.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ble_client {
|
||||||
|
|
||||||
|
class BLETextSensorNotifyTrigger : public Trigger<std::string>, public BLETextSensor {
|
||||||
|
public:
|
||||||
|
explicit BLETextSensorNotifyTrigger(BLETextSensor *sensor) { sensor_ = sensor; }
|
||||||
|
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
|
esp_ble_gattc_cb_param_t *param) override {
|
||||||
|
switch (event) {
|
||||||
|
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||||
|
this->sensor_->node_state = espbt::ClientState::ESTABLISHED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ESP_GATTC_NOTIFY_EVT: {
|
||||||
|
if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle)
|
||||||
|
break;
|
||||||
|
this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len));
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
BLETextSensor *sensor_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ble_client
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
137
esphome/components/ble_client/text_sensor/ble_text_sensor.cpp
Normal file
137
esphome/components/ble_client/text_sensor/ble_text_sensor.cpp
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
#include "ble_text_sensor.h"
|
||||||
|
|
||||||
|
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ble_client {
|
||||||
|
|
||||||
|
static const char *const TAG = "ble_text_sensor";
|
||||||
|
|
||||||
|
static const std::string EMPTY = "";
|
||||||
|
|
||||||
|
uint32_t BLETextSensor::hash_base() { return 193967603UL; }
|
||||||
|
|
||||||
|
void BLETextSensor::loop() {}
|
||||||
|
|
||||||
|
void BLETextSensor::dump_config() {
|
||||||
|
LOG_TEXT_SENSOR("", "BLE Text Sensor", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Descriptor UUID : %s", this->descr_uuid_.to_string().c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Notifications : %s", YESNO(this->notify_));
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
|
esp_ble_gattc_cb_param_t *param) {
|
||||||
|
switch (event) {
|
||||||
|
case ESP_GATTC_OPEN_EVT: {
|
||||||
|
if (param->open.status == ESP_GATT_OK) {
|
||||||
|
ESP_LOGI(TAG, "[%s] Connected successfully!", this->get_name().c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ESP_GATTC_DISCONNECT_EVT: {
|
||||||
|
ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str());
|
||||||
|
this->status_set_warning();
|
||||||
|
this->publish_state(EMPTY);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||||
|
this->handle = 0;
|
||||||
|
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||||
|
if (chr == nullptr) {
|
||||||
|
this->status_set_warning();
|
||||||
|
this->publish_state(EMPTY);
|
||||||
|
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
|
||||||
|
this->char_uuid_.to_string().c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this->handle = chr->handle;
|
||||||
|
if (this->descr_uuid_.get_uuid().len > 0) {
|
||||||
|
auto *descr = chr->get_descriptor(this->descr_uuid_);
|
||||||
|
if (descr == nullptr) {
|
||||||
|
this->status_set_warning();
|
||||||
|
this->publish_state(EMPTY);
|
||||||
|
ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s",
|
||||||
|
this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(),
|
||||||
|
this->descr_uuid_.to_string().c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this->handle = descr->handle;
|
||||||
|
}
|
||||||
|
if (this->notify_) {
|
||||||
|
auto status =
|
||||||
|
esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle);
|
||||||
|
if (status) {
|
||||||
|
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ESP_GATTC_READ_CHAR_EVT: {
|
||||||
|
if (param->read.conn_id != this->parent()->conn_id)
|
||||||
|
break;
|
||||||
|
if (param->read.status != ESP_GATT_OK) {
|
||||||
|
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (param->read.handle == this->handle) {
|
||||||
|
this->status_clear_warning();
|
||||||
|
this->publish_state(this->parse_data(param->read.value, param->read.value_len));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ESP_GATTC_NOTIFY_EVT: {
|
||||||
|
if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle)
|
||||||
|
break;
|
||||||
|
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
|
||||||
|
param->notify.handle, param->notify.value[0]);
|
||||||
|
this->publish_state(this->parse_data(param->notify.value, param->notify.value_len));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||||
|
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BLETextSensor::parse_data(uint8_t *value, uint16_t value_len) {
|
||||||
|
std::string text(value, value + value_len);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BLETextSensor::update() {
|
||||||
|
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||||
|
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->handle == 0) {
|
||||||
|
ESP_LOGW(TAG, "[%s] Cannot poll, no service or characteristic found", this->get_name().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto status =
|
||||||
|
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE);
|
||||||
|
if (status) {
|
||||||
|
this->status_set_warning();
|
||||||
|
this->publish_state(EMPTY);
|
||||||
|
ESP_LOGW(TAG, "[%s] Error sending read request for sensor, status=%d", this->get_name().c_str(), status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ble_client
|
||||||
|
} // namespace esphome
|
||||||
|
#endif
|
47
esphome/components/ble_client/text_sensor/ble_text_sensor.h
Normal file
47
esphome/components/ble_client/text_sensor/ble_text_sensor.h
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/ble_client/ble_client.h"
|
||||||
|
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||||
|
#include "esphome/components/text_sensor/text_sensor.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
#include <esp_gattc_api.h>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ble_client {
|
||||||
|
|
||||||
|
namespace espbt = esphome::esp32_ble_tracker;
|
||||||
|
|
||||||
|
class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, public BLEClientNode {
|
||||||
|
public:
|
||||||
|
void loop() override;
|
||||||
|
void update() override;
|
||||||
|
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
|
esp_ble_gattc_cb_param_t *param) override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||||
|
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||||
|
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||||
|
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||||
|
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||||
|
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||||
|
void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||||
|
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||||
|
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||||
|
void set_enable_notify(bool notify) { this->notify_ = notify; }
|
||||||
|
std::string parse_data(uint8_t *value, uint16_t value_len);
|
||||||
|
uint16_t handle;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t hash_base() override;
|
||||||
|
bool notify_;
|
||||||
|
espbt::ESPBTUUID service_uuid_;
|
||||||
|
espbt::ESPBTUUID char_uuid_;
|
||||||
|
espbt::ESPBTUUID descr_uuid_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ble_client
|
||||||
|
} // namespace esphome
|
||||||
|
#endif
|
|
@ -2496,6 +2496,18 @@ globals:
|
||||||
initial_value: "false"
|
initial_value: "false"
|
||||||
|
|
||||||
text_sensor:
|
text_sensor:
|
||||||
|
- platform: ble_client
|
||||||
|
ble_client_id: ble_foo
|
||||||
|
name: 'Sensor Location'
|
||||||
|
service_uuid: '180d'
|
||||||
|
characteristic_uuid: '2a38'
|
||||||
|
descriptor_uuid: '2902'
|
||||||
|
notify: true
|
||||||
|
update_interval: never
|
||||||
|
on_notify:
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
ESP_LOGD("green_btn", "Location changed: %s", x);
|
||||||
- platform: mqtt_subscribe
|
- platform: mqtt_subscribe
|
||||||
name: "MQTT Subscribe Text"
|
name: "MQTT Subscribe Text"
|
||||||
topic: "the/topic"
|
topic: "the/topic"
|
||||||
|
|
Loading…
Reference in a new issue