Merge branch 'dev' into optolink

This commit is contained in:
j0ta29 2023-03-27 11:03:37 +02:00 committed by GitHub
commit 7e69e68e74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 270 additions and 70 deletions

View file

@ -18,7 +18,7 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v7 - uses: actions/stale@v8
with: with:
days-before-pr-stale: 90 days-before-pr-stale: 90
days-before-pr-close: 7 days-before-pr-close: 7
@ -38,7 +38,7 @@ jobs:
close-issues: close-issues:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v7 - uses: actions/stale@v8
with: with:
days-before-pr-stale: -1 days-before-pr-stale: -1
days-before-pr-close: -1 days-before-pr-close: -1

View file

@ -1156,6 +1156,7 @@ enum BluetoothDeviceRequestType {
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3; BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4; BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4;
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5; BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5;
BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6;
} }
message BluetoothDeviceRequest { message BluetoothDeviceRequest {
@ -1359,3 +1360,13 @@ message BluetoothDeviceUnpairingResponse {
bool success = 2; bool success = 2;
int32 error = 3; int32 error = 3;
} }
message BluetoothDeviceClearCacheResponse {
option (id) = 88;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
bool success = 2;
int32 error = 3;
}

View file

@ -180,7 +180,8 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
ListEntitiesBinarySensorResponse msg; ListEntitiesBinarySensorResponse msg;
msg.object_id = binary_sensor->get_object_id(); msg.object_id = binary_sensor->get_object_id();
msg.key = binary_sensor->get_object_id_hash(); msg.key = binary_sensor->get_object_id_hash();
msg.name = binary_sensor->get_name(); if (binary_sensor->has_own_name())
msg.name = binary_sensor->get_name();
msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor); msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor);
msg.device_class = binary_sensor->get_device_class(); msg.device_class = binary_sensor->get_device_class();
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
@ -212,7 +213,8 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
ListEntitiesCoverResponse msg; ListEntitiesCoverResponse msg;
msg.key = cover->get_object_id_hash(); msg.key = cover->get_object_id_hash();
msg.object_id = cover->get_object_id(); msg.object_id = cover->get_object_id();
msg.name = cover->get_name(); if (cover->has_own_name())
msg.name = cover->get_name();
msg.unique_id = get_default_unique_id("cover", cover); msg.unique_id = get_default_unique_id("cover", cover);
msg.assumed_state = traits.get_is_assumed_state(); msg.assumed_state = traits.get_is_assumed_state();
msg.supports_position = traits.get_supports_position(); msg.supports_position = traits.get_supports_position();
@ -275,7 +277,8 @@ bool APIConnection::send_fan_info(fan::Fan *fan) {
ListEntitiesFanResponse msg; ListEntitiesFanResponse msg;
msg.key = fan->get_object_id_hash(); msg.key = fan->get_object_id_hash();
msg.object_id = fan->get_object_id(); msg.object_id = fan->get_object_id();
msg.name = fan->get_name(); if (fan->has_own_name())
msg.name = fan->get_name();
msg.unique_id = get_default_unique_id("fan", fan); msg.unique_id = get_default_unique_id("fan", fan);
msg.supports_oscillation = traits.supports_oscillation(); msg.supports_oscillation = traits.supports_oscillation();
msg.supports_speed = traits.supports_speed(); msg.supports_speed = traits.supports_speed();
@ -337,7 +340,8 @@ bool APIConnection::send_light_info(light::LightState *light) {
ListEntitiesLightResponse msg; ListEntitiesLightResponse msg;
msg.key = light->get_object_id_hash(); msg.key = light->get_object_id_hash();
msg.object_id = light->get_object_id(); msg.object_id = light->get_object_id();
msg.name = light->get_name(); if (light->has_own_name())
msg.name = light->get_name();
msg.unique_id = get_default_unique_id("light", light); msg.unique_id = get_default_unique_id("light", light);
msg.disabled_by_default = light->is_disabled_by_default(); msg.disabled_by_default = light->is_disabled_by_default();
@ -418,7 +422,8 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
ListEntitiesSensorResponse msg; ListEntitiesSensorResponse msg;
msg.key = sensor->get_object_id_hash(); msg.key = sensor->get_object_id_hash();
msg.object_id = sensor->get_object_id(); msg.object_id = sensor->get_object_id();
msg.name = sensor->get_name(); if (sensor->has_own_name())
msg.name = sensor->get_name();
msg.unique_id = sensor->unique_id(); msg.unique_id = sensor->unique_id();
if (msg.unique_id.empty()) if (msg.unique_id.empty())
msg.unique_id = get_default_unique_id("sensor", sensor); msg.unique_id = get_default_unique_id("sensor", sensor);
@ -448,7 +453,8 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
ListEntitiesSwitchResponse msg; ListEntitiesSwitchResponse msg;
msg.key = a_switch->get_object_id_hash(); msg.key = a_switch->get_object_id_hash();
msg.object_id = a_switch->get_object_id(); msg.object_id = a_switch->get_object_id();
msg.name = a_switch->get_name(); if (a_switch->has_own_name())
msg.name = a_switch->get_name();
msg.unique_id = get_default_unique_id("switch", a_switch); msg.unique_id = get_default_unique_id("switch", a_switch);
msg.icon = a_switch->get_icon(); msg.icon = a_switch->get_icon();
msg.assumed_state = a_switch->assumed_state(); msg.assumed_state = a_switch->assumed_state();
@ -533,7 +539,8 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
ListEntitiesClimateResponse msg; ListEntitiesClimateResponse msg;
msg.key = climate->get_object_id_hash(); msg.key = climate->get_object_id_hash();
msg.object_id = climate->get_object_id(); msg.object_id = climate->get_object_id();
msg.name = climate->get_name(); if (climate->has_own_name())
msg.name = climate->get_name();
msg.unique_id = get_default_unique_id("climate", climate); msg.unique_id = get_default_unique_id("climate", climate);
msg.disabled_by_default = climate->is_disabled_by_default(); msg.disabled_by_default = climate->is_disabled_by_default();
@ -611,7 +618,8 @@ bool APIConnection::send_number_info(number::Number *number) {
ListEntitiesNumberResponse msg; ListEntitiesNumberResponse msg;
msg.key = number->get_object_id_hash(); msg.key = number->get_object_id_hash();
msg.object_id = number->get_object_id(); msg.object_id = number->get_object_id();
msg.name = number->get_name(); if (number->has_own_name())
msg.name = number->get_name();
msg.unique_id = get_default_unique_id("number", number); msg.unique_id = get_default_unique_id("number", number);
msg.icon = number->get_icon(); msg.icon = number->get_icon();
msg.disabled_by_default = number->is_disabled_by_default(); msg.disabled_by_default = number->is_disabled_by_default();
@ -652,7 +660,8 @@ bool APIConnection::send_select_info(select::Select *select) {
ListEntitiesSelectResponse msg; ListEntitiesSelectResponse msg;
msg.key = select->get_object_id_hash(); msg.key = select->get_object_id_hash();
msg.object_id = select->get_object_id(); msg.object_id = select->get_object_id();
msg.name = select->get_name(); if (select->has_own_name())
msg.name = select->get_name();
msg.unique_id = get_default_unique_id("select", select); msg.unique_id = get_default_unique_id("select", select);
msg.icon = select->get_icon(); msg.icon = select->get_icon();
msg.disabled_by_default = select->is_disabled_by_default(); msg.disabled_by_default = select->is_disabled_by_default();
@ -679,7 +688,8 @@ bool APIConnection::send_button_info(button::Button *button) {
ListEntitiesButtonResponse msg; ListEntitiesButtonResponse msg;
msg.key = button->get_object_id_hash(); msg.key = button->get_object_id_hash();
msg.object_id = button->get_object_id(); msg.object_id = button->get_object_id();
msg.name = button->get_name(); if (button->has_own_name())
msg.name = button->get_name();
msg.unique_id = get_default_unique_id("button", button); msg.unique_id = get_default_unique_id("button", button);
msg.icon = button->get_icon(); msg.icon = button->get_icon();
msg.disabled_by_default = button->is_disabled_by_default(); msg.disabled_by_default = button->is_disabled_by_default();
@ -710,7 +720,8 @@ bool APIConnection::send_lock_info(lock::Lock *a_lock) {
ListEntitiesLockResponse msg; ListEntitiesLockResponse msg;
msg.key = a_lock->get_object_id_hash(); msg.key = a_lock->get_object_id_hash();
msg.object_id = a_lock->get_object_id(); msg.object_id = a_lock->get_object_id();
msg.name = a_lock->get_name(); if (a_lock->has_own_name())
msg.name = a_lock->get_name();
msg.unique_id = get_default_unique_id("lock", a_lock); msg.unique_id = get_default_unique_id("lock", a_lock);
msg.icon = a_lock->get_icon(); msg.icon = a_lock->get_icon();
msg.assumed_state = a_lock->traits.get_assumed_state(); msg.assumed_state = a_lock->traits.get_assumed_state();
@ -755,7 +766,8 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play
ListEntitiesMediaPlayerResponse msg; ListEntitiesMediaPlayerResponse msg;
msg.key = media_player->get_object_id_hash(); msg.key = media_player->get_object_id_hash();
msg.object_id = media_player->get_object_id(); msg.object_id = media_player->get_object_id();
msg.name = media_player->get_name(); if (media_player->has_own_name())
msg.name = media_player->get_name();
msg.unique_id = get_default_unique_id("media_player", media_player); msg.unique_id = get_default_unique_id("media_player", media_player);
msg.icon = media_player->get_icon(); msg.icon = media_player->get_icon();
msg.disabled_by_default = media_player->is_disabled_by_default(); msg.disabled_by_default = media_player->is_disabled_by_default();
@ -799,7 +811,8 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
ListEntitiesCameraResponse msg; ListEntitiesCameraResponse msg;
msg.key = camera->get_object_id_hash(); msg.key = camera->get_object_id_hash();
msg.object_id = camera->get_object_id(); msg.object_id = camera->get_object_id();
msg.name = camera->get_name(); if (camera->has_own_name())
msg.name = camera->get_name();
msg.unique_id = get_default_unique_id("camera", camera); msg.unique_id = get_default_unique_id("camera", camera);
msg.disabled_by_default = camera->is_disabled_by_default(); msg.disabled_by_default = camera->is_disabled_by_default();
msg.icon = camera->get_icon(); msg.icon = camera->get_icon();
@ -953,7 +966,9 @@ 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.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 4 : 1; resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active()
? bluetooth_proxy::ACTIVE_CONNECTIONS_VERSION
: bluetooth_proxy::PASSIVE_ONLY_VERSION;
#endif #endif
return resp; return resp;
} }

View file

@ -400,6 +400,8 @@ const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::Bluet
return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE"; return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE";
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE: case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE:
return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE"; return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE";
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE:
return "BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE";
default: default:
return "UNKNOWN"; return "UNKNOWN";
} }
@ -6060,6 +6062,49 @@ void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool BluetoothDeviceClearCacheResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->success = value.as_bool();
return true;
}
case 3: {
this->error = value.as_int32();
return true;
}
default:
return false;
}
}
void BluetoothDeviceClearCacheResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_bool(2, this->success);
buffer.encode_int32(3, this->error);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothDeviceClearCacheResponse {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" success: ");
out.append(YESNO(this->success));
out.append("\n");
out.append(" error: ");
sprintf(buffer, "%d", this->error);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -163,6 +163,7 @@ enum BluetoothDeviceRequestType : uint32_t {
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3, BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3,
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4, BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4,
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5, BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5,
BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6,
}; };
} // namespace enums } // namespace enums
@ -1554,6 +1555,19 @@ class BluetoothDeviceUnpairingResponse : public ProtoMessage {
protected: protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class BluetoothDeviceClearCacheResponse : public ProtoMessage {
public:
uint64_t address{0};
bool success{false};
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;
};
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -441,6 +441,14 @@ bool APIServerConnectionBase::send_bluetooth_device_unpairing_response(const Blu
return this->send_message_<BluetoothDeviceUnpairingResponse>(msg, 86); return this->send_message_<BluetoothDeviceUnpairingResponse>(msg, 86);
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_device_clear_cache_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothDeviceClearCacheResponse>(msg, 88);
}
#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: {

View file

@ -215,6 +215,9 @@ class APIServerConnectionBase : public ProtoService {
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg); bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &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;

View file

@ -331,6 +331,17 @@ void APIServer::send_bluetooth_device_unpairing(uint64_t address, bool success,
} }
} }
void APIServer::send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error) {
BluetoothDeviceClearCacheResponse call;
call.address = address;
call.success = success;
call.error = error;
for (auto &client : this->clients_) {
client->send_bluetooth_device_clear_cache_response(call);
}
}
void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) { void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) {
BluetoothConnectionsFreeResponse call; BluetoothConnectionsFreeResponse call;
call.free = free; call.free = free;

View file

@ -80,6 +80,7 @@ class APIServer : public Component, public Controller {
void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK); void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
void send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK); void send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK);
void send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK); void send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
void send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK);
void send_bluetooth_connections_free(uint8_t free, uint8_t limit); void send_bluetooth_connections_free(uint8_t free, uint8_t limit);
void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call); void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call);
void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call); void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call);

