Merge branch 'add-graphical-layout-system' of github.com:MrMDavidson/esphome into add-graphical-layout-system

This commit is contained in:
Michael Davidson 2023-12-29 18:41:36 +11:00
commit 92f5f03bfe
No known key found for this signature in database
GPG key ID: B8D1A99712B8B0EB
33 changed files with 1058 additions and 253 deletions

View file

@ -52,7 +52,7 @@ esphome/components/bk72xx/* @kuba2k2
esphome/components/bl0939/* @ziceva
esphome/components/bl0940/* @tobias-
esphome/components/bl0942/* @dbuezas
esphome/components/ble_client/* @buxtronix
esphome/components/ble_client/* @buxtronix @clydebarrow
esphome/components/bluetooth_proxy/* @jesserockz
esphome/components/bme680_bsec/* @trvrnrth
esphome/components/bmi160/* @flaviut
@ -317,6 +317,9 @@ esphome/components/ssd1331_base/* @kbx81
esphome/components/ssd1331_spi/* @kbx81
esphome/components/ssd1351_base/* @kbx81
esphome/components/ssd1351_spi/* @kbx81
esphome/components/st7567_base/* @latonita
esphome/components/st7567_i2c/* @latonita
esphome/components/st7567_spi/* @latonita
esphome/components/st7735/* @SenexCrenshaw
esphome/components/st7789v/* @kbx81
esphome/components/st7920/* @marsjan155

View file

@ -319,7 +319,7 @@ void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeo
#ifdef USE_HOMEASSISTANT_TIME
void APIServer::request_time() {
for (auto &client : this->clients_) {
if (!client->remove_ && client->connection_state_ == APIConnection::ConnectionState::CONNECTED)
if (!client->remove_ && client->is_authenticated())
client->send_time_request();
}
}

View file

@ -1,5 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.automation import maybe_simple_id
from esphome.components import esp32_ble_tracker, esp32_ble_client
from esphome.const import (
CONF_CHARACTERISTIC_UUID,
@ -15,7 +16,7 @@ from esphome.const import (
from esphome import automation
AUTO_LOAD = ["esp32_ble_client"]
CODEOWNERS = ["@buxtronix"]
CODEOWNERS = ["@buxtronix", "@clydebarrow"]
DEPENDENCIES = ["esp32_ble_tracker"]
ble_client_ns = cg.esphome_ns.namespace("ble_client")
@ -43,6 +44,10 @@ BLEClientNumericComparisonRequestTrigger = ble_client_ns.class_(
# Actions
BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action)
BLEConnectAction = ble_client_ns.class_("BLEClientConnectAction", automation.Action)
BLEDisconnectAction = ble_client_ns.class_(
"BLEClientDisconnectAction", automation.Action
)
BLEPasskeyReplyAction = ble_client_ns.class_(
"BLEClientPasskeyReplyAction", automation.Action
)
@ -58,6 +63,7 @@ CONF_ACCEPT = "accept"
CONF_ON_PASSKEY_REQUEST = "on_passkey_request"
CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification"
CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request"
CONF_AUTO_CONNECT = "auto_connect"
# Espressif platformio framework is built with MAX_BLE_CONN to 3, so
# enforce this in yaml checks.
@ -69,6 +75,7 @@ CONFIG_SCHEMA = (
cv.GenerateID(): cv.declare_id(BLEClient),
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
cv.Optional(CONF_NAME): cv.string,
cv.Optional(CONF_AUTO_CONNECT, default=True): cv.boolean,
cv.Optional(CONF_ON_CONNECT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
@ -135,6 +142,12 @@ BLE_WRITE_ACTION_SCHEMA = cv.Schema(
}
)
BLE_CONNECT_ACTION_SCHEMA = maybe_simple_id(
{
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
}
)
BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
@ -157,6 +170,24 @@ BLE_REMOVE_BOND_ACTION_SCHEMA = cv.Schema(
)
@automation.register_action(
"ble_client.disconnect", BLEDisconnectAction, BLE_CONNECT_ACTION_SCHEMA
)
async def ble_disconnect_to_code(config, action_id, template_arg, args):
parent = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, parent)
return var
@automation.register_action(
"ble_client.connect", BLEConnectAction, BLE_CONNECT_ACTION_SCHEMA
)
async def ble_connect_to_code(config, action_id, template_arg, args):
parent = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, parent)
return var
@automation.register_action(
"ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA
)
@ -261,6 +292,7 @@ async def to_code(config):
await cg.register_component(var, config)
await esp32_ble_tracker.register_client(var, config)
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
cg.add(var.set_auto_connect(config[CONF_AUTO_CONNECT]))
for conf in config.get(CONF_ON_CONNECT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)

View file

@ -2,76 +2,10 @@
#include "automation.h"
#include <esp_bt_defs.h>
#include <esp_gap_ble_api.h>
#include <esp_gattc_api.h>
#include "esphome/core/log.h"
namespace esphome {
namespace ble_client {
static const char *const TAG = "ble_client.automation";
void BLEWriterClientNode::write(const std::vector<uint8_t> &value) {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "Cannot write to BLE characteristic - not connected");
return;
} else if (this->ble_char_handle_ == 0) {
ESP_LOGW(TAG, "Cannot write to BLE characteristic - characteristic not found");
return;
}
esp_gatt_write_type_t write_type;
if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) {
write_type = ESP_GATT_WRITE_TYPE_RSP;
ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP");
} else if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) {
write_type = ESP_GATT_WRITE_TYPE_NO_RSP;
ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
} else {
ESP_LOGE(TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str());
return;
}
ESP_LOGVV(TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str());
esp_err_t err =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->ble_char_handle_,
value.size(), const_cast<uint8_t *>(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
}
}
void BLEWriterClientNode::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_REG_EVT:
break;
case ESP_GATTC_OPEN_EVT:
this->node_state = espbt::ClientState::ESTABLISHED;
ESP_LOGD(TAG, "Connection established with %s", ble_client_->address_str().c_str());
break;
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW("ble_write_action", "Characteristic %s was not found in service %s",
this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str());
break;
}
this->ble_char_handle_ = chr->handle;
this->char_props_ = chr->properties;
this->node_state = espbt::ClientState::ESTABLISHED;
ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
ble_client_->address_str().c_str());
break;
}
case ESP_GATTC_DISCONNECT_EVT:
this->node_state = espbt::ClientState::IDLE;
this->ble_char_handle_ = 0;
ESP_LOGD(TAG, "Disconnected from %s", ble_client_->address_str().c_str());
break;
default:
break;
}
}
const char *const Automation::TAG = "ble_client.automation";
} // namespace ble_client
} // namespace esphome

View file

@ -7,9 +7,19 @@
#include "esphome/core/automation.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ble_client {
// placeholder class for static TAG .
class Automation {
public:
// could be made inline with C++17
static const char *const TAG;
};
// implement on_connect automation.
class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode {
public:
explicit BLEClientConnectTrigger(BLEClient *parent) { parent->register_ble_node(this); }
@ -23,17 +33,28 @@ class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode {
}
};
// on_disconnect automation
class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
public:
explicit BLEClientDisconnectTrigger(BLEClient *parent) { parent->register_ble_node(this); }
void loop() 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 {
if (event == ESP_GATTC_DISCONNECT_EVT &&
memcmp(param->disconnect.remote_bda, this->parent_->get_remote_bda(), 6) == 0)
this->trigger();
if (event == ESP_GATTC_SEARCH_CMPL_EVT)
this->node_state = espbt::ClientState::ESTABLISHED;
// test for CLOSE and not DISCONNECT - DISCONNECT can occur even if no virtual connection (OPEN event) occurred.
// So this will not trigger unless a complete open has previously succeeded.
switch (event) {
case ESP_GATTC_SEARCH_CMPL_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
break;
}
case ESP_GATTC_CLOSE_EVT: {
this->trigger();
break;
}
default: {
break;
}
}
}
};
@ -42,10 +63,8 @@ class BLEClientPasskeyRequestTrigger : public Trigger<>, public BLEClientNode {
explicit BLEClientPasskeyRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); }
void loop() override {}
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT &&
memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr))
this->trigger();
}
}
};
@ -54,10 +73,8 @@ class BLEClientPasskeyNotificationTrigger : public Trigger<uint32_t>, public BLE
explicit BLEClientPasskeyNotificationTrigger(BLEClient *parent) { parent->register_ble_node(this); }
void loop() override {}
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT &&
memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
uint32_t passkey = param->ble_security.key_notif.passkey;
this->trigger(passkey);
if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) {
this->trigger(param->ble_security.key_notif.passkey);
}
}
};
@ -67,24 +84,20 @@ class BLEClientNumericComparisonRequestTrigger : public Trigger<uint32_t>, publi
explicit BLEClientNumericComparisonRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); }
void loop() override {}
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
if (event == ESP_GAP_BLE_NC_REQ_EVT &&
memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
uint32_t passkey = param->ble_security.key_notif.passkey;
this->trigger(passkey);
if (event == ESP_GAP_BLE_NC_REQ_EVT && this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr)) {
this->trigger(param->ble_security.key_notif.passkey);
}
}
};
class BLEWriterClientNode : public BLEClientNode {
// implement the ble_client.ble_write action.
template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, public BLEClientNode {
public:
BLEWriterClientNode(BLEClient *ble_client) {
BLEClientWriteAction(BLEClient *ble_client) {
ble_client->register_ble_node(this);
ble_client_ = ble_client;
}
// Attempts to write the contents of value to char_uuid_.
void write(const std::vector<uint8_t> &value);
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); }
@ -93,29 +106,6 @@ class BLEWriterClientNode : public BLEClientNode {
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 gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
private:
BLEClient *ble_client_;
int ble_char_handle_ = 0;
esp_gatt_char_prop_t char_props_;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;
};
template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, public BLEWriterClientNode {
public:
BLEClientWriteAction(BLEClient *ble_client) : BLEWriterClientNode(ble_client) {}
void play(Ts... x) override {
if (has_simple_value_) {
return write(this->value_simple_);
} else {
return write(this->value_template_(x...));
}
}
void set_value_template(std::function<std::vector<uint8_t>(Ts...)> func) {
this->value_template_ = std::move(func);
has_simple_value_ = false;
@ -126,10 +116,94 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
has_simple_value_ = true;
}
void play(Ts... x) override {}
void play_complex(Ts... x) override {
this->num_running_++;
this->var_ = std::make_tuple(x...);
auto value = this->has_simple_value_ ? this->value_simple_ : this->value_template_(x...);
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
if (!write(value))
this->play_next_(x...);
}
/**
* Note about logging: the esph_log_X macros are used here because the CI checks complain about use of the ESP LOG
* macros in header files (Can't even write it in a comment!)
* Not sure why, because they seem to work just fine.
* The problem is that the implementation of a templated class can't be placed in a .cpp file when using C++ less than
* 17, so the methods have to be here. The esph_log_X macros are equivalent in function, but don't trigger the CI
* errors.
*/
// initiate the write. Return true if all went well, will be followed by a WRITE_CHAR event.
bool write(const std::vector<uint8_t> &value) {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
return false;
}
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str());
esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(),
this->char_handle_, value.size(), const_cast<uint8_t *>(value.data()),
this->write_type_, ESP_GATT_AUTH_REQ_NONE);
if (err != ESP_OK) {
esph_log_e(Automation::TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
return false;
}
return true;
}
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_WRITE_CHAR_EVT:
// upstream code checked the MAC address, verify the characteristic.
if (param->write.handle == this->char_handle_)
this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); });
break;
case ESP_GATTC_DISCONNECT_EVT:
if (this->num_running_ != 0)
this->stop_complex();
break;
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
esph_log_w("ble_write_action", "Characteristic %s was not found in service %s",
this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str());
break;
}
this->char_handle_ = chr->handle;
this->char_props_ = chr->properties;
if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) {
this->write_type_ = ESP_GATT_WRITE_TYPE_RSP;
esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP");
} else if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) {
this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP;
esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
} else {
esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str());
break;
}
this->node_state = espbt::ClientState::ESTABLISHED;
esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
ble_client_->address_str().c_str());
break;
}
default:
break;
}
}
private:
BLEClient *ble_client_;
bool has_simple_value_ = true;
std::vector<uint8_t> value_simple_;
std::function<std::vector<uint8_t>(Ts...)> value_template_{};
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;
std::tuple<Ts...> var_{};
uint16_t char_handle_{};
esp_gatt_char_prop_t char_props_{};
esp_gatt_write_type_t write_type_{};
};
template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...> {
@ -212,6 +286,92 @@ template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...>
BLEClient *parent_{nullptr};
};
template<typename... Ts> class BLEClientConnectAction : public Action<Ts...>, public BLEClientNode {
public:
BLEClientConnectAction(BLEClient *ble_client) {
ble_client->register_ble_node(this);
ble_client_ = ble_client;
}
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override {
if (this->num_running_ == 0)
return;
switch (event) {
case ESP_GATTC_SEARCH_CMPL_EVT:
this->node_state = espbt::ClientState::ESTABLISHED;
this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); });
break;
// if the connection is closed, terminate the automation chain.
case ESP_GATTC_DISCONNECT_EVT:
this->stop_complex();
break;
default:
break;
}
}
// not used since we override play_complex_
void play(Ts... x) override {}
void play_complex(Ts... x) override {
// it makes no sense to have multiple instances of this running at the same time.
// this would occur only if the same automation was re-triggered while still
// running. So just cancel the second chain if this is detected.
if (this->num_running_ != 0) {
this->stop_complex();
return;
}
this->num_running_++;
if (this->node_state == espbt::ClientState::ESTABLISHED) {
this->play_next_(x...);
} else {
this->var_ = std::make_tuple(x...);
this->ble_client_->connect();
}
}
private:
BLEClient *ble_client_;
std::tuple<Ts...> var_{};
};
template<typename... Ts> class BLEClientDisconnectAction : public Action<Ts...>, public BLEClientNode {
public:
BLEClientDisconnectAction(BLEClient *ble_client) {
ble_client->register_ble_node(this);
ble_client_ = ble_client;
}
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override {
if (this->num_running_ == 0)
return;
switch (event) {
case ESP_GATTC_CLOSE_EVT:
case ESP_GATTC_DISCONNECT_EVT:
this->parent()->run_later([this]() { this->play_next_tuple_(this->var_); });
break;
default:
break;
}
}
// not used since we override play_complex_
void play(Ts... x) override {}
void play_complex(Ts... x) override {
this->num_running_++;
if (this->node_state == espbt::ClientState::IDLE) {
this->play_next_(x...);
} else {
this->var_ = std::make_tuple(x...);
this->ble_client_->disconnect();
}
}
private:
BLEClient *ble_client_;
std::tuple<Ts...> var_{};
};
} // namespace ble_client
} // namespace esphome

