mirror of
https://github.com/esphome/esphome.git
synced 2024-12-01 03:04:12 +01:00
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
2b9224c06c
101 changed files with 1894 additions and 231 deletions
|
@ -343,9 +343,10 @@ def upload_program(config, args, host):
|
||||||
password = ota_conf.get(CONF_PASSWORD, "")
|
password = ota_conf.get(CONF_PASSWORD, "")
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not is_ip_address(CORE.address)
|
not is_ip_address(CORE.address) # pylint: disable=too-many-boolean-expressions
|
||||||
and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
|
and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
|
||||||
and CONF_MQTT in config
|
and CONF_MQTT in config
|
||||||
|
and (not args.device or args.device == "MQTT")
|
||||||
):
|
):
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
|
@ -768,7 +769,9 @@ def parse_args(argv):
|
||||||
)
|
)
|
||||||
|
|
||||||
parser_upload = subparsers.add_parser(
|
parser_upload = subparsers.add_parser(
|
||||||
"upload", help="Validate the configuration and upload the latest binary."
|
"upload",
|
||||||
|
help="Validate the configuration and upload the latest binary.",
|
||||||
|
parents=[mqtt_options],
|
||||||
)
|
)
|
||||||
parser_upload.add_argument(
|
parser_upload.add_argument(
|
||||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||||
|
|
|
@ -18,10 +18,20 @@ from esphome.util import Registry
|
||||||
|
|
||||||
|
|
||||||
def maybe_simple_id(*validators):
|
def maybe_simple_id(*validators):
|
||||||
|
"""Allow a raw ID to be specified in place of a config block.
|
||||||
|
If the value that's being validated is a dictionary, it's passed as-is to the specified validators. Otherwise, it's
|
||||||
|
wrapped in a dict that looks like ``{"id": <value>}``, and that dict is then handed off to the specified validators.
|
||||||
|
"""
|
||||||
return maybe_conf(CONF_ID, *validators)
|
return maybe_conf(CONF_ID, *validators)
|
||||||
|
|
||||||
|
|
||||||
def maybe_conf(conf, *validators):
|
def maybe_conf(conf, *validators):
|
||||||
|
"""Allow a raw value to be specified in place of a config block.
|
||||||
|
If the value that's being validated is a dictionary, it's passed as-is to the specified validators. Otherwise, it's
|
||||||
|
wrapped in a dict that looks like ``{<conf>: <value>}``, and that dict is then handed off to the specified
|
||||||
|
validators.
|
||||||
|
(This is a general case of ``maybe_simple_id`` that allows the wrapping key to be something other than ``id``.)
|
||||||
|
"""
|
||||||
validator = cv.All(*validators)
|
validator = cv.All(*validators)
|
||||||
|
|
||||||
@schema_extractor("maybe")
|
@schema_extractor("maybe")
|
||||||
|
|
|
@ -97,9 +97,11 @@ void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) {
|
||||||
void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
|
void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case ESP_GATTC_OPEN_EVT: {
|
case ESP_GATTC_OPEN_EVT: {
|
||||||
|
if (param->open.status == ESP_GATT_OK) {
|
||||||
this->response_offset_ = 0;
|
this->response_offset_ = 0;
|
||||||
this->response_length_ = 0;
|
this->response_length_ = 0;
|
||||||
ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str());
|
ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_CONNECT_EVT: {
|
case ESP_GATTC_CONNECT_EVT: {
|
||||||
|
|
|
@ -26,7 +26,9 @@ void Am43::setup() {
|
||||||
void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
|
void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case ESP_GATTC_OPEN_EVT: {
|
case ESP_GATTC_OPEN_EVT: {
|
||||||
|
if (param->open.status == ESP_GATT_OK) {
|
||||||
this->logged_in_ = false;
|
this->logged_in_ = false;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_DISCONNECT_EVT: {
|
case ESP_GATTC_DISCONNECT_EVT: {
|
||||||
|
|
|
@ -47,6 +47,7 @@ service APIConnection {
|
||||||
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
|
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
|
||||||
rpc date_command (DateCommandRequest) returns (void) {}
|
rpc date_command (DateCommandRequest) returns (void) {}
|
||||||
rpc time_command (TimeCommandRequest) returns (void) {}
|
rpc time_command (TimeCommandRequest) returns (void) {}
|
||||||
|
rpc datetime_command (DateTimeCommandRequest) returns (void) {}
|
||||||
|
|
||||||
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
||||||
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
|
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
|
||||||
|
@ -1777,3 +1778,40 @@ message ValveCommandRequest {
|
||||||
float position = 3;
|
float position = 3;
|
||||||
bool stop = 4;
|
bool stop = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== DATETIME DATETIME ====================
|
||||||
|
message ListEntitiesDateTimeResponse {
|
||||||
|
option (id) = 112;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_DATETIME_DATETIME";
|
||||||
|
|
||||||
|
string object_id = 1;
|
||||||
|
fixed32 key = 2;
|
||||||
|
string name = 3;
|
||||||
|
string unique_id = 4;
|
||||||
|
|
||||||
|
string icon = 5;
|
||||||
|
bool disabled_by_default = 6;
|
||||||
|
EntityCategory entity_category = 7;
|
||||||
|
}
|
||||||
|
message DateTimeStateResponse {
|
||||||
|
option (id) = 113;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_DATETIME_DATETIME";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
|
fixed32 key = 1;
|
||||||
|
// If the datetime does not have a valid state yet.
|
||||||
|
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||||
|
bool missing_state = 2;
|
||||||
|
fixed32 epoch_seconds = 3;
|
||||||
|
}
|
||||||
|
message DateTimeCommandRequest {
|
||||||
|
option (id) = 114;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_DATETIME_DATETIME";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
|
fixed32 key = 1;
|
||||||
|
fixed32 epoch_seconds = 2;
|
||||||
|
}
|
||||||
|
|
|
@ -772,6 +772,44 @@ void APIConnection::time_command(const TimeCommandRequest &msg) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
|
||||||
|
if (!this->state_subscription_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DateTimeStateResponse resp{};
|
||||||
|
resp.key = datetime->get_object_id_hash();
|
||||||
|
resp.missing_state = !datetime->has_state();
|
||||||
|
if (datetime->has_state()) {
|
||||||
|
ESPTime state = datetime->state_as_esptime();
|
||||||
|
resp.epoch_seconds = state.timestamp;
|
||||||
|
}
|
||||||
|
return this->send_date_time_state_response(resp);
|
||||||
|
}
|
||||||
|
bool APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) {
|
||||||
|
ListEntitiesDateTimeResponse msg;
|
||||||
|
msg.key = datetime->get_object_id_hash();
|
||||||
|
msg.object_id = datetime->get_object_id();
|
||||||
|
if (datetime->has_own_name())
|
||||||
|
msg.name = datetime->get_name();
|
||||||
|
msg.unique_id = get_default_unique_id("datetime", datetime);
|
||||||
|
msg.icon = datetime->get_icon();
|
||||||
|
msg.disabled_by_default = datetime->is_disabled_by_default();
|
||||||
|
msg.entity_category = static_cast<enums::EntityCategory>(datetime->get_entity_category());
|
||||||
|
|
||||||
|
return this->send_list_entities_date_time_response(msg);
|
||||||
|
}
|
||||||
|
void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
|
||||||
|
datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key);
|
||||||
|
if (datetime == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto call = datetime->make_call();
|
||||||
|
call.set_datetime(msg.epoch_seconds);
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
bool APIConnection::send_text_state(text::Text *text, std::string state) {
|
bool APIConnection::send_text_state(text::Text *text, std::string state) {
|
||||||
if (!this->state_subscription_)
|
if (!this->state_subscription_)
|
||||||
|
|
|
@ -82,6 +82,11 @@ class APIConnection : public APIServerConnection {
|
||||||
bool send_time_info(datetime::TimeEntity *time);
|
bool send_time_info(datetime::TimeEntity *time);
|
||||||
void time_command(const TimeCommandRequest &msg) override;
|
void time_command(const TimeCommandRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
bool send_datetime_state(datetime::DateTimeEntity *datetime);
|
||||||
|
bool send_datetime_info(datetime::DateTimeEntity *datetime);
|
||||||
|
void datetime_command(const DateTimeCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
bool send_text_state(text::Text *text, std::string state);
|
bool send_text_state(text::Text *text, std::string state);
|
||||||
bool send_text_info(text::Text *text);
|
bool send_text_info(text::Text *text);
|
||||||
|
|
|
@ -8093,6 +8093,179 @@ void ValveCommandRequest::dump_to(std::string &out) const {
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
bool ListEntitiesDateTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 6: {
|
||||||
|
this->disabled_by_default = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 7: {
|
||||||
|
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool ListEntitiesDateTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 1: {
|
||||||
|
this->object_id = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
this->name = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 4: {
|
||||||
|
this->unique_id = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 5: {
|
||||||
|
this->icon = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool ListEntitiesDateTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 2: {
|
||||||
|
this->key = value.as_fixed32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
|
buffer.encode_string(1, this->object_id);
|
||||||
|
buffer.encode_fixed32(2, this->key);
|
||||||
|
buffer.encode_string(3, this->name);
|
||||||
|
buffer.encode_string(4, this->unique_id);
|
||||||
|
buffer.encode_string(5, this->icon);
|
||||||
|
buffer.encode_bool(6, this->disabled_by_default);
|
||||||
|
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||||
|
}
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void ListEntitiesDateTimeResponse::dump_to(std::string &out) const {
|
||||||
|
__attribute__((unused)) char buffer[64];
|
||||||
|
out.append("ListEntitiesDateTimeResponse {\n");
|
||||||
|
out.append(" object_id: ");
|
||||||
|
out.append("'").append(this->object_id).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" key: ");
|
||||||
|
sprintf(buffer, "%" PRIu32, this->key);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" name: ");
|
||||||
|
out.append("'").append(this->name).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" unique_id: ");
|
||||||
|
out.append("'").append(this->unique_id).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" icon: ");
|
||||||
|
out.append("'").append(this->icon).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" disabled_by_default: ");
|
||||||
|
out.append(YESNO(this->disabled_by_default));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" entity_category: ");
|
||||||
|
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||||
|
out.append("\n");
|
||||||
|
out.append("}");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
bool DateTimeStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 2: {
|
||||||
|
this->missing_state = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool DateTimeStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 1: {
|
||||||
|
this->key = value.as_fixed32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
this->epoch_seconds = value.as_fixed32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void DateTimeStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
|
buffer.encode_fixed32(1, this->key);
|
||||||
|
buffer.encode_bool(2, this->missing_state);
|
||||||
|
buffer.encode_fixed32(3, this->epoch_seconds);
|
||||||
|
}
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void DateTimeStateResponse::dump_to(std::string &out) const {
|
||||||
|
__attribute__((unused)) char buffer[64];
|
||||||
|
out.append("DateTimeStateResponse {\n");
|
||||||
|
out.append(" key: ");
|
||||||
|
sprintf(buffer, "%" PRIu32, this->key);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" missing_state: ");
|
||||||
|
out.append(YESNO(this->missing_state));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" epoch_seconds: ");
|
||||||
|
sprintf(buffer, "%" PRIu32, this->epoch_seconds);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
|
out.append("}");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
bool DateTimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 1: {
|
||||||
|
this->key = value.as_fixed32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
this->epoch_seconds = value.as_fixed32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void DateTimeCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||||
|
buffer.encode_fixed32(1, this->key);
|
||||||
|
buffer.encode_fixed32(2, this->epoch_seconds);
|
||||||
|
}
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void DateTimeCommandRequest::dump_to(std::string &out) const {
|
||||||
|
__attribute__((unused)) char buffer[64];
|
||||||
|
out.append("DateTimeCommandRequest {\n");
|
||||||
|
out.append(" key: ");
|
||||||
|
sprintf(buffer, "%" PRIu32, this->key);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" epoch_seconds: ");
|
||||||
|
sprintf(buffer, "%" PRIu32, this->epoch_seconds);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
|
out.append("}");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -2060,6 +2060,51 @@ class ValveCommandRequest : public ProtoMessage {
|
||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
class ListEntitiesDateTimeResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string object_id{};
|
||||||
|
uint32_t key{0};
|
||||||
|
std::string name{};
|
||||||
|
std::string unique_id{};
|
||||||
|
std::string icon{};
|
||||||
|
bool disabled_by_default{false};
|
||||||
|
enums::EntityCategory entity_category{};
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
class DateTimeStateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0};
|
||||||
|
bool missing_state{false};
|
||||||
|
uint32_t epoch_seconds{0};
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
class DateTimeCommandRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0};
|
||||||
|
uint32_t epoch_seconds{0};
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -591,6 +591,24 @@ bool APIServerConnectionBase::send_valve_state_response(const ValveStateResponse
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VALVE
|
#ifdef USE_VALVE
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
bool APIServerConnectionBase::send_list_entities_date_time_response(const ListEntitiesDateTimeResponse &msg) {
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
ESP_LOGVV(TAG, "send_list_entities_date_time_response: %s", msg.dump().c_str());
|
||||||
|
#endif
|
||||||
|
return this->send_message_<ListEntitiesDateTimeResponse>(msg, 112);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
bool APIServerConnectionBase::send_date_time_state_response(const DateTimeStateResponse &msg) {
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
ESP_LOGVV(TAG, "send_date_time_state_response: %s", msg.dump().c_str());
|
||||||
|
#endif
|
||||||
|
return this->send_message_<DateTimeStateResponse>(msg, 113);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
#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: {
|
||||||
|
@ -1064,6 +1082,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||||
ESP_LOGVV(TAG, "on_valve_command_request: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "on_valve_command_request: %s", msg.dump().c_str());
|
||||||
#endif
|
#endif
|
||||||
this->on_valve_command_request(msg);
|
this->on_valve_command_request(msg);
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 114: {
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
DateTimeCommandRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
ESP_LOGVV(TAG, "on_date_time_command_request: %s", msg.dump().c_str());
|
||||||
|
#endif
|
||||||
|
this->on_date_time_command_request(msg);
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1379,6 +1408,19 @@ void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg)
|
||||||
this->time_command(msg);
|
this->time_command(msg);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->datetime_command(msg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
|
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
|
||||||
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||||
|
|
|
@ -294,6 +294,15 @@ class APIServerConnectionBase : public ProtoService {
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VALVE
|
#ifdef USE_VALVE
|
||||||
virtual void on_valve_command_request(const ValveCommandRequest &value){};
|
virtual void on_valve_command_request(const ValveCommandRequest &value){};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
bool send_list_entities_date_time_response(const ListEntitiesDateTimeResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
bool send_date_time_state_response(const DateTimeStateResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
|
||||||
#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;
|
||||||
|
@ -358,6 +367,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
virtual void time_command(const TimeCommandRequest &msg) = 0;
|
virtual void time_command(const TimeCommandRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
|
||||||
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
|
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
|
@ -453,6 +465,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
void on_time_command_request(const TimeCommandRequest &msg) override;
|
void on_time_command_request(const TimeCommandRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -273,6 +273,15 @@ void APIServer::on_time_update(datetime::TimeEntity *obj) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
void APIServer::on_datetime_update(datetime::DateTimeEntity *obj) {
|
||||||
|
if (obj->is_internal())
|
||||||
|
return;
|
||||||
|
for (auto &c : this->clients_)
|
||||||
|
c->send_datetime_state(obj);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
void APIServer::on_text_update(text::Text *obj, const std::string &state) {
|
void APIServer::on_text_update(text::Text *obj, const std::string &state) {
|
||||||
if (obj->is_internal())
|
if (obj->is_internal())
|
||||||
|
|
|
@ -72,6 +72,9 @@ class APIServer : public Component, public Controller {
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
void on_time_update(datetime::TimeEntity *obj) override;
|
void on_time_update(datetime::TimeEntity *obj) override;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
void on_datetime_update(datetime::DateTimeEntity *obj) override;
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
void on_text_update(text::Text *obj, const std::string &state) override;
|
void on_text_update(text::Text *obj, const std::string &state) override;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -71,6 +71,12 @@ bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { return this->cl
|
||||||
bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_info(time); }
|
bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_info(time); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) {
|
||||||
|
return this->client_->send_datetime_info(datetime);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); }
|
bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); }
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -52,6 +52,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
bool on_time(datetime::TimeEntity *time) override;
|
bool on_time(datetime::TimeEntity *time) override;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
bool on_datetime(datetime::DateTimeEntity *datetime) override;
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
bool on_text(text::Text *text) override;
|
bool on_text(text::Text *text) override;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -48,6 +48,11 @@ bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->cl
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
bool InitialStateIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_state(time); }
|
bool InitialStateIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_state(time); }
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
bool InitialStateIterator::on_datetime(datetime::DateTimeEntity *datetime) {
|
||||||
|
return this->client_->send_datetime_state(datetime);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); }
|
bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); }
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -49,6 +49,9 @@ class InitialStateIterator : public ComponentIterator {
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
bool on_time(datetime::TimeEntity *time) override;
|
bool on_time(datetime::TimeEntity *time) override;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
bool on_datetime(datetime::DateTimeEntity *datetime) override;
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
bool on_text(text::Text *text) override;
|
bool on_text(text::Text *text) override;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -51,15 +51,15 @@ void binary_sensor::MultiClickTrigger::on_state_(bool state) {
|
||||||
MultiClickTriggerEvent evt = this->timing_[*this->at_index_];
|
MultiClickTriggerEvent evt = this->timing_[*this->at_index_];
|
||||||
|
|
||||||
if (evt.max_length != 4294967294UL) {
|
if (evt.max_length != 4294967294UL) {
|
||||||
ESP_LOGV(TAG, "A i=%u min=%" PRIu32 " max=%" PRIu32, *this->at_index_, evt.min_length, evt.max_length); // NOLINT
|
ESP_LOGV(TAG, "A i=%zu min=%" PRIu32 " max=%" PRIu32, *this->at_index_, evt.min_length, evt.max_length); // NOLINT
|
||||||
this->schedule_is_valid_(evt.min_length);
|
this->schedule_is_valid_(evt.min_length);
|
||||||
this->schedule_is_not_valid_(evt.max_length);
|
this->schedule_is_not_valid_(evt.max_length);
|
||||||
} else if (*this->at_index_ + 1 != this->timing_.size()) {
|
} else if (*this->at_index_ + 1 != this->timing_.size()) {
|
||||||
ESP_LOGV(TAG, "B i=%u min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
ESP_LOGV(TAG, "B i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
||||||
this->cancel_timeout("is_not_valid");
|
this->cancel_timeout("is_not_valid");
|
||||||
this->schedule_is_valid_(evt.min_length);
|
this->schedule_is_valid_(evt.min_length);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "C i=%u min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
ESP_LOGV(TAG, "C i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
||||||
this->is_valid_ = false;
|
this->is_valid_ = false;
|
||||||
this->cancel_timeout("is_not_valid");
|
this->cancel_timeout("is_not_valid");
|
||||||
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });
|
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });
|
||||||
|
|
|
@ -25,9 +25,13 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||||
this->proxy_->send_connections_free();
|
this->proxy_->send_connections_free();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_OPEN_EVT: {
|
case ESP_GATTC_CLOSE_EVT: {
|
||||||
if (param->open.conn_id != this->conn_id_)
|
this->proxy_->send_device_connection(this->address_, false, 0, param->close.reason);
|
||||||
|
this->set_address(0);
|
||||||
|
this->proxy_->send_connections_free();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case ESP_GATTC_OPEN_EVT: {
|
||||||
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
||||||
this->proxy_->send_device_connection(this->address_, false, 0, param->open.status);
|
this->proxy_->send_device_connection(this->address_, false, 0, param->open.status);
|
||||||
this->set_address(0);
|
this->set_address(0);
|
||||||
|
@ -39,9 +43,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||||
this->seen_mtu_or_services_ = false;
|
this->seen_mtu_or_services_ = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_CFG_MTU_EVT: {
|
case ESP_GATTC_CFG_MTU_EVT:
|
||||||
if (param->cfg_mtu.conn_id != this->conn_id_)
|
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||||
break;
|
|
||||||
if (!this->seen_mtu_or_services_) {
|
if (!this->seen_mtu_or_services_) {
|
||||||
// We don't know if we will get the MTU or the services first, so
|
// We don't know if we will get the MTU or the services first, so
|
||||||
// only send the device connection true if we have already received
|
// only send the device connection true if we have already received
|
||||||
|
@ -53,24 +56,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||||
this->proxy_->send_connections_free();
|
this->proxy_->send_connections_free();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
|
||||||
if (param->search_cmpl.conn_id != this->conn_id_)
|
|
||||||
break;
|
|
||||||
if (!this->seen_mtu_or_services_) {
|
|
||||||
// We don't know if we will get the MTU or the services first, so
|
|
||||||
// only send the device connection true if we have already received
|
|
||||||
// the mtu.
|
|
||||||
this->seen_mtu_or_services_ = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this->proxy_->send_device_connection(this->address_, true, this->mtu_);
|
|
||||||
this->proxy_->send_connections_free();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ESP_GATTC_READ_DESCR_EVT:
|
case ESP_GATTC_READ_DESCR_EVT:
|
||||||
case ESP_GATTC_READ_CHAR_EVT: {
|
case ESP_GATTC_READ_CHAR_EVT: {
|
||||||
if (param->read.conn_id != this->conn_id_)
|
|
||||||
break;
|
|
||||||
if (param->read.status != ESP_GATT_OK) {
|
if (param->read.status != ESP_GATT_OK) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
|
ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
|
||||||
this->address_str_.c_str(), param->read.handle, param->read.status);
|
this->address_str_.c_str(), param->read.handle, param->read.status);
|
||||||
|
@ -89,8 +76,6 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||||
}
|
}
|
||||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||||
if (param->write.conn_id != this->conn_id_)
|
|
||||||
break;
|
|
||||||
if (param->write.status != ESP_GATT_OK) {
|
if (param->write.status != ESP_GATT_OK) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
|
ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
|
||||||
this->address_str_.c_str(), param->write.handle, param->write.status);
|
this->address_str_.c_str(), param->write.handle, param->write.status);
|
||||||
|
@ -131,8 +116,6 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_NOTIFY_EVT: {
|
case ESP_GATTC_NOTIFY_EVT: {
|
||||||
if (param->notify.conn_id != this->conn_id_)
|
|
||||||
break;
|
|
||||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, this->address_str_.c_str(),
|
||||||
param->notify.handle);
|
param->notify.handle);
|
||||||
api::BluetoothGATTNotifyDataResponse resp;
|
api::BluetoothGATTNotifyDataResponse resp;
|
||||||
|
|
|
@ -4,6 +4,7 @@ from esphome.components import uart
|
||||||
from esphome.const import CONF_ID, CONF_ADDRESS
|
from esphome.const import CONF_ID, CONF_ADDRESS
|
||||||
|
|
||||||
CODEOWNERS = ["@s1lvi0"]
|
CODEOWNERS = ["@s1lvi0"]
|
||||||
|
MULTI_CONF = True
|
||||||
DEPENDENCIES = ["uart"]
|
DEPENDENCIES = ["uart"]
|
||||||
|
|
||||||
CONF_BMS_DALY_ID = "bms_daly_id"
|
CONF_BMS_DALY_ID = "bms_daly_id"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
|
||||||
# import cpp_generator as cpp
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.components import mqtt, time
|
from esphome.components import mqtt, time
|
||||||
|
@ -13,6 +12,7 @@ from esphome.const import (
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
CONF_DATE,
|
CONF_DATE,
|
||||||
|
CONF_DATETIME,
|
||||||
CONF_TIME,
|
CONF_TIME,
|
||||||
CONF_YEAR,
|
CONF_YEAR,
|
||||||
CONF_MONTH,
|
CONF_MONTH,
|
||||||
|
@ -27,6 +27,7 @@ from esphome.cpp_helpers import setup_entity
|
||||||
|
|
||||||
|
|
||||||
CODEOWNERS = ["@rfdarter", "@jesserockz"]
|
CODEOWNERS = ["@rfdarter", "@jesserockz"]
|
||||||
|
DEPENDENCIES = ["time"]
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
@ -34,10 +35,12 @@ datetime_ns = cg.esphome_ns.namespace("datetime")
|
||||||
DateTimeBase = datetime_ns.class_("DateTimeBase", cg.EntityBase)
|
DateTimeBase = datetime_ns.class_("DateTimeBase", cg.EntityBase)
|
||||||
DateEntity = datetime_ns.class_("DateEntity", DateTimeBase)
|
DateEntity = datetime_ns.class_("DateEntity", DateTimeBase)
|
||||||
TimeEntity = datetime_ns.class_("TimeEntity", DateTimeBase)
|
TimeEntity = datetime_ns.class_("TimeEntity", DateTimeBase)
|
||||||
|
DateTimeEntity = datetime_ns.class_("DateTimeEntity", DateTimeBase)
|
||||||
|
|
||||||
# Actions
|
# Actions
|
||||||
DateSetAction = datetime_ns.class_("DateSetAction", automation.Action)
|
DateSetAction = datetime_ns.class_("DateSetAction", automation.Action)
|
||||||
TimeSetAction = datetime_ns.class_("TimeSetAction", automation.Action)
|
TimeSetAction = datetime_ns.class_("TimeSetAction", automation.Action)
|
||||||
|
DateTimeSetAction = datetime_ns.class_("DateTimeSetAction", automation.Action)
|
||||||
|
|
||||||
DateTimeStateTrigger = datetime_ns.class_(
|
DateTimeStateTrigger = datetime_ns.class_(
|
||||||
"DateTimeStateTrigger", automation.Trigger.template(cg.ESPTime)
|
"DateTimeStateTrigger", automation.Trigger.template(cg.ESPTime)
|
||||||
|
@ -46,6 +49,12 @@ DateTimeStateTrigger = datetime_ns.class_(
|
||||||
OnTimeTrigger = datetime_ns.class_(
|
OnTimeTrigger = datetime_ns.class_(
|
||||||
"OnTimeTrigger", automation.Trigger, cg.Component, cg.Parented.template(TimeEntity)
|
"OnTimeTrigger", automation.Trigger, cg.Component, cg.Parented.template(TimeEntity)
|
||||||
)
|
)
|
||||||
|
OnDateTimeTrigger = datetime_ns.class_(
|
||||||
|
"OnDateTimeTrigger",
|
||||||
|
automation.Trigger,
|
||||||
|
cg.Component,
|
||||||
|
cg.Parented.template(DateTimeEntity),
|
||||||
|
)
|
||||||
|
|
||||||
DATETIME_MODES = [
|
DATETIME_MODES = [
|
||||||
"DATE",
|
"DATE",
|
||||||
|
@ -61,45 +70,55 @@ _DATETIME_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||||
}
|
}
|
||||||
).extend(cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA))
|
).extend(cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA))
|
||||||
|
|
||||||
|
|
||||||
def date_schema(class_: MockObjClass) -> cv.Schema:
|
def date_schema(class_: MockObjClass) -> cv.Schema:
|
||||||
schema = {
|
schema = cv.Schema(
|
||||||
|
{
|
||||||
cv.GenerateID(): cv.declare_id(class_),
|
cv.GenerateID(): cv.declare_id(class_),
|
||||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTDateComponent),
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTDateComponent),
|
||||||
cv.Optional(CONF_TYPE, default="DATE"): cv.one_of("DATE", upper=True),
|
cv.Optional(CONF_TYPE, default="DATE"): cv.one_of("DATE", upper=True),
|
||||||
}
|
}
|
||||||
|
)
|
||||||
return _DATETIME_SCHEMA.extend(schema)
|
return _DATETIME_SCHEMA.extend(schema)
|
||||||
|
|
||||||
|
|
||||||
def time_schema(class_: MockObjClass) -> cv.Schema:
|
def time_schema(class_: MockObjClass) -> cv.Schema:
|
||||||
schema = {
|
schema = cv.Schema(
|
||||||
|
{
|
||||||
cv.GenerateID(): cv.declare_id(class_),
|
cv.GenerateID(): cv.declare_id(class_),
|
||||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTimeComponent),
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTimeComponent),
|
||||||
cv.Optional(CONF_TYPE, default="TIME"): cv.one_of("TIME", upper=True),
|
cv.Optional(CONF_TYPE, default="TIME"): cv.one_of("TIME", upper=True),
|
||||||
cv.Inclusive(
|
cv.Optional(CONF_ON_TIME): automation.validate_automation(
|
||||||
CONF_ON_TIME,
|
|
||||||
group_of_inclusion=CONF_ON_TIME,
|
|
||||||
msg="`on_time` and `time_id` must both be specified",
|
|
||||||
): automation.validate_automation(
|
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnTimeTrigger),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnTimeTrigger),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.Inclusive(CONF_TIME_ID, group_of_inclusion=CONF_ON_TIME): cv.use_id(
|
|
||||||
time.RealTimeClock
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
return _DATETIME_SCHEMA.extend(schema)
|
return _DATETIME_SCHEMA.extend(schema)
|
||||||
|
|
||||||
|
|
||||||
def datetime_schema(class_: MockObjClass) -> cv.Schema:
|
def datetime_schema(class_: MockObjClass) -> cv.Schema:
|
||||||
schema = {
|
schema = cv.Schema(
|
||||||
|
{
|
||||||
cv.GenerateID(): cv.declare_id(class_),
|
cv.GenerateID(): cv.declare_id(class_),
|
||||||
cv.Optional(CONF_TYPE, default="DATETIME"): cv.one_of("DATETIME", upper=True),
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||||
|
mqtt.MQTTDateTimeComponent
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_TYPE, default="DATETIME"): cv.one_of(
|
||||||
|
"DATETIME", upper=True
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ON_TIME): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnDateTimeTrigger),
|
||||||
}
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
return _DATETIME_SCHEMA.extend(schema)
|
return _DATETIME_SCHEMA.extend(schema)
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,13 +132,11 @@ async def setup_datetime_core_(var, config):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)
|
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)
|
||||||
|
|
||||||
rtc_id = config.get(CONF_TIME_ID)
|
rtc = await cg.get_variable(config[CONF_TIME_ID])
|
||||||
rtc = None
|
cg.add(var.set_rtc(rtc))
|
||||||
if rtc_id is not None:
|
|
||||||
rtc = await cg.get_variable(rtc_id)
|
|
||||||
for conf in config.get(CONF_ON_TIME, []):
|
for conf in config.get(CONF_ON_TIME, []):
|
||||||
assert rtc is not None
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], rtc)
|
|
||||||
await automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, [], conf)
|
||||||
await cg.register_component(trigger, conf)
|
await cg.register_component(trigger, conf)
|
||||||
await cg.register_parented(trigger, var)
|
await cg.register_parented(trigger, var)
|
||||||
|
@ -161,16 +178,16 @@ async def datetime_date_set_to_code(config, action_id, template_arg, args):
|
||||||
action_var = cg.new_Pvariable(action_id, template_arg)
|
action_var = cg.new_Pvariable(action_id, template_arg)
|
||||||
await cg.register_parented(action_var, config[CONF_ID])
|
await cg.register_parented(action_var, config[CONF_ID])
|
||||||
|
|
||||||
date = config[CONF_DATE]
|
date_config = config[CONF_DATE]
|
||||||
if cg.is_template(date):
|
if cg.is_template(date_config):
|
||||||
template_ = await cg.templatable(config[CONF_DATE], [], cg.ESPTime)
|
template_ = await cg.templatable(date_config, [], cg.ESPTime)
|
||||||
cg.add(action_var.set_date(template_))
|
cg.add(action_var.set_date(template_))
|
||||||
else:
|
else:
|
||||||
date_struct = cg.StructInitializer(
|
date_struct = cg.StructInitializer(
|
||||||
cg.ESPTime,
|
cg.ESPTime,
|
||||||
("day_of_month", date[CONF_DAY]),
|
("day_of_month", date_config[CONF_DAY]),
|
||||||
("month", date[CONF_MONTH]),
|
("month", date_config[CONF_MONTH]),
|
||||||
("year", date[CONF_YEAR]),
|
("year", date_config[CONF_YEAR]),
|
||||||
)
|
)
|
||||||
cg.add(action_var.set_date(date_struct))
|
cg.add(action_var.set_date(date_struct))
|
||||||
return action_var
|
return action_var
|
||||||
|
@ -194,7 +211,7 @@ async def datetime_time_set_to_code(config, action_id, template_arg, args):
|
||||||
|
|
||||||
time_config = config[CONF_TIME]
|
time_config = config[CONF_TIME]
|
||||||
if cg.is_template(time_config):
|
if cg.is_template(time_config):
|
||||||
template_ = await cg.templatable(config[CONF_TIME], [], cg.ESPTime)
|
template_ = await cg.templatable(time_config, [], cg.ESPTime)
|
||||||
cg.add(action_var.set_time(template_))
|
cg.add(action_var.set_time(template_))
|
||||||
else:
|
else:
|
||||||
time_struct = cg.StructInitializer(
|
time_struct = cg.StructInitializer(
|
||||||
|
@ -205,3 +222,35 @@ async def datetime_time_set_to_code(config, action_id, template_arg, args):
|
||||||
)
|
)
|
||||||
cg.add(action_var.set_time(time_struct))
|
cg.add(action_var.set_time(time_struct))
|
||||||
return action_var
|
return action_var
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"datetime.datetime.set",
|
||||||
|
DateTimeSetAction,
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_ID): cv.use_id(DateTimeEntity),
|
||||||
|
cv.Required(CONF_DATETIME): cv.Any(cv.returning_lambda, cv.date_time()),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def datetime_datetime_set_to_code(config, action_id, template_arg, args):
|
||||||
|
action_var = cg.new_Pvariable(action_id, template_arg)
|
||||||
|
await cg.register_parented(action_var, config[CONF_ID])
|
||||||
|
|
||||||
|
datetime_config = config[CONF_DATETIME]
|
||||||
|
if cg.is_template(datetime_config):
|
||||||
|
template_ = await cg.templatable(datetime_config, [], cg.ESPTime)
|
||||||
|
cg.add(action_var.set_datetime(template_))
|
||||||
|
else:
|
||||||
|
datetime_struct = cg.StructInitializer(
|
||||||
|
cg.ESPTime,
|
||||||
|
("second", datetime_config[CONF_SECOND]),
|
||||||
|
("minute", datetime_config[CONF_MINUTE]),
|
||||||
|
("hour", datetime_config[CONF_HOUR]),
|
||||||
|
("day_of_month", datetime_config[CONF_DAY]),
|
||||||
|
("month", datetime_config[CONF_MONTH]),
|
||||||
|
("year", datetime_config[CONF_YEAR]),
|
||||||
|
)
|
||||||
|
cg.add(action_var.set_datetime(datetime_struct))
|
||||||
|
return action_var
|
||||||
|
|
|
@ -40,10 +40,13 @@ void DateCall::validate_() {
|
||||||
if (this->year_.has_value() && (this->year_ < 1970 || this->year_ > 3000)) {
|
if (this->year_.has_value() && (this->year_ < 1970 || this->year_ > 3000)) {
|
||||||
ESP_LOGE(TAG, "Year must be between 1970 and 3000");
|
ESP_LOGE(TAG, "Year must be between 1970 and 3000");
|
||||||
this->year_.reset();
|
this->year_.reset();
|
||||||
|
this->month_.reset();
|
||||||
|
this->day_.reset();
|
||||||
}
|
}
|
||||||
if (this->month_.has_value() && (this->month_ < 1 || this->month_ > 12)) {
|
if (this->month_.has_value() && (this->month_ < 1 || this->month_ > 12)) {
|
||||||
ESP_LOGE(TAG, "Month must be between 1 and 12");
|
ESP_LOGE(TAG, "Month must be between 1 and 12");
|
||||||
this->month_.reset();
|
this->month_.reset();
|
||||||
|
this->day_.reset();
|
||||||
}
|
}
|
||||||
if (this->day_.has_value()) {
|
if (this->day_.has_value()) {
|
||||||
uint16_t year = 0;
|
uint16_t year = 0;
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#include "esphome/core/entity_base.h"
|
#include "esphome/core/entity_base.h"
|
||||||
#include "esphome/core/time.h"
|
#include "esphome/core/time.h"
|
||||||
|
|
||||||
|
#include "esphome/components/time/real_time_clock.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace datetime {
|
namespace datetime {
|
||||||
|
|
||||||
|
@ -17,9 +19,14 @@ class DateTimeBase : public EntityBase {
|
||||||
|
|
||||||
void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
|
void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
|
||||||
|
|
||||||
|
void set_rtc(time::RealTimeClock *rtc) { this->rtc_ = rtc; }
|
||||||
|
time::RealTimeClock *get_rtc() const { return this->rtc_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CallbackManager<void()> state_callback_;
|
CallbackManager<void()> state_callback_;
|
||||||
|
|
||||||
|
time::RealTimeClock *rtc_;
|
||||||
|
|
||||||
bool has_state_{false};
|
bool has_state_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
252
esphome/components/datetime/datetime_entity.cpp
Normal file
252
esphome/components/datetime/datetime_entity.cpp
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
#include "datetime_entity.h"
|
||||||
|
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace datetime {
|
||||||
|
|
||||||
|
static const char *const TAG = "datetime.datetime_entity";
|
||||||
|
|
||||||
|
void DateTimeEntity::publish_state() {
|
||||||
|
if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) {
|
||||||
|
this->has_state_ = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->year_ < 1970 || this->year_ > 3000) {
|
||||||
|
this->has_state_ = false;
|
||||||
|
ESP_LOGE(TAG, "Year must be between 1970 and 3000");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->month_ < 1 || this->month_ > 12) {
|
||||||
|
this->has_state_ = false;
|
||||||
|
ESP_LOGE(TAG, "Month must be between 1 and 12");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->day_ > days_in_month(this->month_, this->year_)) {
|
||||||
|
this->has_state_ = false;
|
||||||
|
ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->hour_ > 23) {
|
||||||
|
this->has_state_ = false;
|
||||||
|
ESP_LOGE(TAG, "Hour must be between 0 and 23");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->minute_ > 59) {
|
||||||
|
this->has_state_ = false;
|
||||||
|
ESP_LOGE(TAG, "Minute must be between 0 and 59");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->second_ > 59) {
|
||||||
|
this->has_state_ = false;
|
||||||
|
ESP_LOGE(TAG, "Second must be between 0 and 59");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->has_state_ = true;
|
||||||
|
ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_,
|
||||||
|
this->month_, this->day_, this->hour_, this->minute_, this->second_);
|
||||||
|
this->state_callback_.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeCall DateTimeEntity::make_call() { return DateTimeCall(this); }
|
||||||
|
|
||||||
|
ESPTime DateTimeEntity::state_as_esptime() const {
|
||||||
|
ESPTime obj;
|
||||||
|
obj.year = this->year_;
|
||||||
|
obj.month = this->month_;
|
||||||
|
obj.day_of_month = this->day_;
|
||||||
|
obj.hour = this->hour_;
|
||||||
|
obj.minute = this->minute_;
|
||||||
|
obj.second = this->second_;
|
||||||
|
obj.day_of_week = 1; // Required to be valid for recalc_timestamp_local but not used.
|
||||||
|
obj.day_of_year = 1; // Required to be valid for recalc_timestamp_local but not used.
|
||||||
|
obj.recalc_timestamp_local(false);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DateTimeCall::validate_() {
|
||||||
|
if (this->year_.has_value() && (this->year_ < 1970 || this->year_ > 3000)) {
|
||||||
|
ESP_LOGE(TAG, "Year must be between 1970 and 3000");
|
||||||
|
this->year_.reset();
|
||||||
|
this->month_.reset();
|
||||||
|
this->day_.reset();
|
||||||
|
}
|
||||||
|
if (this->month_.has_value() && (this->month_ < 1 || this->month_ > 12)) {
|
||||||
|
ESP_LOGE(TAG, "Month must be between 1 and 12");
|
||||||
|
this->month_.reset();
|
||||||
|
this->day_.reset();
|
||||||
|
}
|
||||||
|
if (this->day_.has_value()) {
|
||||||
|
uint16_t year = 0;
|
||||||
|
uint8_t month = 0;
|
||||||
|
if (this->month_.has_value()) {
|
||||||
|
month = *this->month_;
|
||||||
|
} else {
|
||||||
|
if (this->parent_->month != 0) {
|
||||||
|
month = this->parent_->month;
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Month must be set to validate day");
|
||||||
|
this->day_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->year_.has_value()) {
|
||||||
|
year = *this->year_;
|
||||||
|
} else {
|
||||||
|
if (this->parent_->year != 0) {
|
||||||
|
year = this->parent_->year;
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Year must be set to validate day");
|
||||||
|
this->day_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->day_.has_value() && *this->day_ > days_in_month(month, year)) {
|
||||||
|
ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(month, year), month);
|
||||||
|
this->day_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->hour_.has_value() && this->hour_ > 23) {
|
||||||
|
ESP_LOGE(TAG, "Hour must be between 0 and 23");
|
||||||
|
this->hour_.reset();
|
||||||
|
}
|
||||||
|
if (this->minute_.has_value() && this->minute_ > 59) {
|
||||||
|
ESP_LOGE(TAG, "Minute must be between 0 and 59");
|
||||||
|
this->minute_.reset();
|
||||||
|
}
|
||||||
|
if (this->second_.has_value() && this->second_ > 59) {
|
||||||
|
ESP_LOGE(TAG, "Second must be between 0 and 59");
|
||||||
|
this->second_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DateTimeCall::perform() {
|
||||||
|
this->validate_();
|
||||||
|
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||||
|
|
||||||
|
if (this->year_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Year: %d", *this->year_);
|
||||||
|
}
|
||||||
|
if (this->month_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Month: %d", *this->month_);
|
||||||
|
}
|
||||||
|
if (this->day_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Day: %d", *this->day_);
|
||||||
|
}
|
||||||
|
if (this->hour_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Hour: %d", *this->hour_);
|
||||||
|
}
|
||||||
|
if (this->minute_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Minute: %d", *this->minute_);
|
||||||
|
}
|
||||||
|
if (this->second_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Second: %d", *this->second_);
|
||||||
|
}
|
||||||
|
this->parent_->control(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeCall &DateTimeCall::set_datetime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute,
|
||||||
|
uint8_t second) {
|
||||||
|
this->year_ = year;
|
||||||
|
this->month_ = month;
|
||||||
|
this->day_ = day;
|
||||||
|
this->hour_ = hour;
|
||||||
|
this->minute_ = minute;
|
||||||
|
this->second_ = second;
|
||||||
|
return *this;
|
||||||
|
};
|
||||||
|
|
||||||
|
DateTimeCall &DateTimeCall::set_datetime(ESPTime datetime) {
|
||||||
|
return this->set_datetime(datetime.year, datetime.month, datetime.day_of_month, datetime.hour, datetime.minute,
|
||||||
|
datetime.second);
|
||||||
|
};
|
||||||
|
|
||||||
|
DateTimeCall &DateTimeCall::set_datetime(const std::string &datetime) {
|
||||||
|
ESPTime val{};
|
||||||
|
if (!ESPTime::strptime(datetime, val)) {
|
||||||
|
ESP_LOGE(TAG, "Could not convert the time string to an ESPTime object");
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
return this->set_datetime(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeCall &DateTimeCall::set_datetime(time_t epoch_seconds) {
|
||||||
|
ESPTime val = ESPTime::from_epoch_local(epoch_seconds);
|
||||||
|
return this->set_datetime(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeCall DateTimeEntityRestoreState::to_call(DateTimeEntity *datetime) {
|
||||||
|
DateTimeCall call = datetime->make_call();
|
||||||
|
call.set_datetime(this->year, this->month, this->day, this->hour, this->minute, this->second);
|
||||||
|
return call;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DateTimeEntityRestoreState::apply(DateTimeEntity *time) {
|
||||||
|
time->year_ = this->year;
|
||||||
|
time->month_ = this->month;
|
||||||
|
time->day_ = this->day;
|
||||||
|
time->hour_ = this->hour;
|
||||||
|
time->minute_ = this->minute;
|
||||||
|
time->second_ = this->second;
|
||||||
|
time->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
|
||||||
|
// there has been a drastic time synchronization
|
||||||
|
|
||||||
|
void OnDateTimeTrigger::loop() {
|
||||||
|
if (!this->parent_->has_state()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESPTime time = this->parent_->rtc_->now();
|
||||||
|
if (!time.is_valid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->last_check_.has_value()) {
|
||||||
|
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
|
||||||
|
ESP_LOGW(TAG, "Time has jumped back!");
|
||||||
|
} else if (*this->last_check_ >= time) {
|
||||||
|
// already handled this one
|
||||||
|
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) {
|
||||||
|
this->last_check_->increment_second();
|
||||||
|
if (*this->last_check_ >= time)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (this->matches_(*this->last_check_)) {
|
||||||
|
this->trigger();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->last_check_ = time;
|
||||||
|
if (!time.fields_in_range()) {
|
||||||
|
ESP_LOGW(TAG, "Time is out of range!");
|
||||||
|
ESP_LOGD(TAG, "Second=%02u Minute=%02u Hour=%02u Day=%02u Month=%02u Year=%04u", time.second, time.minute,
|
||||||
|
time.hour, time.day_of_month, time.month, time.year);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->matches_(time))
|
||||||
|
this->trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OnDateTimeTrigger::matches_(const ESPTime &time) const {
|
||||||
|
return time.is_valid() && time.year == this->parent_->year && time.month == this->parent_->month &&
|
||||||
|
time.day_of_month == this->parent_->day && time.hour == this->parent_->hour &&
|
||||||
|
time.minute == this->parent_->minute && time.second == this->parent_->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace datetime
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_DATETIME_TIME
|
150
esphome/components/datetime/datetime_entity.h
Normal file
150
esphome/components/datetime/datetime_entity.h
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/time.h"
|
||||||
|
|
||||||
|
#include "datetime_base.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace datetime {
|
||||||
|
|
||||||
|
#define LOG_DATETIME_DATETIME(prefix, type, obj) \
|
||||||
|
if ((obj) != nullptr) { \
|
||||||
|
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||||
|
if (!(obj)->get_icon().empty()) { \
|
||||||
|
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
class DateTimeCall;
|
||||||
|
class DateTimeEntity;
|
||||||
|
|
||||||
|
struct DateTimeEntityRestoreState {
|
||||||
|
uint16_t year;
|
||||||
|
uint8_t month;
|
||||||
|
uint8_t day;
|
||||||
|
uint8_t hour;
|
||||||
|
uint8_t minute;
|
||||||
|
uint8_t second;
|
||||||
|
|
||||||
|
DateTimeCall to_call(DateTimeEntity *datetime);
|
||||||
|
void apply(DateTimeEntity *datetime);
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
class DateTimeEntity : public DateTimeBase {
|
||||||
|
protected:
|
||||||
|
uint16_t year_;
|
||||||
|
uint8_t month_;
|
||||||
|
uint8_t day_;
|
||||||
|
uint8_t hour_;
|
||||||
|
uint8_t minute_;
|
||||||
|
uint8_t second_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void publish_state();
|
||||||
|
DateTimeCall make_call();
|
||||||
|
|
||||||
|
ESPTime state_as_esptime() const override;
|
||||||
|
|
||||||
|
const uint16_t &year = year_;
|
||||||
|
const uint8_t &month = month_;
|
||||||
|
const uint8_t &day = day_;
|
||||||
|
const uint8_t &hour = hour_;
|
||||||
|
const uint8_t &minute = minute_;
|
||||||
|
const uint8_t &second = second_;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend class DateTimeCall;
|
||||||
|
friend struct DateTimeEntityRestoreState;
|
||||||
|
friend class OnDateTimeTrigger;
|
||||||
|
|
||||||
|
virtual void control(const DateTimeCall &call) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DateTimeCall {
|
||||||
|
public:
|
||||||
|
explicit DateTimeCall(DateTimeEntity *parent) : parent_(parent) {}
|
||||||
|
void perform();
|
||||||
|
DateTimeCall &set_datetime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second);
|
||||||
|
DateTimeCall &set_datetime(ESPTime datetime);
|
||||||
|
DateTimeCall &set_datetime(const std::string &datetime);
|
||||||
|
DateTimeCall &set_datetime(time_t epoch_seconds);
|
||||||
|
|
||||||
|
DateTimeCall &set_year(uint16_t year) {
|
||||||
|
this->year_ = year;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
DateTimeCall &set_month(uint8_t month) {
|
||||||
|
this->month_ = month;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
DateTimeCall &set_day(uint8_t day) {
|
||||||
|
this->day_ = day;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
DateTimeCall &set_hour(uint8_t hour) {
|
||||||
|
this->hour_ = hour;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
DateTimeCall &set_minute(uint8_t minute) {
|
||||||
|
this->minute_ = minute;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
DateTimeCall &set_second(uint8_t second) {
|
||||||
|
this->second_ = second;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<uint16_t> get_year() const { return this->year_; }
|
||||||
|
optional<uint8_t> get_month() const { return this->month_; }
|
||||||
|
optional<uint8_t> get_day() const { return this->day_; }
|
||||||
|
optional<uint8_t> get_hour() const { return this->hour_; }
|
||||||
|
optional<uint8_t> get_minute() const { return this->minute_; }
|
||||||
|
optional<uint8_t> get_second() const { return this->second_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void validate_();
|
||||||
|
|
||||||
|
DateTimeEntity *parent_;
|
||||||
|
|
||||||
|
optional<uint16_t> year_;
|
||||||
|
optional<uint8_t> month_;
|
||||||
|
optional<uint8_t> day_;
|
||||||
|
optional<uint8_t> hour_;
|
||||||
|
optional<uint8_t> minute_;
|
||||||
|
optional<uint8_t> second_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class DateTimeSetAction : public Action<Ts...>, public Parented<DateTimeEntity> {
|
||||||
|
public:
|
||||||
|
TEMPLATABLE_VALUE(ESPTime, datetime)
|
||||||
|
|
||||||
|
void play(Ts... x) override {
|
||||||
|
auto call = this->parent_->make_call();
|
||||||
|
|
||||||
|
if (this->datetime_.has_value()) {
|
||||||
|
call.set_datetime(this->datetime_.value(x...));
|
||||||
|
}
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class OnDateTimeTrigger : public Trigger<>, public Component, public Parented<DateTimeEntity> {
|
||||||
|
public:
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool matches_(const ESPTime &time) const;
|
||||||
|
|
||||||
|
optional<ESPTime> last_check_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace datetime
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_DATETIME_DATETIME
|
|
@ -94,8 +94,6 @@ void TimeEntityRestoreState::apply(TimeEntity *time) {
|
||||||
time->publish_state();
|
time->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_TIME
|
|
||||||
|
|
||||||
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
|
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
|
||||||
// there has been a drastic time synchronization
|
// there has been a drastic time synchronization
|
||||||
|
|
||||||
|
@ -103,7 +101,7 @@ void OnTimeTrigger::loop() {
|
||||||
if (!this->parent_->has_state()) {
|
if (!this->parent_->has_state()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ESPTime time = this->rtc_->now();
|
ESPTime time = this->parent_->rtc_->now();
|
||||||
if (!time.is_valid()) {
|
if (!time.is_valid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -148,8 +146,6 @@ bool OnTimeTrigger::matches_(const ESPTime &time) const {
|
||||||
time.second == this->parent_->second;
|
time.second == this->parent_->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace datetime
|
} // namespace datetime
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,6 @@
|
||||||
|
|
||||||
#include "datetime_base.h"
|
#include "datetime_base.h"
|
||||||
|
|
||||||
#ifdef USE_TIME
|
|
||||||
#include "esphome/components/time/real_time_clock.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace datetime {
|
namespace datetime {
|
||||||
|
|
||||||
|
@ -27,6 +23,7 @@ namespace datetime {
|
||||||
|
|
||||||
class TimeCall;
|
class TimeCall;
|
||||||
class TimeEntity;
|
class TimeEntity;
|
||||||
|
class OnTimeTrigger;
|
||||||
|
|
||||||
struct TimeEntityRestoreState {
|
struct TimeEntityRestoreState {
|
||||||
uint8_t hour;
|
uint8_t hour;
|
||||||
|
@ -62,6 +59,7 @@ class TimeEntity : public DateTimeBase {
|
||||||
protected:
|
protected:
|
||||||
friend class TimeCall;
|
friend class TimeCall;
|
||||||
friend struct TimeEntityRestoreState;
|
friend struct TimeEntityRestoreState;
|
||||||
|
friend class OnTimeTrigger;
|
||||||
|
|
||||||
virtual void control(const TimeCall &call) = 0;
|
virtual void control(const TimeCall &call) = 0;
|
||||||
};
|
};
|
||||||
|
@ -115,22 +113,16 @@ template<typename... Ts> class TimeSetAction : public Action<Ts...>, public Pare
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef USE_TIME
|
|
||||||
|
|
||||||
class OnTimeTrigger : public Trigger<>, public Component, public Parented<TimeEntity> {
|
class OnTimeTrigger : public Trigger<>, public Component, public Parented<TimeEntity> {
|
||||||
public:
|
public:
|
||||||
explicit OnTimeTrigger(time::RealTimeClock *rtc) : rtc_(rtc) {}
|
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool matches_(const ESPTime &time) const;
|
bool matches_(const ESPTime &time) const;
|
||||||
|
|
||||||
time::RealTimeClock *rtc_;
|
|
||||||
optional<ESPTime> last_check_;
|
optional<ESPTime> last_check_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace datetime
|
} // namespace datetime
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ DisplayOnPageChangeTrigger = display_ns.class_(
|
||||||
)
|
)
|
||||||
|
|
||||||
CONF_ON_PAGE_CHANGE = "on_page_change"
|
CONF_ON_PAGE_CHANGE = "on_page_change"
|
||||||
|
CONF_SHOW_TEST_CARD = "show_test_card"
|
||||||
|
|
||||||
DISPLAY_ROTATIONS = {
|
DISPLAY_ROTATIONS = {
|
||||||
0: display_ns.DISPLAY_ROTATION_0_DEGREES,
|
0: display_ns.DISPLAY_ROTATION_0_DEGREES,
|
||||||
|
@ -82,6 +83,7 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_AUTO_CLEAR_ENABLED, default=True): cv.boolean,
|
cv.Optional(CONF_AUTO_CLEAR_ENABLED, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -113,6 +115,8 @@ async def setup_display_core_(var, config):
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
trigger, [(DisplayPagePtr, "from"), (DisplayPagePtr, "to")], conf
|
trigger, [(DisplayPagePtr, "from"), (DisplayPagePtr, "to")], conf
|
||||||
)
|
)
|
||||||
|
if config.get(CONF_SHOW_TEST_CARD):
|
||||||
|
cg.add(var.show_test_card())
|
||||||
|
|
||||||
|
|
||||||
async def register_display(var, config):
|
async def register_display(var, config):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
|
#include "display_color_utils.h"
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -507,7 +507,9 @@ void Display::do_update_() {
|
||||||
if (this->auto_clear_enabled_) {
|
if (this->auto_clear_enabled_) {
|
||||||
this->clear();
|
this->clear();
|
||||||
}
|
}
|
||||||
if (this->page_ != nullptr) {
|
if (this->show_test_card_) {
|
||||||
|
this->test_card();
|
||||||
|
} else if (this->page_ != nullptr) {
|
||||||
this->page_->get_writer()(*this);
|
this->page_->get_writer()(*this);
|
||||||
} else if (this->writer_.has_value()) {
|
} else if (this->writer_.has_value()) {
|
||||||
(*this->writer_)(*this);
|
(*this->writer_)(*this);
|
||||||
|
@ -608,6 +610,62 @@ bool Display::clamp_y_(int y, int h, int &min_y, int &max_y) {
|
||||||
return min_y < max_y;
|
return min_y < max_y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uint8_t TESTCARD_FONT[3][8] PROGMEM = {{0x41, 0x7F, 0x7F, 0x09, 0x19, 0x7F, 0x66, 0x00}, // 'R'
|
||||||
|
{0x1C, 0x3E, 0x63, 0x41, 0x51, 0x73, 0x72, 0x00}, // 'G'
|
||||||
|
{0x41, 0x7F, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00}}; // 'B'
|
||||||
|
|
||||||
|
void Display::test_card() {
|
||||||
|
int w = get_width(), h = get_height(), image_w, image_h;
|
||||||
|
this->clear();
|
||||||
|
this->show_test_card_ = false;
|
||||||
|
if (this->get_display_type() == DISPLAY_TYPE_COLOR) {
|
||||||
|
Color r(255, 0, 0), g(0, 255, 0), b(0, 0, 255);
|
||||||
|
image_w = std::min(w - 20, 310);
|
||||||
|
image_h = std::min(h - 20, 255);
|
||||||
|
|
||||||
|
int shift_x = (w - image_w) / 2;
|
||||||
|
int shift_y = (h - image_h) / 2;
|
||||||
|
int line_w = (image_w - 6) / 6;
|
||||||
|
int image_c = image_w / 2;
|
||||||
|
for (auto i = 0; i <= image_h; i++) {
|
||||||
|
int c = esp_scale(i, image_h);
|
||||||
|
this->horizontal_line(shift_x + 0, shift_y + i, line_w, r.fade_to_white(c));
|
||||||
|
this->horizontal_line(shift_x + line_w, shift_y + i, line_w, r.fade_to_black(c)); //
|
||||||
|
|
||||||
|
this->horizontal_line(shift_x + image_c - line_w, shift_y + i, line_w, g.fade_to_white(c));
|
||||||
|
this->horizontal_line(shift_x + image_c, shift_y + i, line_w, g.fade_to_black(c));
|
||||||
|
|
||||||
|
this->horizontal_line(shift_x + image_w - (line_w * 2), shift_y + i, line_w, b.fade_to_white(c));
|
||||||
|
this->horizontal_line(shift_x + image_w - line_w, shift_y + i, line_w, b.fade_to_black(c));
|
||||||
|
}
|
||||||
|
this->rectangle(shift_x, shift_y, image_w, image_h, Color(127, 127, 0));
|
||||||
|
|
||||||
|
uint16_t shift_r = shift_x + line_w - (8 * 3);
|
||||||
|
uint16_t shift_g = shift_x + image_c - (8 * 3);
|
||||||
|
uint16_t shift_b = shift_x + image_w - line_w - (8 * 3);
|
||||||
|
shift_y = h / 2 - (8 * 3);
|
||||||
|
for (auto i = 0; i < 8; i++) {
|
||||||
|
uint8_t ftr = progmem_read_byte(&TESTCARD_FONT[0][i]);
|
||||||
|
uint8_t ftg = progmem_read_byte(&TESTCARD_FONT[1][i]);
|
||||||
|
uint8_t ftb = progmem_read_byte(&TESTCARD_FONT[2][i]);
|
||||||
|
for (auto k = 0; k < 8; k++) {
|
||||||
|
if ((ftr & (1 << k)) != 0) {
|
||||||
|
this->filled_rectangle(shift_r + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
|
||||||
|
}
|
||||||
|
if ((ftg & (1 << k)) != 0) {
|
||||||
|
this->filled_rectangle(shift_g + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
|
||||||
|
}
|
||||||
|
if ((ftb & (1 << k)) != 0) {
|
||||||
|
this->filled_rectangle(shift_b + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->rectangle(0, 0, w, h, Color(127, 0, 127));
|
||||||
|
this->filled_rectangle(0, 0, 10, 10, Color(255, 0, 255));
|
||||||
|
this->stop_poller();
|
||||||
|
}
|
||||||
|
|
||||||
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
|
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
|
||||||
void DisplayPage::show() { this->parent_->show_page(this); }
|
void DisplayPage::show() { this->parent_->show_page(this); }
|
||||||
void DisplayPage::show_next() { this->next_->show(); }
|
void DisplayPage::show_next() { this->next_->show(); }
|
||||||
|
|
|
@ -631,6 +631,9 @@ class Display : public PollingComponent {
|
||||||
*/
|
*/
|
||||||
bool clip(int x, int y);
|
bool clip(int x, int y);
|
||||||
|
|
||||||
|
void test_card();
|
||||||
|
void show_test_card() { this->show_test_card_ = true; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool clamp_x_(int x, int w, int &min_x, int &max_x);
|
bool clamp_x_(int x, int w, int &min_x, int &max_x);
|
||||||
bool clamp_y_(int y, int h, int &min_y, int &max_y);
|
bool clamp_y_(int y, int h, int &min_y, int &max_y);
|
||||||
|
@ -659,6 +662,7 @@ class Display : public PollingComponent {
|
||||||
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
|
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
|
||||||
bool auto_clear_enabled_{true};
|
bool auto_clear_enabled_{true};
|
||||||
std::vector<Rect> clipping_rectangle_;
|
std::vector<Rect> clipping_rectangle_;
|
||||||
|
bool show_test_card_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
class DisplayPage {
|
class DisplayPage {
|
||||||
|
|
|
@ -32,6 +32,7 @@ from esphome.const import (
|
||||||
TYPE_GIT,
|
TYPE_GIT,
|
||||||
TYPE_LOCAL,
|
TYPE_LOCAL,
|
||||||
__version__,
|
__version__,
|
||||||
|
CONF_PLATFORM_VERSION,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, HexInt, TimePeriod
|
from esphome.core import CORE, HexInt, TimePeriod
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
@ -365,8 +366,6 @@ def final_validate(config):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
CONF_PLATFORM_VERSION = "platform_version"
|
|
||||||
|
|
||||||
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
|
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
|
|
|
@ -142,7 +142,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(),
|
||||||
param->open.status);
|
param->open.status);
|
||||||
this->set_state(espbt::ClientState::IDLE);
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
return false;
|
break;
|
||||||
}
|
}
|
||||||
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
|
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ from esphome.const import (
|
||||||
KEY_TARGET_FRAMEWORK,
|
KEY_TARGET_FRAMEWORK,
|
||||||
KEY_TARGET_PLATFORM,
|
KEY_TARGET_PLATFORM,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
|
CONF_PLATFORM_VERSION,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
@ -146,7 +147,6 @@ def _parse_platform_version(value):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
CONF_PLATFORM_VERSION = "platform_version"
|
|
||||||
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
|
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,8 +25,8 @@ IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
DEVICE_CLASSES = [
|
DEVICE_CLASSES = [
|
||||||
DEVICE_CLASS_BUTTON,
|
DEVICE_CLASS_BUTTON,
|
||||||
DEVICE_CLASS_EMPTY,
|
|
||||||
DEVICE_CLASS_DOORBELL,
|
DEVICE_CLASS_DOORBELL,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
DEVICE_CLASS_MOTION,
|
DEVICE_CLASS_MOTION,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -129,7 +129,13 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
|
||||||
|
|
||||||
uint8_t bitmask = 0;
|
uint8_t bitmask = 0;
|
||||||
uint8_t pixel_data = 0;
|
uint8_t pixel_data = 0;
|
||||||
float bpp_max = (1 << this->bpp_) - 1;
|
uint8_t bpp_max = (1 << this->bpp_) - 1;
|
||||||
|
auto diff_r = (float) color.r - (float) background.r;
|
||||||
|
auto diff_g = (float) color.g - (float) background.g;
|
||||||
|
auto diff_b = (float) color.b - (float) background.b;
|
||||||
|
auto b_r = (float) background.r;
|
||||||
|
auto b_g = (float) background.g;
|
||||||
|
auto b_b = (float) background.g;
|
||||||
for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) {
|
for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) {
|
||||||
for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) {
|
for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) {
|
||||||
uint8_t pixel = 0;
|
uint8_t pixel = 0;
|
||||||
|
@ -146,12 +152,9 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
|
||||||
if (pixel == bpp_max) {
|
if (pixel == bpp_max) {
|
||||||
display->draw_pixel_at(glyph_x, glyph_y, color);
|
display->draw_pixel_at(glyph_x, glyph_y, color);
|
||||||
} else if (pixel != 0) {
|
} else if (pixel != 0) {
|
||||||
float on = (float) pixel / bpp_max;
|
auto on = (float) pixel / (float) bpp_max;
|
||||||
float off = 1.0 - on;
|
auto blended =
|
||||||
Color blended;
|
Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g), (uint8_t) (diff_b * on + b_b));
|
||||||
blended.r = color.r * on + background.r * off;
|
|
||||||
blended.g = color.r * on + background.g * off;
|
|
||||||
blended.b = color.r * on + background.b * off;
|
|
||||||
display->draw_pixel_at(glyph_x, glyph_y, blended);
|
display->draw_pixel_at(glyph_x, glyph_y, blended);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,7 +164,7 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo
|
||||||
ESP_LOGV(TAG, "Updating graph. ymin %f, ymax %f", ymin, ymax);
|
ESP_LOGV(TAG, "Updating graph. ymin %f, ymax %f", ymin, ymax);
|
||||||
for (auto *trace : traces_) {
|
for (auto *trace : traces_) {
|
||||||
Color c = trace->get_line_color();
|
Color c = trace->get_line_color();
|
||||||
uint16_t thick = trace->get_line_thickness();
|
int16_t thick = trace->get_line_thickness();
|
||||||
bool continuous = trace->get_continuous();
|
bool continuous = trace->get_continuous();
|
||||||
bool has_prev = false;
|
bool has_prev = false;
|
||||||
bool prev_b = false;
|
bool prev_b = false;
|
||||||
|
@ -177,22 +177,26 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo
|
||||||
bool b = (trace->get_line_type() & bit) == bit;
|
bool b = (trace->get_line_type() & bit) == bit;
|
||||||
if (b) {
|
if (b) {
|
||||||
int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset;
|
int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset;
|
||||||
|
auto draw_pixel_at = [&buff, c, y_offset, this](int16_t x, int16_t y) {
|
||||||
|
if (y >= y_offset && y < y_offset + this->height_)
|
||||||
|
buff->draw_pixel_at(x, y, c);
|
||||||
|
};
|
||||||
if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) {
|
if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) {
|
||||||
for (uint16_t t = 0; t < thick; t++) {
|
for (int16_t t = 0; t < thick; t++) {
|
||||||
buff->draw_pixel_at(x, y + t, c);
|
draw_pixel_at(x, y + t);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int16_t mid_y = (y + prev_y + thick) / 2;
|
int16_t mid_y = (y + prev_y + thick) / 2;
|
||||||
if (y > prev_y) {
|
if (y > prev_y) {
|
||||||
for (uint16_t t = prev_y + thick; t <= mid_y; t++)
|
for (int16_t t = prev_y + thick; t <= mid_y; t++)
|
||||||
buff->draw_pixel_at(x + 1, t, c);
|
draw_pixel_at(x + 1, t);
|
||||||
for (uint16_t t = mid_y + 1; t < y + thick; t++)
|
for (int16_t t = mid_y + 1; t < y + thick; t++)
|
||||||
buff->draw_pixel_at(x, t, c);
|
draw_pixel_at(x, t);
|
||||||
} else {
|
} else {
|
||||||
for (uint16_t t = prev_y - 1; t >= mid_y; t--)
|
for (int16_t t = prev_y - 1; t >= mid_y; t--)
|
||||||
buff->draw_pixel_at(x + 1, t, c);
|
draw_pixel_at(x + 1, t);
|
||||||
for (uint16_t t = mid_y - 1; t >= y; t--)
|
for (int16_t t = mid_y - 1; t >= y; t--)
|
||||||
buff->draw_pixel_at(x, t, c);
|
draw_pixel_at(x, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prev_y = y;
|
prev_y = y;
|
||||||
|
|
|
@ -104,7 +104,8 @@ void GraphicalDisplayMenu::draw(display::Display *display, const display::Rect *
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicalDisplayMenu::draw_menu_internal_(display::Display *display, const display::Rect *bounds) {
|
void GraphicalDisplayMenu::draw_menu_internal_(display::Display *display, const display::Rect *bounds) {
|
||||||
int total_height = 0;
|
int16_t total_height = 0;
|
||||||
|
int16_t max_width = 0;
|
||||||
int y_padding = 2;
|
int y_padding = 2;
|
||||||
bool scroll_menu_items = false;
|
bool scroll_menu_items = false;
|
||||||
std::vector<display::Rect> menu_dimensions;
|
std::vector<display::Rect> menu_dimensions;
|
||||||
|
@ -118,6 +119,7 @@ void GraphicalDisplayMenu::draw_menu_internal_(display::Display *display, const
|
||||||
|
|
||||||
menu_dimensions.push_back(item_dimensions);
|
menu_dimensions.push_back(item_dimensions);
|
||||||
total_height += item_dimensions.h + (i == 0 ? 0 : y_padding);
|
total_height += item_dimensions.h + (i == 0 ? 0 : y_padding);
|
||||||
|
max_width = std::max(max_width, item_dimensions.w);
|
||||||
|
|
||||||
if (total_height <= bounds->h) {
|
if (total_height <= bounds->h) {
|
||||||
number_items_fit_to_screen++;
|
number_items_fit_to_screen++;
|
||||||
|
@ -166,7 +168,8 @@ void GraphicalDisplayMenu::draw_menu_internal_(display::Display *display, const
|
||||||
// Render the items into the view port
|
// Render the items into the view port
|
||||||
display->start_clipping(*bounds);
|
display->start_clipping(*bounds);
|
||||||
|
|
||||||
int y_offset = bounds->y;
|
display->filled_rectangle(bounds->x, bounds->y, max_width, total_height, this->background_color_);
|
||||||
|
auto y_offset = bounds->y;
|
||||||
for (size_t i = first_item_index; i <= last_item_index; i++) {
|
for (size_t i = first_item_index; i <= last_item_index; i++) {
|
||||||
const auto *item = this->displayed_item_->get_item(i);
|
const auto *item = this->displayed_item_->get_item(i);
|
||||||
const bool selected = i == this->cursor_index_;
|
const bool selected = i == this->cursor_index_;
|
||||||
|
@ -176,7 +179,7 @@ void GraphicalDisplayMenu::draw_menu_internal_(display::Display *display, const
|
||||||
dimensions.x = bounds->x;
|
dimensions.x = bounds->x;
|
||||||
this->draw_item(display, item, &dimensions, selected);
|
this->draw_item(display, item, &dimensions, selected);
|
||||||
|
|
||||||
y_offset = dimensions.y + dimensions.h + y_padding;
|
y_offset += dimensions.h + y_padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
display->end_clipping();
|
display->end_clipping();
|
||||||
|
@ -219,9 +222,7 @@ inline void GraphicalDisplayMenu::draw_item(display::Display *display, const dis
|
||||||
// int background_width = std::max(bounds->width, available_width);
|
// int background_width = std::max(bounds->width, available_width);
|
||||||
int background_width = bounds->w;
|
int background_width = bounds->w;
|
||||||
|
|
||||||
if (selected) {
|
|
||||||
display->filled_rectangle(bounds->x, bounds->y, background_width, bounds->h, background_color);
|
display->filled_rectangle(bounds->x, bounds->y, background_width, bounds->h, background_color);
|
||||||
}
|
|
||||||
|
|
||||||
std::string label = item->get_text();
|
std::string label = item->get_text();
|
||||||
if (item->has_value()) {
|
if (item->has_value()) {
|
||||||
|
@ -230,7 +231,7 @@ inline void GraphicalDisplayMenu::draw_item(display::Display *display, const dis
|
||||||
}
|
}
|
||||||
|
|
||||||
display->print(bounds->x, bounds->y, this->font_, foreground_color, display::TextAlign::TOP_LEFT, label.c_str(),
|
display->print(bounds->x, bounds->y, this->font_, foreground_color, display::TextAlign::TOP_LEFT, label.c_str(),
|
||||||
~foreground_color);
|
background_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicalDisplayMenu::draw_item(const display_menu_base::MenuItem *item, const uint8_t row, const bool selected) {
|
void GraphicalDisplayMenu::draw_item(const display_menu_base::MenuItem *item, const uint8_t row, const bool selected) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "abstract_aqi_calculator.h"
|
#include "abstract_aqi_calculator.h"
|
||||||
|
// https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace hm3301 {
|
namespace hm3301 {
|
||||||
|
@ -15,14 +16,16 @@ class AQICalculator : public AbstractAQICalculator {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static const int AMOUNT_OF_LEVELS = 6;
|
static const int AMOUNT_OF_LEVELS = 7;
|
||||||
|
|
||||||
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 51}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
|
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200},
|
||||||
|
{201, 300}, {301, 400}, {401, 500}};
|
||||||
|
|
||||||
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 35}, {36, 55}, {56, 150}, {151, 250}, {251, 500}};
|
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 35}, {36, 55}, {56, 150},
|
||||||
|
{151, 250}, {251, 350}, {351, 500}};
|
||||||
|
|
||||||
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
|
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, {255, 354},
|
||||||
{255, 354}, {355, 424}, {425, 604}};
|
{355, 424}, {425, 504}, {505, 604}};
|
||||||
|
|
||||||
int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
||||||
int grid_index = get_grid_index_(value, array);
|
int grid_index = get_grid_index_(value, array);
|
||||||
|
|
|
@ -61,28 +61,57 @@ void I2SAudioMicrophone::start_() {
|
||||||
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
esp_err_t err;
|
||||||
|
|
||||||
#if SOC_I2S_SUPPORTS_ADC
|
#if SOC_I2S_SUPPORTS_ADC
|
||||||
if (this->adc_) {
|
if (this->adc_) {
|
||||||
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN);
|
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN);
|
||||||
i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
|
err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
|
||||||
|
this->status_set_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Error setting ADC mode: %s", esp_err_to_name(err));
|
||||||
|
this->status_set_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
err = i2s_adc_enable(this->parent_->get_port());
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Error enabling ADC: %s", esp_err_to_name(err));
|
||||||
|
this->status_set_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_);
|
|
||||||
i2s_adc_enable(this->parent_->get_port());
|
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
if (this->pdm_)
|
if (this->pdm_)
|
||||||
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_PDM);
|
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_PDM);
|
||||||
|
|
||||||
i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
|
err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
|
||||||
|
this->status_set_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
i2s_pin_config_t pin_config = this->parent_->get_pin_config();
|
i2s_pin_config_t pin_config = this->parent_->get_pin_config();
|
||||||
pin_config.data_in_num = this->din_pin_;
|
pin_config.data_in_num = this->din_pin_;
|
||||||
|
|
||||||
i2s_set_pin(this->parent_->get_port(), &pin_config);
|
err = i2s_set_pin(this->parent_->get_port(), &pin_config);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Error setting I2S pin: %s", esp_err_to_name(err));
|
||||||
|
this->status_set_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this->state_ = microphone::STATE_RUNNING;
|
this->state_ = microphone::STATE_RUNNING;
|
||||||
this->high_freq_.start();
|
this->high_freq_.start();
|
||||||
|
this->status_clear_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
void I2SAudioMicrophone::stop() {
|
void I2SAudioMicrophone::stop() {
|
||||||
|
@ -96,11 +125,33 @@ void I2SAudioMicrophone::stop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void I2SAudioMicrophone::stop_() {
|
void I2SAudioMicrophone::stop_() {
|
||||||
i2s_stop(this->parent_->get_port());
|
esp_err_t err;
|
||||||
i2s_driver_uninstall(this->parent_->get_port());
|
#if SOC_I2S_SUPPORTS_ADC
|
||||||
|
if (this->adc_) {
|
||||||
|
err = i2s_adc_disable(this->parent_->get_port());
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Error disabling ADC: %s", esp_err_to_name(err));
|
||||||
|
this->status_set_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
err = i2s_stop(this->parent_->get_port());
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Error stopping I2S microphone: %s", esp_err_to_name(err));
|
||||||
|
this->status_set_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
err = i2s_driver_uninstall(this->parent_->get_port());
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Error uninstalling I2S driver: %s", esp_err_to_name(err));
|
||||||
|
this->status_set_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
this->parent_->unlock();
|
this->parent_->unlock();
|
||||||
this->state_ = microphone::STATE_STOPPED;
|
this->state_ = microphone::STATE_STOPPED;
|
||||||
this->high_freq_.stop();
|
this->high_freq_.stop();
|
||||||
|
this->status_clear_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
|
size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
|
||||||
|
|
|
@ -150,6 +150,7 @@ class AutomationLightEffect : public LightEffect {
|
||||||
struct StrobeLightEffectColor {
|
struct StrobeLightEffectColor {
|
||||||
LightColorValues color;
|
LightColorValues color;
|
||||||
uint32_t duration;
|
uint32_t duration;
|
||||||
|
uint32_t transition_length;
|
||||||
};
|
};
|
||||||
|
|
||||||
class StrobeLightEffect : public LightEffect {
|
class StrobeLightEffect : public LightEffect {
|
||||||
|
@ -174,7 +175,7 @@ class StrobeLightEffect : public LightEffect {
|
||||||
}
|
}
|
||||||
call.set_publish(false);
|
call.set_publish(false);
|
||||||
call.set_save(false);
|
call.set_save(false);
|
||||||
call.set_transition_length_if_supported(0);
|
call.set_transition_length_if_supported(this->colors_[this->at_color_].transition_length);
|
||||||
call.perform();
|
call.perform();
|
||||||
this->last_switch_ = now;
|
this->last_switch_ = now;
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,6 +266,9 @@ async def random_effect_to_code(config, effect_id):
|
||||||
cv.Required(
|
cv.Required(
|
||||||
CONF_DURATION
|
CONF_DURATION
|
||||||
): cv.positive_time_period_milliseconds,
|
): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(
|
||||||
|
CONF_TRANSITION_LENGTH, default="0s"
|
||||||
|
): cv.positive_time_period_milliseconds,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.has_at_least_one_key(
|
cv.has_at_least_one_key(
|
||||||
|
@ -310,6 +313,7 @@ async def strobe_effect_to_code(config, effect_id):
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
("duration", color[CONF_DURATION]),
|
("duration", color[CONF_DURATION]),
|
||||||
|
("transition_length", color[CONF_TRANSITION_LENGTH]),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
cg.add(var.set_colors(colors))
|
cg.add(var.set_colors(colors))
|
||||||
|
|
|
@ -115,6 +115,7 @@ MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent)
|
||||||
MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent)
|
MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent)
|
||||||
MQTTDateComponent = mqtt_ns.class_("MQTTDateComponent", MQTTComponent)
|
MQTTDateComponent = mqtt_ns.class_("MQTTDateComponent", MQTTComponent)
|
||||||
MQTTTimeComponent = mqtt_ns.class_("MQTTTimeComponent", MQTTComponent)
|
MQTTTimeComponent = mqtt_ns.class_("MQTTTimeComponent", MQTTComponent)
|
||||||
|
MQTTDateTimeComponent = mqtt_ns.class_("MQTTDateTimeComponent", MQTTComponent)
|
||||||
MQTTTextComponent = mqtt_ns.class_("MQTTTextComponent", MQTTComponent)
|
MQTTTextComponent = mqtt_ns.class_("MQTTTextComponent", MQTTComponent)
|
||||||
MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
|
MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
|
||||||
MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent)
|
MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent)
|
||||||
|
|
84
esphome/components/mqtt/mqtt_datetime.cpp
Normal file
84
esphome/components/mqtt/mqtt_datetime.cpp
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
#include "mqtt_datetime.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include "mqtt_const.h"
|
||||||
|
|
||||||
|
#ifdef USE_MQTT
|
||||||
|
#ifdef USE_DATETIME_TIME
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mqtt {
|
||||||
|
|
||||||
|
static const char *const TAG = "mqtt.datetime.time";
|
||||||
|
|
||||||
|
using namespace esphome::datetime;
|
||||||
|
|
||||||
|
MQTTDateTimeComponent::MQTTDateTimeComponent(DateTimeEntity *datetime) : datetime_(datetime) {}
|
||||||
|
|
||||||
|
void MQTTDateTimeComponent::setup() {
|
||||||
|
this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) {
|
||||||
|
auto call = this->datetime_->make_call();
|
||||||
|
if (root.containsKey("year")) {
|
||||||
|
call.set_year(root["year"]);
|
||||||
|
}
|
||||||
|
if (root.containsKey("month")) {
|
||||||
|
call.set_month(root["month"]);
|
||||||
|
}
|
||||||
|
if (root.containsKey("day")) {
|
||||||
|
call.set_day(root["day"]);
|
||||||
|
}
|
||||||
|
if (root.containsKey("hour")) {
|
||||||
|
call.set_hour(root["hour"]);
|
||||||
|
}
|
||||||
|
if (root.containsKey("minute")) {
|
||||||
|
call.set_minute(root["minute"]);
|
||||||
|
}
|
||||||
|
if (root.containsKey("second")) {
|
||||||
|
call.set_second(root["second"]);
|
||||||
|
}
|
||||||
|
call.perform();
|
||||||
|
});
|
||||||
|
this->datetime_->add_on_state_callback([this]() {
|
||||||
|
this->publish_state(this->datetime_->year, this->datetime_->month, this->datetime_->day, this->datetime_->hour,
|
||||||
|
this->datetime_->minute, this->datetime_->second);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MQTTDateTimeComponent::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "MQTT DateTime '%s':", this->datetime_->get_name().c_str());
|
||||||
|
LOG_MQTT_COMPONENT(true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MQTTDateTimeComponent::component_type() const { return "datetime"; }
|
||||||
|
const EntityBase *MQTTDateTimeComponent::get_entity() const { return this->datetime_; }
|
||||||
|
|
||||||
|
void MQTTDateTimeComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||||
|
// Nothing extra to add here
|
||||||
|
}
|
||||||
|
bool MQTTDateTimeComponent::send_initial_state() {
|
||||||
|
if (this->datetime_->has_state()) {
|
||||||
|
return this->publish_state(this->datetime_->year, this->datetime_->month, this->datetime_->day,
|
||||||
|
this->datetime_->hour, this->datetime_->minute, this->datetime_->second);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool MQTTDateTimeComponent::publish_state(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute,
|
||||||
|
uint8_t second) {
|
||||||
|
return this->publish_json(this->get_state_topic_(), [year, month, day, hour, minute, second](JsonObject root) {
|
||||||
|
root["year"] = year;
|
||||||
|
root["month"] = month;
|
||||||
|
root["day"] = day;
|
||||||
|
root["hour"] = hour;
|
||||||
|
root["minute"] = minute;
|
||||||
|
root["second"] = second;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mqtt
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_DATETIME_TIME
|
||||||
|
#endif // USE_MQTT
|
45
esphome/components/mqtt/mqtt_datetime.h
Normal file
45
esphome/components/mqtt/mqtt_datetime.h
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
|
#ifdef USE_MQTT
|
||||||
|
#ifdef USE_DATETIME_TIME
|
||||||
|
|
||||||
|
#include "esphome/components/datetime/datetime_entity.h"
|
||||||
|
#include "mqtt_component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mqtt {
|
||||||
|
|
||||||
|
class MQTTDateTimeComponent : public mqtt::MQTTComponent {
|
||||||
|
public:
|
||||||
|
/** Construct this MQTTDateTimeComponent instance with the provided friendly_name and time
|
||||||
|
*
|
||||||
|
* @param time The time entity.
|
||||||
|
*/
|
||||||
|
explicit MQTTDateTimeComponent(datetime::DateTimeEntity *time);
|
||||||
|
|
||||||
|
// ========== INTERNAL METHODS ==========
|
||||||
|
// (In most use cases you won't need these)
|
||||||
|
/// Override setup.
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
|
||||||
|
|
||||||
|
bool send_initial_state() override;
|
||||||
|
|
||||||
|
bool publish_state(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::string component_type() const override;
|
||||||
|
const EntityBase *get_entity() const override;
|
||||||
|
|
||||||
|
datetime::DateTimeEntity *datetime_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mqtt
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_DATETIME_DATE
|
||||||
|
#endif // USE_MQTT
|
|
@ -24,8 +24,10 @@ void PVVXDisplay::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t
|
||||||
esp_ble_gattc_cb_param_t *param) {
|
esp_ble_gattc_cb_param_t *param) {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case ESP_GATTC_OPEN_EVT:
|
case ESP_GATTC_OPEN_EVT:
|
||||||
|
if (param->open.status == ESP_GATT_OK) {
|
||||||
ESP_LOGV(TAG, "[%s] Connected successfully!", this->parent_->address_str().c_str());
|
ESP_LOGV(TAG, "[%s] Connected successfully!", this->parent_->address_str().c_str());
|
||||||
this->delayed_disconnect_();
|
this->delayed_disconnect_();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ESP_GATTC_DISCONNECT_EVT:
|
case ESP_GATTC_DISCONNECT_EVT:
|
||||||
ESP_LOGV(TAG, "[%s] Disconnected", this->parent_->address_str().c_str());
|
ESP_LOGV(TAG, "[%s] Disconnected", this->parent_->address_str().c_str());
|
||||||
|
|
|
@ -77,9 +77,18 @@ void QMC5883LComponent::dump_config() {
|
||||||
float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; }
|
float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
void QMC5883LComponent::update() {
|
void QMC5883LComponent::update() {
|
||||||
uint8_t status = false;
|
uint8_t status = false;
|
||||||
if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG)
|
|
||||||
this->read_byte(QMC5883L_REGISTER_STATUS, &status);
|
this->read_byte(QMC5883L_REGISTER_STATUS, &status);
|
||||||
|
|
||||||
|
// Always request X,Y,Z regardless if there are sensors for them
|
||||||
|
// to avoid https://github.com/esphome/issues/issues/5731
|
||||||
|
uint16_t raw_x, raw_y, raw_z;
|
||||||
|
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x) ||
|
||||||
|
!this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y) ||
|
||||||
|
!this->read_byte_16_(QMC5883L_REGISTER_DATA_Z_LSB, &raw_z)) {
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float mg_per_bit;
|
float mg_per_bit;
|
||||||
switch (this->range_) {
|
switch (this->range_) {
|
||||||
case QMC5883L_RANGE_200_UT:
|
case QMC5883L_RANGE_200_UT:
|
||||||
|
@ -93,36 +102,11 @@ void QMC5883LComponent::update() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// in µT
|
// in µT
|
||||||
float x = NAN, y = NAN, z = NAN;
|
const float x = int16_t(raw_x) * mg_per_bit * 0.1f;
|
||||||
if (this->x_sensor_ != nullptr || this->heading_sensor_ != nullptr) {
|
const float y = int16_t(raw_y) * mg_per_bit * 0.1f;
|
||||||
uint16_t raw_x;
|
const float z = int16_t(raw_z) * mg_per_bit * 0.1f;
|
||||||
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x)) {
|
|
||||||
this->status_set_warning();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
x = int16_t(raw_x) * mg_per_bit * 0.1f;
|
|
||||||
}
|
|
||||||
if (this->y_sensor_ != nullptr || this->heading_sensor_ != nullptr) {
|
|
||||||
uint16_t raw_y;
|
|
||||||
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y)) {
|
|
||||||
this->status_set_warning();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
y = int16_t(raw_y) * mg_per_bit * 0.1f;
|
|
||||||
}
|
|
||||||
if (this->z_sensor_ != nullptr) {
|
|
||||||
uint16_t raw_z;
|
|
||||||
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_Z_LSB, &raw_z)) {
|
|
||||||
this->status_set_warning();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
z = int16_t(raw_z) * mg_per_bit * 0.1f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float heading = NAN;
|
float heading = atan2f(0.0f - x, y) * 180.0f / M_PI;
|
||||||
if (this->heading_sensor_ != nullptr) {
|
|
||||||
heading = atan2f(0.0f - x, y) * 180.0f / M_PI;
|
|
||||||
}
|
|
||||||
|
|
||||||
float temp = NAN;
|
float temp = NAN;
|
||||||
if (this->temperature_sensor_ != nullptr) {
|
if (this->temperature_sensor_ != nullptr) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ from esphome.const import (
|
||||||
KEY_TARGET_FRAMEWORK,
|
KEY_TARGET_FRAMEWORK,
|
||||||
KEY_TARGET_PLATFORM,
|
KEY_TARGET_PLATFORM,
|
||||||
PLATFORM_RP2040,
|
PLATFORM_RP2040,
|
||||||
|
CONF_PLATFORM_VERSION,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority, EsphomeError
|
from esphome.core import CORE, coroutine_with_priority, EsphomeError
|
||||||
from esphome.helpers import mkdir_p, write_file, copy_file_if_changed
|
from esphome.helpers import mkdir_p, write_file, copy_file_if_changed
|
||||||
|
@ -125,8 +126,6 @@ def _parse_platform_version(value):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
CONF_PLATFORM_VERSION = "platform_version"
|
|
||||||
|
|
||||||
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
|
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
|
|
|
@ -116,7 +116,7 @@ def do_substitution_pass(config, command_line_substitutions, ignore_missing=Fals
|
||||||
if CONF_SUBSTITUTIONS not in config and not command_line_substitutions:
|
if CONF_SUBSTITUTIONS not in config and not command_line_substitutions:
|
||||||
return
|
return
|
||||||
|
|
||||||
substitutions = config[CONF_SUBSTITUTIONS]
|
substitutions = config.get(CONF_SUBSTITUTIONS)
|
||||||
if substitutions is None:
|
if substitutions is None:
|
||||||
substitutions = command_line_substitutions
|
substitutions = command_line_substitutions
|
||||||
elif command_line_substitutions:
|
elif command_line_substitutions:
|
||||||
|
|
|
@ -31,6 +31,10 @@ TemplateTime = template_ns.class_(
|
||||||
"TemplateTime", datetime.TimeEntity, cg.PollingComponent
|
"TemplateTime", datetime.TimeEntity, cg.PollingComponent
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TemplateDateTime = template_ns.class_(
|
||||||
|
"TemplateDateTime", datetime.DateTimeEntity, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate(config):
|
def validate(config):
|
||||||
config = config.copy()
|
config = config.copy()
|
||||||
|
@ -78,6 +82,13 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.Optional(CONF_INITIAL_VALUE): cv.date_time(allowed_date=False),
|
cv.Optional(CONF_INITIAL_VALUE): cv.date_time(allowed_date=False),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
"DATETIME": datetime.datetime_schema(TemplateDateTime)
|
||||||
|
.extend(_BASE_SCHEMA)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_INITIAL_VALUE): cv.date_time(),
|
||||||
|
}
|
||||||
|
),
|
||||||
},
|
},
|
||||||
upper=True,
|
upper=True,
|
||||||
),
|
),
|
||||||
|
@ -116,6 +127,17 @@ async def to_code(config):
|
||||||
("hour", initial_value[CONF_HOUR]),
|
("hour", initial_value[CONF_HOUR]),
|
||||||
)
|
)
|
||||||
cg.add(var.set_initial_value(time_struct))
|
cg.add(var.set_initial_value(time_struct))
|
||||||
|
elif config[CONF_TYPE] == "DATETIME":
|
||||||
|
datetime_struct = cg.StructInitializer(
|
||||||
|
cg.ESPTime,
|
||||||
|
("second", initial_value[CONF_SECOND]),
|
||||||
|
("minute", initial_value[CONF_MINUTE]),
|
||||||
|
("hour", initial_value[CONF_HOUR]),
|
||||||
|
("day_of_month", initial_value[CONF_DAY]),
|
||||||
|
("month", initial_value[CONF_MONTH]),
|
||||||
|
("year", initial_value[CONF_YEAR]),
|
||||||
|
)
|
||||||
|
cg.add(var.set_initial_value(datetime_struct))
|
||||||
|
|
||||||
if CONF_SET_ACTION in config:
|
if CONF_SET_ACTION in config:
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
|
|
150
esphome/components/template/datetime/template_datetime.cpp
Normal file
150
esphome/components/template/datetime/template_datetime.cpp
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
#include "template_datetime.h"
|
||||||
|
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace template_ {
|
||||||
|
|
||||||
|
static const char *const TAG = "template.datetime";
|
||||||
|
|
||||||
|
void TemplateDateTime::setup() {
|
||||||
|
if (this->f_.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
ESPTime state{};
|
||||||
|
|
||||||
|
if (!this->restore_value_) {
|
||||||
|
state = this->initial_value_;
|
||||||
|
} else {
|
||||||
|
datetime::DateTimeEntityRestoreState temp;
|
||||||
|
this->pref_ = global_preferences->make_preference<datetime::DateTimeEntityRestoreState>(194434090U ^
|
||||||
|
this->get_object_id_hash());
|
||||||
|
if (this->pref_.load(&temp)) {
|
||||||
|
temp.apply(this);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// set to inital value if loading from pref failed
|
||||||
|
state = this->initial_value_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->year_ = state.year;
|
||||||
|
this->month_ = state.month;
|
||||||
|
this->day_ = state.day_of_month;
|
||||||
|
this->hour_ = state.hour;
|
||||||
|
this->minute_ = state.minute;
|
||||||
|
this->second_ = state.second;
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TemplateDateTime::update() {
|
||||||
|
if (!this->f_.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto val = (*this->f_)();
|
||||||
|
if (!val.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->year_ = val->year;
|
||||||
|
this->month_ = val->month;
|
||||||
|
this->day_ = val->day_of_month;
|
||||||
|
this->hour_ = val->hour;
|
||||||
|
this->minute_ = val->minute;
|
||||||
|
this->second_ = val->second;
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TemplateDateTime::control(const datetime::DateTimeCall &call) {
|
||||||
|
bool has_year = call.get_year().has_value();
|
||||||
|
bool has_month = call.get_month().has_value();
|
||||||
|
bool has_day = call.get_day().has_value();
|
||||||
|
bool has_hour = call.get_hour().has_value();
|
||||||
|
bool has_minute = call.get_minute().has_value();
|
||||||
|
bool has_second = call.get_second().has_value();
|
||||||
|
|
||||||
|
ESPTime value = {};
|
||||||
|
if (has_year)
|
||||||
|
value.year = *call.get_year();
|
||||||
|
|
||||||
|
if (has_month)
|
||||||
|
value.month = *call.get_month();
|
||||||
|
|
||||||
|
if (has_day)
|
||||||
|
value.day_of_month = *call.get_day();
|
||||||
|
|
||||||
|
if (has_hour)
|
||||||
|
value.hour = *call.get_hour();
|
||||||
|
|
||||||
|
if (has_minute)
|
||||||
|
value.minute = *call.get_minute();
|
||||||
|
|
||||||
|
if (has_second)
|
||||||
|
value.second = *call.get_second();
|
||||||
|
|
||||||
|
this->set_trigger_->trigger(value);
|
||||||
|
|
||||||
|
if (this->optimistic_) {
|
||||||
|
if (has_year)
|
||||||
|
this->year_ = *call.get_year();
|
||||||
|
if (has_month)
|
||||||
|
this->month_ = *call.get_month();
|
||||||
|
if (has_day)
|
||||||
|
this->day_ = *call.get_day();
|
||||||
|
if (has_hour)
|
||||||
|
this->hour_ = *call.get_hour();
|
||||||
|
if (has_minute)
|
||||||
|
this->minute_ = *call.get_minute();
|
||||||
|
if (has_second)
|
||||||
|
this->second_ = *call.get_second();
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->restore_value_) {
|
||||||
|
datetime::DateTimeEntityRestoreState temp = {};
|
||||||
|
if (has_year) {
|
||||||
|
temp.year = *call.get_year();
|
||||||
|
} else {
|
||||||
|
temp.year = this->year_;
|
||||||
|
}
|
||||||
|
if (has_month) {
|
||||||
|
temp.month = *call.get_month();
|
||||||
|
} else {
|
||||||
|
temp.month = this->month_;
|
||||||
|
}
|
||||||
|
if (has_day) {
|
||||||
|
temp.day = *call.get_day();
|
||||||
|
} else {
|
||||||
|
temp.day = this->day_;
|
||||||
|
}
|
||||||
|
if (has_hour) {
|
||||||
|
temp.hour = *call.get_hour();
|
||||||
|
} else {
|
||||||
|
temp.hour = this->hour_;
|
||||||
|
}
|
||||||
|
if (has_minute) {
|
||||||
|
temp.minute = *call.get_minute();
|
||||||
|
} else {
|
||||||
|
temp.minute = this->minute_;
|
||||||
|
}
|
||||||
|
if (has_second) {
|
||||||
|
temp.second = *call.get_second();
|
||||||
|
} else {
|
||||||
|
temp.second = this->second_;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->pref_.save(&temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TemplateDateTime::dump_config() {
|
||||||
|
LOG_DATETIME_DATETIME("", "Template DateTime", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace template_
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_DATETIME_DATETIME
|
46
esphome/components/template/datetime/template_datetime.h
Normal file
46
esphome/components/template/datetime/template_datetime.h
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
|
#ifdef USE_DATETIME_TIME
|
||||||
|
|
||||||
|
#include "esphome/components/datetime/datetime_entity.h"
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
#include "esphome/core/time.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace template_ {
|
||||||
|
|
||||||
|
class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponent {
|
||||||
|
public:
|
||||||
|
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void update() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
|
Trigger<ESPTime> *get_set_trigger() const { return this->set_trigger_; }
|
||||||
|
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||||
|
|
||||||
|
void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; }
|
||||||
|
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void control(const datetime::DateTimeCall &call) override;
|
||||||
|
|
||||||
|
bool optimistic_{false};
|
||||||
|
ESPTime initial_value_{};
|
||||||
|
bool restore_value_{false};
|
||||||
|
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
|
||||||
|
optional<std::function<optional<ESPTime>()>> f_;
|
||||||
|
|
||||||
|
ESPPreferenceObject pref_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace template_
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_DATETIME_TIME
|
|
@ -13,6 +13,8 @@
|
||||||
#endif
|
#endif
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace time {
|
namespace time {
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ class TimeBasedCover : public cover::Cover, public Component {
|
||||||
void set_has_built_in_endstop(bool value) { this->has_built_in_endstop_ = value; }
|
void set_has_built_in_endstop(bool value) { this->has_built_in_endstop_ = value; }
|
||||||
void set_manual_control(bool value) { this->manual_control_ = value; }
|
void set_manual_control(bool value) { this->manual_control_ = value; }
|
||||||
void set_assumed_state(bool value) { this->assumed_state_ = value; }
|
void set_assumed_state(bool value) { this->assumed_state_ = value; }
|
||||||
|
cover::CoverOperation get_last_operation() const { return this->last_operation_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void control(const cover::CoverCall &call) override;
|
void control(const cover::CoverCall &call) override;
|
||||||
|
|
|
@ -49,6 +49,9 @@ WaveshareEPaper2P9InV2R2 = waveshare_epaper_ns.class_(
|
||||||
"WaveshareEPaper2P9InV2R2", WaveshareEPaper
|
"WaveshareEPaper2P9InV2R2", WaveshareEPaper
|
||||||
)
|
)
|
||||||
GDEY029T94 = waveshare_epaper_ns.class_("GDEY029T94", WaveshareEPaper)
|
GDEY029T94 = waveshare_epaper_ns.class_("GDEY029T94", WaveshareEPaper)
|
||||||
|
WaveshareEPaper2P9InDKE = waveshare_epaper_ns.class_(
|
||||||
|
"WaveshareEPaper2P9InDKE", WaveshareEPaper
|
||||||
|
)
|
||||||
WaveshareEPaper4P2In = waveshare_epaper_ns.class_(
|
WaveshareEPaper4P2In = waveshare_epaper_ns.class_(
|
||||||
"WaveshareEPaper4P2In", WaveshareEPaper
|
"WaveshareEPaper4P2In", WaveshareEPaper
|
||||||
)
|
)
|
||||||
|
@ -115,6 +118,7 @@ MODELS = {
|
||||||
"2.90in-b": ("b", WaveshareEPaper2P9InB),
|
"2.90in-b": ("b", WaveshareEPaper2P9InB),
|
||||||
"2.90in-bv3": ("b", WaveshareEPaper2P9InBV3),
|
"2.90in-bv3": ("b", WaveshareEPaper2P9InBV3),
|
||||||
"2.90inv2-r2": ("c", WaveshareEPaper2P9InV2R2),
|
"2.90inv2-r2": ("c", WaveshareEPaper2P9InV2R2),
|
||||||
|
"2.90in-dke": ("c", WaveshareEPaper2P9InDKE),
|
||||||
"4.20in": ("b", WaveshareEPaper4P2In),
|
"4.20in": ("b", WaveshareEPaper4P2In),
|
||||||
"4.20in-bv2": ("b", WaveshareEPaper4P2InBV2),
|
"4.20in-bv2": ("b", WaveshareEPaper4P2InBV2),
|
||||||
"5.83in": ("b", WaveshareEPaper5P8In),
|
"5.83in": ("b", WaveshareEPaper5P8In),
|
||||||
|
|
|
@ -1127,6 +1127,131 @@ void WaveshareEPaper2P9InB::dump_config() {
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DKE 2.9
|
||||||
|
// https://www.badge.team/docs/badges/sha2017/hardware/#e-ink-display-the-dke-group-depg0290b1
|
||||||
|
// https://www.badge.team/docs/badges/sha2017/hardware/DEPG0290B01V3.0.pdf
|
||||||
|
static const uint8_t LUT_SIZE_DKE = 70;
|
||||||
|
static const uint8_t UPDATE_LUT_DKE[LUT_SIZE_DKE] = {
|
||||||
|
0xA0, 0x90, 0x50, 0x0, 0x0, 0x0, 0x0, 0x50, 0x90, 0xA0, 0x0, 0x0, 0x0, 0x0, 0xA0, 0x90, 0x50, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x50, 0x90, 0xA0, 0x0, 0x0, 0x0, 0x0, 0x00, 0x00, 0x00, 0x0, 0x0, 0x0, 0x0, 0xF,
|
||||||
|
0xF, 0x0, 0x0, 0x0, 0xF, 0xF, 0x0, 0x0, 0x02, 0xF, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
};
|
||||||
|
static const uint8_t PART_UPDATE_LUT_DKE[LUT_SIZE_DKE] = {
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xa0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x50, 0x10, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
|
||||||
|
0x05, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
static const uint8_t FULL_UPDATE_LUT_DKE[LUT_SIZE_DKE] = {
|
||||||
|
0x90, 0x50, 0xa0, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x10, 0xa0, 0xa0, 0x80, 0x00, 0x90, 0x50, 0xa0, 0x50,
|
||||||
|
0x50, 0x00, 0x00, 0x00, 0x00, 0x10, 0xa0, 0xa0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17,
|
||||||
|
0x04, 0x00, 0x00, 0x00, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x06, 0x05, 0x00, 0x00, 0x00, 0x04, 0x05, 0x00, 0x00,
|
||||||
|
0x00, 0x01, 0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
|
void WaveshareEPaper2P9InDKE::initialize() {
|
||||||
|
// Hardware reset
|
||||||
|
delay(10);
|
||||||
|
this->reset_pin_->digital_write(false);
|
||||||
|
delayMicroseconds(200);
|
||||||
|
this->reset_pin_->digital_write(true);
|
||||||
|
delayMicroseconds(200);
|
||||||
|
// Wait for busy low
|
||||||
|
this->wait_until_idle_();
|
||||||
|
// Software reset
|
||||||
|
this->command(0x12);
|
||||||
|
// Wait for busy low
|
||||||
|
this->wait_until_idle_();
|
||||||
|
// Set Analog Block Control
|
||||||
|
this->command(0x74);
|
||||||
|
this->data(0x54);
|
||||||
|
// Set Digital Block Control
|
||||||
|
this->command(0x7E);
|
||||||
|
this->data(0x3B);
|
||||||
|
// Set display size and driver output control
|
||||||
|
this->command(0x01);
|
||||||
|
// this->data(0x27);
|
||||||
|
// this->data(0x01);
|
||||||
|
// this->data(0x00);
|
||||||
|
this->data(this->get_height_internal() - 1);
|
||||||
|
this->data((this->get_height_internal() - 1) >> 8);
|
||||||
|
this->data(0x00); // ? GD = 0, SM = 0, TB = 0
|
||||||
|
// Ram data entry mode
|
||||||
|
this->command(0x11);
|
||||||
|
this->data(0x03);
|
||||||
|
// Set Ram X address
|
||||||
|
this->command(0x44);
|
||||||
|
this->data(0x00);
|
||||||
|
this->data(0x0F);
|
||||||
|
// Set Ram Y address
|
||||||
|
this->command(0x45);
|
||||||
|
this->data(0x00);
|
||||||
|
this->data(0x00);
|
||||||
|
this->data(0x27);
|
||||||
|
this->data(0x01);
|
||||||
|
// Set border
|
||||||
|
this->command(0x3C);
|
||||||
|
// this->data(0x80);
|
||||||
|
this->data(0x01);
|
||||||
|
// Set VCOM value
|
||||||
|
this->command(0x2C);
|
||||||
|
this->data(0x26);
|
||||||
|
// Gate voltage setting
|
||||||
|
this->command(0x03);
|
||||||
|
this->data(0x17);
|
||||||
|
// Source voltage setting
|
||||||
|
this->command(0x04);
|
||||||
|
this->data(0x41);
|
||||||
|
this->data(0x00);
|
||||||
|
this->data(0x32);
|
||||||
|
// Frame setting 50hz
|
||||||
|
this->command(0x3A);
|
||||||
|
this->data(0x30);
|
||||||
|
this->command(0x3B);
|
||||||
|
this->data(0x0A);
|
||||||
|
// Load LUT
|
||||||
|
this->command(0x32);
|
||||||
|
for (uint8_t v : FULL_UPDATE_LUT_DKE)
|
||||||
|
this->data(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HOT WaveshareEPaper2P9InDKE::display() {
|
||||||
|
ESP_LOGI(TAG, "Performing e-paper update.");
|
||||||
|
// Set Ram X address counter
|
||||||
|
this->command(0x4e);
|
||||||
|
this->data(0);
|
||||||
|
// Set Ram Y address counter
|
||||||
|
this->command(0x4f);
|
||||||
|
this->data(0);
|
||||||
|
this->data(0);
|
||||||
|
// Load image (128/8*296)
|
||||||
|
this->command(0x24);
|
||||||
|
this->start_data_();
|
||||||
|
this->write_array(this->buffer_, this->get_buffer_length_());
|
||||||
|
this->end_data_();
|
||||||
|
// Image update
|
||||||
|
this->command(0x22);
|
||||||
|
this->data(0xC7);
|
||||||
|
this->command(0x20);
|
||||||
|
// Wait for busy low
|
||||||
|
this->wait_until_idle_();
|
||||||
|
// Enter deep sleep mode
|
||||||
|
this->command(0x10);
|
||||||
|
this->data(0x01);
|
||||||
|
}
|
||||||
|
int WaveshareEPaper2P9InDKE::get_width_internal() { return 128; }
|
||||||
|
int WaveshareEPaper2P9InDKE::get_height_internal() { return 296; }
|
||||||
|
void WaveshareEPaper2P9InDKE::dump_config() {
|
||||||
|
LOG_DISPLAY("", "Waveshare E-Paper", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Model: 2.9in DKE");
|
||||||
|
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||||
|
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||||
|
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
void WaveshareEPaper2P9InDKE::set_full_update_every(uint32_t full_update_every) {
|
||||||
|
this->full_update_every_ = full_update_every;
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================================
|
// ========================================================
|
||||||
// 2.90in Type B (LUT from OTP)
|
// 2.90in Type B (LUT from OTP)
|
||||||
// Datasheet:
|
// Datasheet:
|
||||||
|
|
|
@ -373,6 +373,30 @@ class WaveshareEPaper2P9InV2R2 : public WaveshareEPaper {
|
||||||
void reset_();
|
void reset_();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class WaveshareEPaper2P9InDKE : public WaveshareEPaper {
|
||||||
|
public:
|
||||||
|
void initialize() override;
|
||||||
|
|
||||||
|
void display() override;
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void deep_sleep() override {
|
||||||
|
// COMMAND DEEP SLEEP
|
||||||
|
this->command(0x10);
|
||||||
|
this->data(0x01);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_full_update_every(uint32_t full_update_every);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t full_update_every_{30};
|
||||||
|
uint32_t at_update_{0};
|
||||||
|
int get_width_internal() override;
|
||||||
|
|
||||||
|
int get_height_internal() override;
|
||||||
|
};
|
||||||
|
|
||||||
class WaveshareEPaper4P2In : public WaveshareEPaper {
|
class WaveshareEPaper4P2In : public WaveshareEPaper {
|
||||||
public:
|
public:
|
||||||
void initialize() override;
|
void initialize() override;
|
||||||
|
|
|
@ -129,6 +129,15 @@ bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) {
|
||||||
|
if (this->web_server_->events_.count() == 0)
|
||||||
|
return true;
|
||||||
|
this->web_server_->events_.send(this->web_server_->datetime_json(datetime, DETAIL_ALL).c_str(), "state");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
bool ListEntitiesIterator::on_text(text::Text *text) {
|
bool ListEntitiesIterator::on_text(text::Text *text) {
|
||||||
if (this->web_server_->events_.count() == 0)
|
if (this->web_server_->events_.count() == 0)
|
||||||
|
|
|
@ -47,6 +47,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
bool on_time(datetime::TimeEntity *time) override;
|
bool on_time(datetime::TimeEntity *time) override;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
bool on_datetime(datetime::DateTimeEntity *datetime) override;
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
bool on_text(text::Text *text) override;
|
bool on_text(text::Text *text) override;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -926,6 +926,8 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con
|
||||||
|
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
void WebServer::on_time_update(datetime::TimeEntity *obj) {
|
void WebServer::on_time_update(datetime::TimeEntity *obj) {
|
||||||
|
if (this->events_.count() == 0)
|
||||||
|
return;
|
||||||
this->events_.send(this->time_json(obj, DETAIL_STATE).c_str(), "state");
|
this->events_.send(this->time_json(obj, DETAIL_STATE).c_str(), "state");
|
||||||
}
|
}
|
||||||
void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||||
|
@ -970,6 +972,55 @@ std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_con
|
||||||
}
|
}
|
||||||
#endif // USE_DATETIME_TIME
|
#endif // USE_DATETIME_TIME
|
||||||
|
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
void WebServer::on_datetime_update(datetime::DateTimeEntity *obj) {
|
||||||
|
if (this->events_.count() == 0)
|
||||||
|
return;
|
||||||
|
this->events_.send(this->datetime_json(obj, DETAIL_STATE).c_str(), "state");
|
||||||
|
}
|
||||||
|
void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||||
|
for (auto *obj : App.get_datetimes()) {
|
||||||
|
if (obj->get_object_id() != match.id)
|
||||||
|
continue;
|
||||||
|
if (request->method() == HTTP_GET && match.method.empty()) {
|
||||||
|
std::string data = this->datetime_json(obj, DETAIL_STATE);
|
||||||
|
request->send(200, "application/json", data.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (match.method != "set") {
|
||||||
|
request->send(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto call = obj->make_call();
|
||||||
|
|
||||||
|
if (!request->hasParam("value")) {
|
||||||
|
request->send(409);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request->hasParam("value")) {
|
||||||
|
std::string value = request->getParam("value")->value().c_str();
|
||||||
|
call.set_datetime(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->schedule_([call]() mutable { call.perform(); });
|
||||||
|
request->send(200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
request->send(404);
|
||||||
|
}
|
||||||
|
std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config) {
|
||||||
|
return json::build_json([obj, start_config](JsonObject root) {
|
||||||
|
set_json_id(root, obj, "datetime-" + obj->get_object_id(), start_config);
|
||||||
|
std::string value = str_sprintf("%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour,
|
||||||
|
obj->minute, obj->second);
|
||||||
|
root["value"] = value;
|
||||||
|
root["state"] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#endif // USE_DATETIME_DATETIME
|
||||||
|
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
void WebServer::on_text_update(text::Text *obj, const std::string &state) {
|
void WebServer::on_text_update(text::Text *obj, const std::string &state) {
|
||||||
if (this->events_.count() == 0)
|
if (this->events_.count() == 0)
|
||||||
|
@ -1458,6 +1509,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
|
||||||
return true;
|
return true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "datetime")
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "text")
|
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "text")
|
||||||
return true;
|
return true;
|
||||||
|
@ -1595,6 +1651,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
if (match.domain == "datetime") {
|
||||||
|
this->handle_datetime_request(request, match);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
if (match.domain == "text") {
|
if (match.domain == "text") {
|
||||||
this->handle_text_request(request, match);
|
this->handle_text_request(request, match);
|
||||||
|
|
|
@ -239,6 +239,15 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||||
std::string time_json(datetime::TimeEntity *obj, JsonDetail start_config);
|
std::string time_json(datetime::TimeEntity *obj, JsonDetail start_config);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
void on_datetime_update(datetime::DateTimeEntity *obj) override;
|
||||||
|
/// Handle a datetime request under '/datetime/<id>'.
|
||||||
|
void handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||||
|
|
||||||
|
/// Dump the datetime state with its value as a JSON string.
|
||||||
|
std::string datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
void on_text_update(text::Text *obj, const std::string &state) override;
|
void on_text_update(text::Text *obj, const std::string &state) override;
|
||||||
/// Handle a text input request under '/text/<id>'.
|
/// Handle a text input request under '/text/<id>'.
|
||||||
|
|
|
@ -374,7 +374,10 @@ class LoadValidationStep(ConfigValidationStep):
|
||||||
path + [CONF_ID],
|
path + [CONF_ID],
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
result.add_str_error("No platform specified! See 'platform' key.", path)
|
result.add_str_error(
|
||||||
|
f"'{self.domain}' requires a 'platform' key but it was not specified.",
|
||||||
|
path,
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
# Remove temp output path and construct new one
|
# Remove temp output path and construct new one
|
||||||
result.remove_output_path(path, p_domain)
|
result.remove_output_path(path, p_domain)
|
||||||
|
@ -449,9 +452,28 @@ class MetadataValidationStep(ConfigValidationStep):
|
||||||
|
|
||||||
success = True
|
success = True
|
||||||
for dependency in self.comp.dependencies:
|
for dependency in self.comp.dependencies:
|
||||||
if dependency not in result:
|
dependency_parts = dependency.split(".")
|
||||||
|
if len(dependency_parts) > 2:
|
||||||
result.add_str_error(
|
result.add_str_error(
|
||||||
f"Component {self.domain} requires component {dependency}",
|
"Dependencies must be specified as a single component or in component.platform format only",
|
||||||
|
self.path,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
component_dep = dependency_parts[0]
|
||||||
|
platform_dep = dependency_parts[-1]
|
||||||
|
if component_dep not in result:
|
||||||
|
result.add_str_error(
|
||||||
|
f"Component {self.domain} requires component {component_dep}",
|
||||||
|
self.path,
|
||||||
|
)
|
||||||
|
success = False
|
||||||
|
elif component_dep != platform_dep and (
|
||||||
|
not isinstance(platform_list := result.get(component_dep), list)
|
||||||
|
or not any(CONF_PLATFORM in p for p in platform_list)
|
||||||
|
or not any(p[CONF_PLATFORM] == platform_dep for p in platform_list)
|
||||||
|
):
|
||||||
|
result.add_str_error(
|
||||||
|
f"Component {self.domain} requires 'platform: {platform_dep}' in component '{component_dep}'",
|
||||||
self.path,
|
self.path,
|
||||||
)
|
)
|
||||||
success = False
|
success = False
|
||||||
|
@ -756,11 +778,11 @@ def validate_config(
|
||||||
CORE.raw_config = config
|
CORE.raw_config = config
|
||||||
|
|
||||||
# 1. Load substitutions
|
# 1. Load substitutions
|
||||||
if CONF_SUBSTITUTIONS in config:
|
if CONF_SUBSTITUTIONS in config or command_line_substitutions:
|
||||||
from esphome.components import substitutions
|
from esphome.components import substitutions
|
||||||
|
|
||||||
result[CONF_SUBSTITUTIONS] = {
|
result[CONF_SUBSTITUTIONS] = {
|
||||||
**config[CONF_SUBSTITUTIONS],
|
**config.get(CONF_SUBSTITUTIONS, {}),
|
||||||
**command_line_substitutions,
|
**command_line_substitutions,
|
||||||
}
|
}
|
||||||
result.add_output_path([CONF_SUBSTITUTIONS], CONF_SUBSTITUTIONS)
|
result.add_output_path([CONF_SUBSTITUTIONS], CONF_SUBSTITUTIONS)
|
||||||
|
|
|
@ -304,7 +304,7 @@ def string(value):
|
||||||
"""Validate that a configuration value is a string. If not, automatically converts to a string.
|
"""Validate that a configuration value is a string. If not, automatically converts to a string.
|
||||||
|
|
||||||
Note that this can be lossy, for example the input value 60.00 (float) will be turned into
|
Note that this can be lossy, for example the input value 60.00 (float) will be turned into
|
||||||
"60.0" (string). For values where this could be a problem `string_string` has to be used.
|
"60.0" (string). For values where this could be a problem `string_strict` has to be used.
|
||||||
"""
|
"""
|
||||||
check_not_templatable(value)
|
check_not_templatable(value)
|
||||||
if isinstance(value, (dict, list)):
|
if isinstance(value, (dict, list)):
|
||||||
|
|
|
@ -179,6 +179,7 @@ CONF_DATA_PINS = "data_pins"
|
||||||
CONF_DATA_RATE = "data_rate"
|
CONF_DATA_RATE = "data_rate"
|
||||||
CONF_DATA_TEMPLATE = "data_template"
|
CONF_DATA_TEMPLATE = "data_template"
|
||||||
CONF_DATE = "date"
|
CONF_DATE = "date"
|
||||||
|
CONF_DATETIME = "datetime"
|
||||||
CONF_DAY = "day"
|
CONF_DAY = "day"
|
||||||
CONF_DAYS_OF_MONTH = "days_of_month"
|
CONF_DAYS_OF_MONTH = "days_of_month"
|
||||||
CONF_DAYS_OF_WEEK = "days_of_week"
|
CONF_DAYS_OF_WEEK = "days_of_week"
|
||||||
|
@ -597,6 +598,7 @@ CONF_PIN_D = "pin_d"
|
||||||
CONF_PINS = "pins"
|
CONF_PINS = "pins"
|
||||||
CONF_PIXEL_MAPPER = "pixel_mapper"
|
CONF_PIXEL_MAPPER = "pixel_mapper"
|
||||||
CONF_PLATFORM = "platform"
|
CONF_PLATFORM = "platform"
|
||||||
|
CONF_PLATFORM_VERSION = "platform_version"
|
||||||
CONF_PLATFORMIO_OPTIONS = "platformio_options"
|
CONF_PLATFORMIO_OPTIONS = "platformio_options"
|
||||||
CONF_PM_0_3UM = "pm_0_3um"
|
CONF_PM_0_3UM = "pm_0_3um"
|
||||||
CONF_PM_0_5UM = "pm_0_5um"
|
CONF_PM_0_5UM = "pm_0_5um"
|
||||||
|
|
|
@ -45,6 +45,9 @@
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
#include "esphome/components/datetime/time_entity.h"
|
#include "esphome/components/datetime/time_entity.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
#include "esphome/components/datetime/datetime_entity.h"
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
#include "esphome/components/text/text.h"
|
#include "esphome/components/text/text.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -141,6 +144,10 @@ class Application {
|
||||||
void register_time(datetime::TimeEntity *time) { this->times_.push_back(time); }
|
void register_time(datetime::TimeEntity *time) { this->times_.push_back(time); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
void register_datetime(datetime::DateTimeEntity *datetime) { this->datetimes_.push_back(datetime); }
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
void register_text(text::Text *text) { this->texts_.push_back(text); }
|
void register_text(text::Text *text) { this->texts_.push_back(text); }
|
||||||
#endif
|
#endif
|
||||||
|
@ -335,6 +342,15 @@ class Application {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
const std::vector<datetime::DateTimeEntity *> &get_datetimes() { return this->datetimes_; }
|
||||||
|
datetime::DateTimeEntity *get_datetime_by_key(uint32_t key, bool include_internal = false) {
|
||||||
|
for (auto *obj : this->datetimes_)
|
||||||
|
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||||
|
return obj;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
const std::vector<text::Text *> &get_texts() { return this->texts_; }
|
const std::vector<text::Text *> &get_texts() { return this->texts_; }
|
||||||
text::Text *get_text_by_key(uint32_t key, bool include_internal = false) {
|
text::Text *get_text_by_key(uint32_t key, bool include_internal = false) {
|
||||||
|
@ -456,6 +472,9 @@ class Application {
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
std::vector<datetime::TimeEntity *> times_{};
|
std::vector<datetime::TimeEntity *> times_{};
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
std::vector<datetime::DateTimeEntity *> datetimes_{};
|
||||||
|
#endif
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
std::vector<select::Select *> selects_{};
|
std::vector<select::Select *> selects_{};
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -85,7 +85,7 @@ class Component {
|
||||||
|
|
||||||
/** priority of setup(). higher -> executed earlier
|
/** priority of setup(). higher -> executed earlier
|
||||||
*
|
*
|
||||||
* Defaults to 0.
|
* Defaults to setup_priority::DATA, i.e. 600.
|
||||||
*
|
*
|
||||||
* @return The setup priority of this component
|
* @return The setup priority of this component
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -232,6 +232,21 @@ void ComponentIterator::advance() {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
case IteratorState::DATETIME_DATETIME:
|
||||||
|
if (this->at_ >= App.get_datetimes().size()) {
|
||||||
|
advance_platform = true;
|
||||||
|
} else {
|
||||||
|
auto *datetime = App.get_datetimes()[this->at_];
|
||||||
|
if (datetime->is_internal() && !this->include_internal_) {
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
success = this->on_datetime(datetime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
case IteratorState::TEXT:
|
case IteratorState::TEXT:
|
||||||
if (this->at_ >= App.get_texts().size()) {
|
if (this->at_ >= App.get_texts().size()) {
|
||||||
|
|
|
@ -63,6 +63,9 @@ class ComponentIterator {
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
virtual bool on_time(datetime::TimeEntity *time) = 0;
|
virtual bool on_time(datetime::TimeEntity *time) = 0;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
virtual bool on_datetime(datetime::DateTimeEntity *datetime) = 0;
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
virtual bool on_text(text::Text *text) = 0;
|
virtual bool on_text(text::Text *text) = 0;
|
||||||
#endif
|
#endif
|
||||||
|
@ -132,6 +135,9 @@ class ComponentIterator {
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
DATETIME_TIME,
|
DATETIME_TIME,
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
DATETIME_DATETIME,
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
TEXT,
|
TEXT,
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -71,6 +71,12 @@ void Controller::setup_controller(bool include_internal) {
|
||||||
obj->add_on_state_callback([this, obj]() { this->on_time_update(obj); });
|
obj->add_on_state_callback([this, obj]() { this->on_time_update(obj); });
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
for (auto *obj : App.get_datetimes()) {
|
||||||
|
if (include_internal || !obj->is_internal())
|
||||||
|
obj->add_on_state_callback([this, obj]() { this->on_datetime_update(obj); });
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
for (auto *obj : App.get_texts()) {
|
for (auto *obj : App.get_texts()) {
|
||||||
if (include_internal || !obj->is_internal())
|
if (include_internal || !obj->is_internal())
|
||||||
|
|
|
@ -37,6 +37,9 @@
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
#include "esphome/components/datetime/time_entity.h"
|
#include "esphome/components/datetime/time_entity.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
#include "esphome/components/datetime/datetime_entity.h"
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
#include "esphome/components/text/text.h"
|
#include "esphome/components/text/text.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -97,6 +100,9 @@ class Controller {
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
virtual void on_time_update(datetime::TimeEntity *obj){};
|
virtual void on_time_update(datetime::TimeEntity *obj){};
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
virtual void on_datetime_update(datetime::DateTimeEntity *obj){};
|
||||||
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
virtual void on_text_update(text::Text *obj, const std::string &state){};
|
virtual void on_text_update(text::Text *obj, const std::string &state){};
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
#define USE_DATETIME
|
#define USE_DATETIME
|
||||||
#define USE_DATETIME_DATE
|
#define USE_DATETIME_DATE
|
||||||
#define USE_DATETIME_TIME
|
#define USE_DATETIME_TIME
|
||||||
|
#define USE_DATETIME_DATETIME
|
||||||
#define USE_OTA
|
#define USE_OTA
|
||||||
#define USE_OTA_PASSWORD
|
#define USE_OTA_PASSWORD
|
||||||
#define USE_OTA_STATE_CALLBACK
|
#define USE_OTA_STATE_CALLBACK
|
||||||
|
|
|
@ -437,7 +437,7 @@ static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c ==
|
||||||
|
|
||||||
std::string base64_encode(const std::vector<uint8_t> &buf) { return base64_encode(buf.data(), buf.size()); }
|
std::string base64_encode(const std::vector<uint8_t> &buf) { return base64_encode(buf.data(), buf.size()); }
|
||||||
|
|
||||||
std::string base64_encode(const char *buf, unsigned int buf_len) {
|
std::string base64_encode(const uint8_t *buf, size_t buf_len) {
|
||||||
std::string ret;
|
std::string ret;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int j = 0;
|
int j = 0;
|
||||||
|
|
|
@ -178,6 +178,15 @@ void ESPTime::recalc_timestamp_utc(bool use_day_of_year) {
|
||||||
this->timestamp = res;
|
this->timestamp = res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ESPTime::recalc_timestamp_local(bool use_day_of_year) {
|
||||||
|
this->recalc_timestamp_utc(use_day_of_year);
|
||||||
|
this->timestamp -= ESPTime::timezone_offset();
|
||||||
|
ESPTime temp = ESPTime::from_epoch_local(this->timestamp);
|
||||||
|
if (temp.is_dst) {
|
||||||
|
this->timestamp -= 3600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int32_t ESPTime::timezone_offset() {
|
int32_t ESPTime::timezone_offset() {
|
||||||
int32_t offset = 0;
|
int32_t offset = 0;
|
||||||
time_t now = ::time(nullptr);
|
time_t now = ::time(nullptr);
|
||||||
|
|
|
@ -99,6 +99,9 @@ struct ESPTime {
|
||||||
/// Recalculate the timestamp field from the other fields of this ESPTime instance (must be UTC).
|
/// Recalculate the timestamp field from the other fields of this ESPTime instance (must be UTC).
|
||||||
void recalc_timestamp_utc(bool use_day_of_year = true);
|
void recalc_timestamp_utc(bool use_day_of_year = true);
|
||||||
|
|
||||||
|
/// Recalculate the timestamp field from the other fields of this ESPTime instance assuming local fields.
|
||||||
|
void recalc_timestamp_local(bool use_day_of_year = true);
|
||||||
|
|
||||||
/// Convert this ESPTime instance back to a tm struct.
|
/// Convert this ESPTime instance back to a tm struct.
|
||||||
struct tm to_c_tm();
|
struct tm to_c_tm();
|
||||||
|
|
||||||
|
|
|
@ -623,6 +623,7 @@ def lint_trailing_whitespace(fname, match):
|
||||||
"esphome/components/cover/cover.h",
|
"esphome/components/cover/cover.h",
|
||||||
"esphome/components/datetime/date_entity.h",
|
"esphome/components/datetime/date_entity.h",
|
||||||
"esphome/components/datetime/time_entity.h",
|
"esphome/components/datetime/time_entity.h",
|
||||||
|
"esphome/components/datetime/datetime_entity.h",
|
||||||
"esphome/components/display/display.h",
|
"esphome/components/display/display.h",
|
||||||
"esphome/components/event/event.h",
|
"esphome/components/event/event.h",
|
||||||
"esphome/components/fan/fan.h",
|
"esphome/components/fan/fan.h",
|
||||||
|
|
|
@ -69,7 +69,9 @@ def create_components_graph():
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
for dependency in comp.dependencies:
|
for dependency in comp.dependencies:
|
||||||
add_item_to_components_graph(components_graph, dependency, name)
|
add_item_to_components_graph(
|
||||||
|
components_graph, dependency.split(".")[0], name
|
||||||
|
)
|
||||||
|
|
||||||
for target_config in TARGET_CONFIGURATIONS:
|
for target_config in TARGET_CONFIGURATIONS:
|
||||||
CORE.data[KEY_CORE] = target_config
|
CORE.data[KEY_CORE] = target_config
|
||||||
|
@ -87,7 +89,9 @@ def create_components_graph():
|
||||||
add_item_to_components_graph(components_graph, platform_name, name)
|
add_item_to_components_graph(components_graph, platform_name, name)
|
||||||
|
|
||||||
for dependency in platform.dependencies:
|
for dependency in platform.dependencies:
|
||||||
add_item_to_components_graph(components_graph, dependency, name)
|
add_item_to_components_graph(
|
||||||
|
components_graph, dependency.split(".")[0], name
|
||||||
|
)
|
||||||
|
|
||||||
for target_config in TARGET_CONFIGURATIONS:
|
for target_config in TARGET_CONFIGURATIONS:
|
||||||
CORE.data[KEY_CORE] = target_config
|
CORE.data[KEY_CORE] = target_config
|
||||||
|
|
|
@ -6,9 +6,11 @@ import re
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||||
from homeassistant.components.button import ButtonDeviceClass
|
from homeassistant.components.button import ButtonDeviceClass
|
||||||
from homeassistant.components.cover import CoverDeviceClass
|
from homeassistant.components.cover import CoverDeviceClass
|
||||||
|
from homeassistant.components.event import EventDeviceClass
|
||||||
from homeassistant.components.number import NumberDeviceClass
|
from homeassistant.components.number import NumberDeviceClass
|
||||||
from homeassistant.components.sensor import SensorDeviceClass
|
from homeassistant.components.sensor import SensorDeviceClass
|
||||||
from homeassistant.components.switch import SwitchDeviceClass
|
from homeassistant.components.switch import SwitchDeviceClass
|
||||||
|
from homeassistant.components.valve import ValveDeviceClass
|
||||||
|
|
||||||
# pylint: enable=import-error
|
# pylint: enable=import-error
|
||||||
|
|
||||||
|
@ -21,9 +23,11 @@ DOMAINS = {
|
||||||
"binary_sensor": BinarySensorDeviceClass,
|
"binary_sensor": BinarySensorDeviceClass,
|
||||||
"button": ButtonDeviceClass,
|
"button": ButtonDeviceClass,
|
||||||
"cover": CoverDeviceClass,
|
"cover": CoverDeviceClass,
|
||||||
|
"event": EventDeviceClass,
|
||||||
"number": NumberDeviceClass,
|
"number": NumberDeviceClass,
|
||||||
"sensor": SensorDeviceClass,
|
"sensor": SensorDeviceClass,
|
||||||
"switch": SwitchDeviceClass,
|
"switch": SwitchDeviceClass,
|
||||||
|
"valve": ValveDeviceClass,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,9 +37,9 @@ start_esphome() {
|
||||||
|
|
||||||
# Start esphome process
|
# Start esphome process
|
||||||
echo "> [$target_component] [$test_name] [$target_platform]"
|
echo "> [$target_component] [$test_name] [$target_platform]"
|
||||||
echo "esphome -s component_name $target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file"
|
echo "esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file"
|
||||||
# TODO: Validate escape of Command line substitution value
|
# TODO: Validate escape of Command line substitution value
|
||||||
esphome -s component_name $target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file
|
esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file
|
||||||
}
|
}
|
||||||
|
|
||||||
# Find all test yaml files.
|
# Find all test yaml files.
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
datetime:
|
datetime:
|
||||||
|
|
||||||
|
time:
|
||||||
|
|
15
tests/components/host/test.host.yaml
Normal file
15
tests/components/host/test.host.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
time:
|
||||||
|
- platform: sntp
|
||||||
|
id: esptime
|
||||||
|
timezone: Australia/Sydney
|
||||||
|
|
||||||
|
logger:
|
||||||
|
level: VERBOSE
|
||||||
|
logs:
|
||||||
|
lvgl: INFO
|
||||||
|
display: DEBUG
|
||||||
|
sensor: INFO
|
||||||
|
vnc: DEBUG
|
||||||
|
|
||||||
|
host:
|
||||||
|
mac_address: "62:23:45:AF:B3:DD"
|
|
@ -183,3 +183,25 @@ datetime:
|
||||||
- x.hour
|
- x.hour
|
||||||
- x.minute
|
- x.minute
|
||||||
- x.second
|
- x.second
|
||||||
|
- platform: template
|
||||||
|
name: DateTime
|
||||||
|
id: test_datetime
|
||||||
|
type: datetime
|
||||||
|
set_action:
|
||||||
|
- logger.log: "set_value"
|
||||||
|
on_value:
|
||||||
|
- logger.log:
|
||||||
|
format: "DateTime: %04d-%02d-%02d %02d:%02d:%02d"
|
||||||
|
args:
|
||||||
|
- x.year
|
||||||
|
- x.month
|
||||||
|
- x.day_of_month
|
||||||
|
- x.hour
|
||||||
|
- x.minute
|
||||||
|
- x.second
|
||||||
|
|
||||||
|
time:
|
||||||
|
- platform: sntp # Required for datetime
|
||||||
|
|
||||||
|
wifi: # Required for sntp time
|
||||||
|
ap:
|
2
tests/components/template/test.bk72xx.yaml
Normal file
2
tests/components/template/test.bk72xx.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
packages:
|
||||||
|
common: !include common.yaml
|
2
tests/components/template/test.esp32-c3-idf.yaml
Normal file
2
tests/components/template/test.esp32-c3-idf.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
packages:
|
||||||
|
common: !include common.yaml
|
2
tests/components/template/test.esp32-c3.yaml
Normal file
2
tests/components/template/test.esp32-c3.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
packages:
|
||||||
|
common: !include common.yaml
|
2
tests/components/template/test.esp32-idf.yaml
Normal file
2
tests/components/template/test.esp32-idf.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
packages:
|
||||||
|
common: !include common.yaml
|
2
tests/components/template/test.esp32-s3-idf.yaml
Normal file
2
tests/components/template/test.esp32-s3-idf.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
packages:
|
||||||
|
common: !include common.yaml
|
2
tests/components/template/test.esp32.yaml
Normal file
2
tests/components/template/test.esp32.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
packages:
|
||||||
|
common: !include common.yaml
|
2
tests/components/template/test.esp8266.yaml
Normal file
2
tests/components/template/test.esp8266.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
packages:
|
||||||
|
common: !include common.yaml
|
2
tests/components/template/test.rp2040.yaml
Normal file
2
tests/components/template/test.rp2040.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
packages:
|
||||||
|
common: !include common.yaml
|
|
@ -83,6 +83,24 @@ display:
|
||||||
full_update_every: 30
|
full_update_every: 30
|
||||||
lambda: |-
|
lambda: |-
|
||||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||||
|
- platform: waveshare_epaper
|
||||||
|
model: 2.90in-dke
|
||||||
|
spi_id: spi_id_1
|
||||||
|
cs_pin:
|
||||||
|
allow_other_uses: true
|
||||||
|
number: GPIO25
|
||||||
|
dc_pin:
|
||||||
|
allow_other_uses: true
|
||||||
|
number: GPIO26
|
||||||
|
busy_pin:
|
||||||
|
allow_other_uses: true
|
||||||
|
number: GPIO27
|
||||||
|
reset_pin:
|
||||||
|
allow_other_uses: true
|
||||||
|
number: GPIO32
|
||||||
|
full_update_every: 1
|
||||||
|
lambda: |-
|
||||||
|
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||||
- platform: waveshare_epaper
|
- platform: waveshare_epaper
|
||||||
model: 2.70in-b
|
model: 2.70in-b
|
||||||
spi_id: spi_id_1
|
spi_id: spi_id_1
|
||||||
|
|
|
@ -12,8 +12,4 @@ packages:
|
||||||
component_under_test: !include
|
component_under_test: !include
|
||||||
file: $component_test_file
|
file: $component_test_file
|
||||||
vars:
|
vars:
|
||||||
component_name: $component_name
|
|
||||||
test_name: $test_name
|
|
||||||
target_platform: $target_platform
|
|
||||||
component_test_file: $component_test_file
|
component_test_file: $component_test_file
|
||||||
component_dir: "../../components/$component_name"
|
|
||||||
|
|
|
@ -14,8 +14,4 @@ packages:
|
||||||
component_under_test: !include
|
component_under_test: !include
|
||||||
file: $component_test_file
|
file: $component_test_file
|
||||||
vars:
|
vars:
|
||||||
component_name: $component_name
|
|
||||||
test_name: $test_name
|
|
||||||
target_platform: $target_platform
|
|
||||||
component_test_file: $component_test_file
|
component_test_file: $component_test_file
|
||||||
component_dir: "../../components/$component_name"
|
|
||||||
|
|
|
@ -14,8 +14,4 @@ packages:
|
||||||
component_under_test: !include
|
component_under_test: !include
|
||||||
file: $component_test_file
|
file: $component_test_file
|
||||||
vars:
|
vars:
|
||||||
component_name: $component_name
|
|
||||||
test_name: $test_name
|
|
||||||
target_platform: $target_platform
|
|
||||||
component_test_file: $component_test_file
|
component_test_file: $component_test_file
|
||||||
component_dir: "../../components/$component_name"
|
|
||||||
|
|
|
@ -14,8 +14,4 @@ packages:
|
||||||
component_under_test: !include
|
component_under_test: !include
|
||||||
file: $component_test_file
|
file: $component_test_file
|
||||||
vars:
|
vars:
|
||||||
component_name: $component_name
|
|
||||||
test_name: $test_name
|
|
||||||
target_platform: $target_platform
|
|
||||||
component_test_file: $component_test_file
|
component_test_file: $component_test_file
|
||||||
component_dir: "../../components/$component_name"
|
|
||||||
|
|
|
@ -14,8 +14,4 @@ packages:
|
||||||
component_under_test: !include
|
component_under_test: !include
|
||||||
file: $component_test_file
|
file: $component_test_file
|
||||||
vars:
|
vars:
|
||||||
component_name: $component_name
|
|
||||||
test_name: $test_name
|
|
||||||
target_platform: $target_platform
|
|
||||||
component_test_file: $component_test_file
|
component_test_file: $component_test_file
|
||||||
component_dir: "../../components/$component_name"
|
|
||||||
|
|
|
@ -15,8 +15,4 @@ packages:
|
||||||
component_under_test: !include
|
component_under_test: !include
|
||||||
file: $component_test_file
|
file: $component_test_file
|
||||||
vars:
|
vars:
|
||||||
component_name: $component_name
|
|
||||||
test_name: $test_name
|
|
||||||
target_platform: $target_platform
|
|
||||||
component_test_file: $component_test_file
|
component_test_file: $component_test_file
|
||||||
component_dir: "../../components/$component_name"
|
|
||||||
|
|
|
@ -15,8 +15,4 @@ packages:
|
||||||
component_under_test: !include
|
component_under_test: !include
|
||||||
file: $component_test_file
|
file: $component_test_file
|
||||||
vars:
|
vars:
|
||||||
component_name: $component_name
|
|
||||||
test_name: $test_name
|
|
||||||
target_platform: $target_platform
|
|
||||||
component_test_file: $component_test_file
|
component_test_file: $component_test_file
|
||||||
component_dir: "../../components/$component_name"
|
|
||||||
|
|
|
@ -15,8 +15,4 @@ packages:
|
||||||
component_under_test: !include
|
component_under_test: !include
|
||||||
file: $component_test_file
|
file: $component_test_file
|
||||||
vars:
|
vars:
|
||||||
component_name: $component_name
|
|
||||||
test_name: $test_name
|
|
||||||
target_platform: $target_platform
|
|
||||||
component_test_file: $component_test_file
|
component_test_file: $component_test_file
|
||||||
component_dir: "../../components/$component_name"
|
|
||||||
|
|
|
@ -15,8 +15,4 @@ packages:
|
||||||
component_under_test: !include
|
component_under_test: !include
|
||||||
file: $component_test_file
|
file: $component_test_file
|
||||||
vars:
|
vars:
|
||||||
component_name: $component_name
|
|
||||||
test_name: $test_name
|
|
||||||
target_platform: $target_platform
|
|
||||||
component_test_file: $component_test_file
|
component_test_file: $component_test_file
|
||||||
component_dir: "../../components/$component_name"
|
|
||||||
|
|
|
@ -12,8 +12,4 @@ packages:
|
||||||
component_under_test: !include
|
component_under_test: !include
|
||||||
file: $component_test_file
|
file: $component_test_file
|
||||||
vars:
|
vars:
|
||||||
component_name: $component_name
|
|
||||||
test_name: $test_name
|
|
||||||
target_platform: $target_platform
|
|
||||||
component_test_file: $component_test_file
|
component_test_file: $component_test_file
|
||||||
component_dir: "../../components/$component_name"
|
|
||||||
|
|
|
@ -12,8 +12,4 @@ packages:
|
||||||
component_under_test: !include
|
component_under_test: !include
|
||||||
file: $component_test_file
|
file: $component_test_file
|
||||||
vars:
|
vars:
|
||||||
component_name: $component_name
|
|
||||||
test_name: $test_name
|
|
||||||
target_platform: $target_platform
|
|
||||||
component_test_file: $component_test_file
|
component_test_file: $component_test_file
|
||||||
component_dir: "../../components/$component_name"
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue