Merge pull request #3900 from esphome/bump-2022.10.0b1

2022.10.0b1
This commit is contained in:
Jesse Hills 2022-10-13 09:50:04 +13:00 committed by GitHub
commit a6c999dea0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
113 changed files with 4248 additions and 1383 deletions

View File

@ -28,6 +28,7 @@ jobs:
name: Build docker containers name: Build docker containers
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false
matrix: matrix:
arch: [amd64, armv7, aarch64] arch: [amd64, armv7, aarch64]
build_type: ["ha-addon", "docker", "lint"] build_type: ["ha-addon", "docker", "lint"]

View File

@ -85,7 +85,7 @@ jobs:
uses: actions/setup-python@v4 uses: actions/setup-python@v4
id: python id: python
with: with:
python-version: "3.8" python-version: "3.9"
- name: Cache virtualenv - name: Cache virtualenv
uses: actions/cache@v3 uses: actions/cache@v3

View File

@ -27,7 +27,7 @@ repos:
- --branch=release - --branch=release
- --branch=beta - --branch=beta
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v2.37.3 rev: v3.0.0
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py38-plus] args: [--py39-plus]

View File

@ -70,6 +70,7 @@ esphome/components/ektf2232/* @jesserockz
esphome/components/ens210/* @itn3rd77 esphome/components/ens210/* @itn3rd77
esphome/components/esp32/* @esphome/core esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble_client/* @jesserockz
esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz
esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron esphome/components/esp32_can/* @Sympatron
@ -257,4 +258,4 @@ esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz esphome/components/xiaomi_rtcgq02lm/* @jesserockz
esphome/components/xpt2046/* @numo68 esphome/components/xpt2046/* @nielsnl68 @numo68

View File

@ -22,6 +22,7 @@ from esphome.cpp_generator import ( # noqa
static_const_array, static_const_array,
statement, statement,
variable, variable,
with_local_variable,
new_variable, new_variable,
Pvariable, Pvariable,
new_Pvariable, new_Pvariable,

View File

@ -38,7 +38,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
} }
case ESP_GATTC_READ_CHAR_EVT: { case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->conn_id) if (param->read.conn_id != this->parent()->get_conn_id())
break; break;
if (param->read.status != ESP_GATT_OK) { if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
@ -88,8 +88,8 @@ void AirthingsWaveMini::update() {
} }
void AirthingsWaveMini::request_read_values_() { void AirthingsWaveMini::request_read_values_() {
auto status = auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE); ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
} }

View File

@ -38,7 +38,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
} }
case ESP_GATTC_READ_CHAR_EVT: { case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->conn_id) if (param->read.conn_id != this->parent()->get_conn_id())
break; break;
if (param->read.status != ESP_GATT_OK) { if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
@ -109,8 +109,8 @@ void AirthingsWavePlus::update() {
} }
void AirthingsWavePlus::request_read_values_() { void AirthingsWavePlus::request_read_values_() {
auto status = auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE); ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
} }

View File

@ -76,9 +76,9 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
if (this->current_sensor_ > 0) { if (this->current_sensor_ > 0) {
if (this->illuminance_ != nullptr) { if (this->illuminance_ != nullptr) {
auto *packet = this->encoder_->get_light_level_request(); auto *packet = this->encoder_->get_light_level_request();
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, this->char_handle_, packet->length, packet->data,
ESP_GATT_AUTH_REQ_NONE); ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
status); status);
@ -102,8 +102,8 @@ void Am43::update() {
if (this->battery_ != nullptr) { if (this->battery_ != nullptr) {
auto *packet = this->encoder_->get_battery_level_request(); auto *packet = this->encoder_->get_battery_level_request();
auto status = auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
} }

View File

@ -27,8 +27,8 @@ void Am43Component::loop() {
if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) { if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) {
auto *packet = this->encoder_->get_send_pin_request(this->pin_); auto *packet = this->encoder_->get_send_pin_request(this->pin_);
auto status = auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str()); ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str());
if (status) { if (status) {
ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status); ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status);
@ -54,8 +54,8 @@ void Am43Component::control(const CoverCall &call) {
if (call.get_stop()) { if (call.get_stop()) {
auto *packet = this->encoder_->get_stop_request(); auto *packet = this->encoder_->get_stop_request();
auto status = auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) if (status)
ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status); ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status);
} }
@ -66,8 +66,8 @@ void Am43Component::control(const CoverCall &call) {
pos = 1 - pos; pos = 1 - pos;
auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100)); auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
auto status = auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) if (status)
ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status); ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status);
} }
@ -92,7 +92,8 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
} }
this->char_handle_ = chr->handle; this->char_handle_ = chr->handle;
auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle); auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
chr->handle);
if (status) { if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
} }
@ -122,9 +123,9 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
if (this->decoder_->pin_ok_) { if (this->decoder_->pin_ok_) {
ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str()); ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str());
auto *packet = this->encoder_->get_position_request(); auto *packet = this->encoder_->get_position_request();
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, this->char_handle_, packet->length, packet->data,
ESP_GATT_AUTH_REQ_NONE); ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) if (status)
ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status); ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status);
} else { } else {

View File

@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ["display"] DEPENDENCIES = ["display"]
MULTI_CONF = True MULTI_CONF = True
Animation_ = display.display_ns.class_("Animation") Animation_ = display.display_ns.class_("Animation", espImage.Image_)
ANIMATION_SCHEMA = cv.Schema( ANIMATION_SCHEMA = cv.Schema(
{ {

View File

@ -34,15 +34,17 @@ void Anova::control(const ClimateCall &call) {
ESP_LOGW(TAG, "Unsupported mode: %d", mode); ESP_LOGW(TAG, "Unsupported mode: %d", mode);
return; return;
} }
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, auto status =
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
} }
if (call.get_target_temperature().has_value()) { if (call.get_target_temperature().has_value()) {
auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature()); auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature());
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, auto status =
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
} }
@ -65,7 +67,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
} }
this->char_handle_ = chr->handle; this->char_handle_ = chr->handle;
auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle); auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
chr->handle);
if (status) { if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
} }
@ -112,8 +115,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
} }
if (pkt != nullptr) { if (pkt != nullptr) {
auto status = auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length, esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
status); status);
@ -137,8 +140,9 @@ void Anova::update() {
auto *pkt = this->codec_->get_read_device_status_request(); auto *pkt = this->codec_->get_read_device_status_request();
if (this->current_request_ == 0) if (this->current_request_ == 0)
this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c'); this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c');
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, auto status =
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
this->current_request_++; this->current_request_++;

View File

@ -27,7 +27,6 @@ service APIConnection {
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {} rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {} rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {} rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
rpc subscribe_bluetooth_le_advertisements (SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc get_time (GetTimeRequest) returns (GetTimeResponse) { rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
option (needs_authentication) = false; option (needs_authentication) = false;
} }
@ -44,6 +43,16 @@ service APIConnection {
rpc button_command (ButtonCommandRequest) returns (void) {} rpc button_command (ButtonCommandRequest) returns (void) {}
rpc lock_command (LockCommandRequest) returns (void) {} rpc lock_command (LockCommandRequest) returns (void) {}
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {} rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
rpc bluetooth_gatt_get_services(BluetoothGATTGetServicesRequest) returns (void) {}
rpc bluetooth_gatt_read(BluetoothGATTReadRequest) returns (void) {}
rpc bluetooth_gatt_write(BluetoothGATTWriteRequest) returns (void) {}
rpc bluetooth_gatt_read_descriptor(BluetoothGATTReadDescriptorRequest) returns (void) {}
rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {}
rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
} }
@ -78,6 +87,8 @@ message HelloRequest {
// Not strictly necessary to send but nice for debugging // Not strictly necessary to send but nice for debugging
// purposes. // purposes.
string client_info = 1; string client_info = 1;
uint32 api_version_major = 2;
uint32 api_version_minor = 3;
} }
// Confirmation of successful connection request. // Confirmation of successful connection request.
@ -192,7 +203,7 @@ message DeviceInfoResponse {
uint32 webserver_port = 10; uint32 webserver_port = 10;
bool has_bluetooth_proxy = 11; uint32 bluetooth_proxy_version = 11;
} }
message ListEntitiesRequest { message ListEntitiesRequest {
@ -1111,7 +1122,8 @@ message SubscribeBluetoothLEAdvertisementsRequest {
message BluetoothServiceData { message BluetoothServiceData {
string uuid = 1; string uuid = 1;
repeated uint32 data = 2 [packed=false]; repeated uint32 legacy_data = 2 [deprecated = true];
bytes data = 3; // Changed in proto version 1.7
} }
message BluetoothLEAdvertisementResponse { message BluetoothLEAdvertisementResponse {
option (id) = 67; option (id) = 67;
@ -1127,3 +1139,190 @@ message BluetoothLEAdvertisementResponse {
repeated BluetoothServiceData service_data = 5; repeated BluetoothServiceData service_data = 5;
repeated BluetoothServiceData manufacturer_data = 6; repeated BluetoothServiceData manufacturer_data = 6;
} }
enum BluetoothDeviceRequestType {
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
}
message BluetoothDeviceRequest {
option (id) = 68;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
BluetoothDeviceRequestType request_type = 2;
}
message BluetoothDeviceConnectionResponse {
option (id) = 69;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
bool connected = 2;
uint32 mtu = 3;
int32 error = 4;
}
message BluetoothGATTGetServicesRequest {
option (id) = 70;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
}
message BluetoothGATTDescriptor {
repeated uint64 uuid = 1;
uint32 handle = 2;
}
message BluetoothGATTCharacteristic {
repeated uint64 uuid = 1;
uint32 handle = 2;
uint32 properties = 3;
repeated BluetoothGATTDescriptor descriptors = 4;
}
message BluetoothGATTService {
repeated uint64 uuid = 1;
uint32 handle = 2;
repeated BluetoothGATTCharacteristic characteristics = 3;
}
message BluetoothGATTGetServicesResponse {
option (id) = 71;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
repeated BluetoothGATTService services = 2;
}
message BluetoothGATTGetServicesDoneResponse {
option (id) = 72;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
}
message BluetoothGATTReadRequest {
option (id) = 73;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
}
message BluetoothGATTReadResponse {
option (id) = 74;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
bytes data = 3;
}
message BluetoothGATTWriteRequest {
option (id) = 75;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
bool response = 3;
bytes data = 4;
}
message BluetoothGATTReadDescriptorRequest {
option (id) = 76;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
}
message BluetoothGATTWriteDescriptorRequest {
option (id) = 77;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
bytes data = 3;
}
message BluetoothGATTNotifyRequest {
option (id) = 78;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
bool enable = 3;
}
message BluetoothGATTNotifyDataResponse {
option (id) = 79;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
bytes data = 3;
}
message SubscribeBluetoothConnectionsFreeRequest {
option (id) = 80;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY";
}
message BluetoothConnectionsFreeResponse {
option (id) = 81;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint32 free = 1;
uint32 limit = 2;
}
message BluetoothGATTErrorResponse {
option (id) = 82;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
int32 error = 3;
}
message BluetoothGATTWriteResponse {
option (id) = 83;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
}
message BluetoothGATTNotifyResponse {
option (id) = 84;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
}

View File

@ -1,10 +1,10 @@
#include "api_connection.h" #include "api_connection.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/log.h"
#include "esphome/components/network/util.h"
#include "esphome/core/version.h"
#include "esphome/core/hal.h"
#include <cerrno> #include <cerrno>
#include "esphome/components/network/util.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/version.h"
#ifdef USE_DEEP_SLEEP #ifdef USE_DEEP_SLEEP
#include "esphome/components/deep_sleep/deep_sleep_component.h" #include "esphome/components/deep_sleep/deep_sleep_component.h"
@ -12,6 +12,9 @@
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
#include "esphome/components/homeassistant/time/homeassistant_time.h" #include "esphome/components/homeassistant/time/homeassistant_time.h"
#endif #endif
#ifdef USE_BLUETOOTH_PROXY
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
#endif
namespace esphome { namespace esphome {
namespace api { namespace api {
@ -823,6 +826,56 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY
bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) {
if (!this->bluetooth_le_advertisement_subscription_)
return false;
if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) {
BluetoothLEAdvertisementResponse resp = msg;
for (auto &service : resp.service_data) {
service.legacy_data.assign(service.data.begin(), service.data.end());
service.data.clear();
}
for (auto &manufacturer_data : resp.manufacturer_data) {
manufacturer_data.legacy_data.assign(manufacturer_data.data.begin(), manufacturer_data.data.end());
manufacturer_data.data.clear();
}
return this->send_bluetooth_le_advertisement_response(resp);
}
return this->send_bluetooth_le_advertisement_response(msg);
}
void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg);
}
void APIConnection::bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read(msg);
}
void APIConnection::bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write(msg);
}
void APIConnection::bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read_descriptor(msg);
}
void APIConnection::bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write_descriptor(msg);
}
void APIConnection::bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_send_services(msg);
}
void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg);
}
BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_free(
const SubscribeBluetoothConnectionsFreeRequest &msg) {
BluetoothConnectionsFreeResponse resp;
resp.free = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_free();
resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit();
return resp;
}
#endif
bool APIConnection::send_log_message(int level, const char *tag, const char *line) { bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
if (this->log_subscription_ < level) if (this->log_subscription_ < level)
return false; return false;
@ -840,11 +893,14 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
HelloResponse APIConnection::hello(const HelloRequest &msg) { HelloResponse APIConnection::hello(const HelloRequest &msg) {
this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")"; this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")";
this->helper_->set_log_info(client_info_); this->helper_->set_log_info(client_info_);
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str()); this->client_api_version_major_ = msg.api_version_major;
this->client_api_version_minor_ = msg.api_version_minor;
ESP_LOGV(TAG, "Hello from client: '%s' | API Version %d.%d", this->client_info_.c_str(),
this->client_api_version_major_, this->client_api_version_minor_);
HelloResponse resp; HelloResponse resp;
resp.api_version_major = 1; resp.api_version_major = 1;
resp.api_version_minor = 6; resp.api_version_minor = 7;
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
resp.name = App.get_name(); resp.name = App.get_name();
@ -888,7 +944,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
resp.webserver_port = USE_WEBSERVER_PORT; resp.webserver_port = USE_WEBSERVER_PORT;
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
resp.has_bluetooth_proxy = true; resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 2 : 1;
#endif #endif
return resp; return resp;
} }

View File

@ -1,15 +1,11 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "api_frame_helper.h"
#include "esphome/core/application.h"
#include "api_pb2.h" #include "api_pb2.h"
#include "api_pb2_service.h" #include "api_pb2_service.h"
#include "api_server.h" #include "api_server.h"
#include "api_frame_helper.h" #include "esphome/core/application.h"
#include "esphome/core/component.h"
#ifdef USE_BLUETOOTH_PROXY
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
#endif
namespace esphome { namespace esphome {
namespace api { namespace api {
@ -99,11 +95,18 @@ class APIConnection : public APIServerConnection {
this->send_homeassistant_service_response(call); this->send_homeassistant_service_response(call);
} }
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) { bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg);
if (!this->bluetooth_le_advertisement_subscription_)
return false; void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
return this->send_bluetooth_le_advertisement_response(call); void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override;
} void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) override;
void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) override;
void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override;
void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override;
void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
const SubscribeBluetoothConnectionsFreeRequest &msg) override;
#endif #endif
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void send_time_request() { void send_time_request() {
@ -181,6 +184,8 @@ class APIConnection : public APIServerConnection {
std::unique_ptr<APIFrameHelper> helper_; std::unique_ptr<APIFrameHelper> helper_;
std::string client_info_; std::string client_info_;
uint32_t client_api_version_major_{0};
uint32_t client_api_version_minor_{0};
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
esp32_camera::CameraImageReader image_reader_; esp32_camera::CameraImageReader image_reader_;
#endif #endif
@ -190,7 +195,7 @@ class APIConnection : public APIServerConnection {
uint32_t last_traffic_; uint32_t last_traffic_;
bool sent_ping_{false}; bool sent_ping_{false};
bool service_call_subscription_{false}; bool service_call_subscription_{false};
bool bluetooth_le_advertisement_subscription_{true}; bool bluetooth_le_advertisement_subscription_{false};
bool next_close_ = false; bool next_close_ = false;
APIServer *parent_; APIServer *parent_;
InitialStateIterator initial_state_iterator_; InitialStateIterator initial_state_iterator_;

View File

@ -340,6 +340,35 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::Me
return "UNKNOWN"; return "UNKNOWN";
} }
} }
template<>
const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::BluetoothDeviceRequestType value) {
switch (value) {
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT:
return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT";
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT:
return "BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT";
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
return "BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR";
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
return "BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR";
default:
return "UNKNOWN";
}
}
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->api_version_major = value.as_uint32();
return true;
}
case 3: {
this->api_version_minor = value.as_uint32();
return true;
}
default:
return false;
}
}
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: { case 1: {
@ -350,7 +379,11 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value)
return false; return false;
} }
} }
void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } void HelloRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->client_info);
buffer.encode_uint32(2, this->api_version_major);
buffer.encode_uint32(3, this->api_version_minor);
}
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void HelloRequest::dump_to(std::string &out) const { void HelloRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
@ -358,6 +391,16 @@ void HelloRequest::dump_to(std::string &out) const {
out.append(" client_info: "); out.append(" client_info: ");
out.append("'").append(this->client_info).append("'"); out.append("'").append(this->client_info).append("'");
out.append("\n"); out.append("\n");
out.append(" api_version_major: ");
sprintf(buffer, "%u", this->api_version_major);
out.append(buffer);
out.append("\n");
out.append(" api_version_minor: ");
sprintf(buffer, "%u", this->api_version_minor);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -496,7 +539,7 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
return true; return true;
} }
case 11: { case 11: {
this->has_bluetooth_proxy = value.as_bool(); this->bluetooth_proxy_version = value.as_uint32();
return true; return true;
} }
default: default:
@ -548,7 +591,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(8, this->project_name); buffer.encode_string(8, this->project_name);
buffer.encode_string(9, this->project_version); buffer.encode_string(9, this->project_version);
buffer.encode_uint32(10, this->webserver_port); buffer.encode_uint32(10, this->webserver_port);
buffer.encode_bool(11, this->has_bluetooth_proxy); buffer.encode_uint32(11, this->bluetooth_proxy_version);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const { void DeviceInfoResponse::dump_to(std::string &out) const {
@ -595,8 +638,9 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" has_bluetooth_proxy: "); out.append(" bluetooth_proxy_version: ");
out.append(YESNO(this->has_bluetooth_proxy)); sprintf(buffer, "%u", this->bluetooth_proxy_version);
out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
} }
@ -4872,7 +4916,7 @@ void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const
bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) { bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 2: { case 2: {
this->data.push_back(value.as_uint32()); this->legacy_data.push_back(value.as_uint32());
return true; return true;
} }
default: default:
@ -4885,15 +4929,20 @@ bool BluetoothServiceData::decode_length(uint32_t field_id, ProtoLengthDelimited
this->uuid = value.as_string(); this->uuid = value.as_string();
return true; return true;
} }
case 3: {
this->data = value.as_string();
return true;
}
default: default:
return false; return false;
} }
} }
void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const { void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->uuid); buffer.encode_string(1, this->uuid);
for (auto &it : this->data) { for (auto &it : this->legacy_data) {
buffer.encode_uint32(2, it, true); buffer.encode_uint32(2, it, true);
} }
buffer.encode_string(3, this->data);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothServiceData::dump_to(std::string &out) const { void BluetoothServiceData::dump_to(std::string &out) const {
@ -4903,12 +4952,16 @@ void BluetoothServiceData::dump_to(std::string &out) const {
out.append("'").append(this->uuid).append("'"); out.append("'").append(this->uuid).append("'");
out.append("\n"); out.append("\n");
for (const auto &it : this->data) { for (const auto &it : this->legacy_data) {
out.append(" data: "); out.append(" legacy_data: ");
sprintf(buffer, "%u", it); sprintf(buffer, "%u", it);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
} }
out.append(" data: ");
out.append("'").append(this->data).append("'");
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -5000,6 +5053,811 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->request_type = value.as_enum<enums::BluetoothDeviceRequestType>();
return true;
}
default:
return false;
}
}
void BluetoothDeviceRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_enum<enums::BluetoothDeviceRequestType>(2, this->request_type);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothDeviceRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothDeviceRequest {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" request_type: ");
out.append(proto_enum_to_string<enums::BluetoothDeviceRequestType>(this->request_type));
out.append("\n");
out.append("}");
}
#endif
bool BluetoothDeviceConnectionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->connected = value.as_bool();
return true;
}
case 3: {
this->mtu = value.as_uint32();
return true;
}
case 4: {
this->error = value.as_int32();
return true;
}
default:
return false;
}
}
void BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_bool(2, this->connected);
buffer.encode_uint32(3, this->mtu);
buffer.encode_int32(4, this->error);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothDeviceConnectionResponse {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" connected: ");
out.append(YESNO(this->connected));
out.append("\n");
out.append(" mtu: ");
sprintf(buffer, "%u", this->mtu);
out.append(buffer);
out.append("\n");
out.append(" error: ");
sprintf(buffer, "%d", this->error);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTGetServicesRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
default:
return false;
}
}
void BluetoothGATTGetServicesRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTGetServicesRequest {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTDescriptor::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->uuid.push_back(value.as_uint64());
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
default:
return false;
}
}
void BluetoothGATTDescriptor::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->uuid) {
buffer.encode_uint64(1, it, true);
}
buffer.encode_uint32(2, this->handle);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTDescriptor::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTDescriptor {\n");
for (const auto &it : this->uuid) {
out.append(" uuid: ");
sprintf(buffer, "%llu", it);
out.append(buffer);
out.append("\n");
}
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTCharacteristic::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->uuid.push_back(value.as_uint64());
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
case 3: {
this->properties = value.as_uint32();
return true;
}
default:
return false;
}
}
bool BluetoothGATTCharacteristic::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 4: {
this->descriptors.push_back(value.as_message<BluetoothGATTDescriptor>());
return true;
}
default:
return false;
}
}
void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->uuid) {
buffer.encode_uint64(1, it, true);
}
buffer.encode_uint32(2, this->handle);
buffer.encode_uint32(3, this->properties);
for (auto &it : this->descriptors) {
buffer.encode_message<BluetoothGATTDescriptor>(4, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTCharacteristic::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTCharacteristic {\n");
for (const auto &it : this->uuid) {
out.append(" uuid: ");
sprintf(buffer, "%llu", it);
out.append(buffer);
out.append("\n");
}
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append(" properties: ");
sprintf(buffer, "%u", this->properties);
out.append(buffer);
out.append("\n");
for (const auto &it : this->descriptors) {
out.append(" descriptors: ");
it.dump_to(out);
out.append("\n");
}
out.append("}");
}
#endif
bool BluetoothGATTService::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->uuid.push_back(value.as_uint64());
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
default:
return false;
}
}
bool BluetoothGATTService::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 3: {
this->characteristics.push_back(value.as_message<BluetoothGATTCharacteristic>());
return true;
}
default:
return false;
}
}
void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->uuid) {
buffer.encode_uint64(1, it, true);
}
buffer.encode_uint32(2, this->handle);
for (auto &it : this->characteristics) {
buffer.encode_message<BluetoothGATTCharacteristic>(3, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTService::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTService {\n");
for (const auto &it : this->uuid) {
out.append(" uuid: ");
sprintf(buffer, "%llu", it);
out.append(buffer);
out.append("\n");
}
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
for (const auto &it : this->characteristics) {
out.append(" characteristics: ");
it.dump_to(out);
out.append("\n");
}
out.append("}");
}
#endif
bool BluetoothGATTGetServicesResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
default:
return false;
}
}
bool BluetoothGATTGetServicesResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->services.push_back(value.as_message<BluetoothGATTService>());
return true;
}
default:
return false;
}
}
void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
for (auto &it : this->services) {
buffer.encode_message<BluetoothGATTService>(2, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTGetServicesResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTGetServicesResponse {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
for (const auto &it : this->services) {
out.append(" services: ");
it.dump_to(out);
out.append("\n");
}
out.append("}");
}
#endif
bool BluetoothGATTGetServicesDoneResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
default:
return false;
}
}
void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTGetServicesDoneResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTGetServicesDoneResponse {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTReadRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
default:
return false;
}
}
void BluetoothGATTReadRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_uint32(2, this->handle);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTReadRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTReadRequest {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTReadResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
default:
return false;
}
}
bool BluetoothGATTReadResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 3: {
this->data = value.as_string();
return true;
}
default:
return false;
}
}
void BluetoothGATTReadResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_uint32(2, this->handle);
buffer.encode_string(3, this->data);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTReadResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTReadResponse {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append(" data: ");
out.append("'").append(this->data).append("'");
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
case 3: {
this->response = value.as_bool();
return true;
}
default:
return false;
}
}
bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 4: {
this->data = value.as_string();
return true;
}
default:
return false;
}
}
void BluetoothGATTWriteRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_uint32(2, this->handle);
buffer.encode_bool(3, this->response);
buffer.encode_string(4, this->data);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTWriteRequest {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append(" response: ");
out.append(YESNO(this->response));
out.append("\n");
out.append(" data: ");
out.append("'").append(this->data).append("'");
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTReadDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
default:
return false;
}
}
void BluetoothGATTReadDescriptorRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_uint32(2, this->handle);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTReadDescriptorRequest {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
default:
return false;
}
}
bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 3: {
this->data = value.as_string();
return true;
}
default:
return false;
}
}
void BluetoothGATTWriteDescriptorRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_uint32(2, this->handle);
buffer.encode_string(3, this->data);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTWriteDescriptorRequest {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append(" data: ");
out.append("'").append(this->data).append("'");
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTNotifyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
case 3: {
this->enable = value.as_bool();
return true;
}
default:
return false;
}
}
void BluetoothGATTNotifyRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_uint32(2, this->handle);
buffer.encode_bool(3, this->enable);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTNotifyRequest {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append(" enable: ");
out.append(YESNO(this->enable));
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTNotifyDataResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
default:
return false;
}
}
bool BluetoothGATTNotifyDataResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 3: {
this->data = value.as_string();
return true;
}
default:
return false;
}
}
void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_uint32(2, this->handle);
buffer.encode_string(3, this->data);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTNotifyDataResponse {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append(" data: ");
out.append("'").append(this->data).append("'");
out.append("\n");
out.append("}");
}
#endif
void SubscribeBluetoothConnectionsFreeRequest::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const {
out.append("SubscribeBluetoothConnectionsFreeRequest {}");
}
#endif
bool BluetoothConnectionsFreeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->free = value.as_uint32();
return true;
}
case 2: {
this->limit = value.as_uint32();
return true;
}
default:
return false;
}
}
void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, this->free);
buffer.encode_uint32(2, this->limit);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothConnectionsFreeResponse {\n");
out.append(" free: ");
sprintf(buffer, "%u", this->free);
out.append(buffer);
out.append("\n");
out.append(" limit: ");
sprintf(buffer, "%u", this->limit);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTErrorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
case 3: {
this->error = value.as_int32();
return true;
}
default:
return false;
}
}
void BluetoothGATTErrorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_uint32(2, this->handle);
buffer.encode_int32(3, this->error);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTErrorResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTErrorResponse {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append(" error: ");
sprintf(buffer, "%d", this->error);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTWriteResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
default:
return false;
}
}
void BluetoothGATTWriteResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_uint32(2, this->handle);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTWriteResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTWriteResponse {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTNotifyResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
default:
return false;
}
}
void BluetoothGATTNotifyResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_uint32(2, this->handle);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTNotifyResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTNotifyResponse {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View File

@ -155,12 +155,20 @@ enum MediaPlayerCommand : uint32_t {
MEDIA_PLAYER_COMMAND_MUTE = 3, MEDIA_PLAYER_COMMAND_MUTE = 3,
MEDIA_PLAYER_COMMAND_UNMUTE = 4, MEDIA_PLAYER_COMMAND_UNMUTE = 4,
}; };
enum BluetoothDeviceRequestType : uint32_t {
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0,
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1,
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2,
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3,
};
} // namespace enums } // namespace enums
class HelloRequest : public ProtoMessage { class HelloRequest : public ProtoMessage {
public: public:
std::string client_info{}; std::string client_info{};
uint32_t api_version_major{0};
uint32_t api_version_minor{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -168,6 +176,7 @@ class HelloRequest : public ProtoMessage {
protected: protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class HelloResponse : public ProtoMessage { class HelloResponse : public ProtoMessage {
public: public:
@ -263,7 +272,7 @@ class DeviceInfoResponse : public ProtoMessage {
std::string project_name{}; std::string project_name{};
std::string project_version{}; std::string project_version{};
uint32_t webserver_port{0}; uint32_t webserver_port{0};
bool has_bluetooth_proxy{false}; uint32_t bluetooth_proxy_version{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -1227,7 +1236,8 @@ class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage {
class BluetoothServiceData : public ProtoMessage { class BluetoothServiceData : public ProtoMessage {
public: public:
std::string uuid{}; std::string uuid{};
std::vector<uint32_t> data{}; std::vector<uint32_t> legacy_data{};
std::string data{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -1254,6 +1264,260 @@ class BluetoothLEAdvertisementResponse : public ProtoMessage {
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class BluetoothDeviceRequest : public ProtoMessage {
public:
uint64_t address{0};
enums::BluetoothDeviceRequestType request_type{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothDeviceConnectionResponse : public ProtoMessage {
public:
uint64_t address{0};
bool connected{false};
uint32_t mtu{0};
int32_t error{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTGetServicesRequest : public ProtoMessage {
public:
uint64_t address{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTDescriptor : public ProtoMessage {
public:
std::vector<uint64_t> uuid{};
uint32_t handle{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTCharacteristic : public ProtoMessage {
public:
std::vector<uint64_t> uuid{};
uint32_t handle{0};
uint32_t properties{0};
std::vector<BluetoothGATTDescriptor> descriptors{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTService : public ProtoMessage {
public:
std::vector<uint64_t> uuid{};
uint32_t handle{0};
std::vector<BluetoothGATTCharacteristic> characteristics{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTGetServicesResponse : public ProtoMessage {
public:
uint64_t address{0};
std::vector<BluetoothGATTService> services{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTGetServicesDoneResponse : public ProtoMessage {
public:
uint64_t address{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTReadRequest : public ProtoMessage {
public:
uint64_t address{0};
uint32_t handle{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTReadResponse : public ProtoMessage {
public:
uint64_t address{0};
uint32_t handle{0};
std::string data{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTWriteRequest : public ProtoMessage {
public:
uint64_t address{0};
uint32_t handle{0};
bool response{false};
std::string data{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTReadDescriptorRequest : public ProtoMessage {
public:
uint64_t address{0};
uint32_t handle{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTWriteDescriptorRequest : public ProtoMessage {
public:
uint64_t address{0};
uint32_t handle{0};
std::string data{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTNotifyRequest : public ProtoMessage {
public:
uint64_t address{0};
uint32_t handle{0};
bool enable{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTNotifyDataResponse : public ProtoMessage {
public:
uint64_t address{0};
uint32_t handle{0};
std::string data{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SubscribeBluetoothConnectionsFreeRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class BluetoothConnectionsFreeResponse : public ProtoMessage {
public:
uint32_t free{0};
uint32_t limit{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTErrorResponse : public ProtoMessage {
public:
uint64_t address{0};
uint32_t handle{0};
int32_t error{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTWriteResponse : public ProtoMessage {
public:
uint64_t address{0};
uint32_t handle{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTNotifyResponse : public ProtoMessage {
public:
uint64_t address{0};
uint32_t handle{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View File

@ -336,6 +336,95 @@ bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const Blu
return this->send_message_<BluetoothLEAdvertisementResponse>(msg, 67); return this->send_message_<BluetoothLEAdvertisementResponse>(msg, 67);
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_device_connection_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothDeviceConnectionResponse>(msg, 69);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothGATTGetServicesResponse>(msg, 71);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_gatt_get_services_done_response(
const BluetoothGATTGetServicesDoneResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_done_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothGATTGetServicesDoneResponse>(msg, 72);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_gatt_read_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothGATTReadResponse>(msg, 74);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#endif
#ifdef USE_BLUETOOTH_PROXY
#endif
#ifdef USE_BLUETOOTH_PROXY
#endif
#ifdef USE_BLUETOOTH_PROXY
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_gatt_notify_data_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothGATTNotifyDataResponse>(msg, 79);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_connections_free_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothConnectionsFreeResponse>(msg, 81);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_gatt_error_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothGATTErrorResponse>(msg, 82);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_gatt_write_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothGATTWriteResponse>(msg, 83);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_gatt_notify_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothGATTNotifyResponse>(msg, 84);
}
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) { switch (msg_type) {
case 1: { case 1: {
@ -612,6 +701,94 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_subscribe_bluetooth_le_advertisements_request(msg); this->on_subscribe_bluetooth_le_advertisements_request(msg);
break; break;
} }
case 68: {
#ifdef USE_BLUETOOTH_PROXY
BluetoothDeviceRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_device_request: %s", msg.dump().c_str());
#endif
this->on_bluetooth_device_request(msg);
#endif
break;
}
case 70: {
#ifdef USE_BLUETOOTH_PROXY
BluetoothGATTGetServicesRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_get_services_request: %s", msg.dump().c_str());
#endif
this->on_bluetooth_gatt_get_services_request(msg);
#endif
break;
}
case 73: {
#ifdef USE_BLUETOOTH_PROXY
BluetoothGATTReadRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_read_request: %s", msg.dump().c_str());
#endif
this->on_bluetooth_gatt_read_request(msg);
#endif
break;
}
case 75: {
#ifdef USE_BLUETOOTH_PROXY
BluetoothGATTWriteRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_write_request: %s", msg.dump().c_str());
#endif
this->on_bluetooth_gatt_write_request(msg);
#endif
break;
}
case 76: {
#ifdef USE_BLUETOOTH_PROXY
BluetoothGATTReadDescriptorRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_read_descriptor_request: %s", msg.dump().c_str());
#endif
this->on_bluetooth_gatt_read_descriptor_request(msg);
#endif
break;
}
case 77: {
#ifdef USE_BLUETOOTH_PROXY
BluetoothGATTWriteDescriptorRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_write_descriptor_request: %s", msg.dump().c_str());
#endif
this->on_bluetooth_gatt_write_descriptor_request(msg);
#endif
break;
}
case 78: {
#ifdef USE_BLUETOOTH_PROXY
BluetoothGATTNotifyRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_notify_request: %s", msg.dump().c_str());
#endif
this->on_bluetooth_gatt_notify_request(msg);
#endif
break;
}
case 80: {
#ifdef USE_BLUETOOTH_PROXY
SubscribeBluetoothConnectionsFreeRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str());
#endif
this->on_subscribe_bluetooth_connections_free_request(msg);
#endif
break;
}
default: default:
return false; return false;
} }
@ -708,18 +885,6 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc
} }
this->subscribe_home_assistant_states(msg); this->subscribe_home_assistant_states(msg);
} }
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->subscribe_bluetooth_le_advertisements(msg);
}
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) { void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
if (!this->is_connection_setup()) { if (!this->is_connection_setup()) {
this->on_no_setup_connection(); this->on_no_setup_connection();
@ -884,6 +1049,126 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma
this->media_player_command(msg); this->media_player_command(msg);
} }
#endif #endif
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->subscribe_bluetooth_le_advertisements(msg);
}
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->bluetooth_device_request(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->bluetooth_gatt_get_services(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->bluetooth_gatt_read(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->bluetooth_gatt_write(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->bluetooth_gatt_read_descriptor(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->bluetooth_gatt_write_descriptor(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->bluetooth_gatt_notify(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
const SubscribeBluetoothConnectionsFreeRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
BluetoothConnectionsFreeResponse ret = this->subscribe_bluetooth_connections_free(msg);
if (!this->send_bluetooth_connections_free_response(ret)) {
this->on_fatal_error();
}
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View File

@ -158,6 +158,57 @@ class APIServerConnectionBase : public ProtoService {
const SubscribeBluetoothLEAdvertisementsRequest &value){}; const SubscribeBluetoothLEAdvertisementsRequest &value){};
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg); bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_get_services_done_response(const BluetoothGATTGetServicesDoneResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg);
#endif #endif
protected: protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@ -175,7 +226,6 @@ class APIServerConnection : public APIServerConnectionBase {
virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0; virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
virtual void execute_service(const ExecuteServiceRequest &msg) = 0; virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#ifdef USE_COVER #ifdef USE_COVER
@ -210,6 +260,32 @@ class APIServerConnection : public APIServerConnectionBase {
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
#endif
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
const SubscribeBluetoothConnectionsFreeRequest &msg) = 0;
#endif #endif
protected: protected:
void on_hello_request(const HelloRequest &msg) override; void on_hello_request(const HelloRequest &msg) override;
@ -222,7 +298,6 @@ class APIServerConnection : public APIServerConnectionBase {
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override; void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override;
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override; void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
void on_get_time_request(const GetTimeRequest &msg) override; void on_get_time_request(const GetTimeRequest &msg) override;
void on_execute_service_request(const ExecuteServiceRequest &msg) override; void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#ifdef USE_COVER #ifdef USE_COVER
@ -257,6 +332,31 @@ class APIServerConnection : public APIServerConnectionBase {
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
#endif
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override;
#endif #endif
}; };

View File

@ -297,6 +297,72 @@ void APIServer::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementRe
client->send_bluetooth_le_advertisement(call); client->send_bluetooth_le_advertisement(call);
} }
} }
void APIServer::send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
BluetoothDeviceConnectionResponse call;
call.address = address;
call.connected = connected;
call.mtu = mtu;
call.error = error;
for (auto &client : this->clients_) {
client->send_bluetooth_device_connection_response(call);
}
}
void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) {
BluetoothConnectionsFreeResponse call;
call.free = free;
call.limit = limit;
for (auto &client : this->clients_) {
client->send_bluetooth_connections_free_response(call);
}
}
void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_read_response(call);
}
}
void APIServer::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_write_response(call);
}
}
void APIServer::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_notify_data_response(call);
}
}
void APIServer::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_notify_response(call);
}
}
void APIServer::send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_get_services_response(call);
}
}
void APIServer::send_bluetooth_gatt_services_done(uint64_t address) {
BluetoothGATTGetServicesDoneResponse call;
call.address = address;
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_get_services_done_response(call);
}
}
void APIServer::send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
BluetoothGATTErrorResponse call;
call.address = address;
call.handle = handle;
call.error = error;
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_error_response(call);
}
}
#endif #endif
APIServer::APIServer() { global_api_server = this; } APIServer::APIServer() { global_api_server = this; }
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute, void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,

View File

@ -75,6 +75,15 @@ class APIServer : public Component, public Controller {
void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call); void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call);
void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error = ESP_OK);
void send_bluetooth_connections_free(uint8_t free, uint8_t limit);
void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call);
void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call);
void send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call);
void send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call);
void send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call);
void send_bluetooth_gatt_services_done(uint64_t address);
void send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error);
#endif #endif
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME

View File

@ -128,9 +128,9 @@ uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) {
} }
return -1; return -1;
} }
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_cmd_, auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
pkt->data_length + 1, (uint8_t *) &pkt->command, ESP_GATT_WRITE_TYPE_NO_RSP, this->char_handle_cmd_, pkt->data_length + 1, (uint8_t *) &pkt->command,
ESP_GATT_AUTH_REQ_NONE); ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
return status; return status;
} }
@ -138,13 +138,13 @@ uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) {
uint8_t BedJetHub::set_notify_(const bool enable) { uint8_t BedJetHub::set_notify_(const bool enable) {
uint8_t status; uint8_t status;
if (enable) { if (enable) {
status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
this->char_handle_status_); this->char_handle_status_);
if (status) { if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
} }
} else { } else {
status = esp_ble_gattc_unregister_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, status = esp_ble_gattc_unregister_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
this->char_handle_status_); this->char_handle_status_);
if (status) { if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->get_name().c_str(), status); ESP_LOGW(TAG, "[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->get_name().c_str(), status);
@ -204,8 +204,8 @@ bool BedJetHub::discover_characteristics_() {
result = false; result = false;
} else { } else {
this->char_handle_name_ = chr->handle; this->char_handle_name_ = chr->handle;
auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_name_, auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
ESP_GATT_AUTH_REQ_NONE); this->char_handle_name_, ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
ESP_LOGI(TAG, "[%s] Unable to read name characteristic: %d", this->get_name().c_str(), status); ESP_LOGI(TAG, "[%s] Unable to read name characteristic: %d", this->get_name().c_str(), status);
} }
@ -230,22 +230,6 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
this->dispatch_state_(false); this->dispatch_state_(false);
break; break;
} }
case ESP_GATTC_OPEN_EVT: {
// FIXME: bug in BLEClient
this->parent_->conn_id = param->open.conn_id;
this->open_conn_id_ = param->open.conn_id;
break;
}
case ESP_GATTC_CONNECT_EVT: {
if (this->parent_->conn_id != param->connect.conn_id && this->open_conn_id_ != 0xff) {
// FIXME: bug in BLEClient
ESP_LOGW(TAG, "[%s] CONNECT_EVT unexpected conn_id; open=%d, parent=%d, param=%d", this->get_name().c_str(),
this->open_conn_id_, this->parent_->conn_id, param->connect.conn_id);
this->parent_->conn_id = this->open_conn_id_;
}
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: { case ESP_GATTC_SEARCH_CMPL_EVT: {
auto result = this->discover_characteristics_(); auto result = this->discover_characteristics_();
@ -301,7 +285,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
break; break;
} }
case ESP_GATTC_READ_CHAR_EVT: { case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent_->conn_id) if (param->read.conn_id != this->parent_->get_conn_id())
break; break;
if (param->read.status != ESP_GATT_OK) { if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
@ -358,9 +342,9 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
if (this->processing_) if (this->processing_)
break; break;
if (param->notify.conn_id != this->parent_->conn_id) { if (param->notify.conn_id != this->parent_->get_conn_id()) {
ESP_LOGW(TAG, "[%s] Received notify event for unexpected parent conn: expect %x, got %x", ESP_LOGW(TAG, "[%s] Received notify event for unexpected parent conn: expect %x, got %x",
this->get_name().c_str(), this->parent_->conn_id, param->notify.conn_id); this->get_name().c_str(), this->parent_->get_conn_id(), param->notify.conn_id);
// FIXME: bug in BLEClient holding wrong conn_id. // FIXME: bug in BLEClient holding wrong conn_id.
} }
@ -394,7 +378,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
if (needs_extra) { if (needs_extra) {
// This means the packet was partial, so read the status characteristic to get the second part. // This means the packet was partial, so read the status characteristic to get the second part.
// Ideally this will complete quickly. We won't process additional notification events until it does. // Ideally this will complete quickly. We won't process additional notification events until it does.
auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
this->char_handle_status_, ESP_GATT_AUTH_REQ_NONE); this->char_handle_status_, ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
ESP_LOGI(TAG, "[%s] Unable to read extended status packet", this->get_name().c_str()); ESP_LOGI(TAG, "[%s] Unable to read extended status packet", this->get_name().c_str());
@ -438,15 +422,15 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) {
// NOTE: BLEClient uses `uint8_t*` of length 1, but BLE spec requires 16 bits. // NOTE: BLEClient uses `uint8_t*` of length 1, but BLE spec requires 16 bits.
uint16_t notify_en = enable ? 1 : 0; uint16_t notify_en = enable ? 1 : 0;
auto status = auto status = esp_ble_gattc_write_char_descr(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), handle,
esp_ble_gattc_write_char_descr(this->parent_->gattc_if, this->parent_->conn_id, handle, sizeof(notify_en), sizeof(notify_en), (uint8_t *) &notify_en, ESP_GATT_WRITE_TYPE_RSP,
(uint8_t *) &notify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status); ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
return status; return status;
} }
ESP_LOGD(TAG, "[%s] wrote notify=%s to status config 0x%04x, for conn %d", this->get_name().c_str(), ESP_LOGD(TAG, "[%s] wrote notify=%s to status config 0x%04x, for conn %d", this->get_name().c_str(),
enable ? "true" : "false", handle, this->parent_->conn_id); enable ? "true" : "false", handle, this->parent_->get_conn_id());
return ESP_GATT_OK; return ESP_GATT_OK;
} }
@ -500,7 +484,7 @@ void BedJetHub::update() { this->dispatch_status_(); }
void BedJetHub::dump_config() { void BedJetHub::dump_config() {
ESP_LOGCONFIG(TAG, "BedJet Hub '%s'", this->get_name().c_str()); ESP_LOGCONFIG(TAG, "BedJet Hub '%s'", this->get_name().c_str());
ESP_LOGCONFIG(TAG, " ble_client.app_id: %d", this->parent()->app_id); ESP_LOGCONFIG(TAG, " ble_client.app_id: %d", this->parent()->app_id);
ESP_LOGCONFIG(TAG, " ble_client.conn_id: %d", this->parent()->conn_id); ESP_LOGCONFIG(TAG, " ble_client.conn_id: %d", this->parent()->get_conn_id());
LOG_UPDATE_INTERVAL(this) LOG_UPDATE_INTERVAL(this)
ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size()); ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size());
for (auto *child : this->children_) { for (auto *child : this->children_) {

View File

@ -167,8 +167,6 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo
uint16_t char_handle_status_; uint16_t char_handle_status_;
uint16_t config_descr_status_; uint16_t config_descr_status_;
uint8_t open_conn_id_ = -1;
uint8_t write_notify_config_descriptor_(bool enable); uint8_t write_notify_config_descriptor_(bool enable);
}; };

View File

@ -1,6 +1,6 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import esp32_ble_tracker from esphome.components import esp32_ble_tracker, esp32_ble_client
from esphome.const import ( from esphome.const import (
CONF_CHARACTERISTIC_UUID, CONF_CHARACTERISTIC_UUID,
CONF_ID, CONF_ID,
@ -14,13 +14,12 @@ from esphome.const import (
) )
from esphome import automation from esphome import automation
AUTO_LOAD = ["esp32_ble_client"]
CODEOWNERS = ["@buxtronix"] CODEOWNERS = ["@buxtronix"]
DEPENDENCIES = ["esp32_ble_tracker"] DEPENDENCIES = ["esp32_ble_tracker"]
ble_client_ns = cg.esphome_ns.namespace("ble_client") ble_client_ns = cg.esphome_ns.namespace("ble_client")
BLEClient = ble_client_ns.class_( BLEClient = ble_client_ns.class_("BLEClient", esp32_ble_client.BLEClientBase)
"BLEClient", cg.Component, esp32_ble_tracker.ESPBTClient
)
BLEClientNode = ble_client_ns.class_("BLEClientNode") BLEClientNode = ble_client_ns.class_("BLEClientNode")
BLEClientNodeConstRef = BLEClientNode.operator("ref").operator("const") BLEClientNodeConstRef = BLEClientNode.operator("ref").operator("const")
# Triggers # Triggers
@ -101,12 +100,40 @@ async def ble_write_to_code(config, action_id, template_arg, args):
else: else:
cg.add(var.set_value_simple(value)) cg.add(var.set_value_simple(value))
serv_uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(var.set_service_uuid128(serv_uuid128)) cg.add(
char_uuid128 = esp32_ble_tracker.as_reversed_hex_array( var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
config[CONF_CHARACTERISTIC_UUID] )
) elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(var.set_char_uuid128(char_uuid128)) cg.add(
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128))
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_char_uuid16(
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
)
)
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_char_uuid32(
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
)
)
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_CHARACTERISTIC_UUID]
)
cg.add(var.set_char_uuid128(uuid128))
return var return var

View File

@ -1,8 +1,8 @@
#include "automation.h" #include "automation.h"
#include <esp_bt_defs.h>
#include <esp_gap_ble_api.h> #include <esp_gap_ble_api.h>
#include <esp_gattc_api.h> #include <esp_gattc_api.h>
#include <esp_bt_defs.h>
#include "esphome/core/log.h" #include "esphome/core/log.h"
@ -31,8 +31,8 @@ void BLEWriterClientNode::write(const std::vector<uint8_t> &value) {
} }
ESP_LOGVV(TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str()); ESP_LOGVV(TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str());
esp_err_t err = esp_err_t err =
esp_ble_gattc_write_char(this->parent()->gattc_if, this->parent()->conn_id, this->ble_char_handle_, value.size(), esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->ble_char_handle_,
const_cast<uint8_t *>(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE); value.size(), const_cast<uint8_t *>(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Error writing to characteristic: %s!", esp_err_to_name(err)); ESP_LOGE(TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
} }

View File

@ -28,7 +28,8 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
void loop() override {} void loop() override {}
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override { esp_ble_gattc_cb_param_t *param) override {
if (event == ESP_GATTC_DISCONNECT_EVT && memcmp(param->disconnect.remote_bda, this->parent_->remote_bda, 6) == 0) if (event == ESP_GATTC_DISCONNECT_EVT &&
memcmp(param->disconnect.remote_bda, this->parent_->get_remote_bda(), 6) == 0)
this->trigger(); this->trigger();
if (event == ESP_GATTC_SEARCH_CMPL_EVT) if (event == ESP_GATTC_SEARCH_CMPL_EVT)
this->node_state = espbt::ClientState::ESTABLISHED; this->node_state = espbt::ClientState::ESTABLISHED;
@ -45,10 +46,14 @@ class BLEWriterClientNode : public BLEClientNode {
// Attempts to write the contents of value to char_uuid_. // Attempts to write the contents of value to char_uuid_.
void write(const std::vector<uint8_t> &value); void write(const std::vector<uint8_t> &value);
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override; esp_ble_gattc_cb_param_t *param) override;

View File

@ -1,8 +1,9 @@
#include "esphome/core/log.h" #include "ble_client.h"
#include "esphome/components/esp32_ble_client/ble_client_base.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/core/log.h"
#include "ble_client.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
@ -11,22 +12,13 @@ namespace ble_client {
static const char *const TAG = "ble_client"; static const char *const TAG = "ble_client";
float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
void BLEClient::setup() { void BLEClient::setup() {
auto ret = esp_ble_gattc_app_register(this->app_id); BLEClientBase::setup();
if (ret) {
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
this->mark_failed();
}
this->set_states_(espbt::ClientState::IDLE);
this->enabled = true; this->enabled = true;
} }
void BLEClient::loop() { void BLEClient::loop() {
if (this->state() == espbt::ClientState::DISCOVERED) { BLEClientBase::loop();
this->connect();
}
for (auto *node : this->nodes_) for (auto *node : this->nodes_)
node->loop(); node->loop();
} }
@ -39,34 +31,7 @@ void BLEClient::dump_config() {
bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {
if (!this->enabled) if (!this->enabled)
return false; return false;
if (device.address_uint64() != this->address) return BLEClientBase::parse_device(device);
return false;
if (this->state() != espbt::ClientState::IDLE)
return false;
ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str());
this->set_states_(espbt::ClientState::DISCOVERED);
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->remote_addr_type = device.get_address_type();
return true;
}
std::string BLEClient::address_str() const {
char buf[20];
sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", (uint8_t)(this->address >> 40) & 0xff,
(uint8_t)(this->address >> 32) & 0xff, (uint8_t)(this->address >> 24) & 0xff,
(uint8_t)(this->address >> 16) & 0xff, (uint8_t)(this->address >> 8) & 0xff,
(uint8_t)(this->address >> 0) & 0xff);
std::string ret;
ret = buf;
return ret;
} }
void BLEClient::set_enabled(bool enabled) { void BLEClient::set_enabled(bool enabled) {
@ -74,7 +39,7 @@ void BLEClient::set_enabled(bool enabled) {
return; return;
if (!enabled && this->state() != espbt::ClientState::IDLE) { if (!enabled && this->state() != espbt::ClientState::IDLE) {
ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str()); ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str());
auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id); auto ret = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
if (ret) { if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret); ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret);
} }
@ -82,125 +47,12 @@ void BLEClient::set_enabled(bool enabled) {
this->enabled = enabled; this->enabled = enabled;
} }
void BLEClient::connect() {
ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str());
auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, this->remote_addr_type, true);
if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret);
this->set_states_(espbt::ClientState::IDLE);
} else {
this->set_states_(espbt::ClientState::CONNECTING);
}
}
void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if, void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *param) {
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
return;
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if)
return;
bool all_established = this->all_nodes_established_(); bool all_established = this->all_nodes_established_();
switch (event) { BLEClientBase::gattc_event_handler(event, esp_gattc_if, param);
case ESP_GATTC_REG_EVT: {
if (param->reg.status == ESP_GATT_OK) {
ESP_LOGV(TAG, "gattc registered app id %d", this->app_id);
this->gattc_if = esp_gattc_if;
} else {
ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status);
}
break;
}
case ESP_GATTC_OPEN_EVT: {
ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
this->conn_id = param->open.conn_id;
if (param->open.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
this->set_states_(espbt::ClientState::IDLE);
break;
}
break;
}
case ESP_GATTC_CONNECT_EVT: {
ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str());
if (this->conn_id != param->connect.conn_id) {
ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d",
this->address_str().c_str(), param->connect.conn_id, this->conn_id);
}
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->connect.conn_id);
if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret);
}
break;
}
case ESP_GATTC_CFG_MTU_EVT: {
if (param->cfg_mtu.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu,
param->cfg_mtu.status);
this->set_states_(espbt::ClientState::IDLE);
break;
}
ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) {
return;
}
ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason);
for (auto &svc : this->services_)
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
this->services_.clear();
this->set_states_(espbt::ClientState::IDLE);
break;
}
case ESP_GATTC_SEARCH_RES_EVT: {
BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
ble_service->start_handle = param->search_res.start_handle;
ble_service->end_handle = param->search_res.end_handle;
ble_service->client = this;
this->services_.push_back(ble_service);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str());
for (auto &svc : this->services_) {
ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str());
ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle);
svc->parse_characteristics();
}
this->set_states_(espbt::ClientState::CONNECTED);
this->set_state(espbt::ClientState::ESTABLISHED);
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
auto *descr = this->get_config_descriptor(param->reg_for_notify.handle);
if (descr == nullptr) {
ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle);
break;
}
if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle,
descr->uuid.to_string().c_str());
break;
}
uint16_t notify_en = 1;
auto status =
esp_ble_gattc_write_char_descr(this->gattc_if, this->conn_id, descr->handle, sizeof(notify_en),
(uint8_t *) &notify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
}
break;
}
default:
break;
}
for (auto *node : this->nodes_) for (auto *node : this->nodes_)
node->gattc_event_handler(event, esp_gattc_if, param); node->gattc_event_handler(event, esp_gattc_if, param);
@ -213,236 +65,26 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
} }
void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
switch (event) { BLEClientBase::gap_event_handler(event, param);
// This event is sent by the server when it requests security
case ESP_GAP_BLE_SEC_REQ_EVT: for (auto *node : this->nodes_)
ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event); node->gap_event_handler(event, param);
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); }
break;
// This event is sent once authentication has completed void BLEClient::set_state(espbt::ClientState state) {
case ESP_GAP_BLE_AUTH_CMPL_EVT: BLEClientBase::set_state(state);
esp_bd_addr_t bd_addr; for (auto &node : nodes_)
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); node->node_state = state;
ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str()); }
if (!param->ble_security.auth_cmpl.success) {
ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason); bool BLEClient::all_nodes_established_() {
} else { if (this->state() != espbt::ClientState::ESTABLISHED)
ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type, return false;
param->ble_security.auth_cmpl.auth_mode); for (auto &node : nodes_) {
} if (node->node_state != espbt::ClientState::ESTABLISHED)
break; return false;
// 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:
break;
} }
} return true;
// Parse GATT values into a float for a sensor.
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
float BLEClient::parse_char_value(uint8_t *value, uint16_t length) {
// A length of one means a single octet value.
if (length == 0)
return 0;
if (length == 1)
return (float) ((uint8_t) value[0]);
switch (value[0]) {
case 0x1: // boolean.
case 0x2: // 2bit.
case 0x3: // nibble.
case 0x4: // uint8.
return (float) ((uint8_t) value[1]);
case 0x5: // uint12.
case 0x6: // uint16.
if (length > 2) {
return (float) ((uint16_t)(value[1] << 8) + (uint16_t) value[2]);
}
case 0x7: // uint24.
if (length > 3) {
return (float) ((uint32_t)(value[1] << 16) + (uint32_t)(value[2] << 8) + (uint32_t)(value[3]));
}
case 0x8: // uint32.
if (length > 4) {
return (float) ((uint32_t)(value[1] << 24) + (uint32_t)(value[2] << 16) + (uint32_t)(value[3] << 8) +
(uint32_t)(value[4]));
}
case 0xC: // int8.
return (float) ((int8_t) value[1]);
case 0xD: // int12.
case 0xE: // int16.
if (length > 2) {
return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]);
}
case 0xF: // int24.
if (length > 3) {
return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3]));
}
case 0x10: // int32.
if (length > 4) {
return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) +
(int32_t)(value[4]));
}
}
ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length);
return NAN;
}
BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) {
for (auto *svc : this->services_) {
if (svc->uuid == uuid)
return svc;
}
return nullptr;
}
BLEService *BLEClient::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
BLECharacteristic *BLEClient::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) {
auto *svc = this->get_service(service);
if (svc == nullptr)
return nullptr;
return svc->get_characteristic(chr);
}
BLECharacteristic *BLEClient::get_characteristic(uint16_t service, uint16_t chr) {
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
}
BLEDescriptor *BLEClient::get_config_descriptor(uint16_t handle) {
for (auto &svc : this->services_) {
for (auto &chr : svc->characteristics) {
if (chr->handle == handle) {
for (auto &desc : chr->descriptors) {
if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902))
return desc;
}
}
}
}
return nullptr;
}
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
for (auto &chr : this->characteristics) {
if (chr->uuid == uuid)
return chr;
}
return nullptr;
}
BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid));
}
BLEDescriptor *BLEClient::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) {
auto *svc = this->get_service(service);
if (svc == nullptr)
return nullptr;
auto *ch = svc->get_characteristic(chr);
if (ch == nullptr)
return nullptr;
return ch->get_descriptor(descr);
}
BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) {
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
espbt::ESPBTUUID::from_uint16(descr));
}
BLEService::~BLEService() {
for (auto &chr : this->characteristics)
delete chr; // NOLINT(cppcoreguidelines-owning-memory)
}
void BLEService::parse_characteristics() {
uint16_t offset = 0;
esp_gattc_char_elem_t result;
while (true) {
uint16_t count = 1;
esp_gatt_status_t status = esp_ble_gattc_get_all_char(
this->client->gattc_if, this->client->conn_id, this->start_handle, this->end_handle, &result, &count, offset);
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
break;
}
if (status != ESP_GATT_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status);
break;
}
if (count == 0) {
break;
}
BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory)
characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
characteristic->properties = result.properties;
characteristic->handle = result.char_handle;
characteristic->service = this;
this->characteristics.push_back(characteristic);
ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(),
characteristic->handle, characteristic->properties);
characteristic->parse_descriptors();
offset++;
}
}
BLECharacteristic::~BLECharacteristic() {
for (auto &desc : this->descriptors)
delete desc; // NOLINT(cppcoreguidelines-owning-memory)
}
void BLECharacteristic::parse_descriptors() {
uint16_t offset = 0;
esp_gattc_descr_elem_t result;
while (true) {
uint16_t count = 1;
esp_gatt_status_t status = esp_ble_gattc_get_all_descr(
this->service->client->gattc_if, this->service->client->conn_id, this->handle, &result, &count, offset);
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
break;
}
if (status != ESP_GATT_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status);
break;
}
if (count == 0) {
break;
}
BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory)
desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
desc->handle = result.handle;
desc->characteristic = this;
this->descriptors.push_back(desc);
ESP_LOGV(TAG, " descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle);
offset++;
}
}
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
for (auto &desc : this->descriptors) {
if (desc->uuid == uuid)
return desc;
}
return nullptr;
}
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
}
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
auto *client = this->service->client;
auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
write_type, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
}
}
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
} }
} // namespace ble_client } // namespace ble_client

View File

@ -1,8 +1,9 @@
#pragma once #pragma once
#include "esphome/components/esp32_ble_client/ble_client_base.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
@ -18,15 +19,16 @@ namespace ble_client {
namespace espbt = esphome::esp32_ble_tracker; namespace espbt = esphome::esp32_ble_tracker;
using namespace esp32_ble_client;
class BLEClient; class BLEClient;
class BLEService;
class BLECharacteristic;
class BLEClientNode { class BLEClientNode {
public: public:
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) = 0; esp_ble_gattc_cb_param_t *param) = 0;
virtual void loop(){}; virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {}
virtual void loop() {}
void set_address(uint64_t address) { address_ = address; } void set_address(uint64_t address) { address_ = address; }
espbt::ESPBTClient *client; espbt::ESPBTClient *client;
// This should be transitioned to Established once the node no longer needs // This should be transitioned to Established once the node no longer needs
@ -42,57 +44,17 @@ class BLEClientNode {
uint64_t address_; uint64_t address_;
}; };
class BLEDescriptor { class BLEClient : public BLEClientBase {
public:
espbt::ESPBTUUID uuid;
uint16_t handle;
BLECharacteristic *characteristic;
};
class BLECharacteristic {
public:
~BLECharacteristic();
espbt::ESPBTUUID uuid;
uint16_t handle;
esp_gatt_char_prop_t properties;
std::vector<BLEDescriptor *> descriptors;
void parse_descriptors();
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
BLEDescriptor *get_descriptor(uint16_t uuid);
void write_value(uint8_t *new_val, int16_t new_val_size);
void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
BLEService *service;
};
class BLEService {
public:
~BLEService();
espbt::ESPBTUUID uuid;
uint16_t start_handle;
uint16_t end_handle;
std::vector<BLECharacteristic *> characteristics;
BLEClient *client;
void parse_characteristics();
BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid);
BLECharacteristic *get_characteristic(uint16_t uuid);
};
class BLEClient : public espbt::ESPBTClient, public Component {
public: public:
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
void loop() override; void loop() override;
float get_setup_priority() const override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override; esp_ble_gattc_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
bool parse_device(const espbt::ESPBTDevice &device) override; bool parse_device(const espbt::ESPBTDevice &device) override;
void on_scan_end() override {}
void connect() override;
void set_address(uint64_t address) { this->address = address; }
void set_enabled(bool enabled); void set_enabled(bool enabled);
@ -102,43 +64,14 @@ class BLEClient : public espbt::ESPBTClient, public Component {
this->nodes_.push_back(node); this->nodes_.push_back(node);
} }
BLEService *get_service(espbt::ESPBTUUID uuid);
BLEService *get_service(uint16_t uuid);
BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr);
BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr);
BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr);
// Get the configuration descriptor for the given characteristic handle.
BLEDescriptor *get_config_descriptor(uint16_t handle);
float parse_char_value(uint8_t *value, uint16_t length);
int gattc_if;
esp_bd_addr_t remote_bda;
esp_ble_addr_type_t remote_addr_type;
uint16_t conn_id;
uint64_t address;
bool enabled; bool enabled;
std::string address_str() const;
void set_state(espbt::ClientState state) override;
protected: protected:
void set_states_(espbt::ClientState st) { bool all_nodes_established_();
this->set_state(st);
for (auto &node : nodes_)
node->node_state = st;
}
bool all_nodes_established_() {
if (this->state() != espbt::ClientState::ESTABLISHED)
return false;
for (auto &node : nodes_) {
if (node->node_state != espbt::ClientState::ESTABLISHED)
return false;
}
return true;
}
std::vector<BLEClientNode *> nodes_; std::vector<BLEClientNode *> nodes_;
std::vector<BLEService *> services_;
}; };
} // namespace ble_client } // namespace ble_client

View File

@ -5,7 +5,11 @@ from esphome.const import (
CONF_CHARACTERISTIC_UUID, CONF_CHARACTERISTIC_UUID,
CONF_LAMBDA, CONF_LAMBDA,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE,
CONF_SERVICE_UUID, CONF_SERVICE_UUID,
DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL_MILLIWATT,
) )
from esphome import automation from esphome import automation
from .. import ble_client_ns from .. import ble_client_ns
@ -16,6 +20,8 @@ CONF_DESCRIPTOR_UUID = "descriptor_uuid"
CONF_NOTIFY = "notify" CONF_NOTIFY = "notify"
CONF_ON_NOTIFY = "on_notify" CONF_ON_NOTIFY = "on_notify"
TYPE_CHARACTERISTIC = "characteristic"
TYPE_RSSI = "rssi"
adv_data_t = cg.std_vector.template(cg.uint8) adv_data_t = cg.std_vector.template(cg.uint8)
adv_data_t_const_ref = adv_data_t.operator("ref").operator("const") adv_data_t_const_ref = adv_data_t.operator("ref").operator("const")
@ -27,33 +33,67 @@ BLESensorNotifyTrigger = ble_client_ns.class_(
"BLESensorNotifyTrigger", automation.Trigger.template(cg.float_) "BLESensorNotifyTrigger", automation.Trigger.template(cg.float_)
) )
CONFIG_SCHEMA = cv.All( BLEClientRssiSensor = ble_client_ns.class_(
sensor.sensor_schema( "BLEClientRSSISensor", sensor.Sensor, cg.PollingComponent, ble_client.BLEClientNode
BLESensor,
accuracy_decimals=0,
)
.extend(
{
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_NOTIFY, default=False): cv.boolean,
cv.Optional(CONF_ON_NOTIFY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
BLESensorNotifyTrigger
),
}
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(ble_client.BLE_CLIENT_SCHEMA)
) )
async def to_code(config): def checkType(value):
if CONF_TYPE not in value and CONF_SERVICE_UUID in value:
raise cv.Invalid(
"Looks like you're trying to create a ble characteristic sensor. Please add `type: characteristic` to your sensor config."
)
return value
CONFIG_SCHEMA = cv.All(
checkType,
cv.typed_schema(
{
TYPE_CHARACTERISTIC: sensor.sensor_schema(
BLESensor,
accuracy_decimals=0,
)
.extend(cv.polling_component_schema("60s"))
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(
{
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_NOTIFY, default=False): cv.boolean,
cv.Optional(CONF_ON_NOTIFY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
BLESensorNotifyTrigger
),
}
),
}
),
TYPE_RSSI: sensor.sensor_schema(
BLEClientRssiSensor,
accuracy_decimals=0,
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(cv.polling_component_schema("60s"))
.extend(ble_client.BLE_CLIENT_SCHEMA),
},
lower=True,
),
)
async def rssi_sensor_to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
async def characteristic_sensor_to_code(config):
var = await sensor.new_sensor(config) var = await sensor.new_sensor(config)
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(
@ -125,3 +165,10 @@ async def to_code(config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await ble_client.register_ble_node(trigger, config) await ble_client.register_ble_node(trigger, config)
await automation.build_automation(trigger, [(float, "x")], conf) await automation.build_automation(trigger, [(float, "x")], conf)
async def to_code(config):
if config[CONF_TYPE] == TYPE_RSSI:
await rssi_sensor_to_code(config)
elif config[CONF_TYPE] == TYPE_CHARACTERISTIC:
await characteristic_sensor_to_code(config)

View File

@ -19,7 +19,8 @@ class BLESensorNotifyTrigger : public Trigger<float>, public BLESensor {
break; break;
} }
case ESP_GATTC_NOTIFY_EVT: { case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle) if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
param->notify.handle != this->sensor_->handle)
break; break;
this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len)); this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len));
} }

View File

@ -0,0 +1,78 @@
#include "ble_rssi_sensor.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef USE_ESP32
namespace esphome {
namespace ble_client {
static const char *const TAG = "ble_rssi_sensor";
void BLEClientRSSISensor::loop() {}
void BLEClientRSSISensor::dump_config() {
LOG_SENSOR("", "BLE Client RSSI Sensor", this);
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str());
LOG_UPDATE_INTERVAL(this);
}
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());
this->status_set_warning();
this->publish_state(NAN);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT:
this->node_state = espbt::ClientState::ESTABLISHED;
break;
default:
break;
}
}
void BLEClientRSSISensor::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
switch (event) {
// server response on RSSI request:
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
if (param->read_rssi_cmpl.status == ESP_BT_STATUS_SUCCESS) {
int8_t rssi = param->read_rssi_cmpl.rssi;
ESP_LOGI(TAG, "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT RSSI: %d", rssi);
this->publish_state(rssi);
}
break;
default:
break;
}
}
void BLEClientRSSISensor::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
return;
}
ESP_LOGV(TAG, "requesting rssi from %s", this->parent()->address_str().c_str());
auto status = esp_ble_gap_read_rssi(this->parent()->get_remote_bda());
if (status != ESP_OK) {
ESP_LOGW(TAG, "esp_ble_gap_read_rssi error, address=%s, status=%d", this->parent()->address_str().c_str(), status);
this->status_set_warning();
this->publish_state(NAN);
}
}
} // namespace ble_client
} // namespace esphome
#endif

View File

@ -0,0 +1,31 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#ifdef USE_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace ble_client {
namespace espbt = esphome::esp32_ble_tracker;
class BLEClientRSSISensor : public sensor::Sensor, public PollingComponent, public BLEClientNode {
public:
void loop() override;
void update() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) 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;
};
} // namespace ble_client
} // namespace esphome
#endif

View File

@ -63,8 +63,8 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
this->handle = descr->handle; this->handle = descr->handle;
} }
if (this->notify_) { if (this->notify_) {
auto status = auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(),
esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle); this->parent()->get_remote_bda(), chr->handle);
if (status) { if (status) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status); ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
} }
@ -74,7 +74,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
break; break;
} }
case ESP_GATTC_READ_CHAR_EVT: { case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->conn_id) if (param->read.conn_id != this->parent()->get_conn_id())
break; break;
if (param->read.status != ESP_GATT_OK) { if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
@ -87,7 +87,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
break; break;
} }
case ESP_GATTC_NOTIFY_EVT: { case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle) if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle)
break; break;
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), 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]); param->notify.handle, param->notify.value[0]);
@ -122,8 +122,8 @@ void BLESensor::update() {
return; return;
} }
auto status = auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle,
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
this->status_set_warning(); this->status_set_warning();
this->publish_state(NAN); this->publish_state(NAN);

View File

@ -19,7 +19,8 @@ class BLETextSensorNotifyTrigger : public Trigger<std::string>, public BLETextSe
break; break;
} }
case ESP_GATTC_NOTIFY_EVT: { case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle) if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
param->notify.handle != this->sensor_->handle)
break; break;
this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len)); this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len));
} }

View File

@ -66,8 +66,8 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
this->handle = descr->handle; this->handle = descr->handle;
} }
if (this->notify_) { if (this->notify_) {
auto status = auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(),
esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle); this->parent()->get_remote_bda(), chr->handle);
if (status) { if (status) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status); ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
} }
@ -77,7 +77,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break; break;
} }
case ESP_GATTC_READ_CHAR_EVT: { case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->conn_id) if (param->read.conn_id != this->parent()->get_conn_id())
break; break;
if (param->read.status != ESP_GATT_OK) { if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
@ -90,7 +90,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break; break;
} }
case ESP_GATTC_NOTIFY_EVT: { case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle) if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle)
break; break;
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), 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]); param->notify.handle, param->notify.value[0]);
@ -121,8 +121,8 @@ void BLETextSensor::update() {
return; return;
} }
auto status = auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle,
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
this->status_set_warning(); this->status_set_warning();
this->publish_state(EMPTY); this->publish_state(EMPTY);

View File

@ -9,7 +9,7 @@ from esphome.const import (
CONF_MAC_ADDRESS, CONF_MAC_ADDRESS,
DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL, UNIT_DECIBEL_MILLIWATT,
) )
DEPENDENCIES = ["esp32_ble_tracker"] DEPENDENCIES = ["esp32_ble_tracker"]
@ -31,7 +31,7 @@ def _validate(config):
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
sensor.sensor_schema( sensor.sensor_schema(
BLERSSISensor, BLERSSISensor,
unit_of_measurement=UNIT_DECIBEL, unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH, device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,

View File

@ -1,19 +1,23 @@
from esphome.components import esp32_ble_tracker from esphome.components import esp32_ble_tracker, esp32_ble_client
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.const import CONF_ID from esphome.const import CONF_ACTIVE, CONF_ID
DEPENDENCIES = ["esp32", "esp32_ble_tracker"] AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
DEPENDENCIES = ["api", "esp32"]
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy") bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy")
BluetoothProxy = bluetooth_proxy_ns.class_("BluetoothProxy", cg.Component) BluetoothProxy = bluetooth_proxy_ns.class_(
"BluetoothProxy", esp32_ble_client.BLEClientBase
)
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(BluetoothProxy), cv.GenerateID(): cv.declare_id(BluetoothProxy),
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
} }
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) ).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
@ -22,6 +26,8 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config) cg.add(var.set_active(config[CONF_ACTIVE]))
await esp32_ble_tracker.register_client(var, config)
cg.add_define("USE_BLUETOOTH_PROXY") cg.add_define("USE_BLUETOOTH_PROXY")

View File

@ -1,29 +1,46 @@
#include "bluetooth_proxy.h" #include "bluetooth_proxy.h"
#ifdef USE_API
#include "esphome/components/api/api_pb2.h"
#include "esphome/components/api/api_server.h"
#endif // USE_API
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "esphome/components/api/api_server.h"
namespace esphome { namespace esphome {
namespace bluetooth_proxy { namespace bluetooth_proxy {
static const char *const TAG = "bluetooth_proxy"; static const char *const TAG = "bluetooth_proxy";
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
static const esp_err_t ESP_GATT_WRONG_ADDRESS = -2;
BluetoothProxy::BluetoothProxy() {
global_bluetooth_proxy = this;
this->address_ = 0;
}
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
if (!api::global_api_server->is_connected())
return false;
ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(), ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(),
device.get_rssi()); device.get_rssi());
this->send_api_packet_(device); this->send_api_packet_(device);
this->address_type_map_[device.address_uint64()] = device.get_address_type();
if (this->address_ == 0)
return true;
if (this->state_ == espbt::ClientState::DISCOVERED) {
ESP_LOGV(TAG, "Connecting to address %s", this->address_str().c_str());
return true;
}
BLEClientBase::parse_device(device);
return true; return true;
} }
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) { void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
#ifndef USE_API
return;
#else
api::BluetoothLEAdvertisementResponse resp; api::BluetoothLEAdvertisementResponse resp;
resp.address = device.address_uint64(); resp.address = device.address_uint64();
if (!device.get_name().empty()) if (!device.get_name().empty())
@ -35,23 +52,372 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
for (auto &data : device.get_service_datas()) { for (auto &data : device.get_service_datas()) {
api::BluetoothServiceData service_data; api::BluetoothServiceData service_data;
service_data.uuid = data.uuid.to_string(); service_data.uuid = data.uuid.to_string();
for (auto d : data.data) service_data.data.assign(data.data.begin(), data.data.end());
service_data.data.push_back(d); resp.service_data.push_back(std::move(service_data));
resp.service_data.push_back(service_data);
} }
for (auto &data : device.get_manufacturer_datas()) { for (auto &data : device.get_manufacturer_datas()) {
api::BluetoothServiceData manufacturer_data; api::BluetoothServiceData manufacturer_data;
manufacturer_data.uuid = data.uuid.to_string(); manufacturer_data.uuid = data.uuid.to_string();
for (auto d : data.data) manufacturer_data.data.assign(data.data.begin(), data.data.end());
manufacturer_data.data.push_back(d); resp.manufacturer_data.push_back(std::move(manufacturer_data));
resp.manufacturer_data.push_back(manufacturer_data);
} }
api::global_api_server->send_bluetooth_le_advertisement(resp); api::global_api_server->send_bluetooth_le_advertisement(resp);
#endif }
void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
BLEClientBase::gattc_event_handler(event, gattc_if, param);
switch (event) {
case ESP_GATTC_DISCONNECT_EVT: {
api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_,
param->disconnect.reason);
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
this->get_bluetooth_connections_limit());
this->address_ = 0;
}
case ESP_GATTC_OPEN_EVT: {
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, param->open.status);
break;
}
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
this->get_bluetooth_connections_limit());
break;
}
case ESP_GATTC_READ_DESCR_EVT:
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->conn_id_)
break;
if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char/descriptor at handle 0x%2X, status=%d", param->read.handle,
param->read.status);
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->read.handle, param->read.status);
break;
}
api::BluetoothGATTReadResponse resp;
resp.address = this->address_;
resp.handle = param->read.handle;
resp.data.reserve(param->read.value_len);
for (uint16_t i = 0; i < param->read.value_len; i++) {
resp.data.push_back(param->read.value[i]);
}
api::global_api_server->send_bluetooth_gatt_read_response(resp);
break;
}
case ESP_GATTC_WRITE_CHAR_EVT:
case ESP_GATTC_WRITE_DESCR_EVT: {
if (param->write.conn_id != this->conn_id_)
break;
if (param->write.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error writing char/descriptor at handle 0x%2X, status=%d", param->write.handle,
param->write.status);
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->write.handle, param->write.status);
break;
}
api::BluetoothGATTWriteResponse resp;
resp.address = this->address_;
resp.handle = param->write.handle;
api::global_api_server->send_bluetooth_gatt_write_response(resp);
break;
}
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
if (param->unreg_for_notify.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error unregistering notifications for handle 0x%2X, status=%d", param->unreg_for_notify.handle,
param->unreg_for_notify.status);
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->unreg_for_notify.handle,
param->unreg_for_notify.status);
break;
}
api::BluetoothGATTNotifyResponse resp;
resp.address = this->address_;
resp.handle = param->unreg_for_notify.handle;
api::global_api_server->send_bluetooth_gatt_notify_response(resp);
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
if (param->reg_for_notify.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error registering notifications for handle 0x%2X, status=%d", param->reg_for_notify.handle,
param->reg_for_notify.status);
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->reg_for_notify.handle,
param->reg_for_notify.status);
break;
}
api::BluetoothGATTNotifyResponse resp;
resp.address = this->address_;
resp.handle = param->reg_for_notify.handle;
api::global_api_server->send_bluetooth_gatt_notify_response(resp);
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->conn_id_)
break;
ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT: handle=0x%2X", param->notify.handle);
api::BluetoothGATTNotifyDataResponse resp;
resp.address = this->address_;
resp.handle = param->notify.handle;
resp.data.reserve(param->notify.value_len);
for (uint16_t i = 0; i < param->notify.value_len; i++) {
resp.data.push_back(param->notify.value[i]);
}
api::global_api_server->send_bluetooth_gatt_notify_data_response(resp);
break;
}
default:
break;
}
} }
void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); } void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); }
void BluetoothProxy::loop() {
BLEClientBase::loop();
if (this->state_ != espbt::ClientState::IDLE && !api::global_api_server->is_connected()) {
ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str());
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
if (err != ERR_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err);
}
}
if (this->send_service_ == this->services_.size()) {
this->send_service_ = -1;
api::global_api_server->send_bluetooth_gatt_services_done(this->address_);
} else if (this->send_service_ >= 0) {
auto &service = this->services_[this->send_service_];
api::BluetoothGATTGetServicesResponse resp;
resp.address = this->address_;
api::BluetoothGATTService service_resp;
service_resp.uuid = {service->uuid.get_128bit_high(), service->uuid.get_128bit_low()};
service_resp.handle = service->start_handle;
for (auto &characteristic : service->characteristics) {
api::BluetoothGATTCharacteristic characteristic_resp;
characteristic_resp.uuid = {characteristic->uuid.get_128bit_high(), characteristic->uuid.get_128bit_low()};
characteristic_resp.handle = characteristic->handle;
characteristic_resp.properties = characteristic->properties;
for (auto &descriptor : characteristic->descriptors) {
api::BluetoothGATTDescriptor descriptor_resp;
descriptor_resp.uuid = {descriptor->uuid.get_128bit_high(), descriptor->uuid.get_128bit_low()};
descriptor_resp.handle = descriptor->handle;
characteristic_resp.descriptors.push_back(std::move(descriptor_resp));
}
service_resp.characteristics.push_back(std::move(characteristic_resp));
}
resp.services.push_back(std::move(service_resp));
api::global_api_server->send_bluetooth_gatt_services(resp);
this->send_service_++;
}
}
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
switch (msg.request_type) {
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
this->address_ = msg.address;
if (this->address_type_map_.find(this->address_) != this->address_type_map_.end()) {
// Utilise the address type cache
this->remote_addr_type_ = this->address_type_map_[this->address_];
} else {
this->remote_addr_type_ = BLE_ADDR_TYPE_PUBLIC;
}
this->remote_bda_[0] = (this->address_ >> 40) & 0xFF;
this->remote_bda_[1] = (this->address_ >> 32) & 0xFF;
this->remote_bda_[2] = (this->address_ >> 24) & 0xFF;
this->remote_bda_[3] = (this->address_ >> 16) & 0xFF;
this->remote_bda_[4] = (this->address_ >> 8) & 0xFF;
this->remote_bda_[5] = (this->address_ >> 0) & 0xFF;
this->set_state(espbt::ClientState::DISCOVERED);
esp_ble_gap_stop_scanning();
break;
}
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
if (this->state() != espbt::ClientState::IDLE) {
ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str());
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
if (err != ERR_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err);
}
}
break;
}
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
break;
}
}
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
if (this->state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected.");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return;
}
if (this->address_ != msg.address) {
ESP_LOGW(TAG, "Address mismatch for read GATT characteristic request");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
return;
}
auto *characteristic = this->get_characteristic(msg.handle);
if (characteristic == nullptr) {
ESP_LOGW(TAG, "Cannot read GATT characteristic, not found.");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
return;
}
ESP_LOGV(TAG, "Reading GATT characteristic %s", characteristic->uuid.to_string().c_str());
esp_err_t err =
esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE);
if (err != ERR_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err);
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
}
}
void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) {
if (this->state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected.");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return;
}
if (this->address_ != msg.address) {
ESP_LOGW(TAG, "Address mismatch for write GATT characteristic request");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
return;
}
auto *characteristic = this->get_characteristic(msg.handle);
if (characteristic == nullptr) {
ESP_LOGW(TAG, "Cannot write GATT characteristic, not found.");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
return;
}
ESP_LOGV(TAG, "Writing GATT characteristic %s", characteristic->uuid.to_string().c_str());
auto err = characteristic->write_value((uint8_t *) msg.data.data(), msg.data.size(),
msg.response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP);
if (err != ERR_OK) {
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
}
}
void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) {
if (this->state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not connected.");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return;
}
if (this->address_ != msg.address) {
ESP_LOGW(TAG, "Address mismatch for read GATT characteristic descriptor request");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
return;
}
auto *descriptor = this->get_descriptor(msg.handle);
if (descriptor == nullptr) {
ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not found.");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
return;
}
ESP_LOGV(TAG, "Reading GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(),
descriptor->uuid.to_string().c_str());
esp_err_t err =
esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE);
if (err != ERR_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err);
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
}
}
void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) {
if (this->state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not connected.");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return;
}
if (this->address_ != msg.address) {
ESP_LOGW(TAG, "Address mismatch for write GATT characteristic descriptor request");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
return;
}
auto *descriptor = this->get_descriptor(msg.handle);
if (descriptor == nullptr) {
ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not found.");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
return;
}
ESP_LOGV(TAG, "Writing GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(),
descriptor->uuid.to_string().c_str());
esp_err_t err =
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, msg.data.size(),
(uint8_t *) msg.data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (err != ERR_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, err=%d", err);
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
}
}
void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) {
if (this->state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "Cannot get GATT services, not connected.");
api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
return;
}
if (this->address_ != msg.address) {
ESP_LOGW(TAG, "Address mismatch for service list request");
api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_WRONG_ADDRESS);
return;
}
this->send_service_ = 0;
}
void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) {
if (this->state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "Cannot configure notify, not connected.");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return;
}
if (this->address_ != msg.address) {
ESP_LOGW(TAG, "Address mismatch for notify");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
return;
}
auto *characteristic = this->get_characteristic(msg.handle);
if (characteristic == nullptr) {
ESP_LOGW(TAG, "Cannot notify GATT characteristic, not found.");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
return;
}
esp_err_t err;
if (msg.enable) {
err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, err=%d", err);
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
}
} else {
err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_unregister_for_notify failed, err=%d", err);
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
}
}
}
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace bluetooth_proxy } // namespace bluetooth_proxy
} // namespace esphome } // namespace esphome

View File

@ -4,22 +4,54 @@
#include <map> #include <map>
#include "esphome/components/api/api_pb2.h"
#include "esphome/components/esp32_ble_client/ble_client_base.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include <map>
namespace esphome { namespace esphome {
namespace bluetooth_proxy { namespace bluetooth_proxy {
class BluetoothProxy : public Component, public esp32_ble_tracker::ESPBTDeviceListener { using namespace esp32_ble_client;
class BluetoothProxy : public BLEClientBase {
public: public:
BluetoothProxy();
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
void dump_config() override; void dump_config() override;
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;
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg);
void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg);
void bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg);
void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg);
void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg);
void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg);
int get_bluetooth_connections_free() { return this->state_ == espbt::ClientState::IDLE ? 1 : 0; }
int get_bluetooth_connections_limit() { return 1; }
void set_active(bool active) { this->active_ = active; }
bool has_active() { return this->active_; }
protected: protected:
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
std::map<uint64_t, esp_ble_addr_type_t> address_type_map_;
int16_t send_service_{-1};
bool active_;
}; };
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace bluetooth_proxy } // namespace bluetooth_proxy
} // namespace esphome } // namespace esphome

View File

@ -3,6 +3,7 @@ from pathlib import Path
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components.packages import validate_source_shorthand from esphome.components.packages import validate_source_shorthand
from esphome.const import CONF_WIFI
from esphome.wizard import wizard_file from esphome.wizard import wizard_file
from esphome.yaml_util import dump from esphome.yaml_util import dump
@ -43,7 +44,9 @@ async def to_code(config):
cg.add(dashboard_import_ns.set_package_import_url(config[CONF_PACKAGE_IMPORT_URL])) cg.add(dashboard_import_ns.set_package_import_url(config[CONF_PACKAGE_IMPORT_URL]))
def import_config(path: str, name: str, project_name: str, import_url: str) -> None: def import_config(
path: str, name: str, project_name: str, import_url: str, network: str = CONF_WIFI
) -> None:
p = Path(path) p = Path(path)
if p.exists(): if p.exists():
@ -69,7 +72,9 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
"name_add_mac_suffix": False, "name_add_mac_suffix": False,
}, },
} }
p.write_text( output = dump(config)
dump(config) + WIFI_CONFIG,
encoding="utf8", if network == CONF_WIFI:
) output += WIFI_CONFIG
p.write_text(output, encoding="utf8")

View File

@ -0,0 +1,12 @@
import esphome.codegen as cg
from esphome.components import esp32_ble_tracker
AUTO_LOAD = ["esp32_ble_tracker"]
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["esp32"]
esp32_ble_client_ns = cg.esphome_ns.namespace("esp32_ble_client")
BLEClientBase = esp32_ble_client_ns.class_(
"BLEClientBase", esp32_ble_tracker.ESPBTClient, cg.Component
)

View File

@ -0,0 +1,84 @@
#include "ble_characteristic.h"
#include "ble_client_base.h"
#include "ble_service.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
namespace esphome {
namespace esp32_ble_client {
static const char *const TAG = "esp32_ble_client.characteristic";
BLECharacteristic::~BLECharacteristic() {
for (auto &desc : this->descriptors)
delete desc; // NOLINT(cppcoreguidelines-owning-memory)
}
void BLECharacteristic::parse_descriptors() {
uint16_t offset = 0;
esp_gattc_descr_elem_t result;
while (true) {
uint16_t count = 1;
esp_gatt_status_t status =
esp_ble_gattc_get_all_descr(this->service->client->get_gattc_if(), this->service->client->get_conn_id(),
this->handle, &result, &count, offset);
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
break;
}
if (status != ESP_GATT_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status);
break;
}
if (count == 0) {
break;
}
BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory)
desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
desc->handle = result.handle;
desc->characteristic = this;
this->descriptors.push_back(desc);
ESP_LOGV(TAG, " descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle);
offset++;
}
}
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
for (auto &desc : this->descriptors) {
if (desc->uuid == uuid)
return desc;
}
return nullptr;
}
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
}
BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) {
for (auto &desc : this->descriptors) {
if (desc->handle == handle)
return desc;
}
return nullptr;
}
esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
auto *client = this->service->client;
auto status = esp_ble_gattc_write_char(client->get_gattc_if(), client->get_conn_id(), this->handle, new_val_size,
new_val, write_type, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
}
return status;
}
esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
return write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
}
} // namespace esp32_ble_client
} // namespace esphome
#endif // USE_ESP32

View File

@ -0,0 +1,35 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "ble_descriptor.h"
namespace esphome {
namespace esp32_ble_client {
namespace espbt = esphome::esp32_ble_tracker;
class BLEService;
class BLECharacteristic {
public:
~BLECharacteristic();
espbt::ESPBTUUID uuid;
uint16_t handle;
esp_gatt_char_prop_t properties;
std::vector<BLEDescriptor *> descriptors;
void parse_descriptors();
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
BLEDescriptor *get_descriptor(uint16_t uuid);
BLEDescriptor *get_descriptor_by_handle(uint16_t handle);
esp_err_t write_value(uint8_t *new_val, int16_t new_val_size);
esp_err_t write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
BLEService *service;
};
} // namespace esp32_ble_client
} // namespace esphome
#endif // USE_ESP32

View File

@ -0,0 +1,324 @@
#include "ble_client_base.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
namespace esphome {
namespace esp32_ble_client {
static const char *const TAG = "esp32_ble_client";
void BLEClientBase::setup() {
auto ret = esp_ble_gattc_app_register(this->app_id);
if (ret) {
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
this->mark_failed();
}
this->set_state(espbt::ClientState::IDLE);
}
void BLEClientBase::loop() {
if (this->state_ == espbt::ClientState::DISCOVERED) {
this->connect();
}
}
float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
if (device.address_uint64() != this->address_)
return false;
if (this->state_ != espbt::ClientState::IDLE)
return false;
ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str());
this->set_state(espbt::ClientState::DISCOVERED);
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->remote_addr_type_ = device.get_address_type();
return true;
}
std::string BLEClientBase::address_str() const {
return str_snprintf("%02x:%02x:%02x:%02x:%02x:%02x", 17, (uint8_t)(this->address_ >> 40) & 0xff,
(uint8_t)(this->address_ >> 32) & 0xff, (uint8_t)(this->address_ >> 24) & 0xff,
(uint8_t)(this->address_ >> 16) & 0xff, (uint8_t)(this->address_ >> 8) & 0xff,
(uint8_t)(this->address_ >> 0) & 0xff);
}
void BLEClientBase::connect() {
ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str());
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret);
this->set_state(espbt::ClientState::IDLE);
} else {
this->set_state(espbt::ClientState::CONNECTING);
}
}
void 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)
return;
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_)
return;
switch (event) {
case ESP_GATTC_REG_EVT: {
if (param->reg.status == ESP_GATT_OK) {
ESP_LOGV(TAG, "gattc registered app id %d", this->app_id);
this->gattc_if_ = esp_gattc_if;
} else {
ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status);
}
break;
}
case ESP_GATTC_OPEN_EVT: {
ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
this->conn_id_ = param->open.conn_id;
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
this->set_state(espbt::ClientState::IDLE);
break;
}
break;
}
case ESP_GATTC_CONNECT_EVT: {
ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str());
if (this->conn_id_ != param->connect.conn_id) {
ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d",
this->address_str().c_str(), param->connect.conn_id, this->conn_id_);
}
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->connect.conn_id);
if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret);
}
break;
}
case ESP_GATTC_CFG_MTU_EVT: {
if (param->cfg_mtu.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu,
param->cfg_mtu.status);
this->set_state(espbt::ClientState::IDLE);
break;
}
ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
this->mtu_ = param->cfg_mtu.mtu;
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0) {
return;
}
ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason);
for (auto &svc : this->services_)
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
this->services_.clear();
this->set_state(espbt::ClientState::IDLE);
break;
}
case ESP_GATTC_SEARCH_RES_EVT: {
BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
ble_service->start_handle = param->search_res.start_handle;
ble_service->end_handle = param->search_res.end_handle;
ble_service->client = this;
this->services_.push_back(ble_service);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str());
for (auto &svc : this->services_) {
ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str());
ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle);
svc->parse_characteristics();
}
this->set_state(espbt::ClientState::CONNECTED);
this->state_ = espbt::ClientState::ESTABLISHED;
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
auto *descr = this->get_config_descriptor(param->reg_for_notify.handle);
if (descr == nullptr) {
ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle);
break;
}
if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle,
descr->uuid.to_string().c_str());
break;
}
uint16_t notify_en = 1;
auto status =
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descr->handle, sizeof(notify_en),
(uint8_t *) &notify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
}
break;
}
default:
break;
}
}
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:
ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", 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:
esp_bd_addr_t bd_addr;
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str());
if (!param->ble_security.auth_cmpl.success) {
ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason);
} else {
ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", 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:
break;
}
}
// Parse GATT values into a float for a sensor.
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
// A length of one means a single octet value.
if (length == 0)
return 0;
if (length == 1)
return (float) ((uint8_t) value[0]);
switch (value[0]) {
case 0x1: // boolean.
case 0x2: // 2bit.
case 0x3: // nibble.
case 0x4: // uint8.
return (float) ((uint8_t) value[1]);
case 0x5: // uint12.
case 0x6: // uint16.
if (length > 2) {
return (float) encode_uint16(value[1], value[2]);
}
case 0x7: // uint24.
if (length > 3) {
return (float) encode_uint24(value[1], value[2], value[3]);
}
case 0x8: // uint32.
if (length > 4) {
return (float) encode_uint32(value[1], value[2], value[3], value[4]);
}
case 0xC: // int8.
return (float) ((int8_t) value[1]);
case 0xD: // int12.
case 0xE: // int16.
if (length > 2) {
return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]);
}
case 0xF: // int24.
if (length > 3) {
return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3]));
}
case 0x10: // int32.
if (length > 4) {
return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) +
(int32_t)(value[4]));
}
}
ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length);
return NAN;
}
BLEService *BLEClientBase::get_service(espbt::ESPBTUUID uuid) {
for (auto *svc : this->services_) {
if (svc->uuid == uuid)
return svc;
}
return nullptr;
}
BLEService *BLEClientBase::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
BLECharacteristic *BLEClientBase::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) {
auto *svc = this->get_service(service);
if (svc == nullptr)
return nullptr;
return svc->get_characteristic(chr);
}
BLECharacteristic *BLEClientBase::get_characteristic(uint16_t service, uint16_t chr) {
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
}
BLECharacteristic *BLEClientBase::get_characteristic(uint16_t handle) {
for (auto *svc : this->services_) {
for (auto *chr : svc->characteristics) {
if (chr->handle == handle)
return chr;
}
}
return nullptr;
}
BLEDescriptor *BLEClientBase::get_config_descriptor(uint16_t handle) {
auto *chr = this->get_characteristic(handle);
if (chr != nullptr) {
for (auto &desc : chr->descriptors) {
if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902))
return desc;
}
}
return nullptr;
}
BLEDescriptor *BLEClientBase::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) {
auto *svc = this->get_service(service);
if (svc == nullptr)
return nullptr;
auto *ch = svc->get_characteristic(chr);
if (ch == nullptr)
return nullptr;
return ch->get_descriptor(descr);
}
BLEDescriptor *BLEClientBase::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) {
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
espbt::ESPBTUUID::from_uint16(descr));
}
BLEDescriptor *BLEClientBase::get_descriptor(uint16_t handle) {
for (auto *svc : this->services_) {
for (auto *chr : svc->characteristics) {
for (auto *desc : chr->descriptors) {
if (desc->handle == handle)
return desc;
}
}
}
return nullptr;
}
} // namespace esp32_ble_client
} // namespace esphome
#endif // USE_ESP32

View File

@ -0,0 +1,72 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/core/component.h"
#include "ble_service.h"
#include <array>
#include <string>
#include <esp_bt_defs.h>
#include <esp_gap_ble_api.h>
#include <esp_gatt_common_api.h>
#include <esp_gattc_api.h>
namespace esphome {
namespace esp32_ble_client {
namespace espbt = esphome::esp32_ble_tracker;
class BLEClientBase : public espbt::ESPBTClient, public Component {
public:
void setup() override;
void loop() override;
float get_setup_priority() const override;
bool parse_device(const espbt::ESPBTDevice &device) override;
void on_scan_end() override {}
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
void connect() override;
void set_address(uint64_t address) { this->address_ = address; }
std::string address_str() const;
BLEService *get_service(espbt::ESPBTUUID uuid);
BLEService *get_service(uint16_t uuid);
BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr);
BLECharacteristic *get_characteristic(uint16_t handle);
BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr);
BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr);
BLEDescriptor *get_descriptor(uint16_t handle);
// Get the configuration descriptor for the given characteristic handle.
BLEDescriptor *get_config_descriptor(uint16_t handle);
float parse_char_value(uint8_t *value, uint16_t length);
int get_gattc_if() const { return this->gattc_if_; }
uint8_t *get_remote_bda() { return this->remote_bda_; }
esp_ble_addr_type_t get_remote_addr_type() const { return this->remote_addr_type_; }
uint16_t get_conn_id() const { return this->conn_id_; }
uint64_t get_address() const { return this->address_; }
protected:
int gattc_if_;
esp_bd_addr_t remote_bda_;
esp_ble_addr_type_t remote_addr_type_;
uint16_t conn_id_;
uint64_t address_;
uint16_t mtu_{23};
std::vector<BLEService *> services_;
};
} // namespace esp32_ble_client
} // namespace esphome
#endif // USE_ESP32

View File

@ -0,0 +1,25 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
namespace esphome {
namespace esp32_ble_client {
namespace espbt = esphome::esp32_ble_tracker;
class BLECharacteristic;
class BLEDescriptor {
public:
espbt::ESPBTUUID uuid;
uint16_t handle;
BLECharacteristic *characteristic;
};
} // namespace esp32_ble_client
} // namespace esphome
#endif // USE_ESP32

View File

@ -0,0 +1,66 @@
#include "ble_service.h"
#include "ble_client_base.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
namespace esphome {
namespace esp32_ble_client {
static const char *const TAG = "esp32_ble_client.service";
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
for (auto &chr : this->characteristics) {
if (chr->uuid == uuid)
return chr;
}
return nullptr;
}
BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid));
}
BLEService::~BLEService() {
for (auto &chr : this->characteristics)
delete chr; // NOLINT(cppcoreguidelines-owning-memory)
}
void BLEService::parse_characteristics() {
uint16_t offset = 0;
esp_gattc_char_elem_t result;
while (true) {
uint16_t count = 1;
esp_gatt_status_t status =
esp_ble_gattc_get_all_char(this->client->get_gattc_if(), this->client->get_conn_id(), this->start_handle,
this->end_handle, &result, &count, offset);
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
break;
}
if (status != ESP_GATT_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status);
break;
}
if (count == 0) {
break;
}
BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory)
characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
characteristic->properties = result.properties;
characteristic->handle = result.char_handle;
characteristic->service = this;
this->characteristics.push_back(characteristic);
ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(),
characteristic->handle, characteristic->properties);
characteristic->parse_descriptors();
offset++;
}
}
} // namespace esp32_ble_client
} // namespace esphome
#endif // USE_ESP32

View File

@ -0,0 +1,32 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "ble_characteristic.h"
namespace esphome {
namespace esp32_ble_client {
namespace espbt = esphome::esp32_ble_tracker;
class BLEClientBase;
class BLEService {
public:
~BLEService();
espbt::ESPBTUUID uuid;
uint16_t start_handle;
uint16_t end_handle;
std::vector<BLECharacteristic *> characteristics;
BLEClientBase *client;
void parse_characteristics();
BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid);
BLECharacteristic *get_characteristic(uint16_t uuid);
};
} // namespace esp32_ble_client
} // namespace esphome
#endif // USE_ESP32

View File

@ -3,6 +3,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.const import ( from esphome.const import (
CONF_ACTIVE,
CONF_ID, CONF_ID,
CONF_INTERVAL, CONF_INTERVAL,
CONF_DURATION, CONF_DURATION,
@ -22,7 +23,6 @@ DEPENDENCIES = ["esp32"]
CONF_ESP32_BLE_ID = "esp32_ble_id" CONF_ESP32_BLE_ID = "esp32_ble_id"
CONF_SCAN_PARAMETERS = "scan_parameters" CONF_SCAN_PARAMETERS = "scan_parameters"
CONF_WINDOW = "window" CONF_WINDOW = "window"
CONF_ACTIVE = "active"
CONF_CONTINUOUS = "continuous" CONF_CONTINUOUS = "continuous"
CONF_ON_SCAN_END = "on_scan_end" CONF_ON_SCAN_END = "on_scan_end"
esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")

View File

@ -518,28 +518,39 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
} }
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; } esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
std::string ESPBTUUID::to_string() const { std::string ESPBTUUID::to_string() const {
char sbuf[64];
switch (this->uuid_.len) { switch (this->uuid_.len) {
case ESP_UUID_LEN_16: case ESP_UUID_LEN_16:
sprintf(sbuf, "0x%02X%02X", this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff); return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
break;
case ESP_UUID_LEN_32: case ESP_UUID_LEN_32:
sprintf(sbuf, "0x%02X%02X%02X%02X", this->uuid_.uuid.uuid32 >> 24, (this->uuid_.uuid.uuid32 >> 16 & 0xff), return str_snprintf("0x%02X%02X%02X%02X", 10, this->uuid_.uuid.uuid32 >> 24,
(this->uuid_.uuid.uuid32 >> 8 & 0xff), this->uuid_.uuid.uuid32 & 0xff); (this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff),
break; this->uuid_.uuid.uuid32 & 0xff);
default: default:
case ESP_UUID_LEN_128: case ESP_UUID_LEN_128:
char *bpos = sbuf; std::string buf;
for (int8_t i = 15; i >= 0; i--) { for (int8_t i = 15; i >= 0; i--) {
sprintf(bpos, "%02X", this->uuid_.uuid.uuid128[i]); buf += str_snprintf("%02X", 2, this->uuid_.uuid.uuid128[i]);
bpos += 2;
if (i == 6 || i == 8 || i == 10 || i == 12) if (i == 6 || i == 8 || i == 10 || i == 12)
sprintf(bpos++, "-"); buf += "-";
} }
sbuf[47] = '\0'; return buf;
break;
} }
return sbuf; return "";
}
uint64_t ESPBTUUID::get_128bit_high() const {
esp_bt_uuid_t uuid = this->as_128bit().get_uuid();
return ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]);
}
uint64_t ESPBTUUID::get_128bit_low() const {
esp_bt_uuid_t uuid = this->as_128bit().get_uuid();
return ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]);
} }
ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); } ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
@ -637,11 +648,17 @@ void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_p
// (called CSS here) // (called CSS here)
switch (record_type) { switch (record_type) {
case ESP_BLE_AD_TYPE_NAME_SHORT:
case ESP_BLE_AD_TYPE_NAME_CMPL: { case ESP_BLE_AD_TYPE_NAME_CMPL: {
// CSS 1.2 LOCAL NAME // CSS 1.2 LOCAL NAME
// "The Local Name data type shall be the same as, or a shortened version of, the local name assigned to the // "The Local Name data type shall be the same as, or a shortened version of, the local name assigned to the
// device." CSS 1: Optional in this context; shall not appear more than once in a block. // device." CSS 1: Optional in this context; shall not appear more than once in a block.
this->name_ = std::string(reinterpret_cast<const char *>(record), record_length); // SHORTENED LOCAL NAME
// "The Shortened Local Name data type defines a shortened version of the Local Name data type. The Shortened
// Local Name data type shall not be used to advertise a name that is longer than the Local Name data type."
if (record_length > this->name_.length()) {
this->name_ = std::string(reinterpret_cast<const char *>(record), record_length);
}
break; break;
} }
case ESP_BLE_AD_TYPE_TX_PWR: { case ESP_BLE_AD_TYPE_TX_PWR: {

View File

@ -41,6 +41,9 @@ class ESPBTUUID {
std::string to_string() const; std::string to_string() const;
uint64_t get_128bit_high() const;
uint64_t get_128bit_low() const;
protected: protected:
esp_bt_uuid_t uuid_; esp_bt_uuid_t uuid_;
}; };
@ -158,7 +161,7 @@ class ESPBTClient : public ESPBTDeviceListener {
esp_ble_gattc_cb_param_t *param) = 0; esp_ble_gattc_cb_param_t *param) = 0;
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
virtual void connect() = 0; virtual void connect() = 0;
void set_state(ClientState st) { this->state_ = st; } virtual void set_state(ClientState st) { this->state_ = st; }
ClientState state() const { return state_; } ClientState state() const { return state_; }
int app_id; int app_id;

View File

@ -72,13 +72,13 @@ class BLEEvent {
// Need to also make a copy of relevant event data. // Need to also make a copy of relevant event data.
switch (e) { switch (e) {
case ESP_GATTC_NOTIFY_EVT: case ESP_GATTC_NOTIFY_EVT:
memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len); this->data.assign(p->notify.value, p->notify.value + p->notify.value_len);
this->event_.gattc.gattc_param.notify.value = this->event_.gattc.data; this->event_.gattc.gattc_param.notify.value = this->data.data();
break; break;
case ESP_GATTC_READ_CHAR_EVT: case ESP_GATTC_READ_CHAR_EVT:
case ESP_GATTC_READ_DESCR_EVT: case ESP_GATTC_READ_DESCR_EVT:
memcpy(this->event_.gattc.data, p->read.value, p->read.value_len); this->data.assign(p->read.value, p->read.value + p->read.value_len);
this->event_.gattc.gattc_param.read.value = this->event_.gattc.data; this->event_.gattc.gattc_param.read.value = this->data.data();
break; break;
default: default:
break; break;
@ -96,9 +96,9 @@ class BLEEvent {
esp_gattc_cb_event_t gattc_event; esp_gattc_cb_event_t gattc_event;
esp_gatt_if_t gattc_if; esp_gatt_if_t gattc_if;
esp_ble_gattc_cb_param_t gattc_param; esp_ble_gattc_cb_param_t gattc_param;
uint8_t data[64];
} gattc; } gattc;
} event_; } event_;
std::vector<uint8_t> data{};
uint8_t type_; // 0=gap 1=gattc uint8_t type_; // 0=gap 1=gattc
}; };

View File

@ -1,6 +1,5 @@
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from typing import List
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
@ -200,7 +199,7 @@ async def esp8266_pin_to_code(config):
@coroutine_with_priority(-999.0) @coroutine_with_priority(-999.0)
async def add_pin_initial_states_array(): async def add_pin_initial_states_array():
# Add includes at the very end, so that they override everything # Add includes at the very end, so that they override everything
initial_states: List[PinInitialState] = CORE.data[KEY_ESP8266][ initial_states: list[PinInitialState] = CORE.data[KEY_ESP8266][
KEY_PIN_INITIAL_STATES KEY_PIN_INITIAL_STATES
] ]
initial_modes_s = ", ".join(str(x.mode) for x in initial_states) initial_modes_s = ", ".join(str(x.mode) for x in initial_states)

View File

@ -98,7 +98,7 @@ async def to_code(config):
def _process_git_config(config: dict, refresh) -> str: def _process_git_config(config: dict, refresh) -> str:
repo_dir = git.clone_or_update( repo_dir, _ = git.clone_or_update(
url=config[CONF_URL], url=config[CONF_URL],
ref=config.get(CONF_REF), ref=config.get(CONF_REF),
refresh=refresh, refresh=refresh,

View File

@ -58,6 +58,7 @@ PROTOCOLS = {
"sharp": Protocol.PROTOCOL_SHARP, "sharp": Protocol.PROTOCOL_SHARP,
"toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI, "toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI,
"toshiba": Protocol.PROTOCOL_TOSHIBA, "toshiba": Protocol.PROTOCOL_TOSHIBA,
"zhlt01": Protocol.PROTOCOL_ZHLT01,
} }
CONF_HORIZONTAL_DEFAULT = "horizontal_default" CONF_HORIZONTAL_DEFAULT = "horizontal_default"

View File

@ -53,6 +53,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
{PROTOCOL_SHARP, []() { return new SharpHeatpumpIR(); }}, // NOLINT {PROTOCOL_SHARP, []() { return new SharpHeatpumpIR(); }}, // NOLINT
{PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT {PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT
{PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT {PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT
{PROTOCOL_ZHLT01, []() { return new ZHLT01HeatpumpIR(); }}, // NOLINT
}; };
void HeatpumpIRClimate::setup() { void HeatpumpIRClimate::setup() {

View File

@ -53,6 +53,7 @@ enum Protocol {
PROTOCOL_SHARP, PROTOCOL_SHARP,
PROTOCOL_TOSHIBA_DAISEIKAI, PROTOCOL_TOSHIBA_DAISEIKAI,
PROTOCOL_TOSHIBA, PROTOCOL_TOSHIBA,
PROTOCOL_ZHLT01,
}; };
// Simple enum to represent horizontal directios // Simple enum to represent horizontal directios

View File

@ -23,6 +23,13 @@ void MCP23S17::setup() {
this->transfer_byte(0b00011000); // Enable HAEN pins for addressing this->transfer_byte(0b00011000); // Enable HAEN pins for addressing
this->disable(); this->disable();
this->enable();
cmd = 0b01001000;
this->transfer_byte(cmd);
this->transfer_byte(mcp23x17_base::MCP23X17_IOCONA);
this->transfer_byte(0b00011000); // Enable HAEN pins for addressing
this->disable();
// Read current output register state // Read current output register state
this->read_reg(mcp23x17_base::MCP23X17_OLATA, &this->olat_a_); this->read_reg(mcp23x17_base::MCP23X17_OLATA, &this->olat_a_);
this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_); this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_);

View File

@ -45,6 +45,12 @@ void MDNSComponent::compile_records_() {
service.txt_records.push_back({"board", ESPHOME_BOARD}); service.txt_records.push_back({"board", ESPHOME_BOARD});
#if defined(USE_WIFI)
service.txt_records.push_back({"network", "wifi"});
#elif defined(USE_ETHERNET)
service.txt_records.push_back({"network", "ethernet"});
#endif
#ifdef ESPHOME_PROJECT_NAME #ifdef ESPHOME_PROJECT_NAME
service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME}); service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME});
service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION}); service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION});

View File

@ -1,5 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, List from typing import Any
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
@ -349,7 +349,7 @@ def _spi_extra_validate(config):
class MethodDescriptor: class MethodDescriptor:
method_schema: Any method_schema: Any
to_code: Any to_code: Any
supported_chips: List[str] supported_chips: list[str]
extra_validate: Any = None extra_validate: Any = None

View File

@ -5,14 +5,17 @@ from esphome.config_helpers import merge_config
from esphome import git, yaml_util from esphome import git, yaml_util
from esphome.const import ( from esphome.const import (
CONF_ESPHOME,
CONF_FILE, CONF_FILE,
CONF_FILES, CONF_FILES,
CONF_MIN_VERSION,
CONF_PACKAGES, CONF_PACKAGES,
CONF_REF, CONF_REF,
CONF_REFRESH, CONF_REFRESH,
CONF_URL, CONF_URL,
CONF_USERNAME, CONF_USERNAME,
CONF_PASSWORD, CONF_PASSWORD,
__version__ as ESPHOME_VERSION,
) )
import esphome.config_validation as cv import esphome.config_validation as cv
@ -104,7 +107,7 @@ CONFIG_SCHEMA = cv.All(
def _process_base_package(config: dict) -> dict: def _process_base_package(config: dict) -> dict:
repo_dir = git.clone_or_update( repo_dir, revert = git.clone_or_update(
url=config[CONF_URL], url=config[CONF_URL],
ref=config.get(CONF_REF), ref=config.get(CONF_REF),
refresh=config[CONF_REFRESH], refresh=config[CONF_REFRESH],
@ -112,21 +115,51 @@ def _process_base_package(config: dict) -> dict:
username=config.get(CONF_USERNAME), username=config.get(CONF_USERNAME),
password=config.get(CONF_PASSWORD), password=config.get(CONF_PASSWORD),
) )
files: str = config[CONF_FILES] files: list[str] = config[CONF_FILES]
def get_packages(files) -> dict:
packages = {}
for file in files:
yaml_file: Path = repo_dir / file
if not yaml_file.is_file():
raise cv.Invalid(
f"{file} does not exist in repository", path=[CONF_FILES]
)
try:
new_yaml = yaml_util.load_yaml(yaml_file)
if (
CONF_ESPHOME in new_yaml
and CONF_MIN_VERSION in new_yaml[CONF_ESPHOME]
):
min_version = new_yaml[CONF_ESPHOME][CONF_MIN_VERSION]
if cv.Version.parse(min_version) > cv.Version.parse(
ESPHOME_VERSION
):
raise cv.Invalid(
f"Current ESPHome Version is too old to use this package: {ESPHOME_VERSION} < {min_version}"
)
packages[file] = new_yaml
except EsphomeError as e:
raise cv.Invalid(
f"{file} is not a valid YAML file. Please check the file contents."
) from e
return packages
packages = {} packages = {}
for file in files:
yaml_file: Path = repo_dir / file
if not yaml_file.is_file(): try:
raise cv.Invalid(f"{file} does not exist in repository", path=[CONF_FILES]) packages = get_packages(files)
except cv.Invalid:
if revert is not None:
revert()
packages = get_packages(files)
finally:
if packages is None:
raise cv.Invalid("Failed to load packages")
try:
packages[file] = yaml_util.load_yaml(yaml_file)
except EsphomeError as e:
raise cv.Invalid(
f"{file} is not a valid YAML file. Please check the file contents."
) from e
return {"packages": packages} return {"packages": packages}

View File

@ -11,62 +11,43 @@ void PulseMeterSensor::setup() {
this->isr_pin_ = pin_->to_isr(); this->isr_pin_ = pin_->to_isr();
this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE);
this->pulse_width_us_ = 0;
this->last_detected_edge_us_ = 0; this->last_detected_edge_us_ = 0;
this->last_valid_low_edge_us_ = 0;
this->last_valid_high_edge_us_ = 0; this->last_valid_high_edge_us_ = 0;
this->last_valid_low_edge_us_ = 0;
this->sensor_is_high_ = this->isr_pin_.digital_read(); this->sensor_is_high_ = this->isr_pin_.digital_read();
this->has_valid_high_edge_ = false;
this->has_valid_low_edge_ = false;
} }
void PulseMeterSensor::loop() { void PulseMeterSensor::loop() {
// Get a local copy of the volatile sensor values, to make sure they are not
// modified by the ISR. This could cause overflow in the following arithmetic
const uint32_t last_valid_high_edge_us = this->last_valid_high_edge_us_;
const bool has_valid_high_edge = this->has_valid_high_edge_;
const uint32_t now = micros(); const uint32_t now = micros();
// Check to see if we should filter this edge out // If we've exceeded our timeout interval without receiving any pulses, assume
if (this->filter_mode_ == FILTER_EDGE) { // 0 pulses/min until we get at least two valid pulses.
if ((this->last_detected_edge_us_ - this->last_valid_high_edge_us_) >= this->filter_us_) { const uint32_t time_since_valid_edge_us = now - last_valid_high_edge_us;
// Don't measure the first valid pulse (we need at least two pulses to measure the width) if ((has_valid_high_edge) && (time_since_valid_edge_us > this->timeout_us_)) {
if (this->last_valid_high_edge_us_ != 0) {
this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_);
}
this->total_pulses_++;
this->last_valid_high_edge_us_ = this->last_detected_edge_us_;
}
} else {
// Make sure the signal has been stable long enough
if ((now - this->last_detected_edge_us_) >= this->filter_us_) {
// Only consider HIGH pulses and "new" edges if sensor state is LOW
if (!this->sensor_is_high_ && this->isr_pin_.digital_read() &&
(this->last_detected_edge_us_ != this->last_valid_high_edge_us_)) {
// Don't measure the first valid pulse (we need at least two pulses to measure the width)
if (this->last_valid_high_edge_us_ != 0) {
this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_);
}
this->sensor_is_high_ = true;
this->total_pulses_++;
this->last_valid_high_edge_us_ = this->last_detected_edge_us_;
}
// Only consider LOW pulses and "new" edges if sensor state is HIGH
else if (this->sensor_is_high_ && !this->isr_pin_.digital_read() &&
(this->last_detected_edge_us_ != this->last_valid_low_edge_us_)) {
this->sensor_is_high_ = false;
this->last_valid_low_edge_us_ = this->last_detected_edge_us_;
}
}
}
// If we've exceeded our timeout interval without receiving any pulses, assume 0 pulses/min until
// we get at least two valid pulses.
const uint32_t time_since_valid_edge_us = now - this->last_valid_high_edge_us_;
if ((this->last_valid_high_edge_us_ != 0) && (time_since_valid_edge_us > this->timeout_us_) &&
(this->pulse_width_us_ != 0)) {
ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000); ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000);
this->pulse_width_us_ = 0; this->pulse_width_us_ = 0;
this->last_detected_edge_us_ = 0;
this->last_valid_high_edge_us_ = 0;
this->last_valid_low_edge_us_ = 0;
this->has_detected_edge_ = false;
this->has_valid_high_edge_ = false;
this->has_valid_low_edge_ = false;
} }
// We quantize our pulse widths to 1 ms to avoid unnecessary jitter // We quantize our pulse widths to 1 ms to avoid unnecessary jitter
const uint32_t pulse_width_ms = this->pulse_width_us_ / 1000; const uint32_t pulse_width_ms = this->pulse_width_us_ / 1000;
if (this->pulse_width_dedupe_.next(pulse_width_ms)) { if (this->pulse_width_dedupe_.next(pulse_width_ms)) {
if (pulse_width_ms == 0) { if (pulse_width_ms == 0) {
// Treat 0 pulse width as 0 pulses/min (normally because we've not detected any pulses for a while) // Treat 0 pulse width as 0 pulses/min (normally because we've not
// detected any pulses for a while)
this->publish_state(0); this->publish_state(0);
} else { } else {
// Calculate pulses/min from the pulse width in ms // Calculate pulses/min from the pulse width in ms
@ -96,9 +77,11 @@ void PulseMeterSensor::dump_config() {
} }
void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) { void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) {
// This is an interrupt handler - we can't call any virtual method from this method // This is an interrupt handler - we can't call any virtual method from this
// method
// Get the current time before we do anything else so the measurements are consistent // Get the current time before we do anything else so the measurements are
// consistent
const uint32_t now = micros(); const uint32_t now = micros();
// We only look at rising edges in EDGE mode, and all edges in PULSE mode // We only look at rising edges in EDGE mode, and all edges in PULSE mode
@ -106,7 +89,45 @@ void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) {
if (sensor->isr_pin_.digital_read()) { if (sensor->isr_pin_.digital_read()) {
sensor->last_detected_edge_us_ = now; sensor->last_detected_edge_us_ = now;
} }
}
// Check to see if we should filter this edge out
if (sensor->filter_mode_ == FILTER_EDGE) {
if ((sensor->last_detected_edge_us_ - sensor->last_valid_high_edge_us_) >= sensor->filter_us_) {
// Don't measure the first valid pulse (we need at least two pulses to
// measure the width)
if (sensor->has_valid_high_edge_) {
sensor->pulse_width_us_ = (sensor->last_detected_edge_us_ - sensor->last_valid_high_edge_us_);
}
sensor->total_pulses_++;
sensor->last_valid_high_edge_us_ = sensor->last_detected_edge_us_;
sensor->has_valid_high_edge_ = true;
}
} else { } else {
// Filter Mode is PULSE
bool pin_val = sensor->isr_pin_.digital_read();
// Ignore false edges that may be caused by bouncing and exit the ISR ASAP
if (pin_val == sensor->sensor_is_high_) {
return;
}
// Make sure the signal has been stable long enough
if (sensor->has_detected_edge_ && (now - sensor->last_detected_edge_us_ >= sensor->filter_us_)) {
if (pin_val) {
sensor->has_valid_high_edge_ = true;
sensor->last_valid_high_edge_us_ = sensor->last_detected_edge_us_;
sensor->sensor_is_high_ = true;
} else {
// Count pulses when a sufficiently long high pulse is concluded.
sensor->total_pulses_++;
if (sensor->has_valid_low_edge_) {
sensor->pulse_width_us_ = sensor->last_detected_edge_us_ - sensor->last_valid_low_edge_us_;
}
sensor->has_valid_low_edge_ = true;
sensor->last_valid_low_edge_us_ = sensor->last_detected_edge_us_;
sensor->sensor_is_high_ = false;
}
}
sensor->has_detected_edge_ = true;
sensor->last_detected_edge_us_ = now; sensor->last_detected_edge_us_ = now;
} }
} }

View File

@ -1,8 +1,8 @@
#pragma once #pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
namespace esphome { namespace esphome {
@ -42,11 +42,14 @@ class PulseMeterSensor : public sensor::Sensor, public Component {
Deduplicator<uint32_t> total_dedupe_; Deduplicator<uint32_t> total_dedupe_;
volatile uint32_t last_detected_edge_us_ = 0; volatile uint32_t last_detected_edge_us_ = 0;
volatile uint32_t last_valid_low_edge_us_ = 0;
volatile uint32_t last_valid_high_edge_us_ = 0; volatile uint32_t last_valid_high_edge_us_ = 0;
volatile uint32_t last_valid_low_edge_us_ = 0;
volatile uint32_t pulse_width_us_ = 0; volatile uint32_t pulse_width_us_ = 0;
volatile uint32_t total_pulses_ = 0; volatile uint32_t total_pulses_ = 0;
volatile bool sensor_is_high_ = false; volatile bool sensor_is_high_ = false;
volatile bool has_detected_edge_ = false;
volatile bool has_valid_high_edge_ = false;
volatile bool has_valid_low_edge_ = false;
}; };
} // namespace pulse_meter } // namespace pulse_meter

View File

@ -102,8 +102,9 @@ void PVVXDisplay::send_to_setup_char_(uint8_t *blk, size_t size) {
ESP_LOGW(TAG, "[%s] Not connected to BLE client.", this->parent_->address_str().c_str()); ESP_LOGW(TAG, "[%s] Not connected to BLE client.", this->parent_->address_str().c_str());
return; return;
} }
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, size, blk, auto status =
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, size,
blk, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
} else { } else {

View File

@ -50,7 +50,7 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
} }
case ESP_GATTC_READ_CHAR_EVT: { case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->conn_id) if (param->read.conn_id != this->parent()->get_conn_id())
break; break;
if (param->read.status != ESP_GATT_OK) { if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
@ -146,17 +146,17 @@ void RadonEyeRD200::update() {
void RadonEyeRD200::write_query_message_() { void RadonEyeRD200::write_query_message_() {
ESP_LOGV(TAG, "writing 0x50 to write service"); ESP_LOGV(TAG, "writing 0x50 to write service");
int request = 0x50; int request = 0x50;
auto status = esp_ble_gattc_write_char_descr(this->parent()->gattc_if, this->parent()->conn_id, this->write_handle_, auto status = esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(),
sizeof(request), (uint8_t *) &request, ESP_GATT_WRITE_TYPE_NO_RSP, this->write_handle_, sizeof(request), (uint8_t *) &request,
ESP_GATT_AUTH_REQ_NONE); ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status); ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status);
} }
} }
void RadonEyeRD200::request_read_values_() { void RadonEyeRD200::request_read_values_() {
auto status = esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->read_handle_, auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(),
ESP_GATT_AUTH_REQ_NONE); this->read_handle_, ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
} }

View File

@ -1,4 +1,3 @@
from typing import List
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation from esphome import automation
@ -60,7 +59,7 @@ SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e
) )
async def setup_select_core_(var, config, *, options: List[str]): async def setup_select_core_(var, config, *, options: list[str]):
await setup_entity(var, config) await setup_entity(var, config)
cg.add(var.traits.set_options(options)) cg.add(var.traits.set_options(options))
@ -76,14 +75,14 @@ async def setup_select_core_(var, config, *, options: List[str]):
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
async def register_select(var, config, *, options: List[str]): async def register_select(var, config, *, options: list[str]):
if not CORE.has_id(config[CONF_ID]): if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var) var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_select(var)) cg.add(cg.App.register_select(var))
await setup_select_core_(var, config, options=options) await setup_select_core_(var, config, options=options)
async def new_select(config, *, options: List[str]): async def new_select(config, *, options: list[str]):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await register_select(var, config, options=options) await register_select(var, config, options=options)
return var return var

View File

@ -29,6 +29,7 @@ from esphome.const import (
CONF_WINDOW_SIZE, CONF_WINDOW_SIZE,
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_FORCE_UPDATE, CONF_FORCE_UPDATE,
DEVICE_CLASS_DISTANCE,
DEVICE_CLASS_DURATION, DEVICE_CLASS_DURATION,
DEVICE_CLASS_EMPTY, DEVICE_CLASS_EMPTY,
DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_APPARENT_POWER,
@ -43,6 +44,7 @@ from esphome.const import (
DEVICE_CLASS_GAS, DEVICE_CLASS_GAS,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_MOISTURE,
DEVICE_CLASS_MONETARY, DEVICE_CLASS_MONETARY,
DEVICE_CLASS_NITROGEN_DIOXIDE, DEVICE_CLASS_NITROGEN_DIOXIDE,
DEVICE_CLASS_NITROGEN_MONOXIDE, DEVICE_CLASS_NITROGEN_MONOXIDE,
@ -56,11 +58,14 @@ from esphome.const import (
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_REACTIVE_POWER, DEVICE_CLASS_REACTIVE_POWER,
DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_SPEED,
DEVICE_CLASS_SULPHUR_DIOXIDE, DEVICE_CLASS_SULPHUR_DIOXIDE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_TIMESTAMP,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_VOLUME,
DEVICE_CLASS_WEIGHT,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
@ -77,12 +82,14 @@ DEVICE_CLASSES = [
DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CARBON_MONOXIDE,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_DATE, DEVICE_CLASS_DATE,
DEVICE_CLASS_DISTANCE,
DEVICE_CLASS_DURATION, DEVICE_CLASS_DURATION,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_FREQUENCY,
DEVICE_CLASS_GAS, DEVICE_CLASS_GAS,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_MOISTURE,
DEVICE_CLASS_MONETARY, DEVICE_CLASS_MONETARY,
DEVICE_CLASS_NITROGEN_DIOXIDE, DEVICE_CLASS_NITROGEN_DIOXIDE,
DEVICE_CLASS_NITROGEN_MONOXIDE, DEVICE_CLASS_NITROGEN_MONOXIDE,
@ -96,11 +103,14 @@ DEVICE_CLASSES = [
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_REACTIVE_POWER, DEVICE_CLASS_REACTIVE_POWER,
DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_SPEED,
DEVICE_CLASS_SULPHUR_DIOXIDE, DEVICE_CLASS_SULPHUR_DIOXIDE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_TIMESTAMP,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_VOLUME,
DEVICE_CLASS_WEIGHT,
] ]
sensor_ns = cg.esphome_ns.namespace("sensor") sensor_ns = cg.esphome_ns.namespace("sensor")

View File

@ -195,11 +195,6 @@ class SPIComponent : public Component {
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, uint32_t DATA_RATE> template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, uint32_t DATA_RATE>
void enable(GPIOPin *cs) { void enable(GPIOPin *cs) {
if (cs != nullptr) {
this->active_cs_ = cs;
this->active_cs_->digital_write(false);
}
#ifdef USE_SPI_ARDUINO_BACKEND #ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) { if (this->hw_spi_ != nullptr) {
uint8_t data_mode = SPI_MODE0; uint8_t data_mode = SPI_MODE0;
@ -220,6 +215,11 @@ class SPIComponent : public Component {
#ifdef USE_SPI_ARDUINO_BACKEND #ifdef USE_SPI_ARDUINO_BACKEND
} }
#endif // USE_SPI_ARDUINO_BACKEND #endif // USE_SPI_ARDUINO_BACKEND
if (cs != nullptr) {
this->active_cs_ = cs;
this->active_cs_->digital_write(false);
}
} }
void disable(); void disable();

View File

@ -76,6 +76,8 @@ void SSD1327::setup() {
this->command(0x55); this->command(0x55);
this->command(SSD1327_SETVCOMHVOLTAGE); // Set High Voltage Level of COM Pin this->command(SSD1327_SETVCOMHVOLTAGE); // Set High Voltage Level of COM Pin
this->command(0x1C); this->command(0x1C);
this->command(SSD1327_SETGPIO); // Switch voltage converter on (for Aliexpress display)
this->command(0x03);
this->command(SSD1327_NORMALDISPLAY); // set display mode this->command(SSD1327_NORMALDISPLAY); // set display mode
set_brightness(this->brightness_); set_brightness(this->brightness_);
this->fill(Color::BLACK); // clear display - ensures we do not see garbage at power-on this->fill(Color::BLACK); // clear display - ensures we do not see garbage at power-on

View File

@ -4,7 +4,6 @@ from esphome import pins
from esphome.components import display, spi from esphome.components import display, spi
from esphome.const import ( from esphome.const import (
CONF_BACKLIGHT_PIN, CONF_BACKLIGHT_PIN,
CONF_CS_PIN,
CONF_DC_PIN, CONF_DC_PIN,
CONF_HEIGHT, CONF_HEIGHT,
CONF_ID, CONF_ID,
@ -69,7 +68,6 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_MODEL): ST7789V_MODEL, cv.Required(CONF_MODEL): ST7789V_MODEL,
cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_EIGHTBITCOLOR, default=False): cv.boolean, cv.Optional(CONF_EIGHTBITCOLOR, default=False): cv.boolean,
cv.Optional(CONF_HEIGHT): cv.int_, cv.Optional(CONF_HEIGHT): cv.int_,
@ -79,7 +77,7 @@ CONFIG_SCHEMA = cv.All(
} }
) )
.extend(cv.polling_component_schema("5s")) .extend(cv.polling_component_schema("5s"))
.extend(spi.spi_device_schema()), .extend(spi.spi_device_schema(cs_pin_required=False)),
validate_st7789v, validate_st7789v,
) )

View File

@ -69,6 +69,8 @@ from esphome.const import (
) )
CONF_PRESET_CHANGE = "preset_change" CONF_PRESET_CHANGE = "preset_change"
CONF_DEFAULT_PRESET = "default_preset"
CONF_ON_BOOT_RESTORE_FROM = "on_boot_restore_from"
CODEOWNERS = ["@kbx81"] CODEOWNERS = ["@kbx81"]
@ -80,6 +82,13 @@ ThermostatClimate = thermostat_ns.class_(
ThermostatClimateTargetTempConfig = thermostat_ns.struct( ThermostatClimateTargetTempConfig = thermostat_ns.struct(
"ThermostatClimateTargetTempConfig" "ThermostatClimateTargetTempConfig"
) )
OnBootRestoreFrom = thermostat_ns.enum("OnBootRestoreFrom")
ON_BOOT_RESTORE_FROM = {
"MEMORY": OnBootRestoreFrom.MEMORY,
"DEFAULT_PRESET": OnBootRestoreFrom.DEFAULT_PRESET,
}
validate_on_boot_restore_from = cv.enum(ON_BOOT_RESTORE_FROM, upper=True)
ClimateMode = climate_ns.enum("ClimateMode") ClimateMode = climate_ns.enum("ClimateMode")
CLIMATE_MODES = { CLIMATE_MODES = {
"OFF": ClimateMode.CLIMATE_MODE_OFF, "OFF": ClimateMode.CLIMATE_MODE_OFF,
@ -125,6 +134,17 @@ def validate_temperature_preset(preset, root_config, name, requirements):
) )
def generate_comparable_preset(config, name):
comparable_preset = f"{CONF_PRESET}:\n" f" - {CONF_NAME}: {name}\n"
if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config:
comparable_preset += f" {CONF_DEFAULT_TARGET_TEMPERATURE_LOW}: {config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]}\n"
if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config:
comparable_preset += f" {CONF_DEFAULT_TARGET_TEMPERATURE_HIGH}: {config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]}\n"
return comparable_preset
def validate_thermostat(config): def validate_thermostat(config):
# verify corresponding action(s) exist(s) for any defined climate mode or action # verify corresponding action(s) exist(s) for any defined climate mode or action
requirements = { requirements = {
@ -277,13 +297,32 @@ def validate_thermostat(config):
CONF_DEFAULT_TARGET_TEMPERATURE_LOW: [CONF_HEAT_ACTION], CONF_DEFAULT_TARGET_TEMPERATURE_LOW: [CONF_HEAT_ACTION],
} }
# Validate temperature requirements for default configuraation # Legacy high/low configs
validate_temperature_preset(config, config, "default", requirements) if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config:
comparable_preset = generate_comparable_preset(config, "Your new preset")
# Validate temperature requirements for away configuration raise cv.Invalid(
f"{CONF_DEFAULT_TARGET_TEMPERATURE_LOW} is no longer valid. Please switch to using a preset for an equivalent experience.\nEquivalent configuration:\n\n"
f"{comparable_preset}"
)
if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config:
comparable_preset = generate_comparable_preset(config, "Your new preset")
raise cv.Invalid(
f"{CONF_DEFAULT_TARGET_TEMPERATURE_HIGH} is no longer valid. Please switch to using a preset for an equivalent experience.\nEquivalent configuration:\n\n"
f"{comparable_preset}"
)
# Legacy away mode - raise an error instructing the user to switch to presets
if CONF_AWAY_CONFIG in config: if CONF_AWAY_CONFIG in config:
away = config[CONF_AWAY_CONFIG] comparable_preset = generate_comparable_preset(config[CONF_AWAY_CONFIG], "Away")
validate_temperature_preset(away, config, "away", requirements)
raise cv.Invalid(
f"{CONF_AWAY_CONFIG} is no longer valid. Please switch to using a preset named "
"Away"
" for an equivalent experience.\nEquivalent configuration:\n\n"
f"{comparable_preset}"
)
# Validate temperature requirements for presets # Validate temperature requirements for presets
if CONF_PRESET in config: if CONF_PRESET in config:
@ -292,7 +331,12 @@ def validate_thermostat(config):
preset_config, config, preset_config[CONF_NAME], requirements preset_config, config, preset_config[CONF_NAME], requirements
) )
# Verify default climate mode is valid given above configuration # Warn about using the removed CONF_DEFAULT_MODE and advise users
if CONF_DEFAULT_MODE in config and config[CONF_DEFAULT_MODE] is not None:
raise cv.Invalid(
f"{CONF_DEFAULT_MODE} is no longer valid. Please switch to using presets and specify a {CONF_DEFAULT_PRESET}."
)
default_mode = config[CONF_DEFAULT_MODE] default_mode = config[CONF_DEFAULT_MODE]
requirements = { requirements = {
"HEAT_COOL": [CONF_COOL_ACTION, CONF_HEAT_ACTION], "HEAT_COOL": [CONF_COOL_ACTION, CONF_HEAT_ACTION],
@ -403,6 +447,38 @@ def validate_thermostat(config):
f"{CONF_SWING_MODE} is set to {swing_mode} for {preset_config[CONF_NAME]} but {req} is not present in the configuration" f"{CONF_SWING_MODE} is set to {swing_mode} for {preset_config[CONF_NAME]} but {req} is not present in the configuration"
) )
# If a default preset is requested then ensure that preset is defined
if CONF_DEFAULT_PRESET in config:
default_preset = config[CONF_DEFAULT_PRESET]
if CONF_PRESET not in config:
raise cv.Invalid(
f"{CONF_DEFAULT_PRESET} is specified but no presets are defined"
)
presets = config[CONF_PRESET]
found_preset = False
for preset in presets:
if preset[CONF_NAME] == default_preset:
found_preset = True
break
if found_preset is False:
raise cv.Invalid(
f"{CONF_DEFAULT_PRESET} set to '{default_preset}' but no such preset has been defined. Available presets: {[preset[CONF_NAME] for preset in presets]}"
)
# If restoring default preset on boot is true then ensure we have a default preset
if (
CONF_ON_BOOT_RESTORE_FROM in config
and config[CONF_ON_BOOT_RESTORE_FROM] is OnBootRestoreFrom.DEFAULT_PRESET
):
if CONF_DEFAULT_PRESET not in config:
raise cv.Invalid(
f"{CONF_DEFAULT_PRESET} must be defined to use {CONF_ON_BOOT_RESTORE_FROM} in DEFAULT_PRESET mode"
)
if config[CONF_FAN_WITH_COOLING] is True and CONF_FAN_ONLY_ACTION not in config: if config[CONF_FAN_WITH_COOLING] is True and CONF_FAN_ONLY_ACTION not in config:
raise cv.Invalid( raise cv.Invalid(
f"{CONF_FAN_ONLY_ACTION} must be defined to use {CONF_FAN_WITH_COOLING}" f"{CONF_FAN_ONLY_ACTION} must be defined to use {CONF_FAN_WITH_COOLING}"
@ -502,9 +578,8 @@ CONFIG_SCHEMA = cv.All(
cv.Optional( cv.Optional(
CONF_TARGET_TEMPERATURE_CHANGE_ACTION CONF_TARGET_TEMPERATURE_CHANGE_ACTION
): automation.validate_automation(single=True), ): automation.validate_automation(single=True),
cv.Optional(CONF_DEFAULT_MODE, default="OFF"): cv.templatable( cv.Optional(CONF_DEFAULT_MODE, default=None): cv.valid,
validate_climate_mode cv.Optional(CONF_DEFAULT_PRESET): cv.templatable(cv.string),
),
cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
cv.Optional( cv.Optional(
@ -542,6 +617,7 @@ CONFIG_SCHEMA = cv.All(
} }
), ),
cv.Optional(CONF_PRESET): cv.ensure_list(PRESET_CONFIG_SCHEMA), cv.Optional(CONF_PRESET): cv.ensure_list(PRESET_CONFIG_SCHEMA),
cv.Optional(CONF_ON_BOOT_RESTORE_FROM): validate_on_boot_restore_from,
cv.Optional(CONF_PRESET_CHANGE): automation.validate_automation( cv.Optional(CONF_PRESET_CHANGE): automation.validate_automation(
single=True single=True
), ),
@ -564,9 +640,10 @@ async def to_code(config):
CONF_COOL_ACTION in config CONF_COOL_ACTION in config
or (config[CONF_FAN_ONLY_COOLING] and CONF_FAN_ONLY_ACTION in config) or (config[CONF_FAN_ONLY_COOLING] and CONF_FAN_ONLY_ACTION in config)
) )
if two_points_available:
cg.add(var.set_supports_two_points(True))
sens = await cg.get_variable(config[CONF_SENSOR]) sens = await cg.get_variable(config[CONF_SENSOR])
cg.add(var.set_default_mode(config[CONF_DEFAULT_MODE]))
cg.add( cg.add(
var.set_set_point_minimum_differential( var.set_set_point_minimum_differential(
config[CONF_SET_POINT_MINIMUM_DIFFERENTIAL] config[CONF_SET_POINT_MINIMUM_DIFFERENTIAL]
@ -579,23 +656,6 @@ async def to_code(config):
cg.add(var.set_heat_deadband(config[CONF_HEAT_DEADBAND])) cg.add(var.set_heat_deadband(config[CONF_HEAT_DEADBAND]))
cg.add(var.set_heat_overrun(config[CONF_HEAT_OVERRUN])) cg.add(var.set_heat_overrun(config[CONF_HEAT_OVERRUN]))
if two_points_available is True:
cg.add(var.set_supports_two_points(True))
normal_config = ThermostatClimateTargetTempConfig(
config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],
)
elif CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config:
cg.add(var.set_supports_two_points(False))
normal_config = ThermostatClimateTargetTempConfig(
config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]
)
elif CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config:
cg.add(var.set_supports_two_points(False))
normal_config = ThermostatClimateTargetTempConfig(
config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]
)
if CONF_MAX_COOLING_RUN_TIME in config: if CONF_MAX_COOLING_RUN_TIME in config:
cg.add( cg.add(
var.set_cooling_maximum_run_time_in_sec(config[CONF_MAX_COOLING_RUN_TIME]) var.set_cooling_maximum_run_time_in_sec(config[CONF_MAX_COOLING_RUN_TIME])
@ -661,7 +721,6 @@ async def to_code(config):
cg.add(var.set_supports_fan_with_heating(config[CONF_FAN_WITH_HEATING])) cg.add(var.set_supports_fan_with_heating(config[CONF_FAN_WITH_HEATING]))
cg.add(var.set_use_startup_delay(config[CONF_STARTUP_DELAY])) cg.add(var.set_use_startup_delay(config[CONF_STARTUP_DELAY]))
cg.add(var.set_preset_config(ClimatePreset.CLIMATE_PRESET_HOME, normal_config))
await automation.build_automation( await automation.build_automation(
var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION] var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION]
@ -808,27 +867,8 @@ async def to_code(config):
config[CONF_TARGET_TEMPERATURE_CHANGE_ACTION], config[CONF_TARGET_TEMPERATURE_CHANGE_ACTION],
) )
if CONF_AWAY_CONFIG in config:
away = config[CONF_AWAY_CONFIG]
if two_points_available is True:
away_config = ThermostatClimateTargetTempConfig(
away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],
)
elif CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in away:
away_config = ThermostatClimateTargetTempConfig(
away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]
)
elif CONF_DEFAULT_TARGET_TEMPERATURE_LOW in away:
away_config = ThermostatClimateTargetTempConfig(
away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]
)
cg.add(var.set_preset_config(ClimatePreset.CLIMATE_PRESET_AWAY, away_config))
if CONF_PRESET in config: if CONF_PRESET in config:
for preset_config in config[CONF_PRESET]: for preset_config in config[CONF_PRESET]:
name = preset_config[CONF_NAME] name = preset_config[CONF_NAME]
standard_preset = None standard_preset = None
if name.upper() in climate.CLIMATE_PRESETS: if name.upper() in climate.CLIMATE_PRESETS:
@ -872,6 +912,19 @@ async def to_code(config):
else: else:
cg.add(var.set_custom_preset_config(name, preset_target_variable)) cg.add(var.set_custom_preset_config(name, preset_target_variable))
if CONF_DEFAULT_PRESET in config:
default_preset_name = config[CONF_DEFAULT_PRESET]
# if the name is a built in preset use the appropriate naming format
if default_preset_name.upper() in climate.CLIMATE_PRESETS:
climate_preset = climate.CLIMATE_PRESETS[default_preset_name.upper()]
cg.add(var.set_default_preset(climate_preset))
else:
cg.add(var.set_default_preset(default_preset_name))
if CONF_ON_BOOT_RESTORE_FROM in config:
cg.add(var.set_on_boot_restore_from(config[CONF_ON_BOOT_RESTORE_FROM]))
if CONF_PRESET_CHANGE in config: if CONF_PRESET_CHANGE in config:
await automation.build_automation( await automation.build_automation(
var.get_preset_change_trigger(), [], config[CONF_PRESET_CHANGE] var.get_preset_change_trigger(), [], config[CONF_PRESET_CHANGE]

View File

@ -25,15 +25,27 @@ void ThermostatClimate::setup() {
this->publish_state(); this->publish_state();
}); });
this->current_temperature = this->sensor_->state; this->current_temperature = this->sensor_->state;
// restore all climate data, if possible
auto restore = this->restore_state_(); auto use_default_preset = true;
if (restore.has_value()) {
restore->to_call(this).perform(); if (this->on_boot_restore_from_ == thermostat::OnBootRestoreFrom::MEMORY) {
} else { // restore all climate data, if possible
// restore from defaults, change_away handles temps for us auto restore = this->restore_state_();
this->mode = this->default_mode_; if (restore.has_value()) {
this->change_preset_(climate::CLIMATE_PRESET_HOME); use_default_preset = false;
restore->to_call(this).perform();
}
} }
// Either we failed to restore state or the user has requested we always apply the default preset
if (use_default_preset) {
if (this->default_preset_ != climate::ClimatePreset::CLIMATE_PRESET_NONE) {
this->change_preset_(this->default_preset_);
} else if (!this->default_custom_preset_.empty()) {
this->change_custom_preset_(this->default_custom_preset_);
}
}
// refresh the climate action based on the restored settings, we'll publish_state() later // refresh the climate action based on the restored settings, we'll publish_state() later
this->switch_to_action_(this->compute_action_(), false); this->switch_to_action_(this->compute_action_(), false);
this->switch_to_supplemental_action_(this->compute_supplemental_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_());
@ -923,9 +935,9 @@ bool ThermostatClimate::supplemental_heating_required_() {
(this->supplemental_action_ == climate::CLIMATE_ACTION_HEATING)); (this->supplemental_action_ == climate::CLIMATE_ACTION_HEATING));
} }
void ThermostatClimate::dump_preset_config_(const std::string &preset, void ThermostatClimate::dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config,
const ThermostatClimateTargetTempConfig &config) { bool is_default_preset) {
const auto *preset_name = preset.c_str(); ESP_LOGCONFIG(TAG, " %s Is Default: %s", preset_name, YESNO(is_default_preset));
if (this->supports_heat_) { if (this->supports_heat_) {
if (this->supports_two_points_) { if (this->supports_two_points_) {
@ -1061,7 +1073,15 @@ ThermostatClimate::ThermostatClimate()
temperature_change_trigger_(new Trigger<>()), temperature_change_trigger_(new Trigger<>()),
preset_change_trigger_(new Trigger<>()) {} preset_change_trigger_(new Trigger<>()) {}
void ThermostatClimate::set_default_mode(climate::ClimateMode default_mode) { this->default_mode_ = default_mode; } void ThermostatClimate::set_default_preset(const std::string &custom_preset) {
this->default_custom_preset_ = custom_preset;
}
void ThermostatClimate::set_default_preset(climate::ClimatePreset preset) { this->default_preset_ = preset; }
void ThermostatClimate::set_on_boot_restore_from(thermostat::OnBootRestoreFrom on_boot_restore_from) {
this->on_boot_restore_from_ = on_boot_restore_from;
}
void ThermostatClimate::set_set_point_minimum_differential(float differential) { void ThermostatClimate::set_set_point_minimum_differential(float differential) {
this->set_point_minimum_differential_ = differential; this->set_point_minimum_differential_ = differential;
} }
@ -1213,8 +1233,9 @@ Trigger<> *ThermostatClimate::get_preset_change_trigger() const { return this->p
void ThermostatClimate::dump_config() { void ThermostatClimate::dump_config() {
LOG_CLIMATE("", "Thermostat", this); LOG_CLIMATE("", "Thermostat", this);
if (this->supports_two_points_) if (this->supports_two_points_) {
ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_); ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_);
}
ESP_LOGCONFIG(TAG, " Start-up Delay Enabled: %s", YESNO(this->use_startup_delay_)); ESP_LOGCONFIG(TAG, " Start-up Delay Enabled: %s", YESNO(this->use_startup_delay_));
if (this->supports_cool_) { if (this->supports_cool_) {
ESP_LOGCONFIG(TAG, " Cooling Parameters:"); ESP_LOGCONFIG(TAG, " Cooling Parameters:");
@ -1284,7 +1305,7 @@ void ThermostatClimate::dump_config() {
const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(it.first)); const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(it.first));
ESP_LOGCONFIG(TAG, " Supports %s: %s", preset_name, YESNO(true)); ESP_LOGCONFIG(TAG, " Supports %s: %s", preset_name, YESNO(true));
this->dump_preset_config_(preset_name, it.second); this->dump_preset_config_(preset_name, it.second, it.first == this->default_preset_);
} }
ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS: "); ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS: ");
@ -1292,8 +1313,10 @@ void ThermostatClimate::dump_config() {
const auto *preset_name = it.first.c_str(); const auto *preset_name = it.first.c_str();
ESP_LOGCONFIG(TAG, " Supports %s: %s", preset_name, YESNO(true)); ESP_LOGCONFIG(TAG, " Supports %s: %s", preset_name, YESNO(true));
this->dump_preset_config_(preset_name, it.second); this->dump_preset_config_(preset_name, it.second, it.first == this->default_custom_preset_);
} }
ESP_LOGCONFIG(TAG, " On boot, restore from: %s",
this->on_boot_restore_from_ == thermostat::DEFAULT_PRESET ? "DEFAULT_PRESET" : "MEMORY");
} }
ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig() = default; ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig() = default;

View File

@ -22,6 +22,7 @@ enum ThermostatClimateTimerIndex : size_t {
TIMER_IDLE_ON = 9, TIMER_IDLE_ON = 9,
}; };
enum OnBootRestoreFrom : size_t { MEMORY = 0, DEFAULT_PRESET = 1 };
struct ThermostatClimateTimer { struct ThermostatClimateTimer {
const std::string name; const std::string name;
bool active; bool active;
@ -57,7 +58,9 @@ class ThermostatClimate : public climate::Climate, public Component {
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
void set_default_mode(climate::ClimateMode default_mode); void set_default_preset(const std::string &custom_preset);
void set_default_preset(climate::ClimatePreset preset);
void set_on_boot_restore_from(thermostat::OnBootRestoreFrom on_boot_restore_from);
void set_set_point_minimum_differential(float differential); void set_set_point_minimum_differential(float differential);
void set_cool_deadband(float deadband); void set_cool_deadband(float deadband);
void set_cool_overrun(float overrun); void set_cool_overrun(float overrun);
@ -225,7 +228,8 @@ class ThermostatClimate : public climate::Climate, public Component {
bool supplemental_cooling_required_(); bool supplemental_cooling_required_();
bool supplemental_heating_required_(); bool supplemental_heating_required_();
void dump_preset_config_(const std::string &preset_name, const ThermostatClimateTargetTempConfig &config); void dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config,
bool is_default_preset);
/// The sensor used for getting the current temperature /// The sensor used for getting the current temperature
sensor::Sensor *sensor_{nullptr}; sensor::Sensor *sensor_{nullptr};
@ -397,7 +401,6 @@ class ThermostatClimate : public climate::Climate, public Component {
/// These are used to determine when a trigger/action needs to be called /// These are used to determine when a trigger/action needs to be called
climate::ClimateAction supplemental_action_{climate::CLIMATE_ACTION_OFF}; climate::ClimateAction supplemental_action_{climate::CLIMATE_ACTION_OFF};
climate::ClimateFanMode prev_fan_mode_{climate::CLIMATE_FAN_ON}; climate::ClimateFanMode prev_fan_mode_{climate::CLIMATE_FAN_ON};
climate::ClimateMode default_mode_{climate::CLIMATE_MODE_OFF};
climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF}; climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF};
climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF}; climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF};
@ -441,6 +444,15 @@ class ThermostatClimate : public climate::Climate, public Component {
std::map<climate::ClimatePreset, ThermostatClimateTargetTempConfig> preset_config_{}; std::map<climate::ClimatePreset, ThermostatClimateTargetTempConfig> preset_config_{};
/// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset") /// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset")
std::map<std::string, ThermostatClimateTargetTempConfig> custom_preset_config_{}; std::map<std::string, ThermostatClimateTargetTempConfig> custom_preset_config_{};
/// Default standard preset to use on start up
climate::ClimatePreset default_preset_{};
/// Default custom preset to use on start up
std::string default_custom_preset_{};
/// If set to DEFAULT_PRESET then the default preset is always used. When MEMORY prior
/// state will attempt to be restored if possible
thermostat::OnBootRestoreFrom on_boot_restore_from_{thermostat::OnBootRestoreFrom::MEMORY};
}; };
} // namespace thermostat } // namespace thermostat

View File

@ -6,6 +6,8 @@ namespace esphome {
namespace time { namespace time {
static const char *const TAG = "automation"; static const char *const TAG = "automation";
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
// there has been a drastic time synchronization
void CronTrigger::add_second(uint8_t second) { this->seconds_[second] = true; } void CronTrigger::add_second(uint8_t second) { this->seconds_[second] = true; }
void CronTrigger::add_minute(uint8_t minute) { this->minutes_[minute] = true; } void CronTrigger::add_minute(uint8_t minute) { this->minutes_[minute] = true; }
@ -23,12 +25,17 @@ void CronTrigger::loop() {
return; return;
if (this->last_check_.has_value()) { if (this->last_check_.has_value()) {
if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > 900) { if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > MAX_TIMESTAMP_DRIFT) {
// We went back in time (a lot), probably caused by time synchronization // We went back in time (a lot), probably caused by time synchronization
ESP_LOGW(TAG, "Time has jumped back!"); ESP_LOGW(TAG, "Time has jumped back!");
} else if (*this->last_check_ >= time) { } else if (*this->last_check_ >= time) {
// already handled this one // already handled this one
return; return;
} else if (time > *this->last_check_ && time.timestamp - this->last_check_->timestamp > MAX_TIMESTAMP_DRIFT) {
// We went ahead in time (a lot), probably caused by time synchronization
ESP_LOGW(TAG, "Time has jumped ahead!");
this->last_check_ = time;
return;
} }
while (true) { while (true) {

View File

@ -23,6 +23,12 @@ class TouchscreenBinarySensor : public binary_sensor::BinarySensor,
this->y_min_ = y_min; this->y_min_ = y_min;
this->y_max_ = y_max; this->y_max_ = y_max;
} }
int16_t get_x_min() { return this->x_min_; }
int16_t get_x_max() { return this->x_max_; }
int16_t get_y_min() { return this->y_min_; }
int16_t get_y_max() { return this->y_max_; }
int16_t get_width() { return this->x_max_ - this->x_min_; }
int16_t get_height() { return this->y_max_ - this->y_min_; }
void set_page(display::DisplayPage *page) { this->page_ = page; } void set_page(display::DisplayPage *page) { this->page_ = page; }

View File

@ -6,7 +6,7 @@ namespace esphome {
namespace web_server { namespace web_server {
const uint8_t INDEX_GZ[] PROGMEM = { const uint8_t INDEX_GZ[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xbd, 0x7d, 0xd9, 0x76, 0xdb, 0xc8, 0x92, 0xe0, 0xf3, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xbd, 0x7d, 0xd9, 0x76, 0xdb, 0xc8, 0x92, 0xe0, 0xf3,
0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x30, 0x4a, 0x6d, 0x03, 0x25, 0x10, 0x22, 0x29, 0xcb, 0x76, 0x81, 0x02, 0x79, 0xe5, 0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x30, 0x4a, 0x6d, 0x03, 0x25, 0x10, 0x22, 0x29, 0xcb, 0x76, 0x81, 0x02, 0x79, 0xe5,
0xa5, 0xae, 0x5d, 0xe5, 0xad, 0x2c, 0xd9, 0x75, 0xab, 0x54, 0x2c, 0x0b, 0x22, 0x93, 0x22, 0xca, 0x20, 0xc0, 0x02, 0xa5, 0xae, 0x5d, 0xe5, 0xad, 0x2c, 0xd9, 0x75, 0xab, 0x54, 0x2c, 0x0b, 0x22, 0x93, 0x22, 0xca, 0x20, 0xc0, 0x02,
0x92, 0x5a, 0x8a, 0x42, 0x9f, 0x7e, 0xea, 0xa7, 0x39, 0x67, 0xd6, 0x87, 0x7e, 0x99, 0xd3, 0xfd, 0x30, 0x1f, 0x31, 0x92, 0x5a, 0x8a, 0x42, 0x9f, 0x7e, 0xea, 0xa7, 0x39, 0x67, 0xd6, 0x87, 0x7e, 0x99, 0xd3, 0xfd, 0x30, 0x1f, 0x31,
@ -524,62 +524,64 @@ const uint8_t INDEX_GZ[] PROGMEM = {
0x5b, 0x06, 0xb9, 0xff, 0xfc, 0x07, 0x47, 0x40, 0x7e, 0x68, 0x06, 0xb9, 0xf7, 0xd8, 0x4a, 0xa0, 0xa3, 0xe9, 0xac, 0x5b, 0x06, 0xb9, 0xff, 0xfc, 0x07, 0x47, 0x40, 0x7e, 0x68, 0x06, 0xb9, 0xf7, 0xd8, 0x4a, 0xa0, 0xa3, 0xe9, 0xac,
0x3d, 0x67, 0xce, 0xe1, 0x8f, 0x6c, 0x88, 0x69, 0xd3, 0xd0, 0xfa, 0x2f, 0xb5, 0x78, 0x50, 0x86, 0x1b, 0x79, 0x8f, 0x3d, 0x67, 0xce, 0xe1, 0x8f, 0x6c, 0x88, 0x69, 0xd3, 0xd0, 0xfa, 0x2f, 0xb5, 0x78, 0x50, 0x86, 0x1b, 0x79, 0x8f,
0x89, 0x9d, 0xcc, 0xf8, 0x15, 0x04, 0xe1, 0xfc, 0x46, 0x82, 0xbc, 0xb8, 0x1d, 0x3d, 0x38, 0xff, 0x63, 0xe9, 0x41, 0x89, 0x9d, 0xcc, 0xf8, 0x15, 0x04, 0xe1, 0xfc, 0x46, 0x82, 0xbc, 0xb8, 0x1d, 0x3d, 0x38, 0xff, 0x63, 0xe9, 0x41,
0x5f, 0xee, 0x57, 0xf4, 0xa8, 0x45, 0xdc, 0x29, 0x62, 0x40, 0x8e, 0xfe, 0x3e, 0xbd, 0x3b, 0xf6, 0x16, 0xc3, 0xb7, 0x5f, 0xee, 0x57, 0xf4, 0xd0, 0x49, 0xe1, 0x85, 0xf1, 0xfb, 0x57, 0x16, 0x79, 0xa2, 0xaf, 0xa8, 0x65, 0x23, 0xca,
0xc2, 0x16, 0xb9, 0x80, 0xef, 0x3e, 0xe7, 0x74, 0x40, 0x64, 0x15, 0x87, 0xb6, 0x0c, 0xc0, 0xcc, 0xf1, 0xdb, 0xb4, 0xf4, 0xf4, 0x91, 0x97, 0xe8, 0x9e, 0x20, 0x54, 0x6e, 0x86, 0xf0, 0x9f, 0xf1, 0x09, 0xb0, 0x2d, 0xfc, 0xd4, 0x9c,
0xea, 0xe5, 0x54, 0xdc, 0x5c, 0x09, 0xe9, 0xda, 0xd9, 0x8e, 0x0e, 0xde, 0x60, 0xa2, 0x77, 0xb8, 0xcc, 0x78, 0x84, 0x1d, 0xc0, 0x4f, 0xaa, 0xb5, 0x19, 0xc6, 0x0a, 0x0a, 0xbb, 0xac, 0x85, 0xf3, 0x29, 0x1c, 0x41, 0x51, 0x84, 0x7d,
0xbf, 0xfc, 0x88, 0xc7, 0x3c, 0xc1, 0x4b, 0xb1, 0xf2, 0x02, 0x19, 0xe6, 0x25, 0x7f, 0x87, 0x39, 0xd5, 0xea, 0x90, 0x7a, 0x77, 0x70, 0x46, 0x91, 0x63, 0xf8, 0xee, 0x73, 0x4e, 0x1d, 0x44, 0xb6, 0x72, 0x68, 0xcb, 0xc0, 0xce, 0x1c,
0x60, 0x86, 0x01, 0x83, 0x57, 0x6c, 0x1c, 0x47, 0x8e, 0xed, 0xcc, 0x61, 0xc7, 0xc2, 0x98, 0xad, 0x5a, 0x42, 0x33, 0xbf, 0x79, 0xab, 0x5e, 0x4e, 0xc5, 0x8d, 0x98, 0x90, 0xae, 0xb3, 0xed, 0xe8, 0xa0, 0x10, 0x26, 0x90, 0x87, 0xcb,
0xe5, 0x32, 0xbb, 0xb6, 0xfa, 0x7d, 0x3b, 0x39, 0x7e, 0xbf, 0x2c, 0x3c, 0x94, 0x01, 0x46, 0x5b, 0x7a, 0x00, 0x30, 0x8c, 0x47, 0xf8, 0x4b, 0x95, 0x78, 0xcc, 0x13, 0xbc, 0x6c, 0x2b, 0x2f, 0xa6, 0x61, 0xbe, 0xf3, 0x77, 0x98, 0xab,
0xbe, 0x2a, 0xc9, 0x51, 0xd8, 0x57, 0x56, 0x83, 0x2d, 0xcc, 0x86, 0x8e, 0xdf, 0x05, 0x37, 0x82, 0x8a, 0xf1, 0x7b, 0xad, 0x30, 0x9e, 0x61, 0x20, 0xe2, 0x15, 0x1b, 0xc7, 0x91, 0x63, 0x3b, 0x73, 0x90, 0x04, 0x30, 0x66, 0xab, 0x96,
0x50, 0x3f, 0x38, 0xad, 0x6d, 0x30, 0x6b, 0x8c, 0x6e, 0x7a, 0xa0, 0xe1, 0x4a, 0x18, 0x49, 0x04, 0x07, 0x1a, 0xa5, 0x28, 0x4d, 0x39, 0xd2, 0xae, 0xad, 0x7e, 0x8f, 0x4f, 0x8e, 0xdf, 0x45, 0x0b, 0x0f, 0x65, 0xe0, 0xd2, 0x96, 0x9e,
0x9e, 0xfe, 0x05, 0x64, 0x55, 0xb8, 0xa8, 0x78, 0x7c, 0x71, 0x20, 0xef, 0x7c, 0xdb, 0x18, 0xb9, 0xa5, 0x88, 0x7d, 0x05, 0x8c, 0xaf, 0x4a, 0x72, 0x54, 0x22, 0x95, 0x35, 0x62, 0x0b, 0x73, 0xa4, 0xe3, 0x77, 0xc1, 0x3d, 0xa1, 0x62,
0xf5, 0xbd, 0xa9, 0x4d, 0x50, 0x17, 0xf4, 0x5b, 0x20, 0xe9, 0xdc, 0x1b, 0x35, 0x02, 0xa6, 0x5c, 0x5b, 0xd2, 0x73, 0xfc, 0xce, 0xd4, 0x0f, 0x4e, 0x6b, 0x1b, 0xcc, 0x25, 0xa3, 0x9b, 0x1e, 0x68, 0xb8, 0x12, 0x9e, 0x12, 0x41, 0x87,
0x08, 0x6d, 0xa1, 0x0f, 0xc6, 0xec, 0x34, 0x1e, 0x49, 0xb1, 0xee, 0x59, 0xf2, 0xaa, 0x48, 0x8b, 0xb0, 0x08, 0x3b, 0x46, 0xa9, 0xa7, 0x7f, 0xb1, 0x59, 0x15, 0x86, 0x2a, 0x1e, 0x5f, 0x1c, 0xc8, 0xbb, 0xe4, 0x36, 0x46, 0x84, 0xe9,
0x9e, 0xf0, 0x9d, 0xe1, 0x05, 0xb5, 0x5a, 0x98, 0x66, 0x76, 0xff, 0x5e, 0x4f, 0x43, 0x52, 0xcf, 0x56, 0xb7, 0xf1, 0x24, 0xa0, 0xfa, 0x8e, 0xd5, 0x26, 0xa8, 0x21, 0xfa, 0xed, 0x92, 0x74, 0x9e, 0x8e, 0x9a, 0x06, 0x53, 0xb9, 0x2d,
0x57, 0x52, 0x1e, 0x82, 0xaf, 0xf6, 0xf7, 0xe1, 0x3d, 0xfc, 0xa5, 0x94, 0xf7, 0x86, 0xb6, 0xeb, 0x93, 0x50, 0xbc, 0xe9, 0x91, 0x84, 0xb6, 0xd0, 0x33, 0x63, 0x76, 0x1a, 0x8f, 0xa4, 0xba, 0xf0, 0x2c, 0x79, 0x05, 0xa5, 0x45, 0x58,
0x57, 0xfd, 0x66, 0x4a, 0x94, 0x08, 0x9b, 0xa0, 0xbf, 0xbc, 0xdb, 0x2a, 0x32, 0xa9, 0xb4, 0xba, 0x3b, 0x95, 0xd2, 0x84, 0x1d, 0x4f, 0xf8, 0xe4, 0xf0, 0x82, 0xda, 0x32, 0x4c, 0x33, 0xbb, 0x7f, 0xaf, 0xa7, 0x21, 0xa9, 0x67, 0xc1,
0x82, 0x67, 0x43, 0x4a, 0x81, 0x00, 0xed, 0xfa, 0x3b, 0x86, 0x28, 0x3c, 0x6d, 0xe1, 0xcf, 0x9a, 0x30, 0xbc, 0x0f, 0xdb, 0xf8, 0xab, 0x2e, 0x0f, 0xc1, 0x07, 0xfc, 0xfb, 0xf0, 0x1e, 0xfe, 0xb2, 0xcb, 0x7b, 0x43, 0xdb, 0xf5, 0x49,
0x0d, 0x94, 0x34, 0x7c, 0x09, 0xcd, 0xb7, 0x85, 0xe0, 0x85, 0x7e, 0x3f, 0x92, 0xa8, 0x12, 0x62, 0xaa, 0xce, 0x31, 0xd8, 0xde, 0xab, 0x7e, 0xe3, 0x25, 0x4a, 0x9a, 0x4d, 0xd0, 0x8b, 0xde, 0x6d, 0x15, 0xa4, 0x54, 0x86, 0xdd, 0x9d,
0x6b, 0x0e, 0x91, 0x44, 0x8e, 0x80, 0xed, 0x19, 0xf1, 0x26, 0xc1, 0xae, 0x32, 0x9a, 0xf2, 0x14, 0xfa, 0x3a, 0xfa, 0x4a, 0x19, 0xc2, 0xb3, 0x21, 0xfd, 0x40, 0x30, 0x77, 0xfd, 0x1d, 0x43, 0xc4, 0x9e, 0xb6, 0xf0, 0x67, 0x4d, 0xc8,
0x33, 0xce, 0xeb, 0xea, 0xbc, 0xda, 0xce, 0x59, 0x33, 0x05, 0x32, 0x7c, 0xe3, 0xa0, 0x8a, 0xae, 0x2e, 0x88, 0xcf, 0xde, 0x87, 0x06, 0x4a, 0xca, 0xbe, 0x84, 0xe6, 0xdb, 0x42, 0xa0, 0x43, 0xbf, 0x1f, 0x49, 0x04, 0x0a, 0xf1, 0x57,
0x99, 0x89, 0x6d, 0x5c, 0x7d, 0xf0, 0x6d, 0x4d, 0xf6, 0xad, 0xb9, 0x29, 0x58, 0xc5, 0x34, 0xb4, 0x2f, 0x30, 0x65, 0xe7, 0x98, 0x35, 0x87, 0x53, 0x22, 0xf7, 0xc0, 0xf6, 0x8c, 0x38, 0x96, 0x60, 0x57, 0x19, 0xa5, 0x79, 0x0a, 0x7d,
0x06, 0x7f, 0x56, 0xc5, 0xea, 0x41, 0x32, 0x94, 0x9f, 0x44, 0xf8, 0xdb, 0x58, 0xe8, 0x47, 0x59, 0x6d, 0x40, 0x4e, 0x1d, 0xfd, 0x79, 0xe8, 0x75, 0x75, 0x5e, 0x6d, 0xd3, 0xac, 0x99, 0x02, 0x19, 0xbe, 0x71, 0x00, 0x46, 0x57, 0x22,
0xdf, 0xab, 0x24, 0x48, 0x5f, 0x8c, 0xcb, 0x26, 0x12, 0x60, 0x2f, 0xe0, 0x2f, 0xf7, 0xab, 0xae, 0x4a, 0xc8, 0x3b, 0xc4, 0x67, 0xd2, 0x84, 0x78, 0xa8, 0x3e, 0x24, 0xb7, 0x26, 0xab, 0xd7, 0xdc, 0x14, 0xac, 0x62, 0x1a, 0xda, 0x17,
0x90, 0x98, 0x53, 0x30, 0x8e, 0x73, 0xba, 0x5a, 0xab, 0xf0, 0xaf, 0x45, 0x34, 0x2b, 0x52, 0xd3, 0xae, 0x64, 0xc5, 0x98, 0x8a, 0x83, 0x3f, 0xab, 0x62, 0xf5, 0x20, 0x19, 0xca, 0x4f, 0x22, 0xfc, 0x2d, 0x2f, 0xf4, 0xa3, 0xac, 0x36,
0xc0, 0xc6, 0x22, 0x3b, 0x90, 0xc9, 0x68, 0xe6, 0x07, 0x9b, 0xcd, 0xbb, 0x8f, 0x63, 0x91, 0x87, 0x86, 0x1f, 0xb4, 0x20, 0xa7, 0xef, 0x60, 0x12, 0xa4, 0x2f, 0xc6, 0x65, 0x13, 0x09, 0xb0, 0x43, 0xf0, 0x97, 0x06, 0x56, 0x57, 0x30,
0xb7, 0x05, 0x91, 0x6d, 0x10, 0x63, 0x57, 0xe2, 0x44, 0xc6, 0x0d, 0x5e, 0x19, 0xac, 0x7e, 0x43, 0x91, 0xb9, 0xe1, 0xe4, 0xdd, 0x4a, 0xcc, 0x55, 0x18, 0xc7, 0x39, 0x5d, 0xd9, 0x55, 0xf8, 0xd7, 0x22, 0xa5, 0x15, 0xa9, 0x69, 0x57,
0x6d, 0x73, 0xb5, 0xf4, 0xb8, 0xb4, 0x0e, 0xae, 0x8c, 0xdf, 0x1d, 0xb3, 0x88, 0xfb, 0x51, 0x4a, 0xb9, 0x49, 0x8e, 0xb2, 0x62, 0x60, 0x63, 0x11, 0x88, 0x1a, 0x91, 0xe4, 0x66, 0x7e, 0x08, 0xda, 0xbc, 0x53, 0x39, 0x16, 0xf9, 0x6d,
0x21, 0x16, 0xbc, 0x0e, 0xdb, 0x76, 0x4b, 0x90, 0x3c, 0xc6, 0xaf, 0x70, 0x12, 0xa4, 0xf7, 0xa1, 0xb0, 0x4a, 0xd8, 0xf8, 0xa1, 0x7c, 0x5b, 0x10, 0xd9, 0x06, 0xf1, 0x78, 0x25, 0x4e, 0x64, 0x34, 0xe1, 0x55, 0xc4, 0xea, 0x37, 0x1f,
0xda, 0x9d, 0x76, 0xfb, 0x6f, 0x0e, 0xf6, 0x2c, 0xb1, 0x9b, 0x77, 0xb7, 0xe0, 0x75, 0x97, 0xdc, 0x61, 0x91, 0x9f, 0x99, 0x1b, 0xde, 0x36, 0x57, 0x4b, 0x8f, 0x4b, 0xeb, 0xe0, 0xca, 0xb8, 0xe0, 0x31, 0x8b, 0xb8, 0x1f, 0xa5, 0x94,
0x11, 0x8a, 0xfc, 0x0c, 0x4b, 0x24, 0x74, 0x85, 0xf6, 0x96, 0x40, 0xd3, 0xb6, 0x58, 0x3a, 0x12, 0x31, 0xbc, 0x19, 0xf3, 0xe4, 0x18, 0x62, 0xc1, 0xeb, 0xb0, 0x6d, 0xb7, 0x04, 0xc9, 0x63, 0xfc, 0x6a, 0x28, 0x41, 0x7a, 0x1f, 0x0a,
0xb8, 0x0b, 0x31, 0x7e, 0xd4, 0x6b, 0x0b, 0xbb, 0xb5, 0x70, 0xa5, 0x6d, 0x95, 0xe1, 0xa2, 0x0c, 0x04, 0x9e, 0xaa, 0xab, 0x44, 0xb0, 0xdd, 0x69, 0xb7, 0xff, 0xe6, 0x60, 0xcf, 0x12, 0xbb, 0x79, 0x77, 0x0b, 0x5e, 0x77, 0xc9, 0xcd,
0x88, 0x1f, 0xa8, 0x75, 0xa6, 0x92, 0x5d, 0xe4, 0x50, 0x3a, 0x27, 0x75, 0xb5, 0x75, 0xb1, 0x38, 0x9e, 0x81, 0x1c, 0x16, 0x79, 0x1f, 0xa1, 0xc8, 0xfb, 0xb0, 0x44, 0xa2, 0x58, 0x68, 0x6f, 0x09, 0x34, 0x6d, 0x8b, 0xa5, 0x23, 0x11,
0x52, 0x09, 0x2a, 0xef, 0x65, 0x87, 0x5d, 0x9a, 0x0a, 0x93, 0x62, 0x57, 0x23, 0x92, 0xd3, 0x4e, 0x7f, 0x37, 0x92, 0x1b, 0x9c, 0x81, 0x1b, 0x12, 0xe3, 0xc7, 0xc2, 0xb6, 0xb0, 0x5b, 0x0b, 0x57, 0xda, 0x56, 0x99, 0x33, 0xca, 0xf0,
0xf6, 0x0e, 0xee, 0xdd, 0x02, 0x36, 0x2f, 0xa8, 0x39, 0x34, 0x2a, 0xfc, 0x38, 0xdb, 0x3a, 0x63, 0xc7, 0xad, 0x68, 0xe0, 0xa9, 0x8a, 0x24, 0x82, 0xb9, 0xc0, 0x54, 0x12, 0x8d, 0x1c, 0x4a, 0xe7, 0xba, 0xae, 0xb6, 0x2e, 0x16, 0xc7,
0x1e, 0x57, 0xe1, 0x3f, 0xd4, 0x7e, 0xfd, 0x5d, 0xa5, 0x08, 0x65, 0x9a, 0xa5, 0x7c, 0x8c, 0x8c, 0x2c, 0x0e, 0x24, 0x33, 0x90, 0x43, 0x2a, 0xf1, 0xe5, 0xbd, 0xec, 0xb0, 0x4b, 0x53, 0x61, 0xb2, 0xed, 0x6a, 0xa4, 0x73, 0xda, 0xe9,
0x1c, 0x31, 0x68, 0x29, 0x63, 0x8b, 0x64, 0x34, 0x02, 0xf1, 0x01, 0x56, 0xe2, 0x5f, 0x15, 0x83, 0x94, 0x9a, 0xa0, 0xef, 0x46, 0xd2, 0x8e, 0xc2, 0xbd, 0x5b, 0xc0, 0xe6, 0x05, 0xf5, 0x89, 0xc6, 0x8a, 0x1f, 0x67, 0x5b, 0x67, 0xec,
0xb4, 0xfb, 0x7f, 0xfd, 0x5f, 0xff, 0x5b, 0x86, 0x15, 0x81, 0xac, 0x00, 0x16, 0xa6, 0xc1, 0x54, 0x27, 0x8c, 0xec, 0xb8, 0x15, 0xcd, 0xe3, 0x2a, 0xac, 0x88, 0x5a, 0xb5, 0xbf, 0xab, 0x14, 0xac, 0x4c, 0xdf, 0x94, 0x8f, 0x91, 0x91,
0x1c, 0x1c, 0xd1, 0x78, 0xdc, 0x9a, 0x46, 0xc9, 0x04, 0x20, 0x28, 0x98, 0xb8, 0xca, 0x24, 0xeb, 0x81, 0x0b, 0x24, 0x1d, 0x82, 0x84, 0x23, 0x06, 0x2d, 0x65, 0xcc, 0x92, 0x8c, 0x51, 0x20, 0x3e, 0xc0, 0x4a, 0xfc, 0xab, 0x62, 0x9b,
0x58, 0xe6, 0xe1, 0xbc, 0x04, 0xaf, 0x5e, 0x84, 0x2b, 0xf6, 0xbb, 0xf2, 0x56, 0x55, 0xbe, 0x30, 0x31, 0xb4, 0x91, 0x52, 0x13, 0x94, 0x76, 0xff, 0xaf, 0xff, 0xeb, 0x7f, 0xcb, 0x70, 0x25, 0x90, 0x15, 0xc0, 0xc2, 0xf4, 0x9a, 0xea,
0xc5, 0x6a, 0xf0, 0x5c, 0x2d, 0x93, 0x55, 0xfd, 0x82, 0x24, 0x29, 0x3c, 0x58, 0x2d, 0x8d, 0x15, 0x5a, 0xea, 0x83, 0xe4, 0x92, 0x9d, 0x83, 0x83, 0x1b, 0x8f, 0x5b, 0xd3, 0x28, 0x99, 0x00, 0x04, 0x05, 0x13, 0xda, 0x50, 0xd6, 0x03,
0x90, 0x7f, 0xfb, 0xe7, 0xff, 0xfc, 0xdf, 0xd5, 0x2b, 0x9e, 0x6f, 0xfc, 0xf5, 0x9f, 0xfe, 0xe1, 0xff, 0xfe, 0x9f, 0x17, 0x48, 0xb0, 0xcc, 0x43, 0x7f, 0x09, 0x5e, 0xbd, 0x08, 0x57, 0xec, 0x77, 0xe5, 0xc3, 0xaa, 0x3c, 0x64, 0x62,
0xff, 0x82, 0x59, 0xc2, 0xf2, 0x0c, 0x84, 0xb6, 0x92, 0x55, 0x1d, 0x80, 0x88, 0x3d, 0x65, 0x55, 0x0e, 0x47, 0x3d, 0x68, 0x23, 0x3b, 0xd6, 0xe0, 0xb9, 0x5a, 0x86, 0xac, 0xfa, 0xc5, 0x4b, 0x52, 0x78, 0xb0, 0x5a, 0x7a, 0x2c, 0xb4,
0xdd, 0x75, 0x9f, 0x26, 0x24, 0xde, 0x94, 0xd0, 0x11, 0x5f, 0x53, 0x7a, 0x34, 0x51, 0xed, 0x1a, 0xf2, 0xc1, 0x52, 0xd4, 0x07, 0x2c, 0xff, 0xf6, 0xcf, 0xff, 0xf9, 0xbf, 0xab, 0x57, 0x3c, 0x37, 0xf9, 0xeb, 0x3f, 0xfd, 0xc3, 0xff,
0x5a, 0x74, 0xac, 0x6f, 0xef, 0xb4, 0xed, 0x6a, 0x79, 0xfb, 0x46, 0xdf, 0x2d, 0x5c, 0x98, 0x5b, 0x65, 0xe0, 0xf8, 0xfd, 0x3f, 0xff, 0x05, 0xb3, 0x8f, 0xe5, 0xd9, 0x0a, 0x6d, 0x25, 0xab, 0x3a, 0x58, 0x11, 0x7b, 0xca, 0xaa, 0x1c,
0x7a, 0xd9, 0x96, 0x2a, 0x8c, 0x85, 0x25, 0x65, 0x55, 0x6e, 0x61, 0x7c, 0x79, 0x89, 0xaf, 0x41, 0xd7, 0x28, 0xa6, 0x99, 0x7a, 0x1a, 0xed, 0x3e, 0x4d, 0x48, 0xbc, 0x29, 0xa1, 0x23, 0xbe, 0xa6, 0xb4, 0x6b, 0xa2, 0xda, 0x35, 0xe4,
0x55, 0xae, 0xf5, 0xe9, 0xfd, 0xb2, 0x00, 0x44, 0x27, 0xb8, 0x34, 0x22, 0x58, 0x46, 0x67, 0xa7, 0x2d, 0xb4, 0x4e, 0x83, 0xa5, 0xb4, 0x28, 0x5d, 0xc0, 0xde, 0x69, 0xdb, 0xd5, 0xf2, 0xf6, 0x8d, 0xbe, 0x5b, 0xb8, 0x30, 0xb7, 0xca,
0x92, 0x8b, 0x92, 0x46, 0x11, 0xde, 0xcc, 0xfd, 0x47, 0x7f, 0x57, 0xfe, 0x69, 0x86, 0x56, 0x81, 0xe5, 0xcc, 0xa2, 0xec, 0xf1, 0xf5, 0xb2, 0x2d, 0x55, 0x78, 0x0c, 0x4b, 0xca, 0xaa, 0xdc, 0xc2, 0xb8, 0xf5, 0x12, 0x5f, 0x83, 0xae,
0x73, 0xe9, 0xe3, 0x3c, 0x68, 0xb7, 0xe7, 0xe7, 0xee, 0xb2, 0x9a, 0xc1, 0xbb, 0x6a, 0x32, 0x0a, 0xb0, 0x99, 0x03, 0x51, 0x4c, 0xab, 0x5c, 0xeb, 0xd3, 0xfb, 0x65, 0x01, 0x88, 0x4e, 0x70, 0x69, 0x44, 0x10, 0x8e, 0xce, 0x64, 0x5b,
0xd2, 0xa1, 0xab, 0x8e, 0xe5, 0x81, 0x59, 0xdf, 0xc6, 0xd0, 0x4f, 0x59, 0x7e, 0xb9, 0xa4, 0x70, 0x52, 0xfc, 0x1b, 0x68, 0xc0, 0x24, 0x17, 0x25, 0x8d, 0x22, 0xbc, 0xa4, 0xfb, 0x8f, 0xfe, 0xae, 0xfc, 0xd3, 0x0c, 0xad, 0x02, 0xcb,
0x1e, 0x8e, 0xca, 0xc8, 0x1b, 0x94, 0x18, 0x58, 0x2c, 0x8d, 0x5e, 0x5d, 0xd1, 0x6b, 0xda, 0x59, 0xcd, 0x4d, 0x31, 0x99, 0x45, 0xe7, 0xd2, 0x77, 0x7a, 0xd0, 0x6e, 0xcf, 0xcf, 0xdd, 0x65, 0x35, 0x83, 0x77, 0xd5, 0x64, 0x14, 0xb8,
0x0f, 0x77, 0xcd, 0x63, 0xd9, 0xfb, 0x78, 0xd0, 0x3a, 0xed, 0x78, 0xd3, 0xee, 0x52, 0x0f, 0xcf, 0x79, 0x36, 0x33, 0x33, 0x07, 0xa4, 0xc3, 0x5c, 0x1d, 0x23, 0x04, 0x77, 0xa1, 0x8d, 0x21, 0xa5, 0xb2, 0xfc, 0x72, 0x49, 0x61, 0xaa,
0x4f, 0x73, 0x59, 0xc4, 0x46, 0x6c, 0xa2, 0x22, 0x96, 0xb2, 0x5e, 0x9c, 0xd4, 0x96, 0x5f, 0xe0, 0x76, 0x03, 0xda, 0xf8, 0x37, 0x3c, 0x74, 0x95, 0x11, 0x3d, 0x28, 0x31, 0xb0, 0x58, 0x1a, 0xbd, 0xba, 0xa2, 0xd7, 0xb4, 0xb3, 0x9a,
0x66, 0x11, 0x0f, 0x88, 0x69, 0x7b, 0xe6, 0x79, 0x6f, 0x84, 0x27, 0xe9, 0xd9, 0xd2, 0x98, 0xab, 0x27, 0x9a, 0x62, 0xf3, 0x62, 0x1e, 0x1a, 0x9b, 0xc7, 0xbd, 0xf7, 0xf1, 0x00, 0x77, 0xda, 0xf1, 0xa6, 0xdd, 0xa5, 0x1e, 0x9e, 0xf3,
0x5c, 0xb0, 0x9e, 0xf7, 0x53, 0xfa, 0xd4, 0xdd, 0x1c, 0x4a, 0x84, 0x15, 0x5e, 0xc8, 0x63, 0xd4, 0x77, 0x35, 0x7f, 0x6c, 0x66, 0x9e, 0x12, 0xb3, 0x88, 0x8d, 0xd8, 0x44, 0x45, 0x42, 0x65, 0xbd, 0x38, 0x01, 0x2e, 0xbf, 0xc0, 0xed,
0x5c, 0x8a, 0x62, 0x70, 0x81, 0xd7, 0xd6, 0x0b, 0xb5, 0x28, 0x6a, 0x5f, 0x80, 0xb5, 0x43, 0x60, 0xda, 0xcd, 0x56, 0x06, 0xb4, 0xcd, 0x22, 0x1e, 0x10, 0xd3, 0xf6, 0xcc, 0x73, 0xe4, 0x08, 0x4f, 0xe8, 0xb3, 0xa5, 0x31, 0x57, 0x4f,
0x54, 0x88, 0xad, 0xde, 0x85, 0x2f, 0xb4, 0xed, 0x1d, 0xcd, 0xe7, 0xd4, 0xd0, 0x05, 0x6e, 0x24, 0x1b, 0x1a, 0x25, 0x34, 0xc5, 0x78, 0x63, 0x3d, 0x9f, 0xa8, 0xf4, 0xa9, 0xbb, 0x39, 0x94, 0x08, 0x57, 0xbc, 0x90, 0xc7, 0xb3, 0xef,
0x05, 0xa5, 0x08, 0x88, 0x13, 0x79, 0xd9, 0x46, 0xb2, 0xad, 0x78, 0x92, 0x67, 0xf5, 0xf4, 0xfb, 0xb6, 0xff, 0x1f, 0x6a, 0x7e, 0xbe, 0x14, 0xc5, 0xe0, 0x5a, 0xaf, 0xad, 0x17, 0x6a, 0x51, 0xd4, 0xbe, 0x00, 0x6b, 0x87, 0xc0, 0xb4,
0x22, 0x28, 0x4d, 0x5d, 0x85, 0x7b, 0x00, 0x00}; 0x9b, 0xad, 0xa8, 0x10, 0x5b, 0xbd, 0x0b, 0x5f, 0x68, 0x9b, 0x3e, 0x9a, 0xcf, 0xa9, 0xa1, 0x0b, 0xdc, 0x48, 0xb6,
0x39, 0x4a, 0x0a, 0x4a, 0x3d, 0x10, 0x27, 0xfd, 0xb2, 0x8d, 0x64, 0x5b, 0xf1, 0x24, 0x73, 0x00, 0xe8, 0xf7, 0x78,
0xff, 0x3f, 0x32, 0x18, 0x26, 0x95, 0xdd, 0x7b, 0x00, 0x00};
} // namespace web_server } // namespace web_server
} // namespace esphome } // namespace esphome

View File

@ -81,6 +81,7 @@ class WebServerBase : public Component {
return; return;
} }
this->server_ = std::make_shared<AsyncWebServer>(this->port_); this->server_ = std::make_shared<AsyncWebServer>(this->port_);
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
this->server_->begin(); this->server_->begin();
for (auto *handler : this->handlers_) for (auto *handler : this->handlers_)

View File

@ -332,8 +332,7 @@ def manual_ip(config):
) )
def wifi_network(config, static_ip): def wifi_network(config, ap, static_ip):
ap = cg.variable(config[CONF_ID], WiFiAP())
if CONF_SSID in config: if CONF_SSID in config:
cg.add(ap.set_ssid(config[CONF_SSID])) cg.add(ap.set_ssid(config[CONF_SSID]))
if CONF_PASSWORD in config: if CONF_PASSWORD in config:
@ -360,14 +359,21 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
for network in config.get(CONF_NETWORKS, []): def add_sta(ap, network):
ip_config = network.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP)) ip_config = network.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP))
cg.add(var.add_sta(wifi_network(network, ip_config))) cg.add(var.add_sta(wifi_network(network, ap, ip_config)))
for network in config.get(CONF_NETWORKS, []):
cg.with_local_variable(network[CONF_ID], WiFiAP(), add_sta, network)
if CONF_AP in config: if CONF_AP in config:
conf = config[CONF_AP] conf = config[CONF_AP]
ip_config = conf.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP)) ip_config = conf.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP))
cg.add(var.set_ap(wifi_network(conf, ip_config))) cg.with_local_variable(
conf[CONF_ID],
WiFiAP(),
lambda ap: cg.add(var.set_ap(wifi_network(conf, ap, ip_config))),
)
cg.add(var.set_ap_timeout(conf[CONF_AP_TIMEOUT])) cg.add(var.set_ap_timeout(conf[CONF_AP_TIMEOUT]))
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))

View File

@ -1,129 +1,5 @@
import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation
from esphome import pins
from esphome.components import spi
from esphome.const import CONF_ID, CONF_ON_STATE, CONF_THRESHOLD, CONF_TRIGGER_ID
CODEOWNERS = ["@numo68"] CONFIG_SCHEMA = cv.invalid(
AUTO_LOAD = ["binary_sensor"] "This component sould now be used as platform of the Touchscreen component."
DEPENDENCIES = ["spi"]
MULTI_CONF = True
CONF_REPORT_INTERVAL = "report_interval"
CONF_CALIBRATION_X_MIN = "calibration_x_min"
CONF_CALIBRATION_X_MAX = "calibration_x_max"
CONF_CALIBRATION_Y_MIN = "calibration_y_min"
CONF_CALIBRATION_Y_MAX = "calibration_y_max"
CONF_DIMENSION_X = "dimension_x"
CONF_DIMENSION_Y = "dimension_y"
CONF_SWAP_X_Y = "swap_x_y"
CONF_IRQ_PIN = "irq_pin"
xpt2046_ns = cg.esphome_ns.namespace("xpt2046")
CONF_XPT2046_ID = "xpt2046_id"
XPT2046Component = xpt2046_ns.class_(
"XPT2046Component", cg.PollingComponent, spi.SPIDevice
) )
XPT2046OnStateTrigger = xpt2046_ns.class_(
"XPT2046OnStateTrigger", automation.Trigger.template(cg.int_, cg.int_, cg.bool_)
)
def validate_xpt2046(config):
if (
abs(
cv.int_(config[CONF_CALIBRATION_X_MAX])
- cv.int_(config[CONF_CALIBRATION_X_MIN])
)
< 1000
):
raise cv.Invalid("Calibration X values difference < 1000")
if (
abs(
cv.int_(config[CONF_CALIBRATION_Y_MAX])
- cv.int_(config[CONF_CALIBRATION_Y_MIN])
)
< 1000
):
raise cv.Invalid("Calibration Y values difference < 1000")
return config
def report_interval(value):
if value == "never":
return 4294967295 # uint32_t max
return cv.positive_time_period_milliseconds(value)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(XPT2046Component),
cv.Optional(CONF_IRQ_PIN): pins.gpio_input_pin_schema,
cv.Optional(CONF_CALIBRATION_X_MIN, default=0): cv.int_range(
min=0, max=4095
),
cv.Optional(CONF_CALIBRATION_X_MAX, default=4095): cv.int_range(
min=0, max=4095
),
cv.Optional(CONF_CALIBRATION_Y_MIN, default=0): cv.int_range(
min=0, max=4095
),
cv.Optional(CONF_CALIBRATION_Y_MAX, default=4095): cv.int_range(
min=0, max=4095
),
cv.Optional(CONF_DIMENSION_X, default=100): cv.positive_not_null_int,
cv.Optional(CONF_DIMENSION_Y, default=100): cv.positive_not_null_int,
cv.Optional(CONF_THRESHOLD, default=400): cv.int_range(min=0, max=4095),
cv.Optional(CONF_REPORT_INTERVAL, default="never"): report_interval,
cv.Optional(CONF_SWAP_X_Y, default=False): cv.boolean,
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
XPT2046OnStateTrigger
),
}
),
}
)
.extend(cv.polling_component_schema("50ms"))
.extend(spi.spi_device_schema()),
validate_xpt2046,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
cg.add(var.set_threshold(config[CONF_THRESHOLD]))
cg.add(var.set_report_interval(config[CONF_REPORT_INTERVAL]))
cg.add(var.set_dimensions(config[CONF_DIMENSION_X], config[CONF_DIMENSION_Y]))
cg.add(
var.set_calibration(
config[CONF_CALIBRATION_X_MIN],
config[CONF_CALIBRATION_X_MAX],
config[CONF_CALIBRATION_Y_MIN],
config[CONF_CALIBRATION_Y_MAX],
)
)
if CONF_SWAP_X_Y in config:
cg.add(var.set_swap_x_y(config[CONF_SWAP_X_Y]))
if CONF_IRQ_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
cg.add(var.set_irq_pin(pin))
for conf in config.get(CONF_ON_STATE, []):
await automation.build_automation(
var.get_on_state_trigger(),
[(cg.int_, "x"), (cg.int_, "y"), (cg.bool_, "touched")],
conf,
)

View File

@ -1,55 +1,3 @@
import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import binary_sensor
from . import ( CONFIG_SCHEMA = cv.invalid("Rename this platform component to Touchscreen.")
xpt2046_ns,
XPT2046Component,
CONF_XPT2046_ID,
)
CONF_X_MIN = "x_min"
CONF_X_MAX = "x_max"
CONF_Y_MIN = "y_min"
CONF_Y_MAX = "y_max"
DEPENDENCIES = ["xpt2046"]
XPT2046Button = xpt2046_ns.class_("XPT2046Button", binary_sensor.BinarySensor)
def validate_xpt2046_button(config):
if cv.int_(config[CONF_X_MAX]) < cv.int_(config[CONF_X_MIN]) or cv.int_(
config[CONF_Y_MAX]
) < cv.int_(config[CONF_Y_MIN]):
raise cv.Invalid("x_max is less than x_min or y_max is less than y_min")
return config
CONFIG_SCHEMA = cv.All(
binary_sensor.binary_sensor_schema(XPT2046Button).extend(
{
cv.GenerateID(CONF_XPT2046_ID): cv.use_id(XPT2046Component),
cv.Required(CONF_X_MIN): cv.int_range(min=0, max=4095),
cv.Required(CONF_X_MAX): cv.int_range(min=0, max=4095),
cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=4095),
cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=4095),
}
),
validate_xpt2046_button,
)
async def to_code(config):
var = await binary_sensor.new_binary_sensor(config)
hub = await cg.get_variable(config[CONF_XPT2046_ID])
cg.add(
var.set_area(
config[CONF_X_MIN],
config[CONF_X_MAX],
config[CONF_Y_MIN],
config[CONF_Y_MAX],
)
)
cg.add(hub.register_button(var))

View File

@ -0,0 +1,116 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import spi, touchscreen
from esphome.const import CONF_ID, CONF_THRESHOLD
CODEOWNERS = ["@numo68", "@nielsnl68"]
DEPENDENCIES = ["spi"]
XPT2046_ns = cg.esphome_ns.namespace("xpt2046")
XPT2046Component = XPT2046_ns.class_(
"XPT2046Component", touchscreen.Touchscreen, cg.PollingComponent, spi.SPIDevice
)
CONF_INTERRUPT_PIN = "interrupt_pin"
CONF_REPORT_INTERVAL = "report_interval"
CONF_CALIBRATION_X_MIN = "calibration_x_min"
CONF_CALIBRATION_X_MAX = "calibration_x_max"
CONF_CALIBRATION_Y_MIN = "calibration_y_min"
CONF_CALIBRATION_Y_MAX = "calibration_y_max"
CONF_SWAP_X_Y = "swap_x_y"
# obsolete Keys
CONF_DIMENSION_X = "dimension_x"
CONF_DIMENSION_Y = "dimension_y"
CONF_IRQ_PIN = "irq_pin"
def validate_xpt2046(config):
if (
abs(
cv.int_(config[CONF_CALIBRATION_X_MAX])
- cv.int_(config[CONF_CALIBRATION_X_MIN])
)
< 1000
):
raise cv.Invalid("Calibration X values difference < 1000")
if (
abs(
cv.int_(config[CONF_CALIBRATION_Y_MAX])
- cv.int_(config[CONF_CALIBRATION_Y_MIN])
)
< 1000
):
raise cv.Invalid("Calibration Y values difference < 1000")
return config
def report_interval(value):
if value == "never":
return 4294967295 # uint32_t max
return cv.positive_time_period_milliseconds(value)
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(XPT2046Component),
cv.Optional(CONF_INTERRUPT_PIN): cv.All(
pins.internal_gpio_input_pin_schema
),
cv.Optional(CONF_CALIBRATION_X_MIN, default=0): cv.int_range(
min=0, max=4095
),
cv.Optional(CONF_CALIBRATION_X_MAX, default=4095): cv.int_range(
min=0, max=4095
),
cv.Optional(CONF_CALIBRATION_Y_MIN, default=0): cv.int_range(
min=0, max=4095
),
cv.Optional(CONF_CALIBRATION_Y_MAX, default=4095): cv.int_range(
min=0, max=4095
),
cv.Optional(CONF_THRESHOLD, default=400): cv.int_range(min=0, max=4095),
cv.Optional(CONF_REPORT_INTERVAL, default="never"): report_interval,
cv.Optional(CONF_SWAP_X_Y, default=False): cv.boolean,
# obsolete Keys
cv.Optional(CONF_IRQ_PIN): cv.invalid("Rename IRQ_PIN to INTERUPT_PIN"),
cv.Optional(CONF_DIMENSION_X): cv.invalid(
"This key is now obsolete, please remove it"
),
cv.Optional(CONF_DIMENSION_Y): cv.invalid(
"This key is now obsolete, please remove it"
),
},
)
.extend(cv.polling_component_schema("50ms"))
.extend(spi.spi_device_schema()),
).add_extra(validate_xpt2046)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
await touchscreen.register_touchscreen(var, config)
cg.add(var.set_threshold(config[CONF_THRESHOLD]))
cg.add(var.set_report_interval(config[CONF_REPORT_INTERVAL]))
cg.add(var.set_swap_x_y(config[CONF_SWAP_X_Y]))
cg.add(
var.set_calibration(
config[CONF_CALIBRATION_X_MIN],
config[CONF_CALIBRATION_X_MAX],
config[CONF_CALIBRATION_Y_MIN],
config[CONF_CALIBRATION_Y_MAX],
)
)
if CONF_INTERRUPT_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
cg.add(var.set_irq_pin(pin))

View File

@ -9,31 +9,38 @@ namespace xpt2046 {
static const char *const TAG = "xpt2046"; static const char *const TAG = "xpt2046";
void XPT2046TouchscreenStore::gpio_intr(XPT2046TouchscreenStore *store) { store->touch = true; }
void XPT2046Component::setup() { void XPT2046Component::setup() {
if (this->irq_pin_ != nullptr) { if (this->irq_pin_ != nullptr) {
// The pin reports a touch with a falling edge. Unfortunately the pin goes also changes state // The pin reports a touch with a falling edge. Unfortunately the pin goes also changes state
// while the channels are read and wiring it as an interrupt is not straightforward and would // while the channels are read and wiring it as an interrupt is not straightforward and would
// need careful masking. A GPIO poll is cheap so we'll just use that. // need careful masking. A GPIO poll is cheap so we'll just use that.
this->irq_pin_->setup(); // INPUT this->irq_pin_->setup(); // INPUT
this->irq_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
this->irq_pin_->setup();
this->store_.pin = this->irq_pin_->to_isr();
this->irq_pin_->attach_interrupt(XPT2046TouchscreenStore::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE);
} }
spi_setup(); spi_setup();
read_adc_(0xD0); // ADC powerdown, enable PENIRQ pin read_adc_(0xD0); // ADC powerdown, enable PENIRQ pin
} }
void XPT2046Component::loop() { void XPT2046Component::loop() {
if (this->irq_pin_ != nullptr) { if ((this->irq_pin_ == nullptr) || (!this->store_.touch))
// Force immediate update if a falling edge (= touched is seen) Ignore if still active return;
// (that would mean that we missed the release because of a too long update interval) this->store_.touch = false;
bool val = this->irq_pin_->digital_read(); check_touch_();
if (!val && this->last_irq_ && !this->touched) {
ESP_LOGD(TAG, "Falling penirq edge, forcing update");
update();
}
this->last_irq_ = val;
}
} }
void XPT2046Component::update() { void XPT2046Component::update() {
if (this->irq_pin_ == nullptr)
check_touch_();
}
void XPT2046Component::check_touch_() {
int16_t data[6]; int16_t data[6];
bool touch = false; bool touch = false;
uint32_t now = millis(); uint32_t now = millis();
@ -42,13 +49,13 @@ void XPT2046Component::update() {
// In case the penirq pin is present only do the SPI transaction if it reports a touch (is low). // In case the penirq pin is present only do the SPI transaction if it reports a touch (is low).
// The touch has to be also confirmed with checking the pressure over threshold // The touch has to be also confirmed with checking the pressure over threshold
if (this->irq_pin_ == nullptr || !this->irq_pin_->digital_read()) { if ((this->irq_pin_ == nullptr) || !this->irq_pin_->digital_read()) {
enable(); enable();
int16_t z1 = read_adc_(0xB1 /* Z1 */); int16_t touch_pressure_1 = read_adc_(0xB1 /* touch_pressure_1 */);
int16_t z2 = read_adc_(0xC1 /* Z2 */); int16_t touch_pressure_2 = read_adc_(0xC1 /* touch_pressure_2 */);
this->z_raw = z1 + 4095 - z2; this->z_raw = touch_pressure_1 + 4095 - touch_pressure_2;
touch = (this->z_raw >= this->threshold_); touch = (this->z_raw >= this->threshold_);
if (touch) { if (touch) {
@ -63,64 +70,73 @@ void XPT2046Component::update() {
data[5] = read_adc_(0x90 /* Y */); // Last Y touch power down data[5] = read_adc_(0x90 /* Y */); // Last Y touch power down
disable(); disable();
}
if (touch) { if (touch) {
this->x_raw = best_two_avg(data[0], data[2], data[4]); this->x_raw = best_two_avg(data[0], data[2], data[4]);
this->y_raw = best_two_avg(data[1], data[3], data[5]); this->y_raw = best_two_avg(data[1], data[3], data[5]);
} else {
this->x_raw = this->y_raw = 0;
}
ESP_LOGV(TAG, "Update [x, y] = [%d, %d], z = %d%s", this->x_raw, this->y_raw, this->z_raw, (touch ? " touched" : "")); ESP_LOGVV(TAG, "Update [x, y] = [%d, %d], z = %d", this->x_raw, this->y_raw, this->z_raw);
if (touch) { TouchPoint touchpoint;
// Normalize raw data according to calibration min and max
int16_t x_val = normalize(this->x_raw, this->x_raw_min_, this->x_raw_max_); touchpoint.x = normalize(this->x_raw, this->x_raw_min_, this->x_raw_max_);
int16_t y_val = normalize(this->y_raw, this->y_raw_min_, this->y_raw_max_); touchpoint.y = normalize(this->y_raw, this->y_raw_min_, this->y_raw_max_);
if (this->swap_x_y_) { if (this->swap_x_y_) {
std::swap(x_val, y_val); std::swap(touchpoint.x, touchpoint.y);
} }
if (this->invert_x_) { if (this->invert_x_) {
x_val = 0x7fff - x_val; touchpoint.x = 0xfff - touchpoint.x;
} }
if (this->invert_y_) { if (this->invert_y_) {
y_val = 0x7fff - y_val; touchpoint.y = 0xfff - touchpoint.y;
} }
x_val = (int16_t)((int) x_val * this->x_dim_ / 0x7fff); switch (static_cast<TouchRotation>(this->display_->get_rotation())) {
y_val = (int16_t)((int) y_val * this->y_dim_ / 0x7fff); case ROTATE_0_DEGREES:
break;
case ROTATE_90_DEGREES:
std::swap(touchpoint.x, touchpoint.y);
touchpoint.y = 0xfff - touchpoint.y;
break;
case ROTATE_180_DEGREES:
touchpoint.x = 0xfff - touchpoint.x;
touchpoint.y = 0xfff - touchpoint.y;
break;
case ROTATE_270_DEGREES:
std::swap(touchpoint.x, touchpoint.y);
touchpoint.x = 0xfff - touchpoint.x;
break;
}
if (!this->touched || (now - this->last_pos_ms_) >= this->report_millis_) { touchpoint.x = (int16_t)((int) touchpoint.x * this->display_->get_width() / 0xfff);
ESP_LOGD(TAG, "Raw [x, y] = [%d, %d], transformed = [%d, %d]", this->x_raw, this->y_raw, x_val, y_val); touchpoint.y = (int16_t)((int) touchpoint.y * this->display_->get_height() / 0xfff);
this->x = x_val; if (!this->touched || (now - this->last_pos_ms_) >= this->report_millis_) {
this->y = y_val; ESP_LOGV(TAG, "Touching at [%03X, %03X] => [%3d, %3d]", this->x_raw, this->y_raw, touchpoint.x, touchpoint.y);
this->touched = true;
this->last_pos_ms_ = now;
this->on_state_trigger_->process(this->x, this->y, true); this->defer([this, touchpoint]() { this->send_touch_(touchpoint); });
for (auto *button : this->buttons_)
button->touch(this->x, this->y);
}
} else {
if (this->touched) {
ESP_LOGD(TAG, "Released [%d, %d]", this->x, this->y);
this->touched = false; this->x = touchpoint.x;
this->y = touchpoint.y;
this->on_state_trigger_->process(this->x, this->y, false); this->touched = true;
for (auto *button : this->buttons_) this->last_pos_ms_ = now;
button->release(); }
} else {
this->x_raw = this->y_raw = 0;
if (this->touched) {
ESP_LOGV(TAG, "Released [%d, %d]", this->x, this->y);
this->touched = false;
for (auto *listener : this->touch_listeners_)
listener->release();
}
} }
} }
} }
void XPT2046Component::set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { void XPT2046Component::set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { // NOLINT
this->x_raw_min_ = std::min(x_min, x_max); this->x_raw_min_ = std::min(x_min, x_max);
this->x_raw_max_ = std::max(x_min, x_max); this->x_raw_max_ = std::max(x_min, x_max);
this->y_raw_min_ = std::min(y_min, y_max); this->y_raw_min_ = std::min(y_min, y_max);
@ -137,11 +153,11 @@ void XPT2046Component::dump_config() {
ESP_LOGCONFIG(TAG, " X max: %d", this->x_raw_max_); ESP_LOGCONFIG(TAG, " X max: %d", this->x_raw_max_);
ESP_LOGCONFIG(TAG, " Y min: %d", this->y_raw_min_); ESP_LOGCONFIG(TAG, " Y min: %d", this->y_raw_min_);
ESP_LOGCONFIG(TAG, " Y max: %d", this->y_raw_max_); ESP_LOGCONFIG(TAG, " Y max: %d", this->y_raw_max_);
ESP_LOGCONFIG(TAG, " X dim: %d", this->x_dim_);
ESP_LOGCONFIG(TAG, " Y dim: %d", this->y_dim_); ESP_LOGCONFIG(TAG, " Swap X/Y: %s", YESNO(this->swap_x_y_));
if (this->swap_x_y_) { ESP_LOGCONFIG(TAG, " Invert X: %s", YESNO(this->invert_x_));
ESP_LOGCONFIG(TAG, " Swap X/Y"); ESP_LOGCONFIG(TAG, " Invert Y: %s", YESNO(this->invert_y_));
}
ESP_LOGCONFIG(TAG, " threshold: %d", this->threshold_); ESP_LOGCONFIG(TAG, " threshold: %d", this->threshold_);
ESP_LOGCONFIG(TAG, " Report interval: %u", this->report_millis_); ESP_LOGCONFIG(TAG, " Report interval: %u", this->report_millis_);
@ -150,8 +166,8 @@ void XPT2046Component::dump_config() {
float XPT2046Component::get_setup_priority() const { return setup_priority::DATA; } float XPT2046Component::get_setup_priority() const { return setup_priority::DATA; }
int16_t XPT2046Component::best_two_avg(int16_t x, int16_t y, int16_t z) { int16_t XPT2046Component::best_two_avg(int16_t x, int16_t y, int16_t z) { // NOLINT
int16_t da, db, dc; int16_t da, db, dc; // NOLINT
int16_t reta = 0; int16_t reta = 0;
da = (x > y) ? x - y : y - x; da = (x > y) ? x - y : y - x;
@ -175,15 +191,15 @@ int16_t XPT2046Component::normalize(int16_t val, int16_t min_val, int16_t max_va
if (val <= min_val) { if (val <= min_val) {
ret = 0; ret = 0;
} else if (val >= max_val) { } else if (val >= max_val) {
ret = 0x7fff; ret = 0xfff;
} else { } else {
ret = (int16_t)((int) 0x7fff * (val - min_val) / (max_val - min_val)); ret = (int16_t)((int) 0xfff * (val - min_val) / (max_val - min_val));
} }
return ret; return ret;
} }
int16_t XPT2046Component::read_adc_(uint8_t ctrl) { int16_t XPT2046Component::read_adc_(uint8_t ctrl) { // NOLINT
uint8_t data[2]; uint8_t data[2];
write_byte(ctrl); write_byte(ctrl);
@ -193,25 +209,5 @@ int16_t XPT2046Component::read_adc_(uint8_t ctrl) {
return ((data[0] << 8) | data[1]) >> 3; return ((data[0] << 8) | data[1]) >> 3;
} }
void XPT2046OnStateTrigger::process(int x, int y, bool touched) { this->trigger(x, y, touched); }
void XPT2046Button::touch(int16_t x, int16_t y) {
bool touched = (x >= this->x_min_ && x <= this->x_max_ && y >= this->y_min_ && y <= this->y_max_);
if (touched) {
this->publish_state(true);
this->state_ = true;
} else {
release();
}
}
void XPT2046Button::release() {
if (this->state_) {
this->publish_state(false);
this->state_ = false;
}
}
} // namespace xpt2046 } // namespace xpt2046
} // namespace esphome } // namespace esphome

View File

@ -3,42 +3,31 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/components/spi/spi.h" #include "esphome/components/spi/spi.h"
#include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome { namespace esphome {
namespace xpt2046 { namespace xpt2046 {
class XPT2046OnStateTrigger : public Trigger<int, int, bool> { using namespace touchscreen;
public:
void process(int x, int y, bool touched); struct XPT2046TouchscreenStore {
volatile bool touch;
ISRInternalGPIOPin pin;
static void gpio_intr(XPT2046TouchscreenStore *store);
}; };
class XPT2046Button : public binary_sensor::BinarySensor { class XPT2046Component : public Touchscreen,
public: public PollingComponent,
/// Set the touch screen area where the button will detect the touch.
void set_area(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) {
this->x_min_ = x_min;
this->x_max_ = x_max;
this->y_min_ = y_min;
this->y_max_ = y_max;
}
void touch(int16_t x, int16_t y);
void release();
protected:
int16_t x_min_, x_max_, y_min_, y_max_;
bool state_{false};
};
class XPT2046Component : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> { spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> {
public: public:
/// Set the logical touch screen dimensions. /// Set the logical touch screen dimensions.
void set_dimensions(int16_t x, int16_t y) { void set_dimensions(int16_t x, int16_t y) {
this->x_dim_ = x; this->display_width_ = x;
this->y_dim_ = y; this->display_height_ = y;
} }
/// Set the coordinates for the touch screen edges. /// Set the coordinates for the touch screen edges.
void set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max); void set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max);
@ -47,14 +36,12 @@ class XPT2046Component : public PollingComponent,
/// Set the interval to report the touch point perodically. /// Set the interval to report the touch point perodically.
void set_report_interval(uint32_t interval) { this->report_millis_ = interval; } void set_report_interval(uint32_t interval) { this->report_millis_ = interval; }
uint32_t get_report_interval() { return this->report_millis_; }
/// Set the threshold for the touch detection. /// Set the threshold for the touch detection.
void set_threshold(int16_t threshold) { this->threshold_ = threshold; } void set_threshold(int16_t threshold) { this->threshold_ = threshold; }
/// Set the pin used to detect the touch. /// Set the pin used to detect the touch.
void set_irq_pin(GPIOPin *pin) { this->irq_pin_ = pin; } void set_irq_pin(InternalGPIOPin *pin) { this->irq_pin_ = pin; }
/// Get an access to the on_state automation trigger
XPT2046OnStateTrigger *get_on_state_trigger() const { return this->on_state_trigger_; }
/// Register a virtual button to the component.
void register_button(XPT2046Button *button) { this->buttons_.push_back(button); }
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
@ -103,21 +90,19 @@ class XPT2046Component : public PollingComponent,
static int16_t normalize(int16_t val, int16_t min_val, int16_t max_val); static int16_t normalize(int16_t val, int16_t min_val, int16_t max_val);
int16_t read_adc_(uint8_t ctrl); int16_t read_adc_(uint8_t ctrl);
void check_touch_();
int16_t threshold_; int16_t threshold_;
int16_t x_raw_min_, x_raw_max_, y_raw_min_, y_raw_max_; int16_t x_raw_min_, x_raw_max_, y_raw_min_, y_raw_max_;
int16_t x_dim_, y_dim_;
bool invert_x_, invert_y_; bool invert_x_, invert_y_;
bool swap_x_y_; bool swap_x_y_;
uint32_t report_millis_; uint32_t report_millis_;
uint32_t last_pos_ms_{0}; uint32_t last_pos_ms_{0};
GPIOPin *irq_pin_{nullptr}; InternalGPIOPin *irq_pin_{nullptr};
bool last_irq_{true}; XPT2046TouchscreenStore store_;
XPT2046OnStateTrigger *on_state_trigger_{new XPT2046OnStateTrigger()};
std::vector<XPT2046Button *> buttons_{};
}; };
} // namespace xpt2046 } // namespace xpt2046

View File

@ -23,7 +23,7 @@ from esphome.core import CORE, EsphomeError
from esphome.helpers import indent from esphome.helpers import indent
from esphome.util import safe_print, OrderedDict from esphome.util import safe_print, OrderedDict
from typing import List, Optional, Tuple, Union from typing import Optional, Union
from esphome.loader import get_component, get_platform, ComponentManifest from esphome.loader import get_component, get_platform, ComponentManifest
from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue
from esphome.voluptuous_schema import ExtraKeysInvalid from esphome.voluptuous_schema import ExtraKeysInvalid
@ -50,10 +50,10 @@ def iter_components(config):
yield p_name, platform, p_config yield p_name, platform, p_config
ConfigPath = List[Union[str, int]] ConfigPath = list[Union[str, int]]
def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool def _path_begins_with(path: ConfigPath, other: ConfigPath) -> bool:
if len(path) < len(other): if len(path) < len(other):
return False return False
return path[: len(other)] == other return path[: len(other)] == other
@ -67,7 +67,7 @@ class _ValidationStepTask:
self.step = step self.step = step
@property @property
def _cmp_tuple(self) -> Tuple[float, int]: def _cmp_tuple(self) -> tuple[float, int]:
return (-self.priority, self.id_number) return (-self.priority, self.id_number)
def __eq__(self, other): def __eq__(self, other):
@ -84,21 +84,20 @@ class Config(OrderedDict, fv.FinalValidateConfig):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
# A list of voluptuous errors # A list of voluptuous errors
self.errors = [] # type: List[vol.Invalid] self.errors: list[vol.Invalid] = []
# A list of paths that should be fully outputted # A list of paths that should be fully outputted
# The values will be the paths to all "domain", for example (['logger'], 'logger') # The values will be the paths to all "domain", for example (['logger'], 'logger')
# or (['sensor', 'ultrasonic'], 'sensor.ultrasonic') # or (['sensor', 'ultrasonic'], 'sensor.ultrasonic')
self.output_paths = [] # type: List[Tuple[ConfigPath, str]] self.output_paths: list[tuple[ConfigPath, str]] = []
# A list of components ids with the config path # A list of components ids with the config path
self.declare_ids = [] # type: List[Tuple[core.ID, ConfigPath]] self.declare_ids: list[tuple[core.ID, ConfigPath]] = []
self._data = {} self._data = {}
# Store pending validation tasks (in heap order) # Store pending validation tasks (in heap order)
self._validation_tasks: List[_ValidationStepTask] = [] self._validation_tasks: list[_ValidationStepTask] = []
# ID to ensure stable order for keys with equal priority # ID to ensure stable order for keys with equal priority
self._validation_tasks_id = 0 self._validation_tasks_id = 0
def add_error(self, error): def add_error(self, error: vol.Invalid) -> None:
# type: (vol.Invalid) -> None
if isinstance(error, vol.MultipleInvalid): if isinstance(error, vol.MultipleInvalid):
for err in error.errors: for err in error.errors:
self.add_error(err) self.add_error(err)
@ -132,20 +131,16 @@ class Config(OrderedDict, fv.FinalValidateConfig):
e.prepend(path) e.prepend(path)
self.add_error(e) self.add_error(e)
def add_str_error(self, message, path): def add_str_error(self, message: str, path: ConfigPath) -> None:
# type: (str, ConfigPath) -> None
self.add_error(vol.Invalid(message, path)) self.add_error(vol.Invalid(message, path))
def add_output_path(self, path, domain): def add_output_path(self, path: ConfigPath, domain: str) -> None:
# type: (ConfigPath, str) -> None
self.output_paths.append((path, domain)) self.output_paths.append((path, domain))
def remove_output_path(self, path, domain): def remove_output_path(self, path: ConfigPath, domain: str) -> None:
# type: (ConfigPath, str) -> None
self.output_paths.remove((path, domain)) self.output_paths.remove((path, domain))
def is_in_error_path(self, path): def is_in_error_path(self, path: ConfigPath) -> bool:
# type: (ConfigPath) -> bool
for err in self.errors: for err in self.errors:
if _path_begins_with(err.path, path): if _path_begins_with(err.path, path):
return True return True
@ -157,16 +152,16 @@ class Config(OrderedDict, fv.FinalValidateConfig):
conf = conf[key] conf = conf[key]
conf[path[-1]] = value conf[path[-1]] = value
def get_error_for_path(self, path): def get_error_for_path(self, path: ConfigPath) -> Optional[vol.Invalid]:
# type: (ConfigPath) -> Optional[vol.Invalid]
for err in self.errors: for err in self.errors:
if self.get_deepest_path(err.path) == path: if self.get_deepest_path(err.path) == path:
self.errors.remove(err) self.errors.remove(err)
return err return err
return None return None
def get_deepest_document_range_for_path(self, path, get_key=False): def get_deepest_document_range_for_path(
# type: (ConfigPath, bool) -> Optional[ESPHomeDataBase] self, path: ConfigPath, get_key: bool = False
) -> Optional[ESPHomeDataBase]:
data = self data = self
doc_range = None doc_range = None
for index, path_item in enumerate(path): for index, path_item in enumerate(path):
@ -207,8 +202,7 @@ class Config(OrderedDict, fv.FinalValidateConfig):
return {} return {}
return data return data
def get_deepest_path(self, path): def get_deepest_path(self, path: ConfigPath) -> ConfigPath:
# type: (ConfigPath) -> ConfigPath
"""Return the path that is the deepest reachable by following path.""" """Return the path that is the deepest reachable by following path."""
data = self data = self
part = [] part = []
@ -532,7 +526,7 @@ class IDPassValidationStep(ConfigValidationStep):
# because the component that did not validate doesn't have any IDs set # because the component that did not validate doesn't have any IDs set
return return
searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]] searching_ids: list[tuple[core.ID, ConfigPath]] = []
for id, path in iter_ids(result): for id, path in iter_ids(result):
if id.is_declaration: if id.is_declaration:
if id.id is not None: if id.id is not None:
@ -780,8 +774,7 @@ def _get_parent_name(path, config):
return path[-1] return path[-1]
def _format_vol_invalid(ex, config): def _format_vol_invalid(ex: vol.Invalid, config: Config) -> str:
# type: (vol.Invalid, Config) -> str
message = "" message = ""
paren = _get_parent_name(ex.path[:-1], config) paren = _get_parent_name(ex.path[:-1], config)
@ -862,8 +855,9 @@ def _print_on_next_line(obj):
return False return False
def dump_dict(config, path, at_root=True): def dump_dict(
# type: (Config, ConfigPath, bool) -> Tuple[str, bool] config: Config, path: ConfigPath, at_root: bool = True
) -> tuple[str, bool]:
conf = config.get_nested_item(path) conf = config.get_nested_item(path)
ret = "" ret = ""
multiline = False multiline = False

View File

@ -5,8 +5,7 @@ from esphome.core import CORE
from esphome.helpers import read_file from esphome.helpers import read_file
def read_config_file(path): def read_config_file(path: str) -> str:
# type: (str) -> str
if CORE.vscode and ( if CORE.vscode and (
not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path) not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path)
): ):