View file

@ -26,6 +26,7 @@ void BLEClient::loop() {
void BLEClient::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Client:");
ESP_LOGCONFIG(TAG, " Address: %s", this->address_str().c_str());
ESP_LOGCONFIG(TAG, " Auto-Connect: %s", TRUEFALSE(this->auto_connect_));
}
bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {
@ -37,31 +38,24 @@ bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {
void BLEClient::set_enabled(bool enabled) {
if (enabled == this->enabled)
return;
if (!enabled && this->state() != espbt::ClientState::IDLE) {
ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str());
auto ret = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret);
}
}
this->enabled = enabled;
if (!enabled) {
ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str());
this->disconnect();
}
}
bool BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
esp_ble_gattc_cb_param_t *param) {
bool all_established = this->all_nodes_established_();
if (!BLEClientBase::gattc_event_handler(event, esp_gattc_if, param))
return false;
for (auto *node : this->nodes_)
node->gattc_event_handler(event, esp_gattc_if, param);
// Delete characteristics after clients have used them to save RAM.
if (!all_established && this->all_nodes_established_()) {
for (auto &svc : this->services_)
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
this->services_.clear();
if (!this->services_.empty() && this->all_nodes_established_()) {
this->release_services();
ESP_LOGD(TAG, "All clients established, services released");
}
return true;
}

View file

@ -19,26 +19,36 @@ void BLEBinaryOutput::dump_config() {
void BLEBinaryOutput::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:
this->client_state_ = espbt::ClientState::ESTABLISHED;
ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str());
break;
case ESP_GATTC_DISCONNECT_EVT:
ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str());
this->client_state_ = espbt::ClientState::IDLE;
break;
case ESP_GATTC_WRITE_CHAR_EVT: {
if (param->write.status == 0) {
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str());
ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_string().c_str(),
this->service_uuid_.to_string().c_str());
break;
}
if (param->write.handle == chr->handle) {
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
this->char_handle_ = chr->handle;
this->char_props_ = chr->properties;
if (this->require_response_ && this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) {
this->write_type_ = ESP_GATT_WRITE_TYPE_RSP;
ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP");
} else if (!this->require_response_ && this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) {
this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP;
ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
} else {
ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_string().c_str(),
this->require_response_ ? "" : "out");
break;
}
this->node_state = espbt::ClientState::ESTABLISHED;
ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
this->parent()->address_str().c_str());
this->node_state = espbt::ClientState::ESTABLISHED;
break;
}
case ESP_GATTC_WRITE_CHAR_EVT: {
if (param->write.handle == this->char_handle_) {
if (param->write.status != 0)
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
}
break;
}
@ -48,26 +58,18 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
}
void BLEBinaryOutput::write_state(bool state) {
if (this->client_state_ != espbt::ClientState::ESTABLISHED) {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.",
this->char_uuid_.to_string().c_str());
return;
}
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] Characteristic not found. State update can not be written.",
this->char_uuid_.to_string().c_str());
return;
}
uint8_t state_as_uint = (uint8_t) state;
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
if (this->require_response_) {
chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_RSP);
} else {
chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_NO_RSP);
}
esp_err_t err =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_,
sizeof(state_as_uint), &state_as_uint, this->write_type_, ESP_GATT_AUTH_REQ_NONE);
if (err != ESP_GATT_OK)
ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_string().c_str(), err);
}
} // namespace ble_client

