mirror of
https://github.com/esphome/esphome.git
synced 2025-02-25 04:22:30 +01:00
Merge branch 'dev' into nvds-status-info
This commit is contained in:
commit
85aebf9659
54 changed files with 1291 additions and 339 deletions
|
@ -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
|
||||
|
@ -316,6 +316,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
|
||||
|
|
|
@ -139,6 +139,9 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
|
|||
VARIANT_ESP32C3: {
|
||||
5: adc2_channel_t.ADC2_CHANNEL_0,
|
||||
},
|
||||
VARIANT_ESP32C2: {},
|
||||
VARIANT_ESP32C6: {},
|
||||
VARIANT_ESP32H2: {},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -35,6 +35,41 @@ void HOT Display::line(int x1, int y1, int x2, int y2, Color color) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
|
||||
ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
|
||||
size_t line_stride = x_offset + w + x_pad; // length of each source line in pixels
|
||||
uint32_t color_value;
|
||||
for (int y = 0; y != h; y++) {
|
||||
size_t source_idx = (y_offset + y) * line_stride + x_offset;
|
||||
size_t source_idx_mod;
|
||||
for (int x = 0; x != w; x++, source_idx++) {
|
||||
switch (bitness) {
|
||||
default:
|
||||
color_value = ptr[source_idx];
|
||||
break;
|
||||
case COLOR_BITNESS_565:
|
||||
source_idx_mod = source_idx * 2;
|
||||
if (big_endian) {
|
||||
color_value = (ptr[source_idx_mod] << 8) + ptr[source_idx_mod + 1];
|
||||
} else {
|
||||
color_value = ptr[source_idx_mod] + (ptr[source_idx_mod + 1] << 8);
|
||||
}
|
||||
break;
|
||||
case COLOR_BITNESS_888:
|
||||
source_idx_mod = source_idx * 3;
|
||||
if (big_endian) {
|
||||
color_value = (ptr[source_idx_mod + 0] << 16) + (ptr[source_idx_mod + 1] << 8) + ptr[source_idx_mod + 2];
|
||||
} else {
|
||||
color_value = ptr[source_idx_mod + 0] + (ptr[source_idx_mod + 1] << 8) + (ptr[source_idx_mod + 2] << 16);
|
||||
}
|
||||
break;
|
||||
}
|
||||
this->draw_pixel_at(x + x_start, y + y_start, ColorUtil::to_color(color_value, order, bitness));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HOT Display::horizontal_line(int x, int y, int width, Color color) {
|
||||
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
|
||||
for (int i = x; i < x + width; i++)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "esphome/core/color.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/time.h"
|
||||
#include "display_color_utils.h"
|
||||
|
||||
#ifdef USE_GRAPH
|
||||
#include "esphome/components/graph/graph.h"
|
||||
|
@ -185,6 +186,34 @@ class Display : public PollingComponent {
|
|||
/// Set a single pixel at the specified coordinates to the given color.
|
||||
virtual void draw_pixel_at(int x, int y, Color color) = 0;
|
||||
|
||||
/** Given an array of pixels encoded in the nominated format, draw these into the display's buffer.
|
||||
* The naive implementation here will work in all cases, but can be overridden by sub-classes
|
||||
* in order to optimise the procedure.
|
||||
* The parameters describe a rectangular block of pixels, potentially within a larger buffer.
|
||||
*
|
||||
* \param x_start The starting destination x position
|
||||
* \param y_start The starting destination y position
|
||||
* \param w the width of the pixel block
|
||||
* \param h the height of the pixel block
|
||||
* \param ptr A pointer to the start of the data to be copied
|
||||
* \param order The ordering of the colors
|
||||
* \param bitness Defines the number of bits and their format for each pixel
|
||||
* \param big_endian True if 16 bit values are stored big-endian
|
||||
* \param x_offset The initial x-offset into the source buffer.
|
||||
* \param y_offset The initial y-offset into the source buffer.
|
||||
* \param x_pad How many pixels are in each line after the end of the pixels to be copied.
|
||||
*
|
||||
* The length of each source buffer line (stride) will be x_offset + w + x_pad.
|
||||
*/
|
||||
virtual void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
|
||||
ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad);
|
||||
|
||||
/// Convenience overload for base case where the pixels are packed into the buffer with no gaps (e.g. suits LVGL.)
|
||||
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
|
||||
ColorBitness bitness, bool big_endian) {
|
||||
this->draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, 0, 0, 0);
|
||||
}
|
||||
|
||||
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
|
||||
void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
|
||||
|
||||
|
|
|
@ -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 *) ¬ify_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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -235,7 +235,7 @@ FILE_SCHEMA = cv.Schema(_file_schema)
|
|||
|
||||
|
||||
DEFAULT_GLYPHS = (
|
||||
' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
|
||||
' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
|
||||
)
|
||||
CONF_RAW_GLYPH_ID = "raw_glyph_id"
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ static const uint8_t GET_TOUCHES[2] = {0x81, 0x4F};
|
|||
static const uint8_t GET_SWITCHES[2] = {0x80, 0x4D};
|
||||
static const uint8_t GET_MAX_VALUES[2] = {0x80, 0x48};
|
||||
static const size_t MAX_TOUCHES = 5; // max number of possible touches reported
|
||||
static const size_t MAX_BUTTONS = 4; // max number of buttons scanned
|
||||
|
||||
#define ERROR_CHECK(err) \
|
||||
if ((err) != i2c::ERROR_OK) { \
|
||||
|
@ -79,9 +80,6 @@ void GT911Touchscreen::update_touches() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (num_of_touches == 0)
|
||||
return;
|
||||
|
||||
err = this->write(GET_TOUCHES, sizeof(GET_TOUCHES), false);
|
||||
ERROR_CHECK(err);
|
||||
// num_of_touches is guaranteed to be 0..5. Also read the key data
|
||||
|
@ -94,10 +92,13 @@ void GT911Touchscreen::update_touches() {
|
|||
uint16_t y = encode_uint16(data[i][4], data[i][3]);
|
||||
this->add_raw_touch_position_(id, x, y);
|
||||
}
|
||||
auto keys = data[num_of_touches][0];
|
||||
for (size_t i = 0; i != 4; i++) {
|
||||
for (auto *listener : this->button_listeners_)
|
||||
listener->update_button(i, (keys & (1 << i)) != 0);
|
||||
auto keys = data[num_of_touches][0] & ((1 << MAX_BUTTONS) - 1);
|
||||
if (keys != this->button_state_) {
|
||||
this->button_state_ = keys;
|
||||
for (size_t i = 0; i != MAX_BUTTONS; i++) {
|
||||
for (auto *listener : this->button_listeners_)
|
||||
listener->update_button(i, (keys & (1 << i)) != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
|
|||
|
||||
InternalGPIOPin *interrupt_pin_{};
|
||||
std::vector<GT911ButtonListener *> button_listeners_;
|
||||
uint8_t button_state_{0xFF}; // last button state. Initial FF guarantees first update.
|
||||
};
|
||||
|
||||
} // namespace gt911
|
||||
|
|
|
@ -495,4 +495,4 @@ async def to_code(config):
|
|||
trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf
|
||||
)
|
||||
# https://github.com/paveldn/HaierProtocol
|
||||
cg.add_library("pavlodn/HaierProtocol", "0.9.24")
|
||||
cg.add_library("pavlodn/HaierProtocol", "0.9.25")
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -276,6 +276,35 @@ void ILI9XXXDisplay::display_() {
|
|||
this->y_high_ = 0;
|
||||
}
|
||||
|
||||
// note that this bypasses the buffer and writes directly to the display.
|
||||
void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr,
|
||||
display::ColorOrder order, display::ColorBitness bitness, bool big_endian,
|
||||
int x_offset, int y_offset, int x_pad) {
|
||||
if (w <= 0 || h <= 0)
|
||||
return;
|
||||
// if color mapping or software rotation is required, hand this off to the parent implementation. This will
|
||||
// do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not
|
||||
// configured the renderer well.
|
||||
if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian ||
|
||||
this->is_18bitdisplay_) {
|
||||
return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
|
||||
x_pad);
|
||||
}
|
||||
this->enable();
|
||||
this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
|
||||
// x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
|
||||
if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
|
||||
// we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother
|
||||
this->write_array(ptr, w * h * 2);
|
||||
} else {
|
||||
auto stride = x_offset + w + x_pad;
|
||||
for (size_t y = 0; y != h; y++) {
|
||||
this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2);
|
||||
}
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
// should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
|
||||
// values per bit is huge
|
||||
uint32_t ILI9XXXDisplay::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/components/display/display_buffer.h"
|
||||
#include "esphome/components/display/display_color_utils.h"
|
||||
#include "ili9xxx_defines.h"
|
||||
#include "ili9xxx_init.h"
|
||||
|
||||
|
@ -84,6 +85,8 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
|||
void setup() override;
|
||||
|
||||
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
||||
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
|
||||
|
||||
protected:
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
|
|
|
@ -309,7 +309,7 @@ async def component_to_code(config):
|
|||
lt_options["LT_UART_SILENT_ENABLED"] = 0
|
||||
lt_options["LT_UART_SILENT_ALL"] = 0
|
||||
# set default UART port
|
||||
if uart_port := framework.get(CONF_UART_PORT, None) is not None:
|
||||
if (uart_port := framework.get(CONF_UART_PORT, None)) is not None:
|
||||
lt_options["LT_UART_DEFAULT_PORT"] = uart_port
|
||||
# add custom options
|
||||
lt_options.update(framework[CONF_OPTIONS])
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -19,7 +19,7 @@ PylontechComponent = pylontech_ns.class_(
|
|||
)
|
||||
PylontechBattery = pylontech_ns.class_("PylontechBattery")
|
||||
|
||||
CV_NUM_BATTERIES = cv.int_range(1, 6)
|
||||
CV_NUM_BATTERIES = cv.int_range(1, 16)
|
||||
|
||||
PYLONTECH_COMPONENT_SCHEMA = cv.Schema(
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "pylontech.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pylontech {
|
||||
|
@ -34,26 +35,30 @@ void PylontechComponent::setup() {
|
|||
void PylontechComponent::update() { this->write_str("pwr\n"); }
|
||||
|
||||
void PylontechComponent::loop() {
|
||||
uint8_t data;
|
||||
|
||||
// pylontech sends a lot of data very suddenly
|
||||
// we need to quickly put it all into our own buffer, otherwise the uart's buffer will overflow
|
||||
while (this->available() > 0) {
|
||||
if (this->read_byte(&data)) {
|
||||
buffer_[buffer_index_write_] += (char) data;
|
||||
if (buffer_[buffer_index_write_].back() == static_cast<char>(ASCII_LF) ||
|
||||
buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) {
|
||||
// complete line received
|
||||
buffer_index_write_ = (buffer_index_write_ + 1) % NUM_BUFFERS;
|
||||
if (this->available() > 0) {
|
||||
// pylontech sends a lot of data very suddenly
|
||||
// we need to quickly put it all into our own buffer, otherwise the uart's buffer will overflow
|
||||
uint8_t data;
|
||||
int recv = 0;
|
||||
while (this->available() > 0) {
|
||||
if (this->read_byte(&data)) {
|
||||
buffer_[buffer_index_write_] += (char) data;
|
||||
recv++;
|
||||
if (buffer_[buffer_index_write_].back() == static_cast<char>(ASCII_LF) ||
|
||||
buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) {
|
||||
// complete line received
|
||||
buffer_index_write_ = (buffer_index_write_ + 1) % NUM_BUFFERS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only process one line per call of loop() to not block esphome for too long
|
||||
if (buffer_index_read_ != buffer_index_write_) {
|
||||
this->process_line_(buffer_[buffer_index_read_]);
|
||||
buffer_[buffer_index_read_].clear();
|
||||
buffer_index_read_ = (buffer_index_read_ + 1) % NUM_BUFFERS;
|
||||
ESP_LOGV(TAG, "received %d bytes", recv);
|
||||
} else {
|
||||
// only process one line per call of loop() to not block esphome for too long
|
||||
if (buffer_index_read_ != buffer_index_write_) {
|
||||
this->process_line_(buffer_[buffer_index_read_]);
|
||||
buffer_[buffer_index_read_].clear();
|
||||
buffer_index_read_ = (buffer_index_read_ + 1) % NUM_BUFFERS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,10 +71,11 @@ void PylontechComponent::process_line_(std::string &buffer) {
|
|||
// clang-format on
|
||||
|
||||
PylontechListener::LineContents l{};
|
||||
const int parsed = sscanf( // NOLINT
|
||||
buffer.c_str(), "%d %d %d %d %d %d %d %d %7s %7s %7s %7s %d%% %*d-%*d-%*d %*d:%*d:%*d %*s %*s %d %*s", // NOLINT
|
||||
&l.bat_num, &l.volt, &l.curr, &l.tempr, &l.tlow, &l.thigh, &l.vlow, &l.vhigh, l.base_st, l.volt_st, // NOLINT
|
||||
l.curr_st, l.temp_st, &l.coulomb, &l.mostempr); // NOLINT
|
||||
char mostempr_s[6];
|
||||
const int parsed = sscanf( // NOLINT
|
||||
buffer.c_str(), "%d %d %d %d %d %d %d %d %7s %7s %7s %7s %d%% %*d-%*d-%*d %*d:%*d:%*d %*s %*s %5s %*s", // NOLINT
|
||||
&l.bat_num, &l.volt, &l.curr, &l.tempr, &l.tlow, &l.thigh, &l.vlow, &l.vhigh, l.base_st, l.volt_st, // NOLINT
|
||||
l.curr_st, l.temp_st, &l.coulomb, mostempr_s); // NOLINT
|
||||
|
||||
if (l.bat_num <= 0) {
|
||||
ESP_LOGD(TAG, "invalid bat_num in line %s", buffer.substr(0, buffer.size() - 2).c_str());
|
||||
|
@ -79,6 +85,13 @@ void PylontechComponent::process_line_(std::string &buffer) {
|
|||
ESP_LOGW(TAG, "invalid line: found only %d items in %s", parsed, buffer.substr(0, buffer.size() - 2).c_str());
|
||||
return;
|
||||
}
|
||||
auto mostempr_parsed = parse_number<int>(mostempr_s);
|
||||
if (mostempr_parsed.has_value()) {
|
||||
l.mostempr = mostempr_parsed.value();
|
||||
} else {
|
||||
l.mostempr = -300;
|
||||
ESP_LOGW(TAG, "bat_num %d: received no mostempr", l.bat_num);
|
||||
}
|
||||
|
||||
for (PylontechListener *listener : this->listeners_) {
|
||||
listener->on_line_read(&l);
|
||||
|
|
|
@ -59,14 +59,14 @@ TYPES: dict[str, cv.Schema] = {
|
|||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
),
|
||||
CONF_VOLTAGE_LOW: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
CONF_VOLTAGE_HIGH: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
CONF_COULOMB: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
|
|
55
esphome/components/st7567_base/__init__.py
Normal file
55
esphome/components/st7567_base/__init__.py
Normal 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_))
|
152
esphome/components/st7567_base/st7567_base.cpp
Normal file
152
esphome/components/st7567_base/st7567_base.cpp
Normal 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
|
100
esphome/components/st7567_base/st7567_base.h
Normal file
100
esphome/components/st7567_base/st7567_base.h
Normal 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
|
1
esphome/components/st7567_i2c/__init__.py
Normal file
1
esphome/components/st7567_i2c/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@latonita"]
|
29
esphome/components/st7567_i2c/display.py
Normal file
29
esphome/components/st7567_i2c/display.py
Normal 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)
|
60
esphome/components/st7567_i2c/st7567_i2c.cpp
Normal file
60
esphome/components/st7567_i2c/st7567_i2c.cpp
Normal 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
|
23
esphome/components/st7567_i2c/st7567_i2c.h
Normal file
23
esphome/components/st7567_i2c/st7567_i2c.h
Normal 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
|
1
esphome/components/st7567_spi/__init__.py
Normal file
1
esphome/components/st7567_spi/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@latonita"]
|
34
esphome/components/st7567_spi/display.py
Normal file
34
esphome/components/st7567_spi/display.py
Normal 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))
|
66
esphome/components/st7567_spi/st7567_spi.cpp
Normal file
66
esphome/components/st7567_spi/st7567_spi.cpp
Normal 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
|
29
esphome/components/st7567_spi/st7567_spi.h
Normal file
29
esphome/components/st7567_spi/st7567_spi.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -39,7 +39,7 @@ lib_deps =
|
|||
bblanchon/ArduinoJson@6.18.5 ; json
|
||||
wjtje/qr-code-generator-library@1.7.0 ; qr_code
|
||||
functionpointer/arduino-MLX90393@1.0.0 ; mlx90393
|
||||
pavlodn/HaierProtocol@0.9.24 ; haier
|
||||
pavlodn/HaierProtocol@0.9.25 ; haier
|
||||
; This is using the repository until a new release is published to PlatformIO
|
||||
https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library
|
||||
build_flags =
|
||||
|
|
3
requirements_dev.txt
Normal file
3
requirements_dev.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Useful stuff when working in a development environment
|
||||
clang-format==13.0.1
|
||||
clang-tidy==14.0.6
|
|
@ -11,5 +11,3 @@ pytest-mock==3.12.0
|
|||
pytest-asyncio==0.23.2
|
||||
asyncmock==0.4.2
|
||||
hypothesis==5.49.0
|
||||
|
||||
clang-format==13.0.1 ; platform_machine != 'armv7l'
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from helpers import print_error_for_file, get_output, git_ls_files, filter_changed
|
||||
from helpers import (
|
||||
print_error_for_file,
|
||||
get_output,
|
||||
git_ls_files,
|
||||
filter_changed,
|
||||
get_binary,
|
||||
)
|
||||
import argparse
|
||||
import click
|
||||
import colorama
|
||||
|
@ -13,11 +19,12 @@ import sys
|
|||
import threading
|
||||
|
||||
|
||||
def run_format(args, queue, lock, failed_files):
|
||||
|
||||
def run_format(executable, args, queue, lock, failed_files):
|
||||
"""Takes filenames out of queue and runs clang-format on them."""
|
||||
while True:
|
||||
path = queue.get()
|
||||
invocation = ["clang-format-13"]
|
||||
invocation = [executable]
|
||||
if args.inplace:
|
||||
invocation.append("-i")
|
||||
else:
|
||||
|
@ -58,22 +65,6 @@ def main():
|
|||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
get_output("clang-format-13", "-version")
|
||||
except:
|
||||
print(
|
||||
"""
|
||||
Oops. It looks like clang-format is not installed.
|
||||
|
||||
Please check you can run "clang-format-13 -version" in your terminal and install
|
||||
clang-format (v13) if necessary.
|
||||
|
||||
Note you can also upload your code as a pull request on GitHub and see the CI check
|
||||
output to apply clang-format.
|
||||
"""
|
||||
)
|
||||
return 1
|
||||
|
||||
files = []
|
||||
for path in git_ls_files(["*.cpp", "*.h", "*.tcc"]):
|
||||
files.append(os.path.relpath(path, os.getcwd()))
|
||||
|
@ -90,11 +81,12 @@ def main():
|
|||
|
||||
failed_files = []
|
||||
try:
|
||||
executable = get_binary("clang-format", 13)
|
||||
task_queue = queue.Queue(args.jobs)
|
||||
lock = threading.Lock()
|
||||
for _ in range(args.jobs):
|
||||
t = threading.Thread(
|
||||
target=run_format, args=(args, task_queue, lock, failed_files)
|
||||
target=run_format, args=(executable, args, task_queue, lock, failed_files)
|
||||
)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
@ -109,13 +101,18 @@ def main():
|
|||
# Wait for all threads to be done.
|
||||
task_queue.join()
|
||||
|
||||
except FileNotFoundError as ex:
|
||||
return 1
|
||||
except KeyboardInterrupt:
|
||||
print()
|
||||
print("Ctrl-C detected, goodbye.")
|
||||
# Kill subprocesses (and ourselves!)
|
||||
# No simple, clean alternative appears to be available.
|
||||
os.kill(0, 9)
|
||||
return 2 # Will not execute.
|
||||
|
||||
sys.exit(len(failed_files))
|
||||
return len(failed_files)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(main())
|
||||
|
|
|
@ -11,6 +11,7 @@ from helpers import (
|
|||
load_idedata,
|
||||
root_path,
|
||||
basepath,
|
||||
get_binary,
|
||||
)
|
||||
import argparse
|
||||
import click
|
||||
|
@ -26,6 +27,7 @@ import tempfile
|
|||
import threading
|
||||
|
||||
|
||||
|
||||
def clang_options(idedata):
|
||||
cmd = []
|
||||
|
||||
|
@ -110,10 +112,12 @@ def clang_options(idedata):
|
|||
return cmd
|
||||
|
||||
|
||||
def run_tidy(args, options, tmpdir, queue, lock, failed_files):
|
||||
pids = set()
|
||||
|
||||
def run_tidy(executable, args, options, tmpdir, queue, lock, failed_files):
|
||||
while True:
|
||||
path = queue.get()
|
||||
invocation = ["clang-tidy-14"]
|
||||
invocation = [executable]
|
||||
|
||||
if tmpdir is not None:
|
||||
invocation.append("--export-fixes")
|
||||
|
@ -193,22 +197,6 @@ def main():
|
|||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
get_output("clang-tidy-14", "-version")
|
||||
except:
|
||||
print(
|
||||
"""
|
||||
Oops. It looks like clang-tidy-14 is not installed.
|
||||
|
||||
Please check you can run "clang-tidy-14 -version" in your terminal and install
|
||||
clang-tidy (v14) if necessary.
|
||||
|
||||
Note you can also upload your code as a pull request on GitHub and see the CI check
|
||||
output to apply clang-tidy.
|
||||
"""
|
||||
)
|
||||
return 1
|
||||
|
||||
idedata = load_idedata(args.environment)
|
||||
options = clang_options(idedata)
|
||||
|
||||
|
@ -242,12 +230,13 @@ def main():
|
|||
|
||||
failed_files = []
|
||||
try:
|
||||
executable = get_binary("clang-tidy", 14)
|
||||
task_queue = queue.Queue(args.jobs)
|
||||
lock = threading.Lock()
|
||||
for _ in range(args.jobs):
|
||||
t = threading.Thread(
|
||||
target=run_tidy,
|
||||
args=(args, options, tmpdir, task_queue, lock, failed_files),
|
||||
args=(executable, args, options, tmpdir, task_queue, lock, failed_files),
|
||||
)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
@ -262,12 +251,17 @@ def main():
|
|||
# Wait for all threads to be done.
|
||||
task_queue.join()
|
||||
|
||||
except FileNotFoundError as ex:
|
||||
return 1
|
||||
except KeyboardInterrupt:
|
||||
print()
|
||||
print("Ctrl-C detected, goodbye.")
|
||||
if tmpdir:
|
||||
shutil.rmtree(tmpdir)
|
||||
# Kill subprocesses (and ourselves!)
|
||||
# No simple, clean alternative appears to be available.
|
||||
os.kill(0, 9)
|
||||
return 2 # Will not execute.
|
||||
|
||||
if args.fix and failed_files:
|
||||
print("Applying fixes ...")
|
||||
|
@ -277,8 +271,8 @@ def main():
|
|||
print("Error applying fixes.\n", file=sys.stderr)
|
||||
raise
|
||||
|
||||
sys.exit(len(failed_files))
|
||||
return len(failed_files)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(main())
|
||||
|
|
|
@ -153,3 +153,39 @@ def load_idedata(environment):
|
|||
|
||||
temp_idedata.write_text(json.dumps(data, indent=2) + "\n")
|
||||
return data
|
||||
|
||||
|
||||
def get_binary(name: str, version: str) -> str:
|
||||
binary_file = f"{name}-{version}"
|
||||
try:
|
||||
result = subprocess.check_output([binary_file, "-version"])
|
||||
if result.returncode == 0:
|
||||
return binary_file
|
||||
except Exception:
|
||||
pass
|
||||
binary_file = name
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[binary_file, "-version"], text=True, capture_output=True
|
||||
)
|
||||
if result.returncode == 0 and (f"version {version}") in result.stdout:
|
||||
return binary_file
|
||||
raise FileNotFoundError(f"{name} not found")
|
||||
|
||||
except FileNotFoundError as ex:
|
||||
print(
|
||||
f"""
|
||||
Oops. It looks like {name} is not installed. It should be available under venv/bin
|
||||
and in PATH after running in turn:
|
||||
script/setup
|
||||
source venv/bin/activate.
|
||||
|
||||
Please confirm you can run "{name} -version" or "{name}-{version} -version"
|
||||
in your terminal and install
|
||||
{name} (v{version}) if necessary.
|
||||
|
||||
Note you can also upload your code as a pull request on GitHub and see the CI check
|
||||
output to apply {name}
|
||||
"""
|
||||
)
|
||||
raise
|
||||
|
|
|
@ -15,10 +15,14 @@ if [ -n "$DEVCONTAINER" ];then
|
|||
git config --global --add safe.directory "$PWD"
|
||||
fi
|
||||
|
||||
pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
|
||||
pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt -r requirements_dev.txt
|
||||
pip3 install setuptools wheel
|
||||
pip3 install --no-use-pep517 -e .
|
||||
|
||||
pre-commit install
|
||||
|
||||
script/platformio_install_deps.py platformio.ini --libraries --tools --platforms
|
||||
|
||||
echo
|
||||
echo
|
||||
echo "Virtual environment created; source venv/bin/activate to use it"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1024,6 +1024,7 @@ climate:
|
|||
name: Haier AC
|
||||
uart_id: uart_12
|
||||
wifi_signal: true
|
||||
answer_timeout: 200ms
|
||||
beeper: true
|
||||
outdoor_temperature:
|
||||
name: Haier AC outdoor temperature
|
||||
|
|
Loading…
Add table
Reference in a new issue