View File

@ -1689,7 +1689,7 @@ class Version:
@classmethod @classmethod
def parse(cls, value: str) -> "Version": def parse(cls, value: str) -> "Version":
match = re.match(r"(\d+).(\d+).(\d+)", value) match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value)
if match is None: if match is None:
raise ValueError(f"Not a valid version number {value}") raise ValueError(f"Not a valid version number {value}")
major = int(match[1]) major = int(match[1])
@ -1703,7 +1703,7 @@ def version_number(value):
try: try:
return str(Version.parse(value)) return str(Version.parse(value))
except ValueError as e: except ValueError as e:
raise Invalid("Not a version number") from e raise Invalid("Not a valid version number") from e
def platformio_version_constraint(value): def platformio_version_constraint(value):

View File

@ -1,6 +1,6 @@
"""Constants used by esphome.""" """Constants used by esphome."""
__version__ = "2022.9.0b5" __version__ = "2022.10.0b1"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
@ -23,6 +23,7 @@ CONF_ACCURACY = "accuracy"
CONF_ACCURACY_DECIMALS = "accuracy_decimals" CONF_ACCURACY_DECIMALS = "accuracy_decimals"
CONF_ACTION_ID = "action_id" CONF_ACTION_ID = "action_id"
CONF_ACTION_STATE_TOPIC = "action_state_topic" CONF_ACTION_STATE_TOPIC = "action_state_topic"
CONF_ACTIVE = "active"
CONF_ACTIVE_POWER = "active_power" CONF_ACTIVE_POWER = "active_power"
CONF_ADDRESS = "address" CONF_ADDRESS = "address"
CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"
@ -395,6 +396,7 @@ CONF_MIN_POWER = "min_power"
CONF_MIN_RANGE = "min_range" CONF_MIN_RANGE = "min_range"
CONF_MIN_TEMPERATURE = "min_temperature" CONF_MIN_TEMPERATURE = "min_temperature"
CONF_MIN_VALUE = "min_value" CONF_MIN_VALUE = "min_value"
CONF_MIN_VERSION = "min_version"
CONF_MINUTE = "minute" CONF_MINUTE = "minute"
CONF_MINUTES = "minutes" CONF_MINUTES = "minutes"
CONF_MISO_PIN = "miso_pin" CONF_MISO_PIN = "miso_pin"
@ -903,7 +905,6 @@ DEVICE_CLASS_GARAGE_DOOR = "garage_door"
DEVICE_CLASS_HEAT = "heat" DEVICE_CLASS_HEAT = "heat"
DEVICE_CLASS_LIGHT = "light" DEVICE_CLASS_LIGHT = "light"
DEVICE_CLASS_LOCK = "lock" DEVICE_CLASS_LOCK = "lock"
DEVICE_CLASS_MOISTURE = "moisture"
DEVICE_CLASS_MOTION = "motion" DEVICE_CLASS_MOTION = "motion"
DEVICE_CLASS_MOVING = "moving" DEVICE_CLASS_MOVING = "moving"
DEVICE_CLASS_OCCUPANCY = "occupancy" DEVICE_CLASS_OCCUPANCY = "occupancy"
@ -921,15 +922,17 @@ DEVICE_CLASS_WINDOW = "window"
# device classes of both binary_sensor and sensor component # device classes of both binary_sensor and sensor component
DEVICE_CLASS_EMPTY = "" DEVICE_CLASS_EMPTY = ""
DEVICE_CLASS_BATTERY = "battery" DEVICE_CLASS_BATTERY = "battery"
DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide"
DEVICE_CLASS_GAS = "gas" DEVICE_CLASS_GAS = "gas"
DEVICE_CLASS_MOISTURE = "moisture"
DEVICE_CLASS_POWER = "power" DEVICE_CLASS_POWER = "power"
# device classes of sensor component # device classes of sensor component
DEVICE_CLASS_APPARENT_POWER = "apparent_power" DEVICE_CLASS_APPARENT_POWER = "apparent_power"
DEVICE_CLASS_AQI = "aqi" DEVICE_CLASS_AQI = "aqi"
DEVICE_CLASS_CARBON_DIOXIDE = "carbon_dioxide" DEVICE_CLASS_CARBON_DIOXIDE = "carbon_dioxide"
DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide"
DEVICE_CLASS_CURRENT = "current" DEVICE_CLASS_CURRENT = "current"
DEVICE_CLASS_DATE = "date" DEVICE_CLASS_DATE = "date"
DEVICE_CLASS_DISTANCE = "distance"
DEVICE_CLASS_DURATION = "duration" DEVICE_CLASS_DURATION = "duration"
DEVICE_CLASS_ENERGY = "energy" DEVICE_CLASS_ENERGY = "energy"
DEVICE_CLASS_FREQUENCY = "frequency" DEVICE_CLASS_FREQUENCY = "frequency"
@ -947,11 +950,14 @@ DEVICE_CLASS_POWER_FACTOR = "power_factor"
DEVICE_CLASS_PRESSURE = "pressure" DEVICE_CLASS_PRESSURE = "pressure"
DEVICE_CLASS_REACTIVE_POWER = "reactive_power" DEVICE_CLASS_REACTIVE_POWER = "reactive_power"
DEVICE_CLASS_SIGNAL_STRENGTH = "signal_strength" DEVICE_CLASS_SIGNAL_STRENGTH = "signal_strength"
DEVICE_CLASS_SPEED = "speed"
DEVICE_CLASS_SULPHUR_DIOXIDE = "sulphur_dioxide" DEVICE_CLASS_SULPHUR_DIOXIDE = "sulphur_dioxide"
DEVICE_CLASS_TEMPERATURE = "temperature" DEVICE_CLASS_TEMPERATURE = "temperature"
DEVICE_CLASS_TIMESTAMP = "timestamp" DEVICE_CLASS_TIMESTAMP = "timestamp"
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds"
DEVICE_CLASS_VOLTAGE = "voltage" DEVICE_CLASS_VOLTAGE = "voltage"
DEVICE_CLASS_VOLUME = "volume"
DEVICE_CLASS_WEIGHT = "weight"
# device classes of both binary_sensor and button component # device classes of both binary_sensor and button component
DEVICE_CLASS_UPDATE = "update" DEVICE_CLASS_UPDATE = "update"
# device classes of button component # device classes of button component