View file

@ -32,7 +32,9 @@ class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, publi
bool require_response_;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;
espbt::ClientState client_state_;
uint16_t char_handle_{};
esp_gatt_char_prop_t char_props_{};
esp_gatt_write_type_t write_type_{};
};
} // namespace ble_client

View file

@ -14,15 +14,17 @@ class BLESensorNotifyTrigger : public Trigger<float>, public BLESensor {
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;
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.handle == this->sensor_->handle)
this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len));
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
param->notify.handle != this->sensor_->handle)
break;
this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len));
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
// confirms notifications are being listened for. While enabling of notifications may still be in
// progress by the parent, we assume it will happen.
if (param->reg_for_notify.status == ESP_GATT_OK && param->reg_for_notify.handle == this->sensor_->handle)
this->node_state = espbt::ClientState::ESTABLISHED;
break;
}
default:
break;

View file

@ -22,26 +22,19 @@ void BLEClientRSSISensor::dump_config() {
void BLEClientRSSISensor::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());
case ESP_GATTC_CLOSE_EVT: {
this->status_set_warning();
this->publish_state(NAN);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT:
case ESP_GATTC_SEARCH_CMPL_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
if (this->should_update_) {
this->should_update_ = false;
this->get_rssi_();
}
break;
}
default:
break;
}

View file

@ -33,7 +33,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
case ESP_GATTC_CLOSE_EVT: {
ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str());
this->status_set_warning();
this->publish_state(NAN);
@ -74,8 +74,6 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
break;
}
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->get_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;
@ -87,15 +85,23 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->parent()->get_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(),
ESP_LOGD(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
param->notify.handle, param->notify.value[0]);
if (param->notify.handle != this->handle)
break;
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;
if (param->reg_for_notify.handle == this->handle) {
if (param->reg_for_notify.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error registering for notifications at handle %d, status=%d", param->reg_for_notify.handle,
param->reg_for_notify.status);
break;
}
this->node_state = espbt::ClientState::ESTABLISHED;
ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_string().c_str());
}
break;
}
default:

View file

@ -17,14 +17,11 @@ void BLEClientSwitch::write_state(bool state) {
void BLEClientSwitch::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_REG_EVT:
case ESP_GATTC_CLOSE_EVT:
this->publish_state(this->parent_->enabled);
break;
case ESP_GATTC_OPEN_EVT:
case ESP_GATTC_SEARCH_CMPL_EVT:
this->node_state = espbt::ClientState::ESTABLISHED;
break;
case ESP_GATTC_DISCONNECT_EVT:
this->node_state = espbt::ClientState::IDLE;
this->publish_state(this->parent_->enabled);
break;
default:

View file

@ -36,8 +36,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str());
case ESP_GATTC_CLOSE_EVT: {
this->status_set_warning();
this->publish_state(EMPTY);
break;
@ -77,20 +76,18 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
}
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->get_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) {
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;
}
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()->get_conn_id() || param->notify.handle != this->handle)
if (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]);
@ -98,7 +95,8 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
if (param->reg_for_notify.status == ESP_GATT_OK && param->reg_for_notify.handle == this->handle)
this->node_state = espbt::ClientState::ESTABLISHED;
break;
}
default:

