mirror of
https://github.com/esphome/esphome.git
synced 2024-12-23 05:54:56 +01:00
Merge branch 'beta' into bump-2022.10.0
This commit is contained in:
commit
67c911c37f
86 changed files with 1547 additions and 813 deletions
1
.github/workflows/ci-docker.yml
vendored
1
.github/workflows/ci-docker.yml
vendored
|
@ -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"]
|
||||||
|
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -258,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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
{
|
{
|
||||||
|
|
|
@ -1298,3 +1298,31 @@ message BluetoothConnectionsFreeResponse {
|
||||||
uint32 free = 1;
|
uint32 free = 1;
|
||||||
uint32 limit = 2;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -5746,6 +5746,118 @@ void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const {
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
||||||
|
|
|
@ -1481,6 +1481,43 @@ class BluetoothConnectionsFreeResponse : 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 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
|
||||||
|
|
|
@ -401,6 +401,30 @@ bool APIServerConnectionBase::send_bluetooth_connections_free_response(const Blu
|
||||||
return this->send_message_<BluetoothConnectionsFreeResponse>(msg, 81);
|
return this->send_message_<BluetoothConnectionsFreeResponse>(msg, 81);
|
||||||
}
|
}
|
||||||
#endif
|
#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: {
|
||||||
|
|
|
@ -200,6 +200,15 @@ class APIServerConnectionBase : public ProtoService {
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
bool send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg);
|
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;
|
||||||
|
|
|
@ -324,11 +324,21 @@ void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadRespons
|
||||||
client->send_bluetooth_gatt_read_response(call);
|
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) {
|
void APIServer::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call) {
|
||||||
for (auto &client : this->clients_) {
|
for (auto &client : this->clients_) {
|
||||||
client->send_bluetooth_gatt_notify_data_response(call);
|
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) {
|
void APIServer::send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call) {
|
||||||
for (auto &client : this->clients_) {
|
for (auto &client : this->clients_) {
|
||||||
client->send_bluetooth_gatt_get_services_response(call);
|
client->send_bluetooth_gatt_get_services_response(call);
|
||||||
|
@ -342,6 +352,17 @@ void APIServer::send_bluetooth_gatt_services_done(uint64_t address) {
|
||||||
client->send_bluetooth_gatt_get_services_done_response(call);
|
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,
|
||||||
|
|
|
@ -78,9 +78,12 @@ class APIServer : public Component, public Controller {
|
||||||
void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error = ESP_OK);
|
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_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_notify_data_response(const BluetoothGATTNotifyDataResponse &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(const BluetoothGATTGetServicesResponse &call);
|
||||||
void send_bluetooth_gatt_services_done(uint64_t address);
|
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
|
||||||
|
|
|
@ -100,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]))
|
||||||
|
)
|
||||||
|
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
|
||||||
|
cg.add(
|
||||||
|
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||||
|
)
|
||||||
|
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||||
|
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
|
||||||
|
cg.add(var.set_service_uuid128(uuid128))
|
||||||
|
|
||||||
|
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||||
|
cg.add(
|
||||||
|
var.set_char_uuid16(
|
||||||
|
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||||
|
esp32_ble_tracker.bt_uuid32_format
|
||||||
|
):
|
||||||
|
cg.add(
|
||||||
|
var.set_char_uuid32(
|
||||||
|
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||||
|
esp32_ble_tracker.bt_uuid128_format
|
||||||
|
):
|
||||||
|
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
|
||||||
config[CONF_CHARACTERISTIC_UUID]
|
config[CONF_CHARACTERISTIC_UUID]
|
||||||
)
|
)
|
||||||
cg.add(var.set_char_uuid128(char_uuid128))
|
cg.add(var.set_char_uuid128(uuid128))
|
||||||
|
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -46,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;
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,13 @@ 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) {
|
||||||
|
BLEClientBase::gap_event_handler(event, param);
|
||||||
|
|
||||||
|
for (auto *node : this->nodes_)
|
||||||
|
node->gap_event_handler(event, param);
|
||||||
|
}
|
||||||
|
|
||||||
void BLEClient::set_state(espbt::ClientState state) {
|
void BLEClient::set_state(espbt::ClientState state) {
|
||||||
BLEClientBase::set_state(state);
|
BLEClientBase::set_state(state);
|
||||||
for (auto &node : nodes_)
|
for (auto &node : nodes_)
|
||||||
|
|
|
@ -27,7 +27,8 @@ 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
|
||||||
|
@ -51,6 +52,8 @@ class BLEClient : public BLEClientBase {
|
||||||
|
|
||||||
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;
|
||||||
bool parse_device(const espbt::ESPBTDevice &device) override;
|
bool parse_device(const espbt::ESPBTDevice &device) override;
|
||||||
|
|
||||||
void set_enabled(bool enabled);
|
void set_enabled(bool enabled);
|
||||||
|
|
|
@ -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,11 +33,29 @@ BLESensorNotifyTrigger = ble_client_ns.class_(
|
||||||
"BLESensorNotifyTrigger", automation.Trigger.template(cg.float_)
|
"BLESensorNotifyTrigger", automation.Trigger.template(cg.float_)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
BLEClientRssiSensor = ble_client_ns.class_(
|
||||||
|
"BLEClientRSSISensor", sensor.Sensor, cg.PollingComponent, ble_client.BLEClientNode
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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(
|
CONFIG_SCHEMA = cv.All(
|
||||||
sensor.sensor_schema(
|
checkType,
|
||||||
|
cv.typed_schema(
|
||||||
|
{
|
||||||
|
TYPE_CHARACTERISTIC: sensor.sensor_schema(
|
||||||
BLESensor,
|
BLESensor,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
)
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||||
.extend(
|
.extend(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||||
|
@ -47,13 +71,29 @@ CONFIG_SCHEMA = cv.All(
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
),
|
||||||
|
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(cv.polling_component_schema("60s"))
|
||||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
.extend(ble_client.BLE_CLIENT_SCHEMA),
|
||||||
|
},
|
||||||
|
lower=True,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
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)
|
||||||
|
|
78
esphome/components/ble_client/sensor/ble_rssi_sensor.cpp
Normal file
78
esphome/components/ble_client/sensor/ble_rssi_sensor.cpp
Normal 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
|
31
esphome/components/ble_client/sensor/ble_rssi_sensor.h
Normal file
31
esphome/components/ble_client/sensor/ble_rssi_sensor.h
Normal 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
|
|
@ -58,7 +58,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
||||||
case MATCH_BY_SERVICE_UUID:
|
case MATCH_BY_SERVICE_UUID:
|
||||||
for (auto uuid : device.get_service_uuids()) {
|
for (auto uuid : device.get_service_uuids()) {
|
||||||
if (this->uuid_ == uuid) {
|
if (this->uuid_ == uuid) {
|
||||||
this->publish_state(device.get_rssi());
|
this->publish_state(true);
|
||||||
this->found_ = true;
|
this->found_ = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->publish_state(device.get_rssi());
|
this->publish_state(true);
|
||||||
this->found_ = true;
|
this->found_ = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import esphome.codegen as cg
|
||||||
from esphome.const import CONF_ACTIVE, CONF_ID
|
from esphome.const import CONF_ACTIVE, CONF_ID
|
||||||
|
|
||||||
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["api", "esp32"]
|
||||||
CODEOWNERS = ["@jesserockz"]
|
CODEOWNERS = ["@jesserockz"]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,43 +4,36 @@
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
#ifdef USE_API
|
|
||||||
#include "esphome/components/api/api_server.h"
|
#include "esphome/components/api/api_server.h"
|
||||||
#endif
|
|
||||||
|
|
||||||
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() {
|
BluetoothProxy::BluetoothProxy() {
|
||||||
global_bluetooth_proxy = this;
|
global_bluetooth_proxy = this;
|
||||||
this->address_ = 0;
|
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)
|
if (this->address_ == 0)
|
||||||
return true;
|
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);
|
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())
|
||||||
|
@ -62,7 +55,113 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
|
||||||
resp.manufacturer_data.push_back(std::move(manufacturer_data));
|
resp.manufacturer_data.push_back(std::move(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::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
|
@ -141,7 +240,6 @@ void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); }
|
||||||
|
|
||||||
void BluetoothProxy::loop() {
|
void BluetoothProxy::loop() {
|
||||||
BLEClientBase::loop();
|
BLEClientBase::loop();
|
||||||
#ifdef USE_API
|
|
||||||
if (this->state_ != espbt::ClientState::IDLE && !api::global_api_server->is_connected()) {
|
if (this->state_ != espbt::ClientState::IDLE && !api::global_api_server->is_connected()) {
|
||||||
ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str());
|
ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str());
|
||||||
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
||||||
|
@ -177,28 +275,12 @@ void BluetoothProxy::loop() {
|
||||||
api::global_api_server->send_bluetooth_gatt_services(resp);
|
api::global_api_server->send_bluetooth_gatt_services(resp);
|
||||||
this->send_service_++;
|
this->send_service_++;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_API
|
|
||||||
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
|
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
|
||||||
switch (msg.request_type) {
|
switch (msg.request_type) {
|
||||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
|
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
|
||||||
this->address_ = msg.address;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
|
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
|
||||||
|
@ -220,16 +302,19 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
||||||
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
|
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
|
||||||
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
||||||
ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected.");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (this->address_ != msg.address) {
|
if (this->address_ != msg.address) {
|
||||||
ESP_LOGW(TAG, "Address mismatch for read GATT characteristic request");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *characteristic = this->get_characteristic(msg.handle);
|
auto *characteristic = this->get_characteristic(msg.handle);
|
||||||
if (characteristic == nullptr) {
|
if (characteristic == nullptr) {
|
||||||
ESP_LOGW(TAG, "Cannot read GATT characteristic, not found.");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,43 +324,53 @@ void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &ms
|
||||||
esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE);
|
esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE);
|
||||||
if (err != ERR_OK) {
|
if (err != ERR_OK) {
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err);
|
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) {
|
void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) {
|
||||||
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
||||||
ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected.");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (this->address_ != msg.address) {
|
if (this->address_ != msg.address) {
|
||||||
ESP_LOGW(TAG, "Address mismatch for write GATT characteristic request");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *characteristic = this->get_characteristic(msg.handle);
|
auto *characteristic = this->get_characteristic(msg.handle);
|
||||||
if (characteristic == nullptr) {
|
if (characteristic == nullptr) {
|
||||||
ESP_LOGW(TAG, "Cannot write GATT characteristic, not found.");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Writing GATT characteristic %s", characteristic->uuid.to_string().c_str());
|
ESP_LOGV(TAG, "Writing GATT characteristic %s", characteristic->uuid.to_string().c_str());
|
||||||
characteristic->write_value((uint8_t *) msg.data.data(), msg.data.size(),
|
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);
|
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) {
|
void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) {
|
||||||
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
||||||
ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not connected.");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (this->address_ != msg.address) {
|
if (this->address_ != msg.address) {
|
||||||
ESP_LOGW(TAG, "Address mismatch for read GATT characteristic descriptor request");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *descriptor = this->get_descriptor(msg.handle);
|
auto *descriptor = this->get_descriptor(msg.handle);
|
||||||
if (descriptor == nullptr) {
|
if (descriptor == nullptr) {
|
||||||
ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not found.");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,22 +381,26 @@ void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTRead
|
||||||
esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE);
|
esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE);
|
||||||
if (err != ERR_OK) {
|
if (err != ERR_OK) {
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err);
|
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) {
|
void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) {
|
||||||
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
||||||
ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not connected.");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (this->address_ != msg.address) {
|
if (this->address_ != msg.address) {
|
||||||
ESP_LOGW(TAG, "Address mismatch for write GATT characteristic descriptor request");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *descriptor = this->get_descriptor(msg.handle);
|
auto *descriptor = this->get_descriptor(msg.handle);
|
||||||
if (descriptor == nullptr) {
|
if (descriptor == nullptr) {
|
||||||
ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not found.");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,20 +412,34 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri
|
||||||
(uint8_t *) msg.data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
(uint8_t *) msg.data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||||
if (err != ERR_OK) {
|
if (err != ERR_OK) {
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, err=%d", err);
|
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) {
|
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) {
|
if (this->address_ != msg.address) {
|
||||||
ESP_LOGW(TAG, "Address mismatch for service list request");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
this->send_service_ = 0;
|
this->send_service_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) {
|
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) {
|
if (this->address_ != msg.address) {
|
||||||
ESP_LOGW(TAG, "Address mismatch for notify");
|
ESP_LOGW(TAG, "Address mismatch for notify");
|
||||||
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,6 +447,7 @@ void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest
|
||||||
|
|
||||||
if (characteristic == nullptr) {
|
if (characteristic == nullptr) {
|
||||||
ESP_LOGW(TAG, "Cannot notify GATT characteristic, not found.");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,17 +456,17 @@ void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest
|
||||||
err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
|
err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, err=%d", err);
|
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 {
|
} else {
|
||||||
err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
|
err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_unregister_for_notify failed, err=%d", err);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
} // namespace bluetooth_proxy
|
} // namespace bluetooth_proxy
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#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_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"
|
||||||
|
@ -12,10 +13,6 @@
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#ifdef USE_API
|
|
||||||
#include "esphome/components/api/api_pb2.h"
|
|
||||||
#endif // USE_API
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace bluetooth_proxy {
|
namespace bluetooth_proxy {
|
||||||
|
|
||||||
|
@ -31,7 +28,6 @@ class BluetoothProxy : public BLEClientBase {
|
||||||
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;
|
||||||
|
|
||||||
#ifdef USE_API
|
|
||||||
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
|
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
|
||||||
void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg);
|
void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg);
|
||||||
void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg);
|
void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg);
|
||||||
|
@ -39,7 +35,6 @@ class BluetoothProxy : public BLEClientBase {
|
||||||
void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg);
|
void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg);
|
||||||
void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg);
|
void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg);
|
||||||
void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg);
|
void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg);
|
||||||
#endif
|
|
||||||
|
|
||||||
int get_bluetooth_connections_free() { return this->state_ == espbt::ClientState::IDLE ? 1 : 0; }
|
int get_bluetooth_connections_free() { return this->state_ == espbt::ClientState::IDLE ? 1 : 0; }
|
||||||
int get_bluetooth_connections_limit() { return 1; }
|
int get_bluetooth_connections_limit() { return 1; }
|
||||||
|
@ -50,7 +45,6 @@ class BluetoothProxy : public BLEClientBase {
|
||||||
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};
|
int16_t send_service_{-1};
|
||||||
bool active_;
|
bool active_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -64,17 +64,18 @@ BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
|
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 *client = this->service->client;
|
||||||
auto status = esp_ble_gattc_write_char(client->get_gattc_if(), client->get_conn_id(), this->handle, new_val_size,
|
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);
|
new_val, write_type, ESP_GATT_AUTH_REQ_NONE);
|
||||||
if (status) {
|
if (status) {
|
||||||
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
|
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
|
||||||
}
|
}
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
|
esp_err_t 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);
|
return write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esp32_ble_client
|
} // namespace esp32_ble_client
|
||||||
|
|
|
@ -24,8 +24,8 @@ class BLECharacteristic {
|
||||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
|
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
|
||||||
BLEDescriptor *get_descriptor(uint16_t uuid);
|
BLEDescriptor *get_descriptor(uint16_t uuid);
|
||||||
BLEDescriptor *get_descriptor_by_handle(uint16_t handle);
|
BLEDescriptor *get_descriptor_by_handle(uint16_t handle);
|
||||||
void 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);
|
||||||
void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
|
esp_err_t write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
|
||||||
BLEService *service;
|
BLEService *service;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -648,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.
|
||||||
|
// 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);
|
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: {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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_);
|
||||||
|
|
|
@ -571,24 +571,16 @@ int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sens
|
||||||
static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
|
static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
|
||||||
} break;
|
} break;
|
||||||
case SensorValueType::U_QWORD:
|
case SensorValueType::U_QWORD:
|
||||||
// Ignore bitmask for U_QWORD
|
|
||||||
value = get_data<uint64_t>(data, offset);
|
|
||||||
break;
|
|
||||||
case SensorValueType::S_QWORD:
|
case SensorValueType::S_QWORD:
|
||||||
// Ignore bitmask for S_QWORD
|
// Ignore bitmask for QWORD
|
||||||
value = get_data<int64_t>(data, offset);
|
value = get_data<uint64_t>(data, offset);
|
||||||
break;
|
break;
|
||||||
case SensorValueType::U_QWORD_R:
|
case SensorValueType::U_QWORD_R:
|
||||||
// Ignore bitmask for U_QWORD
|
case SensorValueType::S_QWORD_R: {
|
||||||
value = get_data<uint64_t>(data, offset);
|
// Ignore bitmask for QWORD
|
||||||
value = static_cast<uint64_t>(value & 0xFFFF) << 48 | (value & 0xFFFF000000000000) >> 48 |
|
uint64_t tmp = get_data<uint64_t>(data, offset);
|
||||||
static_cast<uint64_t>(value & 0xFFFF0000) << 32 | (value & 0x0000FFFF00000000) >> 32 |
|
value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000);
|
||||||
static_cast<uint64_t>(value & 0xFFFF00000000) << 16 | (value & 0x00000000FFFF0000) >> 16;
|
} break;
|
||||||
break;
|
|
||||||
case SensorValueType::S_QWORD_R:
|
|
||||||
// Ignore bitmask for S_QWORD
|
|
||||||
value = get_data<int64_t>(data, offset);
|
|
||||||
break;
|
|
||||||
case SensorValueType::RAW:
|
case SensorValueType::RAW:
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 = {}
|
packages = {}
|
||||||
for file in files:
|
for file in files:
|
||||||
yaml_file: Path = repo_dir / file
|
yaml_file: Path = repo_dir / file
|
||||||
|
|
||||||
if not yaml_file.is_file():
|
if not yaml_file.is_file():
|
||||||
raise cv.Invalid(f"{file} does not exist in repository", path=[CONF_FILES])
|
raise cv.Invalid(
|
||||||
|
f"{file} does not exist in repository", path=[CONF_FILES]
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
packages[file] = yaml_util.load_yaml(yaml_file)
|
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:
|
except EsphomeError as e:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"{file} is not a valid YAML file. Please check the file contents."
|
f"{file} is not a valid YAML file. Please check the file contents."
|
||||||
) from e
|
) from e
|
||||||
|
return packages
|
||||||
|
|
||||||
|
packages = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
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")
|
||||||
|
|
||||||
return {"packages": packages}
|
return {"packages": packages}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
auto use_default_preset = true;
|
||||||
|
|
||||||
|
if (this->on_boot_restore_from_ == thermostat::OnBootRestoreFrom::MEMORY) {
|
||||||
// restore all climate data, if possible
|
// restore all climate data, if possible
|
||||||
auto restore = this->restore_state_();
|
auto restore = this->restore_state_();
|
||||||
if (restore.has_value()) {
|
if (restore.has_value()) {
|
||||||
|
use_default_preset = false;
|
||||||
restore->to_call(this).perform();
|
restore->to_call(this).perform();
|
||||||
} else {
|
|
||||||
// restore from defaults, change_away handles temps for us
|
|
||||||
this->mode = this->default_mode_;
|
|
||||||
this->change_preset_(climate::CLIMATE_PRESET_HOME);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_) {
|
||||||
|
@ -962,9 +974,19 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
|
||||||
auto config = this->preset_config_.find(preset);
|
auto config = this->preset_config_.find(preset);
|
||||||
|
|
||||||
if (config != this->preset_config_.end()) {
|
if (config != this->preset_config_.end()) {
|
||||||
ESP_LOGI(TAG, "Switching to preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
|
ESP_LOGI(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
|
||||||
this->change_preset_internal_(config->second);
|
if (this->change_preset_internal_(config->second) || (!this->preset.has_value()) ||
|
||||||
|
this->preset.value() != preset) {
|
||||||
|
// Fire any preset changed trigger if defined
|
||||||
|
Trigger<> *trig = this->preset_change_trigger_;
|
||||||
|
assert(trig != nullptr);
|
||||||
|
trig->trigger();
|
||||||
|
|
||||||
|
this->refresh();
|
||||||
|
ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "No changes required to apply preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
|
||||||
|
}
|
||||||
this->custom_preset.reset();
|
this->custom_preset.reset();
|
||||||
this->preset = preset;
|
this->preset = preset;
|
||||||
} else {
|
} else {
|
||||||
|
@ -976,9 +998,19 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset)
|
||||||
auto config = this->custom_preset_config_.find(custom_preset);
|
auto config = this->custom_preset_config_.find(custom_preset);
|
||||||
|
|
||||||
if (config != this->custom_preset_config_.end()) {
|
if (config != this->custom_preset_config_.end()) {
|
||||||
ESP_LOGI(TAG, "Switching to custom preset %s", custom_preset.c_str());
|
ESP_LOGI(TAG, "Custom preset %s requested", custom_preset.c_str());
|
||||||
this->change_preset_internal_(config->second);
|
if (this->change_preset_internal_(config->second) || (!this->custom_preset.has_value()) ||
|
||||||
|
this->custom_preset.value() != custom_preset) {
|
||||||
|
// Fire any preset changed trigger if defined
|
||||||
|
Trigger<> *trig = this->preset_change_trigger_;
|
||||||
|
assert(trig != nullptr);
|
||||||
|
trig->trigger();
|
||||||
|
|
||||||
|
this->refresh();
|
||||||
|
ESP_LOGI(TAG, "Custom preset %s applied", custom_preset.c_str());
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "No changes required to apply custom preset %s", custom_preset.c_str());
|
||||||
|
}
|
||||||
this->preset.reset();
|
this->preset.reset();
|
||||||
this->custom_preset = custom_preset;
|
this->custom_preset = custom_preset;
|
||||||
} else {
|
} else {
|
||||||
|
@ -986,39 +1018,46 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTempConfig &config) {
|
bool ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTempConfig &config) {
|
||||||
|
bool something_changed = false;
|
||||||
|
|
||||||
if (this->supports_two_points_) {
|
if (this->supports_two_points_) {
|
||||||
|
if (this->target_temperature_low != config.default_temperature_low) {
|
||||||
this->target_temperature_low = config.default_temperature_low;
|
this->target_temperature_low = config.default_temperature_low;
|
||||||
|
something_changed = true;
|
||||||
|
}
|
||||||
|
if (this->target_temperature_high != config.default_temperature_high) {
|
||||||
this->target_temperature_high = config.default_temperature_high;
|
this->target_temperature_high = config.default_temperature_high;
|
||||||
|
something_changed = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (this->target_temperature != config.default_temperature) {
|
||||||
this->target_temperature = config.default_temperature;
|
this->target_temperature = config.default_temperature;
|
||||||
|
something_changed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: The mode, fan_mode, and swing_mode can all be defined on the preset but if the climate.control call
|
// Note: The mode, fan_mode and swing_mode can all be defined in the preset but if the climate.control call
|
||||||
// also specifies them then the control's version will override these for that call
|
// also specifies them then the climate.control call's values will override the preset's values for that call
|
||||||
if (config.mode_.has_value()) {
|
if (config.mode_.has_value() && (this->mode != config.mode_.value())) {
|
||||||
this->mode = *config.mode_;
|
|
||||||
ESP_LOGV(TAG, "Setting mode to %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
|
ESP_LOGV(TAG, "Setting mode to %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
|
||||||
|
this->mode = *config.mode_;
|
||||||
|
something_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.fan_mode_.has_value()) {
|
if (config.fan_mode_.has_value() && (this->fan_mode != config.fan_mode_.value())) {
|
||||||
this->fan_mode = *config.fan_mode_;
|
|
||||||
ESP_LOGV(TAG, "Setting fan mode to %s", LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
|
ESP_LOGV(TAG, "Setting fan mode to %s", LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
|
||||||
|
this->fan_mode = *config.fan_mode_;
|
||||||
|
something_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.swing_mode_.has_value()) {
|
if (config.swing_mode_.has_value() && (this->swing_mode != config.swing_mode_.value())) {
|
||||||
ESP_LOGV(TAG, "Setting swing mode to %s", LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_)));
|
ESP_LOGV(TAG, "Setting swing mode to %s", LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_)));
|
||||||
this->swing_mode = *config.swing_mode_;
|
this->swing_mode = *config.swing_mode_;
|
||||||
|
something_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fire any preset changed trigger if defined
|
return something_changed;
|
||||||
if (this->preset != preset) {
|
|
||||||
Trigger<> *trig = this->preset_change_trigger_;
|
|
||||||
assert(trig != nullptr);
|
|
||||||
trig->trigger();
|
|
||||||
}
|
|
||||||
|
|
||||||
this->refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThermostatClimate::set_preset_config(climate::ClimatePreset preset,
|
void ThermostatClimate::set_preset_config(climate::ClimatePreset preset,
|
||||||
|
@ -1061,7 +1100,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 +1260,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 +1332,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 +1340,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;
|
||||||
|
|
|
@ -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);
|
||||||
|
@ -165,7 +168,8 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||||
|
|
||||||
/// Applies the temperature, mode, fan, and swing modes of the provided config.
|
/// Applies the temperature, mode, fan, and swing modes of the provided config.
|
||||||
/// This is agnostic of custom vs built in preset
|
/// This is agnostic of custom vs built in preset
|
||||||
void change_preset_internal_(const ThermostatClimateTargetTempConfig &config);
|
/// Returns true if something was changed
|
||||||
|
bool change_preset_internal_(const ThermostatClimateTargetTempConfig &config);
|
||||||
|
|
||||||
/// Return the traits of this controller.
|
/// Return the traits of this controller.
|
||||||
climate::ClimateTraits traits() override;
|
climate::ClimateTraits traits() override;
|
||||||
|
@ -225,7 +229,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 +402,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 +445,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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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_)
|
||||||
|
|
|
@ -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]))
|
||||||
|
|
|
@ -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,
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -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))
|
|
||||||
|
|
116
esphome/components/xpt2046/touchscreen.py
Normal file
116
esphome/components/xpt2046/touchscreen.py
Normal 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))
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
touchpoint.x = (int16_t)((int) touchpoint.x * this->display_->get_width() / 0xfff);
|
||||||
|
touchpoint.y = (int16_t)((int) touchpoint.y * this->display_->get_height() / 0xfff);
|
||||||
|
|
||||||
if (!this->touched || (now - this->last_pos_ms_) >= this->report_millis_) {
|
if (!this->touched || (now - this->last_pos_ms_) >= this->report_millis_) {
|
||||||
ESP_LOGD(TAG, "Raw [x, y] = [%d, %d], transformed = [%d, %d]", this->x_raw, this->y_raw, x_val, y_val);
|
ESP_LOGV(TAG, "Touching at [%03X, %03X] => [%3d, %3d]", this->x_raw, this->y_raw, touchpoint.x, touchpoint.y);
|
||||||
|
|
||||||
this->x = x_val;
|
this->defer([this, touchpoint]() { this->send_touch_(touchpoint); });
|
||||||
this->y = y_val;
|
|
||||||
|
this->x = touchpoint.x;
|
||||||
|
this->y = touchpoint.y;
|
||||||
this->touched = true;
|
this->touched = true;
|
||||||
this->last_pos_ms_ = now;
|
this->last_pos_ms_ = now;
|
||||||
|
|
||||||
this->on_state_trigger_->process(this->x, this->y, true);
|
|
||||||
for (auto *button : this->buttons_)
|
|
||||||
button->touch(this->x, this->y);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
this->x_raw = this->y_raw = 0;
|
||||||
if (this->touched) {
|
if (this->touched) {
|
||||||
ESP_LOGD(TAG, "Released [%d, %d]", this->x, this->y);
|
ESP_LOGV(TAG, "Released [%d, %d]", this->x, this->y);
|
||||||
|
|
||||||
this->touched = false;
|
this->touched = false;
|
||||||
|
for (auto *listener : this->touch_listeners_)
|
||||||
this->on_state_trigger_->process(this->x, this->y, false);
|
listener->release();
|
||||||
for (auto *button : this->buttons_)
|
}
|
||||||
button->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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
):
|
):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Constants used by esphome."""
|
"""Constants used by esphome."""
|
||||||
|
|
||||||
__version__ = "2022.9.4"
|
__version__ = "2022.10.0b2"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
|
|
||||||
|
@ -396,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"
|
||||||
|
@ -904,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"
|
||||||
|
@ -922,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"
|
||||||
|
@ -948,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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
if register:
|
||||||
CORE.register_variable(id_, obj)
|
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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -533,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)
|
||||||
|
@ -829,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()
|
||||||
|
@ -945,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}"
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
|
@ -10,15 +10,13 @@ import esphome.config_validation as cv
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
def _get_invalid_range(res, invalid):
|
def _get_invalid_range(res: Config, invalid: cv.Invalid) -> Optional[DocumentRange]:
|
||||||
# type: (Config, cv.Invalid) -> Optional[DocumentRange]
|
|
||||||
return res.get_deepest_document_range_for_path(
|
return res.get_deepest_document_range_for_path(
|
||||||
invalid.path, invalid.error_message == "extra keys not allowed"
|
invalid.path, invalid.error_message == "extra keys not allowed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _dump_range(range):
|
def _dump_range(range: Optional[DocumentRange]) -> Optional[dict]:
|
||||||
# type: (Optional[DocumentRange]) -> Optional[dict]
|
|
||||||
if range is None:
|
if range is None:
|
||||||
return None
|
return None
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Union
|
from typing import Union
|
||||||
|
|
||||||
from esphome.config import iter_components
|
from esphome.config import iter_components
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
@ -98,7 +98,7 @@ def replace_file_content(text, pattern, repl):
|
||||||
return content_new, count
|
return content_new, count
|
||||||
|
|
||||||
|
|
||||||
def storage_should_clean(old, new): # type: (StorageJSON, StorageJSON) -> bool
|
def storage_should_clean(old: StorageJSON, new: StorageJSON) -> bool:
|
||||||
if old is None:
|
if old is None:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ def update_storage_json():
|
||||||
new.save(path)
|
new.save(path)
|
||||||
|
|
||||||
|
|
||||||
def format_ini(data: Dict[str, Union[str, List[str]]]) -> str:
|
def format_ini(data: dict[str, Union[str, list[str]]]) -> str:
|
||||||
content = ""
|
content = ""
|
||||||
for key, value in sorted(data.items()):
|
for key, value in sorted(data.items()):
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
|
@ -226,7 +226,7 @@ the custom_components folder or the external_components feature.
|
||||||
|
|
||||||
|
|
||||||
def copy_src_tree():
|
def copy_src_tree():
|
||||||
source_files: List[loader.FileResource] = []
|
source_files: list[loader.FileResource] = []
|
||||||
for _, component, _ in iter_components(CORE.config):
|
for _, component, _ in iter_components(CORE.config):
|
||||||
source_files += component.resources
|
source_files += component.resources
|
||||||
source_files_map = {
|
source_files_map = {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import Dict, Optional
|
from typing import Optional
|
||||||
import logging
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@ -71,12 +71,12 @@ class DashboardStatus(threading.Thread):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.zc = zc
|
self.zc = zc
|
||||||
self.query_hosts: set[str] = set()
|
self.query_hosts: set[str] = set()
|
||||||
self.key_to_host: Dict[str, str] = {}
|
self.key_to_host: dict[str, str] = {}
|
||||||
self.stop_event = threading.Event()
|
self.stop_event = threading.Event()
|
||||||
self.query_event = threading.Event()
|
self.query_event = threading.Event()
|
||||||
self.on_update = on_update
|
self.on_update = on_update
|
||||||
|
|
||||||
def request_query(self, hosts: Dict[str, str]) -> None:
|
def request_query(self, hosts: dict[str, str]) -> None:
|
||||||
self.query_hosts = set(hosts.values())
|
self.query_hosts = set(hosts.values())
|
||||||
self.key_to_host = hosts
|
self.key_to_host = hosts
|
||||||
self.query_event.set()
|
self.query_event.set()
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[tool.black]
|
[tool.black]
|
||||||
target-version = ["py36", "py37", "py38"]
|
target-version = ["py39", "py310"]
|
||||||
exclude = 'generated'
|
exclude = 'generated'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
pylint==2.15.2
|
pylint==2.15.3
|
||||||
flake8==5.0.4
|
flake8==5.0.4
|
||||||
black==22.8.0 # also change in .pre-commit-config.yaml when updating
|
black==22.8.0 # also change in .pre-commit-config.yaml when updating
|
||||||
pyupgrade==2.37.3 # also change in .pre-commit-config.yaml when updating
|
pyupgrade==3.0.0 # also change in .pre-commit-config.yaml when updating
|
||||||
pre-commit
|
pre-commit
|
||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
|
|
|
@ -109,7 +109,7 @@ def main():
|
||||||
print_error(file_, linno, msg)
|
print_error(file_, linno, msg)
|
||||||
errors += 1
|
errors += 1
|
||||||
|
|
||||||
PYUPGRADE_TARGET = "--py38-plus"
|
PYUPGRADE_TARGET = "--py39-plus"
|
||||||
cmd = ["pyupgrade", PYUPGRADE_TARGET] + files
|
cmd = ["pyupgrade", PYUPGRADE_TARGET] + files
|
||||||
print()
|
print()
|
||||||
print("Running pyupgrade...")
|
print("Running pyupgrade...")
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -74,7 +74,7 @@ setup(
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
platforms="any",
|
platforms="any",
|
||||||
test_suite="tests",
|
test_suite="tests",
|
||||||
python_requires=">=3.8,<4.0",
|
python_requires=">=3.9.0",
|
||||||
install_requires=REQUIRES,
|
install_requires=REQUIRES,
|
||||||
keywords=["home", "automation"],
|
keywords=["home", "automation"],
|
||||||
entry_points={"console_scripts": ["esphome = esphome.__main__:main"]},
|
entry_points={"console_scripts": ["esphome = esphome.__main__:main"]},
|
||||||
|
|
|
@ -290,8 +290,6 @@ adalight:
|
||||||
|
|
||||||
esp32_ble_tracker:
|
esp32_ble_tracker:
|
||||||
|
|
||||||
bluetooth_proxy:
|
|
||||||
|
|
||||||
ble_client:
|
ble_client:
|
||||||
- mac_address: AA:BB:CC:DD:EE:FF
|
- mac_address: AA:BB:CC:DD:EE:FF
|
||||||
id: ble_foo
|
id: ble_foo
|
||||||
|
@ -321,6 +319,7 @@ mcp23s17:
|
||||||
|
|
||||||
sensor:
|
sensor:
|
||||||
- platform: ble_client
|
- platform: ble_client
|
||||||
|
type: characteristic
|
||||||
ble_client_id: ble_foo
|
ble_client_id: ble_foo
|
||||||
name: Green iTag btn
|
name: Green iTag btn
|
||||||
service_uuid: ffe0
|
service_uuid: ffe0
|
||||||
|
@ -335,6 +334,11 @@ sensor:
|
||||||
then:
|
then:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
ESP_LOGD("green_btn", "Button was pressed, val%f", x);
|
ESP_LOGD("green_btn", "Button was pressed, val%f", x);
|
||||||
|
- platform: ble_client
|
||||||
|
type: rssi
|
||||||
|
ble_client_id: ble_foo
|
||||||
|
name: Green iTag RSSI
|
||||||
|
update_interval: 15s
|
||||||
- platform: adc
|
- platform: adc
|
||||||
pin: A0
|
pin: A0
|
||||||
name: Living Room Brightness
|
name: Living Room Brightness
|
||||||
|
|
|
@ -506,6 +506,9 @@ xiaomi_ble:
|
||||||
|
|
||||||
mopeka_ble:
|
mopeka_ble:
|
||||||
|
|
||||||
|
bluetooth_proxy:
|
||||||
|
active: true
|
||||||
|
|
||||||
xiaomi_rtcgq02lm:
|
xiaomi_rtcgq02lm:
|
||||||
- id: motion_rtcgq02lm
|
- id: motion_rtcgq02lm
|
||||||
mac_address: 01:02:03:04:05:06
|
mac_address: 01:02:03:04:05:06
|
||||||
|
|
|
@ -1061,8 +1061,13 @@ climate:
|
||||||
- platform: thermostat
|
- platform: thermostat
|
||||||
name: Thermostat Climate
|
name: Thermostat Climate
|
||||||
sensor: ha_hello_world
|
sensor: ha_hello_world
|
||||||
|
preset:
|
||||||
|
- name: Default Preset
|
||||||
default_target_temperature_low: 18°C
|
default_target_temperature_low: 18°C
|
||||||
default_target_temperature_high: 24°C
|
default_target_temperature_high: 24°C
|
||||||
|
- name: Away
|
||||||
|
default_target_temperature_low: 16°C
|
||||||
|
default_target_temperature_high: 20°C
|
||||||
idle_action:
|
idle_action:
|
||||||
- switch.turn_on: gpio_switch1
|
- switch.turn_on: gpio_switch1
|
||||||
cool_action:
|
cool_action:
|
||||||
|
@ -1137,9 +1142,6 @@ climate:
|
||||||
fan_only_cooling: true
|
fan_only_cooling: true
|
||||||
fan_with_cooling: true
|
fan_with_cooling: true
|
||||||
fan_with_heating: true
|
fan_with_heating: true
|
||||||
away_config:
|
|
||||||
default_target_temperature_low: 16°C
|
|
||||||
default_target_temperature_high: 20°C
|
|
||||||
- platform: pid
|
- platform: pid
|
||||||
id: pid_climate
|
id: pid_climate
|
||||||
name: PID Climate Controller
|
name: PID Climate Controller
|
||||||
|
|
|
@ -348,15 +348,16 @@ binary_sensor:
|
||||||
on_state:
|
on_state:
|
||||||
then:
|
then:
|
||||||
- lambda: 'ESP_LOGI("ar1:", "%d", x);'
|
- lambda: 'ESP_LOGI("ar1:", "%d", x);'
|
||||||
- platform: xpt2046
|
- platform: touchscreen
|
||||||
xpt2046_id: xpt_touchscreen
|
touchscreen_id: xpt_touchscreen
|
||||||
id: touch_key0
|
id: touch_key0
|
||||||
x_min: 80
|
x_min: 80
|
||||||
x_max: 160
|
x_max: 160
|
||||||
y_min: 106
|
y_min: 106
|
||||||
y_max: 212
|
y_max: 212
|
||||||
on_state:
|
on_press:
|
||||||
- lambda: 'ESP_LOGI("main", "key0: %s", (x ? "touch" : "release"));'
|
- logger.log: Touched
|
||||||
|
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
name: GPIO SX1509 test
|
name: GPIO SX1509 test
|
||||||
pin:
|
pin:
|
||||||
|
@ -598,33 +599,6 @@ external_components:
|
||||||
components: [bh1750]
|
components: [bh1750]
|
||||||
- source: ../esphome/components
|
- source: ../esphome/components
|
||||||
components: [sntp]
|
components: [sntp]
|
||||||
xpt2046:
|
|
||||||
id: xpt_touchscreen
|
|
||||||
cs_pin: 17
|
|
||||||
irq_pin: 16
|
|
||||||
update_interval: 50ms
|
|
||||||
report_interval: 1s
|
|
||||||
threshold: 400
|
|
||||||
dimension_x: 240
|
|
||||||
dimension_y: 320
|
|
||||||
calibration_x_min: 3860
|
|
||||||
calibration_x_max: 280
|
|
||||||
calibration_y_min: 340
|
|
||||||
calibration_y_max: 3860
|
|
||||||
swap_x_y: false
|
|
||||||
on_state:
|
|
||||||
# yamllint disable rule:line-length
|
|
||||||
- lambda: |-
|
|
||||||
ESP_LOGI("main", "args x=%d, y=%d, touched=%s", x, y, (touched ? "touch" : "release"));
|
|
||||||
ESP_LOGI("main", "member x=%d, y=%d, touched=%d, x_raw=%d, y_raw=%d, z_raw=%d",
|
|
||||||
id(xpt_touchscreen).x,
|
|
||||||
id(xpt_touchscreen).y,
|
|
||||||
(int) id(xpt_touchscreen).touched,
|
|
||||||
id(xpt_touchscreen).x_raw,
|
|
||||||
id(xpt_touchscreen).y_raw,
|
|
||||||
id(xpt_touchscreen).z_raw
|
|
||||||
);
|
|
||||||
# yamllint enable rule:line-length
|
|
||||||
|
|
||||||
button:
|
button:
|
||||||
- platform: restart
|
- platform: restart
|
||||||
|
@ -648,6 +622,25 @@ touchscreen:
|
||||||
format: Touch at (%d, %d)
|
format: Touch at (%d, %d)
|
||||||
args: [touch.x, touch.y]
|
args: [touch.x, touch.y]
|
||||||
|
|
||||||
|
- platform: xpt2046
|
||||||
|
id: xpt_touchscreen
|
||||||
|
cs_pin: 17
|
||||||
|
interrupt_pin: 16
|
||||||
|
display: inkplate_display
|
||||||
|
update_interval: 50ms
|
||||||
|
report_interval: 1s
|
||||||
|
threshold: 400
|
||||||
|
calibration_x_min: 3860
|
||||||
|
calibration_x_max: 280
|
||||||
|
calibration_y_min: 340
|
||||||
|
calibration_y_max: 3860
|
||||||
|
swap_x_y: false
|
||||||
|
on_touch:
|
||||||
|
- logger.log:
|
||||||
|
format: Touch at (%d, %d)
|
||||||
|
args: [touch.x, touch.y]
|
||||||
|
|
||||||
|
|
||||||
- platform: lilygo_t5_47
|
- platform: lilygo_t5_47
|
||||||
id: lilygo_touchscreen
|
id: lilygo_touchscreen
|
||||||
interrupt_pin: GPIO36
|
interrupt_pin: GPIO36
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
from typing import Text
|
|
||||||
|
|
||||||
import hypothesis.strategies._internal.core as st
|
import hypothesis.strategies._internal.core as st
|
||||||
from hypothesis.strategies._internal.strategies import SearchStrategy
|
from hypothesis.strategies._internal.strategies import SearchStrategy
|
||||||
|
|
||||||
|
|
||||||
@st.defines_strategy(force_reusable_values=True)
|
@st.defines_strategy(force_reusable_values=True)
|
||||||
def mac_addr_strings():
|
def mac_addr_strings() -> SearchStrategy[str]:
|
||||||
# type: () -> SearchStrategy[Text]
|
|
||||||
"""A strategy for MAC address strings.
|
"""A strategy for MAC address strings.
|
||||||
|
|
||||||
This consists of six strings representing integers [0..255],
|
This consists of six strings representing integers [0..255],
|
||||||
|
|
Loading…
Reference in a new issue