mirror of
https://github.com/esphome/esphome.git
synced 2025-01-31 02:00:55 +00:00
Add datetime entities (#6513)
This commit is contained in:
parent
bcef64a6fa
commit
8ef7b41c91
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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>'.
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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",
|
||||||
|
@ -1 +1,3 @@
|
|||||||
datetime:
|
datetime:
|
||||||
|
|
||||||
|
time:
|
||||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user