View file

@ -45,21 +45,19 @@ void BLEClientBase::loop() {
float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
if (!this->auto_connect_)
return false;
if (this->address_ == 0 || device.address_uint64() != this->address_)
return false;
if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING)
return false;
ESP_LOGD(TAG, "[%d] [%s] Found device", this->connection_index_, this->address_str_.c_str());
this->set_state(espbt::ClientState::DISCOVERED);
this->log_event_("Found device");
if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG)
esp32_ble_tracker::global_esp32_ble_tracker->print_bt_device_info(device);
auto addr = device.address_uint64();
this->remote_bda_[0] = (addr >> 40) & 0xFF;
this->remote_bda_[1] = (addr >> 32) & 0xFF;
this->remote_bda_[2] = (addr >> 24) & 0xFF;
this->remote_bda_[3] = (addr >> 16) & 0xFF;
this->remote_bda_[4] = (addr >> 8) & 0xFF;
this->remote_bda_[5] = (addr >> 0) & 0xFF;
this->set_state(espbt::ClientState::DISCOVERED);
this->set_address(device.address_uint64());
this->remote_addr_type_ = device.get_address_type();
return true;
}
@ -108,6 +106,10 @@ void BLEClientBase::release_services() {
#endif
}
void BLEClientBase::log_event_(const char *name) {
ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), name);
}
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
esp_ble_gattc_cb_param_t *param) {
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
@ -131,51 +133,73 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
}
case ESP_GATTC_OPEN_EVT: {
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT", this->connection_index_, this->address_str_.c_str());
if (!this->check_addr(param->open.remote_bda))
return false;
this->log_event_("ESP_GATTC_OPEN_EVT");
this->conn_id_ = param->open.conn_id;
this->service_count_ = 0;
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(),
param->open.status);
this->set_state(espbt::ClientState::IDLE);
break;
return false;
}
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
if (ret) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_,
this->address_str_.c_str(), ret);
}
this->set_state(espbt::ClientState::CONNECTED);
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str());
this->set_state(espbt::ClientState::CONNECTED);
// only set our state, subclients might have more stuff to do yet.
this->state_ = espbt::ClientState::ESTABLISHED;
break;
}
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
break;
}
case ESP_GATTC_CFG_MTU_EVT: {
if (param->cfg_mtu.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_,
this->address_str_.c_str(), param->cfg_mtu.mtu, param->cfg_mtu.status);
this->set_state(espbt::ClientState::IDLE);
break;
}
ESP_LOGV(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_.c_str(),
param->cfg_mtu.status, param->cfg_mtu.mtu);
this->mtu_ = param->cfg_mtu.mtu;
case ESP_GATTC_CONNECT_EVT: {
if (!this->check_addr(param->connect.remote_bda))
return false;
this->log_event_("ESP_GATTC_CONNECT_EVT");
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0)
if (!this->check_addr(param->disconnect.remote_bda))
return false;
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_,
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_,
this->address_str_.c_str(), param->disconnect.reason);
this->release_services();
this->set_state(espbt::ClientState::IDLE);
break;
}
case ESP_GATTC_CFG_MTU_EVT: {
if (this->conn_id_ != param->cfg_mtu.conn_id)
return false;
if (param->cfg_mtu.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_,
this->address_str_.c_str(), param->cfg_mtu.mtu, param->cfg_mtu.status);
// No state change required here - disconnect event will follow if needed.
break;
}
ESP_LOGD(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_.c_str(),
param->cfg_mtu.status, param->cfg_mtu.mtu);
this->mtu_ = param->cfg_mtu.mtu;
break;
}
case ESP_GATTC_CLOSE_EVT: {
if (this->conn_id_ != param->close.conn_id)
return false;
this->log_event_("ESP_GATTC_CLOSE_EVT");
this->release_services();
this->set_state(espbt::ClientState::IDLE);
break;
}
case ESP_GATTC_SEARCH_RES_EVT: {
if (this->conn_id_ != param->search_res.conn_id)
return false;
this->service_count_++;
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
// V3 clients don't need services initialized since
@ -191,7 +215,9 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_SEARCH_CMPL_EVT", this->connection_index_, this->address_str_.c_str());
if (this->conn_id_ != param->search_cmpl.conn_id)
return false;
this->log_event_("ESP_GATTC_SEARCH_CMPL_EVT");
for (auto &svc : this->services_) {
ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
svc->uuid.to_string().c_str());
@ -199,11 +225,41 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
this->address_str_.c_str(), svc->start_handle, svc->end_handle);
}
ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str());
this->set_state(espbt::ClientState::CONNECTED);
this->state_ = espbt::ClientState::ESTABLISHED;
break;
}
case ESP_GATTC_READ_DESCR_EVT: {
if (this->conn_id_ != param->write.conn_id)
return false;
this->log_event_("ESP_GATTC_READ_DESCR_EVT");
break;
}
case ESP_GATTC_WRITE_DESCR_EVT: {
if (this->conn_id_ != param->write.conn_id)
return false;
this->log_event_("ESP_GATTC_WRITE_DESCR_EVT");
break;
}
case ESP_GATTC_WRITE_CHAR_EVT: {
if (this->conn_id_ != param->write.conn_id)
return false;
this->log_event_("ESP_GATTC_WRITE_CHAR_EVT");
break;
}
case ESP_GATTC_READ_CHAR_EVT: {
if (this->conn_id_ != param->read.conn_id)
return false;
this->log_event_("ESP_GATTC_READ_CHAR_EVT");
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (this->conn_id_ != param->notify.conn_id)
return false;
this->log_event_("ESP_GATTC_NOTIFY_EVT");
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->log_event_("ESP_GATTC_REG_FOR_NOTIFY_EVT");
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
// Client is responsible for flipping the descriptor value
@ -212,9 +268,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
}
esp_gattc_descr_elem_t desc_result;
uint16_t count = 1;
esp_gatt_status_t descr_status =
esp_ble_gattc_get_descr_by_char_handle(this->gattc_if_, this->connection_index_, param->reg_for_notify.handle,
NOTIFY_DESC_UUID, &desc_result, &count);
esp_gatt_status_t descr_status = esp_ble_gattc_get_descr_by_char_handle(
this->gattc_if_, this->conn_id_, param->reg_for_notify.handle, NOTIFY_DESC_UUID, &desc_result, &count);
if (descr_status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_descr_by_char_handle error, status=%d", this->connection_index_,
this->address_str_.c_str(), descr_status);
@ -222,7 +277,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
}
esp_gattc_char_elem_t char_result;
esp_gatt_status_t char_status =
esp_ble_gattc_get_all_char(this->gattc_if_, this->connection_index_, param->reg_for_notify.handle,
esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, param->reg_for_notify.handle,
param->reg_for_notify.handle, &char_result, &count, 0);
if (char_status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_,
@ -238,6 +293,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
esp_err_t status =
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en),
(uint8_t *) &notify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
ESP_LOGD(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
if (status) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, status=%d", this->connection_index_,
this->address_str_.c_str(), status);
@ -246,24 +302,31 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
}
default:
// ideally would check all other events for matching conn_id
ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_.c_str(), event);
break;
}
return true;
}
// clients can't call defer() directly since it's protected.
void BLEClientBase::run_later(std::function<void()> &&f) { // NOLINT
this->defer(std::move(f));
}
void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
switch (event) {
// This event is sent by the server when it requests security
case ESP_GAP_BLE_SEC_REQ_EVT:
if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
break;
if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr))
return;
ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event);
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
break;
// This event is sent once authentication has completed
case ESP_GAP_BLE_AUTH_CMPL_EVT:
if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
break;
if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr))
return;
esp_bd_addr_t bd_addr;
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(),
@ -273,11 +336,12 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_
param->ble_security.auth_cmpl.fail_reason);
} else {
this->paired_ = true;
ESP_LOGV(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
ESP_LOGD(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type,
param->ble_security.auth_cmpl.auth_mode);
}
break;
// There are other events we'll want to implement at some point to support things like pass key
// https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md
default:

View file

@ -27,6 +27,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
void loop() override;
float get_setup_priority() const override;
void run_later(std::function<void()> &&f); // NOLINT
bool parse_device(const espbt::ESPBTDevice &device) override;
void on_scan_end() override {}
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
@ -39,10 +40,17 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
bool connected() { return this->state_ == espbt::ClientState::ESTABLISHED; }
void set_auto_connect(bool auto_connect) { this->auto_connect_ = auto_connect; }
void set_address(uint64_t address) {
this->address_ = address;
this->remote_bda_[0] = (address >> 40) & 0xFF;
this->remote_bda_[1] = (address >> 32) & 0xFF;
this->remote_bda_[2] = (address >> 24) & 0xFF;
this->remote_bda_[3] = (address >> 16) & 0xFF;
this->remote_bda_[4] = (address >> 8) & 0xFF;
this->remote_bda_[5] = (address >> 0) & 0xFF;
if (address == 0) {
memset(this->remote_bda_, 0, sizeof(this->remote_bda_));
this->address_str_ = "";
} else {
this->address_str_ =
@ -79,20 +87,24 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
virtual void set_connection_type(espbt::ConnectionType ct) { this->connection_type_ = ct; }
bool check_addr(esp_bd_addr_t &addr) { return memcmp(addr, this->remote_bda_, sizeof(esp_bd_addr_t)) == 0; }
protected:
int gattc_if_;
esp_bd_addr_t remote_bda_;
esp_ble_addr_type_t remote_addr_type_;
esp_ble_addr_type_t remote_addr_type_{BLE_ADDR_TYPE_PUBLIC};
uint16_t conn_id_{0xFFFF};
uint64_t address_{0};
bool auto_connect_{false};
std::string address_str_{};
uint8_t connection_index_;
int16_t service_count_{0};
uint16_t mtu_{23};
bool paired_{false};
espbt::ConnectionType connection_type_{espbt::ConnectionType::V1};
std::vector<BLEService *> services_;
void log_event_(const char *name);
};
} // namespace esp32_ble_client

View file

@ -17,6 +17,14 @@ from esphome.const import (
CONF_WIDTH,
CONF_HEIGHT,
CONF_ROTATION,
CONF_MIRROR_X,
CONF_MIRROR_Y,
CONF_SWAP_XY,
CONF_COLOR_ORDER,
CONF_OFFSET_HEIGHT,
CONF_OFFSET_WIDTH,
CONF_TRANSFORM,
CONF_INVERT_COLORS,
)
DEPENDENCIES = ["spi"]
@ -70,14 +78,6 @@ COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE")
CONF_LED_PIN = "led_pin"
CONF_COLOR_PALETTE_IMAGES = "color_palette_images"
CONF_INVERT_DISPLAY = "invert_display"
CONF_INVERT_COLORS = "invert_colors"
CONF_MIRROR_X = "mirror_x"
CONF_MIRROR_Y = "mirror_y"
CONF_SWAP_XY = "swap_xy"
CONF_COLOR_ORDER = "color_order"
CONF_OFFSET_HEIGHT = "offset_height"
CONF_OFFSET_WIDTH = "offset_width"
CONF_TRANSFORM = "transform"
def _validate(config):

View file

@ -960,6 +960,21 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
this->exit_reparse_on_start_ = exit_reparse_on_start;
}
/**
* @brief Retrieves the number of commands pending in the Nextion command queue.
*
* This function returns the current count of commands that have been queued but not yet processed
* for the Nextion display. The Nextion command queue is used to store commands that are sent to
* the Nextion display for various operations like updating the display, changing interface elements,
* or other interactive features. A larger queue size might indicate a higher processing time or potential
* delays in command execution. This function is useful for monitoring the command flow and managing
* the execution efficiency of the Nextion display interface.
*
* @return size_t The number of commands currently in the Nextion queue. This count includes all commands
* that have been added to the queue and are awaiting processing.
*/
size_t queue_size() { return this->nextion_queue_.size(); }
protected:
std::deque<NextionQueue *> nextion_queue_;
std::deque<NextionQueue *> waveform_queue_;