View file

@ -306,6 +306,13 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
api::global_api_server->send_bluetooth_device_unpairing(msg.address, ret == ESP_OK, ret); api::global_api_server->send_bluetooth_device_unpairing(msg.address, ret == ESP_OK, ret);
break; break;
} }
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: {
esp_bd_addr_t address;
uint64_to_bd_addr(msg.address, address);
esp_err_t ret = esp_ble_gattc_cache_clean(address);
api::global_api_server->send_bluetooth_device_clear_cache(msg.address, ret == ESP_OK, ret);
break;
}
} }
} }

View file

@ -68,6 +68,14 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
// Version 1: Initial version without active connections
// Version 2: Support for active connections
// Version 3: New connection API
// Version 4: Pairing support
// Version 5: Cache clear support
static const uint32_t ACTIVE_CONNECTIONS_VERSION = 5;
static const uint32_t PASSIVE_ONLY_VERSION = 1;
} // namespace bluetooth_proxy } // namespace bluetooth_proxy
} // namespace esphome } // namespace esphome

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 i2c from esphome.components import i2c, esp32
from esphome.const import CONF_ID from esphome.const import CONF_ID
CODEOWNERS = ["@trvrnrth"] CODEOWNERS = ["@trvrnrth"]
@ -32,22 +32,31 @@ BME680BSECComponent = bme680_bsec_ns.class_(
"BME680BSECComponent", cg.Component, i2c.I2CDevice "BME680BSECComponent", cg.Component, i2c.I2CDevice
) )
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.All(
{ cv.Schema(
cv.GenerateID(): cv.declare_id(BME680BSECComponent), {
cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature, cv.GenerateID(): cv.declare_id(BME680BSECComponent),
cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum( cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature,
IAQ_MODE_OPTIONS, upper=True cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum(
), IAQ_MODE_OPTIONS, upper=True
cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum( ),
SAMPLE_RATE_OPTIONS, upper=True cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum(
), SAMPLE_RATE_OPTIONS, upper=True
cv.Optional( ),
CONF_STATE_SAVE_INTERVAL, default="6hours" cv.Optional(
): cv.positive_time_period_minutes, CONF_STATE_SAVE_INTERVAL, default="6hours"
}, ): cv.positive_time_period_minutes,
}
).extend(i2c.i2c_device_schema(0x76)),
cv.only_with_arduino, cv.only_with_arduino,
).extend(i2c.i2c_device_schema(0x76)) cv.Any(
cv.only_on_esp8266,
cv.All(
cv.only_on_esp32,
esp32.only_on_variant(supported=[esp32.const.VARIANT_ESP32]),
),
),
)
async def to_code(config): async def to_code(config):