View File

@ -2,7 +2,7 @@ import logging
import math import math
import os import os
import re import re
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union from typing import TYPE_CHECKING, Optional, Union
from esphome.const import ( from esphome.const import (
CONF_COMMENT, CONF_COMMENT,
@ -469,19 +469,19 @@ class EsphomeCore:
# Task counter for pending tasks # Task counter for pending tasks
self.task_counter = 0 self.task_counter = 0
# The variable cache, for each ID this holds a MockObj of the variable obj # The variable cache, for each ID this holds a MockObj of the variable obj
self.variables: Dict[str, "MockObj"] = {} self.variables: dict[str, "MockObj"] = {}
# A list of statements that go in the main setup() block # A list of statements that go in the main setup() block
self.main_statements: List["Statement"] = [] self.main_statements: list["Statement"] = []
# A list of statements to insert in the global block (includes and global variables) # A list of statements to insert in the global block (includes and global variables)
self.global_statements: List["Statement"] = [] self.global_statements: list["Statement"] = []
# A set of platformio libraries to add to the project # A set of platformio libraries to add to the project
self.libraries: List[Library] = [] self.libraries: list[Library] = []
# A set of build flags to set in the platformio project # A set of build flags to set in the platformio project
self.build_flags: Set[str] = set() self.build_flags: set[str] = set()
# A set of defines to set for the compile process in esphome/core/defines.h # A set of defines to set for the compile process in esphome/core/defines.h
self.defines: Set["Define"] = set() self.defines: set["Define"] = set()
# A map of all platformio options to apply # A map of all platformio options to apply
self.platformio_options: Dict[str, Union[str, List[str]]] = {} self.platformio_options: dict[str, Union[str, list[str]]] = {}
# A set of strings of names of loaded integrations, used to find namespace ID conflicts # A set of strings of names of loaded integrations, used to find namespace ID conflicts
self.loaded_integrations = set() self.loaded_integrations = set()
# A set of component IDs to track what Component subclasses are declared # A set of component IDs to track what Component subclasses are declared
@ -701,7 +701,7 @@ class EsphomeCore:
_LOGGER.debug("Adding define: %s", define) _LOGGER.debug("Adding define: %s", define)
return define return define
def add_platformio_option(self, key: str, value: Union[str, List[str]]) -> None: def add_platformio_option(self, key: str, value: Union[str, list[str]]) -> None:
new_val = value new_val = value
old_val = self.platformio_options.get(key) old_val = self.platformio_options.get(key)
if isinstance(old_val, list): if isinstance(old_val, list):
@ -734,7 +734,7 @@ class EsphomeCore:
_LOGGER.debug("Waiting for variable %s", id) _LOGGER.debug("Waiting for variable %s", id)
yield yield
async def get_variable_with_full_id(self, id: ID) -> Tuple[ID, "MockObj"]: async def get_variable_with_full_id(self, id: ID) -> tuple[ID, "MockObj"]:
if not isinstance(id, ID): if not isinstance(id, ID):
raise ValueError(f"ID {id!r} must be of type ID!") raise ValueError(f"ID {id!r} must be of type ID!")
return await _FakeAwaitable(self._get_variable_with_full_id_generator(id)) return await _FakeAwaitable(self._get_variable_with_full_id_generator(id))

View File

@ -15,6 +15,7 @@ from esphome.const import (
CONF_FRAMEWORK, CONF_FRAMEWORK,
CONF_INCLUDES, CONF_INCLUDES,
CONF_LIBRARIES, CONF_LIBRARIES,
CONF_MIN_VERSION,
CONF_NAME, CONF_NAME,
CONF_ON_BOOT, CONF_ON_BOOT,
CONF_ON_LOOP, CONF_ON_LOOP,
@ -30,6 +31,7 @@ from esphome.const import (
KEY_CORE, KEY_CORE,
TARGET_PLATFORMS, TARGET_PLATFORMS,
PLATFORM_ESP8266, PLATFORM_ESP8266,
__version__ as ESPHOME_VERSION,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.helpers import copy_file_if_changed, walk_files from esphome.helpers import copy_file_if_changed, walk_files
@ -96,6 +98,16 @@ def valid_project_name(value: str):
return value return value
def validate_version(value: str):
min_version = cv.Version.parse(value)
current_version = cv.Version.parse(ESPHOME_VERSION)
if current_version < min_version:
raise cv.Invalid(
f"Your ESPHome version is too old. Please update to at least {min_version}"
)
return value
CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash"
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
@ -136,6 +148,9 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_VERSION): cv.string_strict, cv.Required(CONF_VERSION): cv.string_strict,
} }
), ),
cv.Optional(CONF_MIN_VERSION, default=ESPHOME_VERSION): cv.All(
cv.version_number, validate_version
),
} }
), ),
validate_hostname, validate_hostname,