View file

@ -0,0 +1,55 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import display
from esphome.const import (
CONF_LAMBDA,
CONF_RESET_PIN,
CONF_MIRROR_X,
CONF_MIRROR_Y,
CONF_TRANSFORM,
CONF_INVERT_COLORS,
)
CODEOWNERS = ["@latonita"]
st7567_base_ns = cg.esphome_ns.namespace("st7567_base")
ST7567 = st7567_base_ns.class_("ST7567", cg.PollingComponent, display.DisplayBuffer)
ST7567Model = st7567_base_ns.enum("ST7567Model")
# todo in future: reuse following constants from const.py when they are released
ST7567_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend(
{
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean,
cv.Optional(CONF_TRANSFORM): cv.Schema(
{
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
}
),
}
).extend(cv.polling_component_schema("1s"))
async def setup_st7567(var, config):
await display.register_display(var, config)
if CONF_RESET_PIN in config:
reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(reset))
cg.add(var.init_invert_colors(config[CONF_INVERT_COLORS]))
if CONF_TRANSFORM in config:
transform = config[CONF_TRANSFORM]
cg.add(var.init_mirror_x(transform[CONF_MIRROR_X]))
cg.add(var.init_mirror_y(transform[CONF_MIRROR_Y]))
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))

View file

@ -0,0 +1,152 @@
#include "st7567_base.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace st7567_base {
static const char *const TAG = "st7567";
void ST7567::setup() {
this->init_internal_(this->get_buffer_length_());
this->display_init_();
}
void ST7567::display_init_() {
ESP_LOGD(TAG, "Initializing ST7567 display...");
this->display_init_registers_();
this->clear();
this->write_display_data();
this->command(ST7567_DISPLAY_ON);
}
void ST7567::display_init_registers_() {
this->command(ST7567_BIAS_9);
this->command(this->mirror_x_ ? ST7567_SEG_REVERSE : ST7567_SEG_NORMAL);
this->command(this->mirror_y_ ? ST7567_COM_NORMAL : ST7567_COM_REMAP);
this->command(ST7567_POWER_CTL | 0x4);
this->command(ST7567_POWER_CTL | 0x6);
this->command(ST7567_POWER_CTL | 0x7);
this->set_brightness(this->brightness_);
this->set_contrast(this->contrast_);
this->command(ST7567_INVERT_OFF | this->invert_colors_);
this->command(ST7567_BOOSTER_ON);
this->command(ST7567_REGULATOR_ON);
this->command(ST7567_POWER_ON);
this->command(ST7567_SCAN_START_LINE);
this->command(ST7567_PIXELS_NORMAL | this->all_pixels_on_);
}
void ST7567::display_sw_refresh_() {
ESP_LOGD(TAG, "Performing refresh sequence...");
this->command(ST7567_SW_REFRESH);
this->display_init_registers_();
}
void ST7567::request_refresh() {
// as per datasheet: It is recommended to use the refresh sequence regularly in a specified interval.
this->refresh_requested_ = true;
}
void ST7567::update() {
this->do_update_();
if (this->refresh_requested_) {
this->refresh_requested_ = false;
this->display_sw_refresh_();
}
this->write_display_data();
}
void ST7567::set_all_pixels_on(bool enable) {
this->all_pixels_on_ = enable;
this->command(ST7567_PIXELS_NORMAL | this->all_pixels_on_);
}
void ST7567::set_invert_colors(bool invert_colors) {
this->invert_colors_ = invert_colors;
this->command(ST7567_INVERT_OFF | this->invert_colors_);
}
void ST7567::set_contrast(uint8_t val) {
this->contrast_ = val & 0b111111;
// 0..63, 26 is normal
// two byte command
// first byte 0x81
// second byte 0-63
this->command(ST7567_SET_EV_CMD);
this->command(this->contrast_);
}
void ST7567::set_brightness(uint8_t val) {
this->brightness_ = val & 0b111;
// 0..7, 5 normal
//********Adjust display brightness********
// 0x20-0x27 is the internal Rb/Ra resistance
// adjustment setting of V5 voltage RR=4.5V
this->command(ST7567_RESISTOR_RATIO | this->brightness_);
}
bool ST7567::is_on() { return this->is_on_; }
void ST7567::turn_on() {
this->command(ST7567_DISPLAY_ON);
this->is_on_ = true;
}
void ST7567::turn_off() {
this->command(ST7567_DISPLAY_OFF);
this->is_on_ = false;
}
void ST7567::set_scroll(uint8_t line) { this->start_line_ = line % this->get_height_internal(); }
int ST7567::get_width_internal() { return 128; }
int ST7567::get_height_internal() { return 64; }
// 128x64, but memory size 132x64, line starts from 0, but if mirrored then it starts from 131, not 127
size_t ST7567::get_buffer_length_() {
return size_t(this->get_width_internal() + 4) * size_t(this->get_height_internal()) / 8u;
}
void HOT ST7567::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
return;
}
uint16_t pos = x + (y / 8) * this->get_width_internal();
uint8_t subpos = y & 0x07;
if (color.is_on()) {
this->buffer_[pos] |= (1 << subpos);
} else {
this->buffer_[pos] &= ~(1 << subpos);
}
}
void ST7567::fill(Color color) { memset(buffer_, color.is_on() ? 0xFF : 0x00, this->get_buffer_length_()); }
void ST7567::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
const char *ST7567::model_str_() { return "ST7567 128x64"; }
} // namespace st7567_base
} // namespace esphome