View file

@ -14,12 +14,15 @@ from .. import copy_ns
CopySelect = copy_ns.class_("CopySelect", select.Select, cg.Component) CopySelect = copy_ns.class_("CopySelect", select.Select, cg.Component)
CONFIG_SCHEMA = select.SELECT_SCHEMA.extend( CONFIG_SCHEMA = (
{ select.select_schema(CopySelect)
cv.GenerateID(): cv.declare_id(CopySelect), .extend(
cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select), {
} cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select),
).extend(cv.COMPONENT_SCHEMA) }
)
.extend(cv.COMPONENT_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = cv.All( FINAL_VALIDATE_SCHEMA = cv.All(
inherit_property_from(CONF_ICON, CONF_SOURCE_ID), inherit_property_from(CONF_ICON, CONF_SOURCE_ID),

View file

@ -156,7 +156,7 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
{ {
cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number,
cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All(
cv.frequency, cv.Range(min=10e6, max=20e6) cv.frequency, cv.Range(min=8e6, max=20e6)
), ),
} }
), ),

View file

@ -22,20 +22,12 @@ ESP32ImprovComponent = esp32_improv_ns.class_(
) )
def validate_none_(value):
if value in ("none", "None"):
return None
if cv.boolean(value) is False:
return None
raise cv.Invalid("Must be none")
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(ESP32ImprovComponent), cv.GenerateID(): cv.declare_id(ESP32ImprovComponent),
cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble_server.BLEServer), cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble_server.BLEServer),
cv.Required(CONF_AUTHORIZER): cv.Any( cv.Required(CONF_AUTHORIZER): cv.Any(
validate_none_, cv.use_id(binary_sensor.BinarySensor) cv.none, cv.use_id(binary_sensor.BinarySensor)
), ),
cv.Optional(CONF_STATUS_INDICATOR): cv.use_id(output.BinaryOutput), cv.Optional(CONF_STATUS_INDICATOR): cv.use_id(output.BinaryOutput),
cv.Optional( cv.Optional(

View file

@ -64,9 +64,10 @@ INTEGER_SENSOR_VALUE_TYPE = {
} }
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
select.SELECT_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( select.select_schema(ModbusSelect)
.extend(cv.COMPONENT_SCHEMA)
.extend(
{ {
cv.GenerateID(): cv.declare_id(ModbusSelect),
cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
cv.Required(CONF_ADDRESS): cv.positive_int, cv.Required(CONF_ADDRESS): cv.positive_int,
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum( cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(

View file

@ -204,14 +204,13 @@ def number_schema(
async def setup_number_core_( async def setup_number_core_(
var, config, *, min_value: float, max_value: float, step: Optional[float] var, config, *, min_value: float, max_value: float, step: float
): ):
await setup_entity(var, config) await setup_entity(var, config)
cg.add(var.traits.set_min_value(min_value)) cg.add(var.traits.set_min_value(min_value))
cg.add(var.traits.set_max_value(max_value)) cg.add(var.traits.set_max_value(max_value))
if step is not None: cg.add(var.traits.set_step(step))
cg.add(var.traits.set_step(step))
cg.add(var.traits.set_mode(config[CONF_MODE])) cg.add(var.traits.set_mode(config[CONF_MODE]))
@ -239,7 +238,7 @@ async def setup_number_core_(
async def register_number( async def register_number(
var, config, *, min_value: float, max_value: float, step: Optional[float] = None var, config, *, min_value: float, max_value: float, step: float
): ):
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)

View file

@ -3,6 +3,8 @@ import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.components import mqtt from esphome.components import mqtt
from esphome.const import ( from esphome.const import (
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID, CONF_ID,
CONF_ON_VALUE, CONF_ON_VALUE,
CONF_OPTION, CONF_OPTION,
@ -14,6 +16,7 @@ from esphome.const import (
CONF_INDEX, CONF_INDEX,
) )
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_helpers import setup_entity from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
@ -43,8 +46,6 @@ SELECT_OPERATION_OPTIONS = {
"LAST": SelectOperation.SELECT_OP_LAST, "LAST": SelectOperation.SELECT_OP_LAST,
} }
icon = cv.icon
SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{ {
@ -58,6 +59,30 @@ SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e
} }
) )
_UNDEF = object()
def select_schema(
class_: MockObjClass = _UNDEF,
*,
entity_category: str = _UNDEF,
icon: str = _UNDEF,
):
schema = cv.Schema({})
if class_ is not _UNDEF:
schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)})
if entity_category is not _UNDEF:
schema = schema.extend(
{
cv.Optional(
CONF_ENTITY_CATEGORY, default=entity_category
): cv.entity_category
}
)
if icon is not _UNDEF:
schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon})
return SELECT_SCHEMA.extend(schema)
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)