View File

@ -48,7 +48,8 @@ import heapq
import inspect import inspect
import logging import logging
import types import types
from typing import Any, Awaitable, Callable, Generator, Iterator, List, Tuple from typing import Any, Callable
from collections.abc import Awaitable, Generator, Iterator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -177,7 +178,7 @@ class _Task:
return _Task(priority, self.id_number, self.iterator, self.original_function) return _Task(priority, self.id_number, self.iterator, self.original_function)
@property @property
def _cmp_tuple(self) -> Tuple[float, int]: def _cmp_tuple(self) -> tuple[float, int]:
return (-self.priority, self.id_number) return (-self.priority, self.id_number)
def __eq__(self, other): def __eq__(self, other):
@ -194,7 +195,7 @@ class FakeEventLoop:
"""Emulate an asyncio EventLoop to run some registered coroutine jobs in sequence.""" """Emulate an asyncio EventLoop to run some registered coroutine jobs in sequence."""
def __init__(self): def __init__(self):
self._pending_tasks: List[_Task] = [] self._pending_tasks: list[_Task] = []
self._task_counter = 0 self._task_counter = 0
def add_job(self, func, *args, **kwargs): def add_job(self, func, *args, **kwargs):

View File

@ -5,7 +5,13 @@ import re
from esphome.yaml_util import ESPHomeDataBase from esphome.yaml_util import ESPHomeDataBase
# pylint: disable=unused-import, wrong-import-order # pylint: disable=unused-import, wrong-import-order
from typing import Any, Generator, List, Optional, Tuple, Type, Union, Sequence from typing import (
Any,
Callable,
Optional,
Union,
)
from collections.abc import Generator, Sequence
from esphome.core import ( # noqa from esphome.core import ( # noqa
CORE, CORE,
@ -44,9 +50,9 @@ SafeExpType = Union[
int, int,
float, float,
TimePeriod, TimePeriod,
Type[bool], type[bool],
Type[int], type[int],
Type[float], type[float],
Sequence[Any], Sequence[Any],
] ]
@ -140,7 +146,7 @@ class CallExpression(Expression):
class StructInitializer(Expression): class StructInitializer(Expression):
__slots__ = ("base", "args") __slots__ = ("base", "args")
def __init__(self, base: Expression, *args: Tuple[str, Optional[SafeExpType]]): def __init__(self, base: Expression, *args: tuple[str, Optional[SafeExpType]]):
self.base = base self.base = base
# TODO: args is always a Tuple, is this check required? # TODO: args is always a Tuple, is this check required?
if not isinstance(args, OrderedDict): if not isinstance(args, OrderedDict):
@ -200,7 +206,7 @@ class ParameterListExpression(Expression):
__slots__ = ("parameters",) __slots__ = ("parameters",)
def __init__( def __init__(
self, *parameters: Union[ParameterExpression, Tuple[SafeExpType, str]] self, *parameters: Union[ParameterExpression, tuple[SafeExpType, str]]
): ):
self.parameters = [] self.parameters = []
for parameter in parameters: for parameter in parameters:
@ -468,7 +474,9 @@ def statement(expression: Union[Expression, Statement]) -> Statement:
return ExpressionStatement(expression) return ExpressionStatement(expression)
def variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": def variable(
id_: ID, rhs: SafeExpType, type_: "MockObj" = None, register=True
) -> "MockObj":
"""Declare a new variable, not pointer type, in the code generation. """Declare a new variable, not pointer type, in the code generation.
:param id_: The ID used to declare the variable. :param id_: The ID used to declare the variable.
@ -485,10 +493,37 @@ def variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
id_.type = type_ id_.type = type_
assignment = AssignmentExpression(id_.type, "", id_, rhs) assignment = AssignmentExpression(id_.type, "", id_, rhs)
CORE.add(assignment) CORE.add(assignment)
CORE.register_variable(id_, obj) if register:
CORE.register_variable(id_, obj)
return obj return obj
def with_local_variable(
id_: ID, rhs: SafeExpType, callback: Callable[["MockObj"], None], *args
) -> None:
"""Declare a new variable, not pointer type, in the code generation, within a scoped block
The variable is only usable within the callback
The callback cannot be async.
:param id_: The ID used to declare the variable.
:param rhs: The expression to place on the right hand side of the assignment.
:param callback: The function to invoke that will receive the temporary variable
:param args: args to pass to the callback in addition to the temporary variable
"""
# throw if the callback is async:
assert not inspect.iscoroutinefunction(
callback
), "with_local_variable() callback cannot be async!"
CORE.add(RawStatement("{")) # output opening curly brace
obj = variable(id_, rhs, None, True)
# invoke user-provided callback to generate code with this local variable
callback(obj, *args)
CORE.add(RawStatement("}")) # output closing curly brace
def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
"""Declare and define a new variable, not pointer type, in the code generation. """Declare and define a new variable, not pointer type, in the code generation.
@ -590,7 +625,7 @@ def add_define(name: str, value: SafeExpType = None):
CORE.add_define(Define(name, safe_exp(value))) CORE.add_define(Define(name, safe_exp(value)))
def add_platformio_option(key: str, value: Union[str, List[str]]): def add_platformio_option(key: str, value: Union[str, list[str]]):
CORE.add_platformio_option(key, value) CORE.add_platformio_option(key, value)
@ -607,7 +642,7 @@ async def get_variable(id_: ID) -> "MockObj":
return await CORE.get_variable(id_) return await CORE.get_variable(id_)
async def get_variable_with_full_id(id_: ID) -> Tuple[ID, "MockObj"]: async def get_variable_with_full_id(id_: ID) -> tuple[ID, "MockObj"]:
""" """
Wait for the given ID to be defined in the code generation and Wait for the given ID to be defined in the code generation and
return it as a MockObj. return it as a MockObj.
@ -622,7 +657,7 @@ async def get_variable_with_full_id(id_: ID) -> Tuple[ID, "MockObj"]:
async def process_lambda( async def process_lambda(
value: Lambda, value: Lambda,
parameters: List[Tuple[SafeExpType, str]], parameters: list[tuple[SafeExpType, str]],
capture: str = "=", capture: str = "=",
return_type: SafeExpType = None, return_type: SafeExpType = None,
) -> Generator[LambdaExpression, None, None]: ) -> Generator[LambdaExpression, None, None]:
@ -676,7 +711,7 @@ def is_template(value):
async def templatable( async def templatable(
value: Any, value: Any,
args: List[Tuple[SafeExpType, str]], args: list[tuple[SafeExpType, str]],
output_type: Optional[SafeExpType], output_type: Optional[SafeExpType],
to_exp: Any = None, to_exp: Any = None,
): ):
@ -724,7 +759,7 @@ class MockObj(Expression):
attr = attr[1:] attr = attr[1:]
return MockObj(f"{self.base}{self.op}{attr}", next_op) return MockObj(f"{self.base}{self.op}{attr}", next_op)
def __call__(self, *args): # type: (SafeExpType) -> MockObj def __call__(self, *args: SafeExpType) -> "MockObj":
call = CallExpression(self.base, *args) call = CallExpression(self.base, *args)
return MockObj(call, self.op) return MockObj(call, self.op)