View file

@ -0,0 +1,100 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome {
namespace st7567_base {
static const uint8_t ST7567_BOOSTER_ON = 0x2C; // internal power supply on
static const uint8_t ST7567_REGULATOR_ON = 0x2E; // internal power supply on
static const uint8_t ST7567_POWER_ON = 0x2F; // internal power supply on
static const uint8_t ST7567_DISPLAY_ON = 0xAF; // Display ON. Normal Display Mode.
static const uint8_t ST7567_DISPLAY_OFF = 0xAE; // Display OFF. All SEGs/COMs output with VSS
static const uint8_t ST7567_SET_START_LINE = 0x40;
static const uint8_t ST7567_POWER_CTL = 0x28;
static const uint8_t ST7567_SEG_NORMAL = 0xA0; //
static const uint8_t ST7567_SEG_REVERSE = 0xA1; // mirror X axis (horizontal)
static const uint8_t ST7567_COM_NORMAL = 0xC0; //
static const uint8_t ST7567_COM_REMAP = 0xC8; // mirror Y axis (vertical)
static const uint8_t ST7567_PIXELS_NORMAL = 0xA4; // display ram content
static const uint8_t ST7567_PIXELS_ALL_ON = 0xA5; // all pixels on
static const uint8_t ST7567_INVERT_OFF = 0xA6; // normal pixels
static const uint8_t ST7567_INVERT_ON = 0xA7; // inverted pixels
static const uint8_t ST7567_SCAN_START_LINE = 0x40; // scrolling = 0x40 + (0..63)
static const uint8_t ST7567_COL_ADDR_H = 0x10; // x pos (0..95) 4 MSB
static const uint8_t ST7567_COL_ADDR_L = 0x00; // x pos (0..95) 4 LSB
static const uint8_t ST7567_PAGE_ADDR = 0xB0; // y pos, 8.5 rows (0..8)
static const uint8_t ST7567_BIAS_9 = 0xA2;
static const uint8_t ST7567_CONTRAST = 0x80; // 0x80 + (0..31)
static const uint8_t ST7567_SET_EV_CMD = 0x81;
static const uint8_t ST7567_SET_EV_PARAM = 0x00;
static const uint8_t ST7567_RESISTOR_RATIO = 0x20;
static const uint8_t ST7567_SW_REFRESH = 0xE2;
class ST7567 : public display::DisplayBuffer {
public:
void setup() override;
void update() override;
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
void init_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; }
void init_mirror_y(bool mirror_y) { this->mirror_y_ = mirror_y; }
void init_invert_colors(bool invert_colors) { this->invert_colors_ = invert_colors; }
void set_invert_colors(bool invert_colors); // inversion of screen colors
void set_contrast(uint8_t val); // 0..63, 27-30 normal
void set_brightness(uint8_t val); // 0..7, 5 normal
void set_all_pixels_on(bool enable); // turn on all pixels, this doesn't affect RAM
void set_scroll(uint8_t line); // set display start line: for screen scrolling w/o affecting RAM
bool is_on();
void turn_on();
void turn_off();
void request_refresh(); // from datasheet: It is recommended to use the refresh sequence regularly in a specified
// interval.
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
void fill(Color color) override;
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; }
protected:
virtual void command(uint8_t value) = 0;
virtual void write_display_data() = 0;
void init_reset_();
void display_init_();
void display_init_registers_();
void display_sw_refresh_();
void draw_absolute_pixel_internal(int x, int y, Color color) override;
int get_height_internal() override;
int get_width_internal() override;
size_t get_buffer_length_();
int get_offset_x_() { return mirror_x_ ? 4 : 0; };
const char *model_str_();
GPIOPin *reset_pin_{nullptr};
bool is_on_{false};
// float contrast_{1.0};
// float brightness_{1.0};
uint8_t contrast_{27};
uint8_t brightness_{5};
bool mirror_x_{true};
bool mirror_y_{true};
bool invert_colors_{false};
bool all_pixels_on_{false};
uint8_t start_line_{0};
bool refresh_requested_{false};
};
} // namespace st7567_base
} // namespace esphome

