mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'dev' into nrf52
This commit is contained in:
		| @@ -47,6 +47,7 @@ service APIConnection { | ||||
|   rpc media_player_command (MediaPlayerCommandRequest) returns (void) {} | ||||
|   rpc date_command (DateCommandRequest) returns (void) {} | ||||
|   rpc time_command (TimeCommandRequest) returns (void) {} | ||||
|   rpc datetime_command (DateTimeCommandRequest) returns (void) {} | ||||
|  | ||||
|   rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} | ||||
|   rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {} | ||||
| @@ -1777,3 +1778,40 @@ message ValveCommandRequest { | ||||
|   float position = 3; | ||||
|   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 | ||||
|  | ||||
| #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 | ||||
| bool APIConnection::send_text_state(text::Text *text, std::string state) { | ||||
|   if (!this->state_subscription_) | ||||
|   | ||||
| @@ -82,6 +82,11 @@ class APIConnection : public APIServerConnection { | ||||
|   bool send_time_info(datetime::TimeEntity *time); | ||||
|   void time_command(const TimeCommandRequest &msg) override; | ||||
| #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 | ||||
|   bool send_text_state(text::Text *text, std::string state); | ||||
|   bool send_text_info(text::Text *text); | ||||
|   | ||||
| @@ -8093,6 +8093,179 @@ void ValveCommandRequest::dump_to(std::string &out) const { | ||||
|   out.append("}"); | ||||
| } | ||||
| #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 esphome | ||||
|   | ||||
| @@ -2060,6 +2060,51 @@ class ValveCommandRequest : public ProtoMessage { | ||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit 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 esphome | ||||
|   | ||||
| @@ -591,6 +591,24 @@ bool APIServerConnectionBase::send_valve_state_response(const ValveStateResponse | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
| #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) { | ||||
|   switch (msg_type) { | ||||
|     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()); | ||||
| #endif | ||||
|       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 | ||||
|       break; | ||||
|     } | ||||
| @@ -1379,6 +1408,19 @@ void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) | ||||
|   this->time_command(msg); | ||||
| } | ||||
| #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 | ||||
| void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( | ||||
|     const SubscribeBluetoothLEAdvertisementsRequest &msg) { | ||||
|   | ||||
| @@ -294,6 +294,15 @@ class APIServerConnectionBase : public ProtoService { | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   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 | ||||
|  protected: | ||||
|   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 | ||||
|   virtual void time_command(const TimeCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   virtual void datetime_command(const DateTimeCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; | ||||
| #endif | ||||
| @@ -453,6 +465,9 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   void on_time_command_request(const TimeCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   void on_date_time_command_request(const DateTimeCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; | ||||
| #endif | ||||
|   | ||||
| @@ -273,6 +273,15 @@ void APIServer::on_time_update(datetime::TimeEntity *obj) { | ||||
| } | ||||
| #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 | ||||
| void APIServer::on_text_update(text::Text *obj, const std::string &state) { | ||||
|   if (obj->is_internal()) | ||||
|   | ||||
| @@ -72,6 +72,9 @@ class APIServer : public Component, public Controller { | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   void on_time_update(datetime::TimeEntity *obj) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   void on_datetime_update(datetime::DateTimeEntity *obj) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   void on_text_update(text::Text *obj, const std::string &state) override; | ||||
| #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); } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) { | ||||
|   return this->client_->send_datetime_info(datetime); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_TEXT | ||||
| bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); } | ||||
| #endif | ||||
|   | ||||
| @@ -52,6 +52,9 @@ class ListEntitiesIterator : public ComponentIterator { | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool on_time(datetime::TimeEntity *time) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool on_datetime(datetime::DateTimeEntity *datetime) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   bool on_text(text::Text *text) override; | ||||
| #endif | ||||
|   | ||||
| @@ -48,6 +48,11 @@ bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->cl | ||||
| #ifdef USE_DATETIME_TIME | ||||
| bool InitialStateIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_state(time); } | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| bool InitialStateIterator::on_datetime(datetime::DateTimeEntity *datetime) { | ||||
|   return this->client_->send_datetime_state(datetime); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
| bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); } | ||||
| #endif | ||||
|   | ||||
| @@ -49,6 +49,9 @@ class InitialStateIterator : public ComponentIterator { | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool on_time(datetime::TimeEntity *time) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool on_datetime(datetime::DateTimeEntity *datetime) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   bool on_text(text::Text *text) override; | ||||
| #endif | ||||
|   | ||||
| @@ -4,6 +4,7 @@ from esphome.components import uart | ||||
| from esphome.const import CONF_ID, CONF_ADDRESS | ||||
|  | ||||
| CODEOWNERS = ["@s1lvi0"] | ||||
| MULTI_CONF = True | ||||
| DEPENDENCIES = ["uart"] | ||||
|  | ||||
| CONF_BMS_DALY_ID = "bms_daly_id" | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import esphome.codegen as cg | ||||
|  | ||||
| # import cpp_generator as cpp | ||||
| import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.components import mqtt, time | ||||
| @@ -13,6 +12,7 @@ from esphome.const import ( | ||||
|     CONF_TYPE, | ||||
|     CONF_MQTT_ID, | ||||
|     CONF_DATE, | ||||
|     CONF_DATETIME, | ||||
|     CONF_TIME, | ||||
|     CONF_YEAR, | ||||
|     CONF_MONTH, | ||||
| @@ -27,6 +27,7 @@ from esphome.cpp_helpers import setup_entity | ||||
|  | ||||
|  | ||||
| CODEOWNERS = ["@rfdarter", "@jesserockz"] | ||||
| DEPENDENCIES = ["time"] | ||||
|  | ||||
| IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| @@ -34,10 +35,12 @@ datetime_ns = cg.esphome_ns.namespace("datetime") | ||||
| DateTimeBase = datetime_ns.class_("DateTimeBase", cg.EntityBase) | ||||
| DateEntity = datetime_ns.class_("DateEntity", DateTimeBase) | ||||
| TimeEntity = datetime_ns.class_("TimeEntity", DateTimeBase) | ||||
| DateTimeEntity = datetime_ns.class_("DateTimeEntity", DateTimeBase) | ||||
|  | ||||
| # Actions | ||||
| DateSetAction = datetime_ns.class_("DateSetAction", automation.Action) | ||||
| TimeSetAction = datetime_ns.class_("TimeSetAction", automation.Action) | ||||
| DateTimeSetAction = datetime_ns.class_("DateTimeSetAction", automation.Action) | ||||
|  | ||||
| DateTimeStateTrigger = datetime_ns.class_( | ||||
|     "DateTimeStateTrigger", automation.Trigger.template(cg.ESPTime) | ||||
| @@ -46,6 +49,12 @@ DateTimeStateTrigger = datetime_ns.class_( | ||||
| OnTimeTrigger = datetime_ns.class_( | ||||
|     "OnTimeTrigger", automation.Trigger, cg.Component, cg.Parented.template(TimeEntity) | ||||
| ) | ||||
| OnDateTimeTrigger = datetime_ns.class_( | ||||
|     "OnDateTimeTrigger", | ||||
|     automation.Trigger, | ||||
|     cg.Component, | ||||
|     cg.Parented.template(DateTimeEntity), | ||||
| ) | ||||
|  | ||||
| DATETIME_MODES = [ | ||||
|     "DATE", | ||||
| @@ -61,45 +70,55 @@ _DATETIME_SCHEMA = cv.Schema( | ||||
|                 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)) | ||||
|  | ||||
|  | ||||
| def date_schema(class_: MockObjClass) -> cv.Schema: | ||||
|     schema = { | ||||
|         cv.GenerateID(): cv.declare_id(class_), | ||||
|         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTDateComponent), | ||||
|         cv.Optional(CONF_TYPE, default="DATE"): cv.one_of("DATE", upper=True), | ||||
|     } | ||||
|     schema = cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(class_), | ||||
|             cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTDateComponent), | ||||
|             cv.Optional(CONF_TYPE, default="DATE"): cv.one_of("DATE", upper=True), | ||||
|         } | ||||
|     ) | ||||
|     return _DATETIME_SCHEMA.extend(schema) | ||||
|  | ||||
|  | ||||
| def time_schema(class_: MockObjClass) -> cv.Schema: | ||||
|     schema = { | ||||
|         cv.GenerateID(): cv.declare_id(class_), | ||||
|         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTimeComponent), | ||||
|         cv.Optional(CONF_TYPE, default="TIME"): cv.one_of("TIME", upper=True), | ||||
|         cv.Inclusive( | ||||
|             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.Inclusive(CONF_TIME_ID, group_of_inclusion=CONF_ON_TIME): cv.use_id( | ||||
|             time.RealTimeClock | ||||
|         ), | ||||
|     } | ||||
|     schema = cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(class_), | ||||
|             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_ON_TIME): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnTimeTrigger), | ||||
|                 } | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     return _DATETIME_SCHEMA.extend(schema) | ||||
|  | ||||
|  | ||||
| def datetime_schema(class_: MockObjClass) -> cv.Schema: | ||||
|     schema = { | ||||
|         cv.GenerateID(): cv.declare_id(class_), | ||||
|         cv.Optional(CONF_TYPE, default="DATETIME"): cv.one_of("DATETIME", upper=True), | ||||
|     } | ||||
|     schema = cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(class_), | ||||
|             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) | ||||
|  | ||||
|  | ||||
| @@ -113,13 +132,11 @@ async def setup_datetime_core_(var, config): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf) | ||||
|  | ||||
|     rtc_id = config.get(CONF_TIME_ID) | ||||
|     rtc = None | ||||
|     if rtc_id is not None: | ||||
|         rtc = await cg.get_variable(rtc_id) | ||||
|     rtc = await cg.get_variable(config[CONF_TIME_ID]) | ||||
|     cg.add(var.set_rtc(rtc)) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_TIME, []): | ||||
|         assert rtc is not None | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], rtc) | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) | ||||
|         await automation.build_automation(trigger, [], conf) | ||||
|         await cg.register_component(trigger, conf) | ||||
|         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) | ||||
|     await cg.register_parented(action_var, config[CONF_ID]) | ||||
|  | ||||
|     date = config[CONF_DATE] | ||||
|     if cg.is_template(date): | ||||
|         template_ = await cg.templatable(config[CONF_DATE], [], cg.ESPTime) | ||||
|     date_config = config[CONF_DATE] | ||||
|     if cg.is_template(date_config): | ||||
|         template_ = await cg.templatable(date_config, [], cg.ESPTime) | ||||
|         cg.add(action_var.set_date(template_)) | ||||
|     else: | ||||
|         date_struct = cg.StructInitializer( | ||||
|             cg.ESPTime, | ||||
|             ("day_of_month", date[CONF_DAY]), | ||||
|             ("month", date[CONF_MONTH]), | ||||
|             ("year", date[CONF_YEAR]), | ||||
|             ("day_of_month", date_config[CONF_DAY]), | ||||
|             ("month", date_config[CONF_MONTH]), | ||||
|             ("year", date_config[CONF_YEAR]), | ||||
|         ) | ||||
|         cg.add(action_var.set_date(date_struct)) | ||||
|     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] | ||||
|     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_)) | ||||
|     else: | ||||
|         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)) | ||||
|     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)) { | ||||
|     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; | ||||
|   | ||||
| @@ -5,6 +5,8 @@ | ||||
| #include "esphome/core/entity_base.h" | ||||
| #include "esphome/core/time.h" | ||||
|  | ||||
| #include "esphome/components/time/real_time_clock.h" | ||||
|  | ||||
| namespace esphome { | ||||
| 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 set_rtc(time::RealTimeClock *rtc) { this->rtc_ = rtc; } | ||||
|   time::RealTimeClock *get_rtc() const { return this->rtc_; } | ||||
|  | ||||
|  protected: | ||||
|   CallbackManager<void()> state_callback_; | ||||
|  | ||||
|   time::RealTimeClock *rtc_; | ||||
|  | ||||
|   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(); | ||||
| } | ||||
|  | ||||
| #ifdef USE_TIME | ||||
|  | ||||
| static const int MAX_TIMESTAMP_DRIFT = 900;  // how far can the clock drift before we consider | ||||
|                                              // there has been a drastic time synchronization | ||||
|  | ||||
| @@ -103,7 +101,7 @@ void OnTimeTrigger::loop() { | ||||
|   if (!this->parent_->has_state()) { | ||||
|     return; | ||||
|   } | ||||
|   ESPTime time = this->rtc_->now(); | ||||
|   ESPTime time = this->parent_->rtc_->now(); | ||||
|   if (!time.is_valid()) { | ||||
|     return; | ||||
|   } | ||||
| @@ -148,8 +146,6 @@ bool OnTimeTrigger::matches_(const ESPTime &time) const { | ||||
|          time.second == this->parent_->second; | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| }  // namespace datetime | ||||
| }  // namespace esphome | ||||
|  | ||||
|   | ||||
| @@ -10,10 +10,6 @@ | ||||
|  | ||||
| #include "datetime_base.h" | ||||
|  | ||||
| #ifdef USE_TIME | ||||
| #include "esphome/components/time/real_time_clock.h" | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace datetime { | ||||
|  | ||||
| @@ -27,6 +23,7 @@ namespace datetime { | ||||
|  | ||||
| class TimeCall; | ||||
| class TimeEntity; | ||||
| class OnTimeTrigger; | ||||
|  | ||||
| struct TimeEntityRestoreState { | ||||
|   uint8_t hour; | ||||
| @@ -62,6 +59,7 @@ class TimeEntity : public DateTimeBase { | ||||
|  protected: | ||||
|   friend class TimeCall; | ||||
|   friend struct TimeEntityRestoreState; | ||||
|   friend class OnTimeTrigger; | ||||
|  | ||||
|   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> { | ||||
|  public: | ||||
|   explicit OnTimeTrigger(time::RealTimeClock *rtc) : rtc_(rtc) {} | ||||
|   void loop() override; | ||||
|  | ||||
|  protected: | ||||
|   bool matches_(const ESPTime &time) const; | ||||
|  | ||||
|   time::RealTimeClock *rtc_; | ||||
|   optional<ESPTime> last_check_; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|  | ||||
| }  // namespace datetime | ||||
| }  // namespace esphome | ||||
|  | ||||
|   | ||||
| @@ -129,7 +129,13 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo | ||||
|  | ||||
|     uint8_t bitmask = 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_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) { | ||||
|         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) { | ||||
|           display->draw_pixel_at(glyph_x, glyph_y, color); | ||||
|         } else if (pixel != 0) { | ||||
|           float on = (float) pixel / bpp_max; | ||||
|           float off = 1.0 - on; | ||||
|           Color blended; | ||||
|           blended.r = color.r * on + background.r * off; | ||||
|           blended.g = color.r * on + background.g * off; | ||||
|           blended.b = color.r * on + background.b * off; | ||||
|           auto on = (float) pixel / (float) bpp_max; | ||||
|           auto blended = | ||||
|               Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g), (uint8_t) (diff_b * on + b_b)); | ||||
|           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); | ||||
|   for (auto *trace : traces_) { | ||||
|     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 has_prev = false; | ||||
|     bool prev_b = false; | ||||
| @@ -178,20 +178,20 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo | ||||
|         if (b) { | ||||
|           int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset; | ||||
|           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); | ||||
|             } | ||||
|           } else { | ||||
|             int16_t mid_y = (y + prev_y + thick) / 2; | ||||
|             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); | ||||
|               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); | ||||
|             } 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); | ||||
|               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); | ||||
|             } | ||||
|           } | ||||
|   | ||||
| @@ -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) { | ||||
|   int total_height = 0; | ||||
|   int16_t total_height = 0; | ||||
|   int16_t max_width = 0; | ||||
|   int y_padding = 2; | ||||
|   bool scroll_menu_items = false; | ||||
|   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); | ||||
|     total_height += item_dimensions.h + (i == 0 ? 0 : y_padding); | ||||
|     max_width = std::max(max_width, item_dimensions.w); | ||||
|  | ||||
|     if (total_height <= bounds->h) { | ||||
|       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 | ||||
|   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++) { | ||||
|     const auto *item = this->displayed_item_->get_item(i); | ||||
|     const bool selected = i == this->cursor_index_; | ||||
| @@ -176,7 +179,7 @@ void GraphicalDisplayMenu::draw_menu_internal_(display::Display *display, const | ||||
|     dimensions.x = bounds->x; | ||||
|     this->draw_item(display, item, &dimensions, selected); | ||||
|  | ||||
|     y_offset = dimensions.y + dimensions.h + y_padding; | ||||
|     y_offset += dimensions.h + y_padding; | ||||
|   } | ||||
|  | ||||
|   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 = 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(); | ||||
|   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(), | ||||
|                  ~foreground_color); | ||||
|                  background_color); | ||||
| } | ||||
|  | ||||
| void GraphicalDisplayMenu::draw_item(const display_menu_base::MenuItem *item, const uint8_t row, const bool selected) { | ||||
|   | ||||
| @@ -115,6 +115,7 @@ MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) | ||||
| MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) | ||||
| MQTTDateComponent = mqtt_ns.class_("MQTTDateComponent", MQTTComponent) | ||||
| MQTTTimeComponent = mqtt_ns.class_("MQTTTimeComponent", MQTTComponent) | ||||
| MQTTDateTimeComponent = mqtt_ns.class_("MQTTDateTimeComponent", MQTTComponent) | ||||
| MQTTTextComponent = mqtt_ns.class_("MQTTTextComponent", MQTTComponent) | ||||
| MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", 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 | ||||
| ) | ||||
|  | ||||
| TemplateDateTime = template_ns.class_( | ||||
|     "TemplateDateTime", datetime.DateTimeEntity, cg.PollingComponent | ||||
| ) | ||||
|  | ||||
|  | ||||
| def validate(config): | ||||
|     config = config.copy() | ||||
| @@ -78,6 +82,13 @@ CONFIG_SCHEMA = cv.All( | ||||
|                     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, | ||||
|     ), | ||||
| @@ -116,6 +127,17 @@ async def to_code(config): | ||||
|                     ("hour", initial_value[CONF_HOUR]), | ||||
|                 ) | ||||
|                 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: | ||||
|         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 | ||||
| #include <cerrno> | ||||
|  | ||||
| #include <cinttypes> | ||||
|  | ||||
| namespace esphome { | ||||
| 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_manual_control(bool value) { this->manual_control_ = value; } | ||||
|   void set_assumed_state(bool value) { this->assumed_state_ = value; } | ||||
|   cover::CoverOperation get_last_operation() const { return this->last_operation_; } | ||||
|  | ||||
|  protected: | ||||
|   void control(const cover::CoverCall &call) override; | ||||
|   | ||||
| @@ -129,6 +129,15 @@ bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { | ||||
| } | ||||
| #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 | ||||
| bool ListEntitiesIterator::on_text(text::Text *text) { | ||||
|   if (this->web_server_->events_.count() == 0) | ||||
|   | ||||
| @@ -47,6 +47,9 @@ class ListEntitiesIterator : public ComponentIterator { | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool on_time(datetime::TimeEntity *time) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool on_datetime(datetime::DateTimeEntity *datetime) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   bool on_text(text::Text *text) override; | ||||
| #endif | ||||
|   | ||||
| @@ -926,6 +926,8 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con | ||||
|  | ||||
| #ifdef USE_DATETIME_TIME | ||||
| 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"); | ||||
| } | ||||
| 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 | ||||
|  | ||||
| #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 | ||||
| void WebServer::on_text_update(text::Text *obj, const std::string &state) { | ||||
|   if (this->events_.count() == 0) | ||||
| @@ -1458,6 +1509,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { | ||||
|     return true; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "datetime") | ||||
|     return true; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_TEXT | ||||
|   if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "text") | ||||
|     return true; | ||||
| @@ -1595,6 +1651,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { | ||||
|   } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   if (match.domain == "datetime") { | ||||
|     this->handle_datetime_request(request, match); | ||||
|     return; | ||||
|   } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_TEXT | ||||
|   if (match.domain == "text") { | ||||
|     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); | ||||
| #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 | ||||
|   void on_text_update(text::Text *obj, const std::string &state) override; | ||||
|   /// Handle a text input request under '/text/<id>'. | ||||
|   | ||||
| @@ -181,6 +181,7 @@ CONF_DATA_PINS = "data_pins" | ||||
| CONF_DATA_RATE = "data_rate" | ||||
| CONF_DATA_TEMPLATE = "data_template" | ||||
| CONF_DATE = "date" | ||||
| CONF_DATETIME = "datetime" | ||||
| CONF_DAY = "day" | ||||
| CONF_DAYS_OF_MONTH = "days_of_month" | ||||
| CONF_DAYS_OF_WEEK = "days_of_week" | ||||
|   | ||||
| @@ -45,6 +45,9 @@ | ||||
| #ifdef USE_DATETIME_TIME | ||||
| #include "esphome/components/datetime/time_entity.h" | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| #include "esphome/components/datetime/datetime_entity.h" | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
| #include "esphome/components/text/text.h" | ||||
| #endif | ||||
| @@ -141,6 +144,10 @@ class Application { | ||||
|   void register_time(datetime::TimeEntity *time) { this->times_.push_back(time); } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   void register_datetime(datetime::DateTimeEntity *datetime) { this->datetimes_.push_back(datetime); } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_TEXT | ||||
|   void register_text(text::Text *text) { this->texts_.push_back(text); } | ||||
| #endif | ||||
| @@ -335,6 +342,15 @@ class Application { | ||||
|     return nullptr; | ||||
|   } | ||||
| #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 | ||||
|   const std::vector<text::Text *> &get_texts() { return this->texts_; } | ||||
|   text::Text *get_text_by_key(uint32_t key, bool include_internal = false) { | ||||
| @@ -456,6 +472,9 @@ class Application { | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   std::vector<datetime::TimeEntity *> times_{}; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   std::vector<datetime::DateTimeEntity *> datetimes_{}; | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
|   std::vector<select::Select *> selects_{}; | ||||
| #endif | ||||
|   | ||||
| @@ -232,6 +232,21 @@ void ComponentIterator::advance() { | ||||
|       } | ||||
|       break; | ||||
| #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 | ||||
|     case IteratorState::TEXT: | ||||
|       if (this->at_ >= App.get_texts().size()) { | ||||
|   | ||||
| @@ -63,6 +63,9 @@ class ComponentIterator { | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   virtual bool on_time(datetime::TimeEntity *time) = 0; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   virtual bool on_datetime(datetime::DateTimeEntity *datetime) = 0; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   virtual bool on_text(text::Text *text) = 0; | ||||
| #endif | ||||
| @@ -132,6 +135,9 @@ class ComponentIterator { | ||||
| #ifdef USE_DATETIME_TIME | ||||
|     DATETIME_TIME, | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|     DATETIME_DATETIME, | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|     TEXT, | ||||
| #endif | ||||
|   | ||||
| @@ -71,6 +71,12 @@ void Controller::setup_controller(bool include_internal) { | ||||
|       obj->add_on_state_callback([this, obj]() { this->on_time_update(obj); }); | ||||
|   } | ||||
| #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 | ||||
|   for (auto *obj : App.get_texts()) { | ||||
|     if (include_internal || !obj->is_internal()) | ||||
|   | ||||
| @@ -37,6 +37,9 @@ | ||||
| #ifdef USE_DATETIME_TIME | ||||
| #include "esphome/components/datetime/time_entity.h" | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| #include "esphome/components/datetime/datetime_entity.h" | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
| #include "esphome/components/text/text.h" | ||||
| #endif | ||||
| @@ -97,6 +100,9 @@ class Controller { | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   virtual void on_time_update(datetime::TimeEntity *obj){}; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   virtual void on_datetime_update(datetime::DateTimeEntity *obj){}; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   virtual void on_text_update(text::Text *obj, const std::string &state){}; | ||||
| #endif | ||||
|   | ||||
| @@ -39,6 +39,7 @@ | ||||
| #define USE_DATETIME | ||||
| #define USE_DATETIME_DATE | ||||
| #define USE_DATETIME_TIME | ||||
| #define USE_DATETIME_DATETIME | ||||
| #define USE_OTA | ||||
| #define USE_OTA_PASSWORD | ||||
| #define USE_OTA_STATE_CALLBACK | ||||
|   | ||||
| @@ -178,6 +178,15 @@ void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { | ||||
|   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 offset = 0; | ||||
|   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). | ||||
|   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. | ||||
|   struct tm to_c_tm(); | ||||
|  | ||||
|   | ||||
| @@ -624,6 +624,7 @@ def lint_trailing_whitespace(fname, match): | ||||
|         "esphome/components/cover/cover.h", | ||||
|         "esphome/components/datetime/date_entity.h", | ||||
|         "esphome/components/datetime/time_entity.h", | ||||
|         "esphome/components/datetime/datetime_entity.h", | ||||
|         "esphome/components/display/display.h", | ||||
|         "esphome/components/event/event.h", | ||||
|         "esphome/components/fan/fan.h", | ||||
|   | ||||
| @@ -6,9 +6,12 @@ import re | ||||
| from homeassistant.components.binary_sensor import BinarySensorDeviceClass | ||||
| from homeassistant.components.button import ButtonDeviceClass | ||||
| from homeassistant.components.cover import CoverDeviceClass | ||||
| from homeassistant.components.event import EventDeviceClass | ||||
| from homeassistant.components.number import NumberDeviceClass | ||||
| from homeassistant.components.sensor import SensorDeviceClass | ||||
| from homeassistant.components.switch import SwitchDeviceClass | ||||
| from homeassistant.components.text_sensor import TextSensorDeviceClass | ||||
| from homeassistant.components.valve import ValveDeviceClass | ||||
|  | ||||
| # pylint: enable=import-error | ||||
|  | ||||
| @@ -21,9 +24,12 @@ DOMAINS = { | ||||
|     "binary_sensor": BinarySensorDeviceClass, | ||||
|     "button": ButtonDeviceClass, | ||||
|     "cover": CoverDeviceClass, | ||||
|     "event": EventDeviceClass, | ||||
|     "number": NumberDeviceClass, | ||||
|     "sensor": SensorDeviceClass, | ||||
|     "switch": SwitchDeviceClass, | ||||
|     "text_sensor": TextSensorDeviceClass, | ||||
|     "valve": ValveDeviceClass, | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1 +1,3 @@ | ||||
| datetime: | ||||
|  | ||||
| time: | ||||
|   | ||||
| @@ -183,3 +183,25 @@ datetime: | ||||
|             - x.hour | ||||
|             - x.minute | ||||
|             - 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 | ||||
		Reference in New Issue
	
	Block a user