View File

@ -13,7 +13,7 @@ from esphome.const import (
# pylint: disable=unused-import # pylint: disable=unused-import
from esphome.core import coroutine, ID, CORE from esphome.core import coroutine, ID, CORE
from esphome.types import ConfigType from esphome.types import ConfigType, ConfigFragmentType
from esphome.cpp_generator import add, get_variable from esphome.cpp_generator import add, get_variable
from esphome.cpp_types import App from esphome.cpp_types import App
from esphome.util import Registry, RegistryEntry from esphome.util import Registry, RegistryEntry
@ -107,8 +107,10 @@ async def setup_entity(var, config):
add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) add(var.set_entity_category(config[CONF_ENTITY_CATEGORY]))
def extract_registry_entry_config(registry, full_config): def extract_registry_entry_config(
# type: (Registry, ConfigType) -> RegistryEntry registry: Registry,
full_config: ConfigType,
) -> tuple[RegistryEntry, ConfigFragmentType]:
key, config = next((k, v) for k, v in full_config.items() if k in registry) key, config = next((k, v) for k, v in full_config.items() if k in registry)
return registry[key], config return registry[key], config

View File

@ -395,11 +395,22 @@ class ImportRequestHandler(BaseHandler):
args = json.loads(self.request.body.decode()) args = json.loads(self.request.body.decode())
try: try:
name = args["name"] name = args["name"]
imported_device = next(
(res for res in IMPORT_RESULT.values() if res.device_name == name), None
)
if imported_device is not None:
network = imported_device.network
else:
network = const.CONF_WIFI
import_config( import_config(
settings.rel_path(f"{name}.yaml"), settings.rel_path(f"{name}.yaml"),
name, name,
args["project_name"], args["project_name"],
args["package_import_url"], args["package_import_url"],
network,
) )
except FileExistsError: except FileExistsError:
self.set_status(500) self.set_status(500)
@ -522,7 +533,7 @@ class DashboardEntry:
return os.path.basename(self.path) return os.path.basename(self.path)
@property @property
def storage(self): # type: () -> Optional[StorageJSON] def storage(self) -> Optional[StorageJSON]:
if not self._loaded_storage: if not self._loaded_storage:
self._storage = StorageJSON.load( self._storage = StorageJSON.load(
ext_storage_path(settings.config_dir, self.filename) ext_storage_path(settings.config_dir, self.filename)
@ -613,6 +624,7 @@ class ListDevicesHandler(BaseHandler):
"package_import_url": res.package_import_url, "package_import_url": res.package_import_url,
"project_name": res.project_name, "project_name": res.project_name,
"project_version": res.project_version, "project_version": res.project_version,
"network": res.network,
} }
for res in IMPORT_RESULT.values() for res in IMPORT_RESULT.values()
if res.device_name not in configured if res.device_name not in configured
@ -817,7 +829,7 @@ class UndoDeleteRequestHandler(BaseHandler):
shutil.move(os.path.join(trash_path, configuration), config_file) shutil.move(os.path.join(trash_path, configuration), config_file)
PING_RESULT = {} # type: dict PING_RESULT: dict = {}
IMPORT_RESULT = {} IMPORT_RESULT = {}
STOP_EVENT = threading.Event() STOP_EVENT = threading.Event()
PING_REQUEST = threading.Event() PING_REQUEST = threading.Event()
@ -933,7 +945,7 @@ def get_static_path(*args):
return os.path.join(get_base_frontend_path(), "static", *args) return os.path.join(get_base_frontend_path(), "static", *args)
@functools.lru_cache(maxsize=None) @functools.cache
def get_static_file_url(name): def get_static_file_url(name):
base = f"./static/{name}" base = f"./static/{name}"