View file

@ -0,0 +1 @@
CODEOWNERS = ["@latonita"]

View file

@ -0,0 +1,29 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import st7567_base, i2c
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_PAGES
CODEOWNERS = ["@latonita"]
AUTO_LOAD = ["st7567_base"]
DEPENDENCIES = ["i2c"]
st7567_i2c = cg.esphome_ns.namespace("st7567_i2c")
I2CST7567 = st7567_i2c.class_("I2CST7567", st7567_base.ST7567, i2c.I2CDevice)
CONFIG_SCHEMA = cv.All(
st7567_base.ST7567_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(I2CST7567),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x3F)),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await st7567_base.setup_st7567(var, config)
await i2c.register_i2c_device(var, config)

View file

@ -0,0 +1,60 @@
#include "st7567_i2c.h"
#include "esphome/core/log.h"
namespace esphome {
namespace st7567_i2c {
static const char *const TAG = "st7567_i2c";
void I2CST7567::setup() {
ESP_LOGCONFIG(TAG, "Setting up I2C ST7567 display...");
this->init_reset_();
auto err = this->write(nullptr, 0);
if (err != i2c::ERROR_OK) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
ST7567::setup();
}
void I2CST7567::dump_config() {
LOG_DISPLAY("", "I2CST7567", this);
LOG_I2C_DEVICE(this);
ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_());
LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " Mirror X: %s", YESNO(this->mirror_x_));
ESP_LOGCONFIG(TAG, " Mirror Y: %s", YESNO(this->mirror_y_));
ESP_LOGCONFIG(TAG, " Invert Colors: %s", YESNO(this->invert_colors_));
LOG_UPDATE_INTERVAL(this);
if (this->error_code_ == COMMUNICATION_FAILED) {
ESP_LOGE(TAG, "Communication with I2C ST7567 failed!");
}
}
void I2CST7567::command(uint8_t value) { this->write_byte(0x00, value); }
void HOT I2CST7567::write_display_data() {
// ST7567A has built-in RAM with 132x65 bit capacity which stores the display data.
// but only first 128 pixels from each line are shown on screen
// if screen got flipped horizontally then it shows last 128 pixels,
// so we need to write x coordinate starting from column 4, not column 0
this->command(esphome::st7567_base::ST7567_SET_START_LINE + this->start_line_);
for (uint8_t y = 0; y < (uint8_t) this->get_height_internal() / 8; y++) {
this->command(esphome::st7567_base::ST7567_PAGE_ADDR + y); // Set Page
this->command(esphome::st7567_base::ST7567_COL_ADDR_H); // Set MSB Column address
this->command(esphome::st7567_base::ST7567_COL_ADDR_L + this->get_offset_x_()); // Set LSB Column address
static const size_t BLOCK_SIZE = 64;
for (uint8_t x = 0; x < (uint8_t) this->get_width_internal(); x += BLOCK_SIZE) {
this->write_register(esphome::st7567_base::ST7567_SET_START_LINE, &buffer_[y * this->get_width_internal() + x],
this->get_width_internal() - x > BLOCK_SIZE ? BLOCK_SIZE : this->get_width_internal() - x,
true);
}
}
}
} // namespace st7567_i2c
} // namespace esphome

View file

@ -0,0 +1,23 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/st7567_base/st7567_base.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace st7567_i2c {
class I2CST7567 : public st7567_base::ST7567, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
protected:
void command(uint8_t value) override;
void write_display_data() override;
enum ErrorCode { NONE = 0, COMMUNICATION_FAILED } error_code_{NONE};
};
} // namespace st7567_i2c
} // namespace esphome

View file

@ -0,0 +1 @@
CODEOWNERS = ["@latonita"]

View file

@ -0,0 +1,34 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import spi, st7567_base
from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES
CODEOWNERS = ["@latonita"]
AUTO_LOAD = ["st7567_base"]
DEPENDENCIES = ["spi"]
st7567_spi = cg.esphome_ns.namespace("st7567_spi")
SPIST7567 = st7567_spi.class_("SPIST7567", st7567_base.ST7567, spi.SPIDevice)
CONFIG_SCHEMA = cv.All(
st7567_base.ST7567_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(SPIST7567),
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(spi.spi_device_schema()),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await st7567_base.setup_st7567(var, config)
await spi.register_spi_device(var, config)
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
cg.add(var.set_dc_pin(dc))

View file

@ -0,0 +1,66 @@
#include "st7567_spi.h"
#include "esphome/core/log.h"
namespace esphome {
namespace st7567_spi {
static const char *const TAG = "st7567_spi";
void SPIST7567::setup() {
ESP_LOGCONFIG(TAG, "Setting up SPI ST7567 display...");
this->spi_setup();
this->dc_pin_->setup();
if (this->cs_)
this->cs_->setup();
this->init_reset_();
ST7567::setup();
}
void SPIST7567::dump_config() {
LOG_DISPLAY("", "SPI ST7567", this);
ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_());
LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " Mirror X: %s", YESNO(this->mirror_x_));
ESP_LOGCONFIG(TAG, " Mirror Y: %s", YESNO(this->mirror_y_));
ESP_LOGCONFIG(TAG, " Invert Colors: %s", YESNO(this->invert_colors_));
LOG_UPDATE_INTERVAL(this);
}
void SPIST7567::command(uint8_t value) {
if (this->cs_)
this->cs_->digital_write(true);
this->dc_pin_->digital_write(false);
delay(1);
this->enable();
if (this->cs_)
this->cs_->digital_write(false);
this->write_byte(value);
if (this->cs_)
this->cs_->digital_write(true);
this->disable();
}
void HOT SPIST7567::write_display_data() {
// ST7567A has built-in RAM with 132x65 bit capacity which stores the display data.
// but only first 128 pixels from each line are shown on screen
// if screen got flipped horizontally then it shows last 128 pixels,
// so we need to write x coordinate starting from column 4, not column 0
this->command(esphome::st7567_base::ST7567_SET_START_LINE + this->start_line_);
for (uint8_t y = 0; y < (uint8_t) this->get_height_internal() / 8; y++) {
this->dc_pin_->digital_write(false);
this->command(esphome::st7567_base::ST7567_PAGE_ADDR + y); // Set Page
this->command(esphome::st7567_base::ST7567_COL_ADDR_H); // Set MSB Column address
this->command(esphome::st7567_base::ST7567_COL_ADDR_L + this->get_offset_x_()); // Set LSB Column address
this->dc_pin_->digital_write(true);
this->enable();
this->write_array(&this->buffer_[y * this->get_width_internal()], this->get_width_internal());
this->disable();
}
}
} // namespace st7567_spi
} // namespace esphome