View file

@ -42,6 +42,9 @@ void SX1509Component::dump_config() {
void SX1509Component::loop() { void SX1509Component::loop() {
if (this->has_keypad_) { if (this->has_keypad_) {
if (millis() - this->last_loop_timestamp_ < min_loop_period_)
return;
this->last_loop_timestamp_ = millis();
uint16_t key_data = this->read_key_data(); uint16_t key_data = this->read_key_data();
for (auto *binary_sensor : this->keypad_binary_sensors_) for (auto *binary_sensor : this->keypad_binary_sensors_)
binary_sensor->process(key_data); binary_sensor->process(key_data);

View file

@ -69,6 +69,9 @@ class SX1509Component : public Component, public i2c::I2CDevice {
uint8_t debounce_time_ = 1; uint8_t debounce_time_ = 1;
std::vector<SX1509Processor *> keypad_binary_sensors_; std::vector<SX1509Processor *> keypad_binary_sensors_;
uint32_t last_loop_timestamp_ = 0;
const uint32_t min_loop_period_ = 15; // ms
void setup_keypad_(); void setup_keypad_();
void set_debounce_config_(uint8_t config_value); void set_debounce_config_(uint8_t config_value);
void set_debounce_time_(uint8_t time); void set_debounce_time_(uint8_t time);

View file

@ -43,9 +43,9 @@ def validate(config):
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
select.SELECT_SCHEMA.extend( select.select_schema(TemplateSelect)
.extend(
{ {
cv.GenerateID(): cv.declare_id(TemplateSelect),
cv.Required(CONF_OPTIONS): cv.All( cv.Required(CONF_OPTIONS): cv.All(
cv.ensure_list(cv.string_strict), cv.Length(min=1) cv.ensure_list(cv.string_strict), cv.Length(min=1)
), ),
@ -55,7 +55,8 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_INITIAL_OPTION): cv.string_strict, cv.Optional(CONF_INITIAL_OPTION): cv.string_strict,
cv.Optional(CONF_RESTORE_VALUE): cv.boolean, cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
} }
).extend(cv.polling_component_schema("60s")), )
.extend(cv.polling_component_schema("60s")),
validate, validate,
) )

View file

@ -25,15 +25,18 @@ def ensure_option_map(value):
return value return value
CONFIG_SCHEMA = select.SELECT_SCHEMA.extend( CONFIG_SCHEMA = (
{ select.select_schema(TuyaSelect)
cv.GenerateID(): cv.declare_id(TuyaSelect), .extend(
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), {
cv.Required(CONF_ENUM_DATAPOINT): cv.uint8_t, cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
cv.Required(CONF_OPTIONS): ensure_option_map, cv.Required(CONF_ENUM_DATAPOINT): cv.uint8_t,
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, cv.Required(CONF_OPTIONS): ensure_option_map,
} cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
).extend(cv.COMPONENT_SCHEMA) }
)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config): async def to_code(config):

View file

@ -1514,6 +1514,8 @@ def _entity_base_validator(config):
config[CONF_NAME] = id.id config[CONF_NAME] = id.id
config[CONF_INTERNAL] = True config[CONF_INTERNAL] = True
return config return config
if config[CONF_NAME] is None:
config[CONF_NAME] = ""
return config return config
@ -1573,6 +1575,23 @@ def validate_registry_entry(name, registry):
return validator return validator
def none(value):
if value in ("none", "None"):
return None
if boolean(value) is False:
return None
raise Invalid("Must be none")
def requires_friendly_name(message):
def validate(value):
if CORE.friendly_name is None:
raise Invalid(message)
return value
return validate
def validate_registry(name, registry): def validate_registry(name, registry):
return ensure_list(validate_registry_entry(name, registry)) return ensure_list(validate_registry_entry(name, registry))
@ -1632,7 +1651,15 @@ MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend(
ENTITY_BASE_SCHEMA = Schema( ENTITY_BASE_SCHEMA = Schema(
{ {
Optional(CONF_NAME): string, Optional(CONF_NAME): Any(
All(
none,
requires_friendly_name(
"Name cannot be None when esphome->friendly_name is not set!"
),
),
string,
),
Optional(CONF_INTERNAL): boolean, Optional(CONF_INTERNAL): boolean,
Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean,
Optional(CONF_ICON): icon, Optional(CONF_ICON): icon,

View file

@ -1,4 +1,5 @@
#include "esphome/core/entity_base.h" #include "esphome/core/entity_base.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
namespace esphome { namespace esphome {
@ -10,7 +11,13 @@ EntityBase::EntityBase(std::string name) : name_(std::move(name)) { this->calc_o
// Entity Name // Entity Name
const std::string &EntityBase::get_name() const { return this->name_; } const std::string &EntityBase::get_name() const { return this->name_; }
void EntityBase::set_name(const std::string &name) { void EntityBase::set_name(const std::string &name) {
this->name_ = name; if (name.empty()) {
this->name_ = App.get_friendly_name();
this->has_own_name_ = false;
} else {
this->name_ = name;
this->has_own_name_ = true;
}
this->calc_object_id_(); this->calc_object_id_();
} }

View file

@ -21,6 +21,9 @@ class EntityBase {
const std::string &get_name() const; const std::string &get_name() const;
void set_name(const std::string &name); void set_name(const std::string &name);
// Get whether this Entity has its own name or it should use the device friendly_name.
bool has_own_name() const { return this->has_own_name_; }
// Get the sanitized name of this Entity as an ID. Caching it internally. // Get the sanitized name of this Entity as an ID. Caching it internally.
const std::string &get_object_id(); const std::string &get_object_id();
@ -52,6 +55,7 @@ class EntityBase {
void calc_object_id_(); void calc_object_id_();
std::string name_; std::string name_;
bool has_own_name_{false};
std::string object_id_; std::string object_id_;
const char *icon_c_str_{nullptr}; const char *icon_c_str_{nullptr};
uint32_t object_id_hash_; uint32_t object_id_hash_;