View File

@ -1,5 +1,5 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Dict, Any from typing import Any
import contextvars import contextvars
from esphome.types import ConfigFragmentType, ID, ConfigPathType from esphome.types import ConfigFragmentType, ID, ConfigPathType
@ -9,7 +9,7 @@ import esphome.config_validation as cv
class FinalValidateConfig(ABC): class FinalValidateConfig(ABC):
@property @property
@abstractmethod @abstractmethod
def data(self) -> Dict[str, Any]: def data(self) -> dict[str, Any]:
"""A dictionary that can be used by post validation functions to store """A dictionary that can be used by post validation functions to store
global data during the validation phase. Each component should store its global data during the validation phase. Each component should store its
data under a unique key data under a unique key

View File

@ -2,6 +2,7 @@ from pathlib import Path
import subprocess import subprocess
import hashlib import hashlib
import logging import logging
from typing import Callable, Optional
import urllib.parse import urllib.parse
from datetime import datetime from datetime import datetime
@ -12,7 +13,7 @@ import esphome.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def run_git_command(cmd, cwd=None): def run_git_command(cmd, cwd=None) -> str:
try: try:
ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False) ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False)
except FileNotFoundError as err: except FileNotFoundError as err:
@ -28,6 +29,8 @@ def run_git_command(cmd, cwd=None):
raise cv.Invalid(lines[-1][len("fatal: ") :]) raise cv.Invalid(lines[-1][len("fatal: ") :])
raise cv.Invalid(err_str) raise cv.Invalid(err_str)
return ret.stdout.decode("utf-8").strip()
def _compute_destination_path(key: str, domain: str) -> Path: def _compute_destination_path(key: str, domain: str) -> Path:
base_dir = Path(CORE.config_dir) / ".esphome" / domain base_dir = Path(CORE.config_dir) / ".esphome" / domain
@ -44,7 +47,7 @@ def clone_or_update(
domain: str, domain: str,
username: str = None, username: str = None,
password: str = None, password: str = None,
) -> Path: ) -> tuple[Path, Optional[Callable[[], None]]]:
key = f"{url}@{ref}" key = f"{url}@{ref}"
if username is not None and password is not None: if username is not None and password is not None:
@ -78,6 +81,7 @@ def clone_or_update(
file_timestamp = Path(repo_dir / ".git" / "HEAD") file_timestamp = Path(repo_dir / ".git" / "HEAD")
age = datetime.now() - datetime.fromtimestamp(file_timestamp.stat().st_mtime) age = datetime.now() - datetime.fromtimestamp(file_timestamp.stat().st_mtime)
if age.total_seconds() > refresh.total_seconds: if age.total_seconds() > refresh.total_seconds:
old_sha = run_git_command(["git", "rev-parse", "HEAD"], str(repo_dir))
_LOGGER.info("Updating %s", key) _LOGGER.info("Updating %s", key)
_LOGGER.debug("Location: %s", repo_dir) _LOGGER.debug("Location: %s", repo_dir)
# Stash local changes (if any) # Stash local changes (if any)
@ -92,4 +96,10 @@ def clone_or_update(
# Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch)
run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir))
return repo_dir def revert():
_LOGGER.info("Reverting changes to %s -> %s", key, old_sha)
run_git_command(["git", "reset", "--hard", old_sha], str(repo_dir))
return repo_dir, revert
return repo_dir, None