View file

@ -0,0 +1,29 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/st7567_base/st7567_base.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace st7567_spi {
class SPIST7567 : public st7567_base::ST7567,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
spi::DATA_RATE_8MHZ> {
public:
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
void setup() override;
void dump_config() override;
protected:
void command(uint8_t value) override;
void write_display_data() override;
GPIOPin *dc_pin_;
};
} // namespace st7567_spi
} // namespace esphome

View file

@ -10,6 +10,7 @@ from esphome.const import (
CONF_MODEL,
CONF_RESET_PIN,
CONF_PAGES,
CONF_INVERT_COLORS,
)
from . import st7735_ns
@ -23,7 +24,6 @@ CONF_ROW_START = "row_start"
CONF_COL_START = "col_start"
CONF_EIGHT_BIT_COLOR = "eight_bit_color"
CONF_USE_BGR = "use_bgr"
CONF_INVERT_COLORS = "invert_colors"
SPIST7735 = st7735_ns.class_(
"ST7735", cg.PollingComponent, display.DisplayBuffer, spi.SPIDevice

View file

@ -14,12 +14,12 @@ from esphome.const import (
CONF_POWER_SUPPLY,
CONF_ROTATION,
CONF_CS_PIN,
CONF_OFFSET_HEIGHT,
CONF_OFFSET_WIDTH,
)
from . import st7789v_ns
CONF_EIGHTBITCOLOR = "eightbitcolor"
CONF_OFFSET_HEIGHT = "offset_height"
CONF_OFFSET_WIDTH = "offset_width"
CODEOWNERS = ["@kbx81"]

View file

@ -3,7 +3,14 @@ import esphome.codegen as cg
from esphome.components import display
from esphome import automation
from esphome.const import CONF_ON_TOUCH, CONF_ON_RELEASE
from esphome.const import (
CONF_ON_TOUCH,
CONF_ON_RELEASE,
CONF_MIRROR_X,
CONF_MIRROR_Y,
CONF_SWAP_XY,
CONF_TRANSFORM,
)
from esphome.core import coroutine_with_priority
CODEOWNERS = ["@jesserockz", "@nielsnl68"]
@ -26,11 +33,6 @@ CONF_REPORT_INTERVAL = "report_interval" # not used yet:
CONF_ON_UPDATE = "on_update"
CONF_TOUCH_TIMEOUT = "touch_timeout"
CONF_MIRROR_X = "mirror_x"
CONF_MIRROR_Y = "mirror_y"
CONF_SWAP_XY = "swap_xy"
CONF_TRANSFORM = "transform"
def touchscreen_schema(default_touch_timeout):
return cv.Schema(

View file

@ -127,6 +127,7 @@ CONF_COLOR_BRIGHTNESS = "color_brightness"
CONF_COLOR_CORRECT = "color_correct"
CONF_COLOR_INTERLOCK = "color_interlock"
CONF_COLOR_MODE = "color_mode"
CONF_COLOR_ORDER = "color_order"
CONF_COLOR_PALETTE = "color_palette"
CONF_COLOR_TEMPERATURE = "color_temperature"
CONF_COLORS = "colors"
@ -370,6 +371,7 @@ CONF_INTERRUPT_PIN = "interrupt_pin"
CONF_INTERVAL = "interval"
CONF_INVALID_COOLDOWN = "invalid_cooldown"
CONF_INVERT = "invert"
CONF_INVERT_COLORS = "invert_colors"
CONF_INVERTED = "inverted"
CONF_IP_ADDRESS = "ip_address"
CONF_IRQ_PIN = "irq_pin"
@ -454,6 +456,8 @@ CONF_MIN_VALUE = "min_value"
CONF_MIN_VERSION = "min_version"
CONF_MINUTE = "minute"
CONF_MINUTES = "minutes"
CONF_MIRROR_X = "mirror_x"
CONF_MIRROR_Y = "mirror_y"
CONF_MISO_PIN = "miso_pin"
CONF_MODE = "mode"
CONF_MODE_COMMAND_TOPIC = "mode_command_topic"
@ -485,6 +489,8 @@ CONF_NUMBER_DATAPOINT = "number_datapoint"
CONF_OFF_MODE = "off_mode"
CONF_OFF_SPEED_CYCLE = "off_speed_cycle"
CONF_OFFSET = "offset"
CONF_OFFSET_HEIGHT = "offset_height"
CONF_OFFSET_WIDTH = "offset_width"
CONF_ON = "on"
CONF_ON_BLE_ADVERTISE = "on_ble_advertise"
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE = "on_ble_manufacturer_data_advertise"
@ -749,6 +755,7 @@ CONF_SUPPORTED_PRESETS = "supported_presets"
CONF_SUPPORTED_SWING_MODES = "supported_swing_modes"
CONF_SUPPORTS_COOL = "supports_cool"
CONF_SUPPORTS_HEAT = "supports_heat"
CONF_SWAP_XY = "swap_xy"
CONF_SWING_BOTH_ACTION = "swing_both_action"
CONF_SWING_HORIZONTAL_ACTION = "swing_horizontal_action"
CONF_SWING_MODE = "swing_mode"
@ -799,6 +806,7 @@ CONF_TOPIC_PREFIX = "topic_prefix"
CONF_TOTAL = "total"
CONF_TOTAL_POWER = "total_power"
CONF_TRACES = "traces"
CONF_TRANSFORM = "transform"
CONF_TRANSITION_LENGTH = "transition_length"
CONF_TRIGGER_ID = "trigger_id"
CONF_TRIGGER_PIN = "trigger_pin"

View file

@ -358,8 +358,10 @@ esp32_ble_tracker:
ble_client:
- mac_address: AA:BB:CC:DD:EE:FF
id: ble_foo
auto_connect: true
- mac_address: 11:22:33:44:55:66
id: ble_blah
auto_connect: false
on_connect:
then:
- switch.turn_on: ble1_status
@ -3026,6 +3028,16 @@ interval:
page_id: page1
then:
- logger.log: Seeing page 1
- interval: 60min
then:
- ble_client.connect: ble_blah
- ble_client.ble_write:
id: ble_blah
service_uuid: EBE0CCB0-7A0A-4B0C-8A1A-6FF2997DA3A6
characteristic_uuid: EBE0CCB7-7A0A-4B0C-8A1A-6FF2997DA3A6
value: !lambda |-
return {1, 0};
- ble_client.disconnect: ble_blah
color:
- id: kbx_red
@ -3259,6 +3271,25 @@ display:
inverted: true
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
- platform: st7567_i2c
id: st7735_display_i2c
address: 0x3F
i2c_id: i2c_bus
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
- platform: st7567_spi
id: st7735_display_spi
cs_pin:
allow_other_uses: true
number: GPIO5
dc_pin:
allow_other_uses: true
number: GPIO16
reset_pin:
allow_other_uses: true
number: GPIO23
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
- platform: st7735
id: st7735_display
model: INITR_BLACKTAB