View File

@ -6,6 +6,7 @@ import os
from pathlib import Path from pathlib import Path
from typing import Union from typing import Union
import tempfile import tempfile
from urllib.parse import urlparse
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -40,7 +41,7 @@ def indent(text, padding=" "):
# From https://stackoverflow.com/a/14945195/8924614 # From https://stackoverflow.com/a/14945195/8924614
def cpp_string_escape(string, encoding="utf-8"): def cpp_string_escape(string, encoding="utf-8"):
def _should_escape(byte): # type: (int) -> bool def _should_escape(byte: int) -> bool:
if not 32 <= byte < 127: if not 32 <= byte < 127:
return True return True
if byte in (ord("\\"), ord('"')): if byte in (ord("\\"), ord('"')):
@ -134,7 +135,8 @@ def resolve_ip_address(host):
errs.append(str(err)) errs.append(str(err))
try: try:
return socket.gethostbyname(host) host_url = host if (urlparse(host).scheme != "") else "http://" + host
return socket.gethostbyname(urlparse(host_url).hostname)
except OSError as err: except OSError as err:
errs.append(str(err)) errs.append(str(err))
raise EsphomeError(f"Error resolving IP address: {', '.join(errs)}") from err raise EsphomeError(f"Error resolving IP address: {', '.join(errs)}") from err

View File

@ -1,5 +1,5 @@
import logging import logging
from typing import Callable, List, Optional, Any, ContextManager from typing import Callable, Optional, Any, ContextManager
from types import ModuleType from types import ModuleType
import importlib import importlib
import importlib.util import importlib.util
@ -62,19 +62,19 @@ class ComponentManifest:
return getattr(self.module, "to_code", None) return getattr(self.module, "to_code", None)
@property @property
def dependencies(self) -> List[str]: def dependencies(self) -> list[str]:
return getattr(self.module, "DEPENDENCIES", []) return getattr(self.module, "DEPENDENCIES", [])
@property @property
def conflicts_with(self) -> List[str]: def conflicts_with(self) -> list[str]:
return getattr(self.module, "CONFLICTS_WITH", []) return getattr(self.module, "CONFLICTS_WITH", [])
@property @property
def auto_load(self) -> List[str]: def auto_load(self) -> list[str]:
return getattr(self.module, "AUTO_LOAD", []) return getattr(self.module, "AUTO_LOAD", [])
@property @property
def codeowners(self) -> List[str]: def codeowners(self) -> list[str]:
return getattr(self.module, "CODEOWNERS", []) return getattr(self.module, "CODEOWNERS", [])
@property @property
@ -87,7 +87,7 @@ class ComponentManifest:
return getattr(self.module, "FINAL_VALIDATE_SCHEMA", None) return getattr(self.module, "FINAL_VALIDATE_SCHEMA", None)
@property @property
def resources(self) -> List[FileResource]: def resources(self) -> list[FileResource]:
"""Return a list of all file resources defined in the package of this component. """Return a list of all file resources defined in the package of this component.
This will return all cpp source files that are located in the same folder as the This will return all cpp source files that are located in the same folder as the
@ -106,7 +106,7 @@ class ComponentManifest:
class ComponentMetaFinder(importlib.abc.MetaPathFinder): class ComponentMetaFinder(importlib.abc.MetaPathFinder):
def __init__( def __init__(
self, components_path: Path, allowed_components: Optional[List[str]] = None self, components_path: Path, allowed_components: Optional[list[str]] = None
) -> None: ) -> None:
self._allowed_components = allowed_components self._allowed_components = allowed_components
self._finders = [] self._finders = []
@ -117,7 +117,7 @@ class ComponentMetaFinder(importlib.abc.MetaPathFinder):
continue continue
self._finders.append(finder) self._finders.append(finder)
def find_spec(self, fullname: str, path: Optional[List[str]], target=None): def find_spec(self, fullname: str, path: Optional[list[str]], target=None):
if not fullname.startswith("esphome.components."): if not fullname.startswith("esphome.components."):
return None return None
parts = fullname.split(".") parts = fullname.split(".")
@ -144,7 +144,7 @@ def clear_component_meta_finders():
def install_meta_finder( def install_meta_finder(
components_path: Path, allowed_components: Optional[List[str]] = None components_path: Path, allowed_components: Optional[list[str]] = None
): ):
sys.meta_path.insert(0, ComponentMetaFinder(components_path, allowed_components)) sys.meta_path.insert(0, ComponentMetaFinder(components_path, allowed_components))

View File

@ -1,6 +1,6 @@
from dataclasses import dataclass from dataclasses import dataclass
import json import json
from typing import List, Union from typing import Union
from pathlib import Path from pathlib import Path
import logging import logging
@ -310,7 +310,7 @@ class IDEData:
return str(Path(self.firmware_elf_path).with_suffix(".bin")) return str(Path(self.firmware_elf_path).with_suffix(".bin"))
@property @property
def extra_flash_images(self) -> List[FlashImage]: def extra_flash_images(self) -> list[FlashImage]:
return [ return [
FlashImage(path=entry["path"], offset=entry["offset"]) FlashImage(path=entry["path"], offset=entry["offset"])
for entry in self.raw["extra"]["flash_images"] for entry in self.raw["extra"]["flash_images"]

View File

@ -4,7 +4,7 @@ from datetime import datetime
import json import json
import logging import logging
import os import os
from typing import Any, Optional, List from typing import Optional
from esphome import const from esphome import const
from esphome.core import CORE from esphome.core import CORE
@ -15,19 +15,19 @@ from esphome.types import CoreType
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def storage_path(): # type: () -> str def storage_path() -> str:
return CORE.relative_internal_path(f"{CORE.config_filename}.json") return CORE.relative_internal_path(f"{CORE.config_filename}.json")
def ext_storage_path(base_path, config_filename): # type: (str, str) -> str def ext_storage_path(base_path: str, config_filename: str) -> str:
return os.path.join(base_path, ".esphome", f"{config_filename}.json") return os.path.join(base_path, ".esphome", f"{config_filename}.json")
def esphome_storage_path(base_path): # type: (str) -> str def esphome_storage_path(base_path: str) -> str:
return os.path.join(base_path, ".esphome", "esphome.json") return os.path.join(base_path, ".esphome", "esphome.json")
def trash_storage_path(base_path): # type: (str) -> str def trash_storage_path(base_path: str) -> str:
return os.path.join(base_path, ".esphome", "trash") return os.path.join(base_path, ".esphome", "trash")
@ -49,29 +49,29 @@ class StorageJSON:
): ):
# Version of the storage JSON schema # Version of the storage JSON schema
assert storage_version is None or isinstance(storage_version, int) assert storage_version is None or isinstance(storage_version, int)
self.storage_version = storage_version # type: int self.storage_version: int = storage_version
# The name of the node # The name of the node
self.name = name # type: str self.name: str = name
# The comment of the node # The comment of the node
self.comment = comment # type: str self.comment: str = comment
# The esphome version this was compiled with # The esphome version this was compiled with
self.esphome_version = esphome_version # type: str self.esphome_version: str = esphome_version
# The version of the file in src/main.cpp - Used to migrate the file # The version of the file in src/main.cpp - Used to migrate the file
assert src_version is None or isinstance(src_version, int) assert src_version is None or isinstance(src_version, int)
self.src_version = src_version # type: int self.src_version: int = src_version
# Address of the ESP, for example livingroom.local or a static IP # Address of the ESP, for example livingroom.local or a static IP
self.address = address # type: str self.address: str = address
# Web server port of the ESP, for example 80 # Web server port of the ESP, for example 80
assert web_port is None or isinstance(web_port, int) assert web_port is None or isinstance(web_port, int)
self.web_port = web_port # type: int self.web_port: int = web_port
# The type of hardware in use, like "ESP32", "ESP32C3", "ESP8266", etc. # The type of hardware in use, like "ESP32", "ESP32C3", "ESP8266", etc.
self.target_platform = target_platform # type: str self.target_platform: str = target_platform
# The absolute path to the platformio project # The absolute path to the platformio project
self.build_path = build_path # type: str self.build_path: str = build_path
# The absolute path to the firmware binary # The absolute path to the firmware binary
self.firmware_bin_path = firmware_bin_path # type: str self.firmware_bin_path: str = firmware_bin_path
# A list of strings of names of loaded integrations # A list of strings of names of loaded integrations
self.loaded_integrations = loaded_integrations # type: List[str] self.loaded_integrations: list[str] = loaded_integrations
self.loaded_integrations.sort() self.loaded_integrations.sort()
def as_dict(self): def as_dict(self):
@ -97,8 +97,8 @@ class StorageJSON:
@staticmethod @staticmethod
def from_esphome_core( def from_esphome_core(
esph, old esph: CoreType, old: Optional["StorageJSON"]
): # type: (CoreType, Optional[StorageJSON]) -> StorageJSON ) -> "StorageJSON":
hardware = esph.target_platform.upper() hardware = esph.target_platform.upper()
if esph.is_esp32: if esph.is_esp32:
from esphome.components import esp32 from esphome.components import esp32
@ -135,7 +135,7 @@ class StorageJSON:
) )
@staticmethod @staticmethod
def _load_impl(path): # type: (str) -> Optional[StorageJSON] def _load_impl(path: str) -> Optional["StorageJSON"]:
with codecs.open(path, "r", encoding="utf-8") as f_handle: with codecs.open(path, "r", encoding="utf-8") as f_handle:
storage = json.load(f_handle) storage = json.load(f_handle)
storage_version = storage["storage_version"] storage_version = storage["storage_version"]
@ -166,13 +166,13 @@ class StorageJSON:
) )
@staticmethod @staticmethod
def load(path): # type: (str) -> Optional[StorageJSON] def load(path: str) -> Optional["StorageJSON"]:
try: try:
return StorageJSON._load_impl(path) return StorageJSON._load_impl(path)
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
return None return None
def __eq__(self, o): # type: (Any) -> bool def __eq__(self, o) -> bool:
return isinstance(o, StorageJSON) and self.as_dict() == o.as_dict() return isinstance(o, StorageJSON) and self.as_dict() == o.as_dict()
@ -182,15 +182,15 @@ class EsphomeStorageJSON:
): ):
# Version of the storage JSON schema # Version of the storage JSON schema
assert storage_version is None or isinstance(storage_version, int) assert storage_version is None or isinstance(storage_version, int)
self.storage_version = storage_version # type: int self.storage_version: int = storage_version
# The cookie secret for the dashboard # The cookie secret for the dashboard
self.cookie_secret = cookie_secret # type: str self.cookie_secret: str = cookie_secret
# The last time ESPHome checked for an update as an isoformat encoded str # The last time ESPHome checked for an update as an isoformat encoded str
self.last_update_check_str = last_update_check # type: str self.last_update_check_str: str = last_update_check
# Cache of the version gotten in the last version check # Cache of the version gotten in the last version check
self.remote_version = remote_version # type: Optional[str] self.remote_version: Optional[str] = remote_version
def as_dict(self): # type: () -> dict def as_dict(self) -> dict:
return { return {
"storage_version": self.storage_version, "storage_version": self.storage_version,
"cookie_secret": self.cookie_secret, "cookie_secret": self.cookie_secret,
@ -199,24 +199,24 @@ class EsphomeStorageJSON:
} }
@property @property
def last_update_check(self): # type: () -> Optional[datetime] def last_update_check(self) -> Optional[datetime]:
try: try:
return datetime.strptime(self.last_update_check_str, "%Y-%m-%dT%H:%M:%S") return datetime.strptime(self.last_update_check_str, "%Y-%m-%dT%H:%M:%S")
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
return None return None
@last_update_check.setter @last_update_check.setter
def last_update_check(self, new): # type: (datetime) -> None def last_update_check(self, new: datetime) -> None:
self.last_update_check_str = new.strftime("%Y-%m-%dT%H:%M:%S") self.last_update_check_str = new.strftime("%Y-%m-%dT%H:%M:%S")
def to_json(self): # type: () -> dict def to_json(self) -> dict:
return f"{json.dumps(self.as_dict(), indent=2)}\n" return f"{json.dumps(self.as_dict(), indent=2)}\n"
def save(self, path): # type: (str) -> None def save(self, path: str) -> None:
write_file_if_changed(path, self.to_json()) write_file_if_changed(path, self.to_json())
@staticmethod @staticmethod
def _load_impl(path): # type: (str) -> Optional[EsphomeStorageJSON] def _load_impl(path: str) -> Optional["EsphomeStorageJSON"]:
with codecs.open(path, "r", encoding="utf-8") as f_handle: with codecs.open(path, "r", encoding="utf-8") as f_handle:
storage = json.load(f_handle) storage = json.load(f_handle)
storage_version = storage["storage_version"] storage_version = storage["storage_version"]
@ -228,14 +228,14 @@ class EsphomeStorageJSON:
) )
@staticmethod @staticmethod
def load(path): # type: (str) -> Optional[EsphomeStorageJSON] def load(path: str) -> Optional["EsphomeStorageJSON"]:
try: try:
return EsphomeStorageJSON._load_impl(path) return EsphomeStorageJSON._load_impl(path)
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
return None return None
@staticmethod @staticmethod
def get_default(): # type: () -> EsphomeStorageJSON def get_default() -> "EsphomeStorageJSON":
return EsphomeStorageJSON( return EsphomeStorageJSON(
storage_version=1, storage_version=1,
cookie_secret=binascii.hexlify(os.urandom(64)).decode(), cookie_secret=binascii.hexlify(os.urandom(64)).decode(),
@ -243,5 +243,5 @@ class EsphomeStorageJSON:
remote_version=None, remote_version=None,
) )
def __eq__(self, o): # type: (Any) -> bool def __eq__(self, o) -> bool:
return isinstance(o, EsphomeStorageJSON) and self.as_dict() == o.as_dict() return isinstance(o, EsphomeStorageJSON) and self.as_dict() == o.as_dict()

View File

@ -1,5 +1,5 @@
"""This helper module tracks commonly used types in the esphome python codebase.""" """This helper module tracks commonly used types in the esphome python codebase."""
from typing import Dict, Union, List from typing import Union
from esphome.core import ID, Lambda, EsphomeCore from esphome.core import ID, Lambda, EsphomeCore
@ -8,11 +8,11 @@ ConfigFragmentType = Union[
int, int,
float, float,
None, None,
Dict[Union[str, int], "ConfigFragmentType"], dict[Union[str, int], "ConfigFragmentType"],
List["ConfigFragmentType"], list["ConfigFragmentType"],
ID, ID,
Lambda, Lambda,
] ]
ConfigType = Dict[str, ConfigFragmentType] ConfigType = dict[str, ConfigFragmentType]
CoreType = EsphomeCore CoreType = EsphomeCore
ConfigPathType = Union[str, int] ConfigPathType = Union[str, int]

View File

@ -1,5 +1,4 @@
import typing from typing import Union
from typing import Union, List
import collections import collections
import io import io
@ -35,7 +34,7 @@ class RegistryEntry:
return Schema(self.raw_schema) return Schema(self.raw_schema)
class Registry(dict): class Registry(dict[str, RegistryEntry]):
def __init__(self, base_schema=None, type_id_key=None): def __init__(self, base_schema=None, type_id_key=None):
super().__init__() super().__init__()
self.base_schema = base_schema or {} self.base_schema = base_schema or {}
@ -242,7 +241,7 @@ def is_dev_esphome_version():
return "dev" in const.__version__ return "dev" in const.__version__
def parse_esphome_version() -> typing.Tuple[int, int, int]: def parse_esphome_version() -> tuple[int, int, int]:
match = re.match(r"^(\d+).(\d+).(\d+)(-dev\d*|b\d*)?$", const.__version__) match = re.match(r"^(\d+).(\d+).(\d+)(-dev\d*|b\d*)?$", const.__version__)
if match is None: if match is None:
raise ValueError(f"Failed to parse ESPHome version '{const.__version__}'") raise ValueError(f"Failed to parse ESPHome version '{const.__version__}'")
@ -282,7 +281,7 @@ class SerialPort:
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py # from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
def get_serial_ports() -> List[SerialPort]: def get_serial_ports() -> list[SerialPort]:
from serial.tools.list_ports import comports from serial.tools.list_ports import comports
result = [] result = []

Some files were not shown because too many files have changed in this diff Show More