diff --git a/components/esp_matter/esp_matter_attribute.cpp b/components/esp_matter/esp_matter_attribute.cpp index aec6b098f..49ba10f37 100644 --- a/components/esp_matter/esp_matter_attribute.cpp +++ b/components/esp_matter/esp_matter_attribute.cpp @@ -5028,5 +5028,94 @@ attribute_t *create_location_directory(cluster_t *cluster, uint8_t *value, uint1 } /* attribute */ } /* ecosystem_information */ +namespace time_synchronization { +namespace attribute { +attribute_t *create_utc_time(cluster_t *cluster, nullable value) +{ + return esp_matter::attribute::create(cluster, TimeSynchronization::Attributes::UTCTime::Id, + ATTRIBUTE_FLAG_NULLABLE | ATTRIBUTE_FLAG_MANAGED_INTERNALLY, + esp_matter_nullable_uint64(value)); +} + +attribute_t *create_granularity(cluster_t *cluster, uint8_t value) +{ + return esp_matter::attribute::create(cluster, TimeSynchronization::Attributes::Granularity::Id, + ATTRIBUTE_FLAG_MANAGED_INTERNALLY, esp_matter_enum8(value)); +} + +attribute_t *create_time_source(cluster_t *cluster, uint8_t value) +{ + return esp_matter::attribute::create(cluster, TimeSynchronization::Attributes::TimeSource::Id, + ATTRIBUTE_FLAG_NONE, esp_matter_enum8(value)); +} + +attribute_t *create_trusted_time_source(cluster_t *cluster, uint8_t *value, uint16_t length, uint16_t count) +{ + return esp_matter::attribute::create(cluster, TimeSynchronization::Attributes::TrustedTimeSource::Id, + ATTRIBUTE_FLAG_MANAGED_INTERNALLY | ATTRIBUTE_FLAG_NULLABLE | ATTRIBUTE_FLAG_NONVOLATILE, + esp_matter_array(value, length, count)); +} + +attribute_t *create_default_ntp(cluster_t *cluster, char *value, uint16_t length) +{ + return esp_matter::attribute::create(cluster, TimeSynchronization::Attributes::DefaultNTP::Id, + ATTRIBUTE_FLAG_MANAGED_INTERNALLY | ATTRIBUTE_FLAG_NULLABLE | ATTRIBUTE_FLAG_NONVOLATILE, + esp_matter_char_str(value, length)); +} + +attribute_t *create_time_zone(cluster_t *cluster, uint8_t *value, uint16_t length, uint16_t count) +{ + return esp_matter::attribute::create(cluster, TimeSynchronization::Attributes::TimeZone::Id, + ATTRIBUTE_FLAG_MANAGED_INTERNALLY | ATTRIBUTE_FLAG_NONVOLATILE, + esp_matter_array(value, length, count)); +} + +attribute_t *create_dst_offset(cluster_t *cluster, uint8_t *value, uint16_t length, uint16_t count) +{ + return esp_matter::attribute::create(cluster, TimeSynchronization::Attributes::DSTOffset::Id, + ATTRIBUTE_FLAG_MANAGED_INTERNALLY | ATTRIBUTE_FLAG_NONVOLATILE, + esp_matter_array(value, length, count)); +} + +attribute_t *create_local_time(cluster_t *cluster, nullable value) +{ + return esp_matter::attribute::create(cluster, TimeSynchronization::Attributes::LocalTime::Id, + ATTRIBUTE_FLAG_MANAGED_INTERNALLY | ATTRIBUTE_FLAG_NULLABLE, + esp_matter_nullable_uint64(value)); +} + +attribute_t *create_time_zone_database(cluster_t *cluster, uint8_t value) +{ + return esp_matter::attribute::create(cluster, TimeSynchronization::Attributes::TimeZoneDatabase::Id, + ATTRIBUTE_FLAG_NONE, esp_matter_enum8(value)); +} + +attribute_t *create_ntp_server_available(cluster_t *cluster, bool value) +{ + return esp_matter::attribute::create(cluster, TimeSynchronization::Attributes::NTPServerAvailable::Id, + ATTRIBUTE_FLAG_NONE, esp_matter_bool(value)); +} + +attribute_t *create_time_zone_list_max_size(cluster_t *cluster, uint8_t value) +{ + return esp_matter::attribute::create(cluster, TimeSynchronization::Attributes::TimeZoneListMaxSize::Id, + ATTRIBUTE_FLAG_MANAGED_INTERNALLY, esp_matter_uint8(value)); +} + +attribute_t *create_dst_offset_list_max_size(cluster_t *cluster, uint8_t value) +{ + return esp_matter::attribute::create(cluster, TimeSynchronization::Attributes::DSTOffsetListMaxSize::Id, + ATTRIBUTE_FLAG_MANAGED_INTERNALLY, esp_matter_uint8(value)); +} + +attribute_t *create_supports_dns_resolve(cluster_t *cluster, bool value) +{ + return esp_matter::attribute::create(cluster, TimeSynchronization::Attributes::SupportsDNSResolve::Id, + ATTRIBUTE_FLAG_NONE, esp_matter_uint64(value)); +} + +} /* attribute */ +} /* time_synchronization */ + } /* cluster */ } /* esp_matter */ diff --git a/components/esp_matter/esp_matter_attribute.h b/components/esp_matter/esp_matter_attribute.h index 3422eb7a6..5a8e497c7 100644 --- a/components/esp_matter/esp_matter_attribute.h +++ b/components/esp_matter/esp_matter_attribute.h @@ -1245,5 +1245,23 @@ attribute_t *create_location_directory(cluster_t *cluster, uint8_t *value, uint1 } /* attribute */ } /* ecosystem_information */ +namespace time_synchronization { +namespace attribute { +attribute_t *create_utc_time(cluster_t *cluster, nullable value); +attribute_t *create_granularity(cluster_t *cluster, uint8_t value); +attribute_t *create_time_source(cluster_t *cluster, uint8_t value); +attribute_t *create_trusted_time_source(cluster_t *cluster, uint8_t *value, uint16_t length, uint16_t count); +attribute_t *create_default_ntp(cluster_t *cluster, char *value, uint16_t length); +attribute_t *create_time_zone(cluster_t *cluster, uint8_t *value, uint16_t length, uint16_t count); +attribute_t *create_dst_offset(cluster_t *cluster, uint8_t *value, uint16_t length, uint16_t count); +attribute_t *create_local_time(cluster_t *cluster, nullable value); +attribute_t *create_time_zone_database(cluster_t *cluster, uint8_t value); +attribute_t *create_ntp_server_available(cluster_t *cluster, bool value); +attribute_t *create_time_zone_list_max_size(cluster_t *cluster, uint8_t value); +attribute_t *create_dst_offset_list_max_size(cluster_t *cluster, uint8_t value); +attribute_t *create_supports_dns_resolve(cluster_t *cluster, bool value); +} /* attribute */ +} /* time_synchronization */ + } /* cluster */ } /* esp_matter */ diff --git a/components/esp_matter/esp_matter_cluster.cpp b/components/esp_matter/esp_matter_cluster.cpp index 01751030f..a79f7262d 100644 --- a/components/esp_matter/esp_matter_cluster.cpp +++ b/components/esp_matter/esp_matter_cluster.cpp @@ -679,9 +679,14 @@ cluster_t *create(endpoint_t *endpoint, config_t *config, uint8_t flags) /* Attributes managed internally */ global::attribute::create_feature_map(cluster, 0); + attribute::create_utc_time(cluster, nullable()); + attribute::create_granularity(cluster, 0); /* Attributes not managed internally */ global::attribute::create_cluster_revision(cluster, cluster_revision); + + /* Commands */ + command::create_set_utc_time(cluster); } event::create_time_failure(cluster); diff --git a/components/esp_matter/esp_matter_command.cpp b/components/esp_matter/esp_matter_command.cpp index 51707f800..c17beaa91 100644 --- a/components/esp_matter/esp_matter_command.cpp +++ b/components/esp_matter/esp_matter_command.cpp @@ -1555,6 +1555,62 @@ static esp_err_t esp_matter_command_callback_close(const ConcreteCommandPath &co return ESP_OK; } +static esp_err_t esp_matter_command_callback_set_utc_time(const ConcreteCommandPath &command_path, TLVReader &tlv_data, + void *opaque_ptr) +{ + chip::app::Clusters::TimeSynchronization::Commands::SetUTCTime::DecodableType command_data; + CHIP_ERROR error = Decode(tlv_data, command_data); + if (error == CHIP_NO_ERROR) { + emberAfTimeSynchronizationClusterSetUTCTimeCallback((CommandHandler *)opaque_ptr, command_path, command_data); + } + return ESP_OK; +} + +static esp_err_t esp_matter_command_callback_set_trusted_time_source(const ConcreteCommandPath &command_path, + TLVReader &tlv_data, void *opaque_ptr) +{ + chip::app::Clusters::TimeSynchronization::Commands::SetTrustedTimeSource::DecodableType command_data; + CHIP_ERROR error = Decode(tlv_data, command_data); + if (error == CHIP_NO_ERROR) { + emberAfTimeSynchronizationClusterSetTrustedTimeSourceCallback((CommandHandler *)opaque_ptr, command_path, + command_data); + } + return ESP_OK; +} + +static esp_err_t esp_matter_command_callback_set_time_zone(const ConcreteCommandPath &command_path, TLVReader &tlv_data, + void *opaque_ptr) +{ + chip::app::Clusters::TimeSynchronization::Commands::SetTimeZone::DecodableType command_data; + CHIP_ERROR error = Decode(tlv_data, command_data); + if (error == CHIP_NO_ERROR) { + emberAfTimeSynchronizationClusterSetTimeZoneCallback((CommandHandler *)opaque_ptr, command_path, command_data); + } + return ESP_OK; +} + +static esp_err_t esp_matter_command_callback_set_dst_offset(const ConcreteCommandPath &command_path, TLVReader &tlv_data, + void *opaque_ptr) +{ + chip::app::Clusters::TimeSynchronization::Commands::SetDSTOffset::DecodableType command_data; + CHIP_ERROR error = Decode(tlv_data, command_data); + if (error == CHIP_NO_ERROR) { + emberAfTimeSynchronizationClusterSetDSTOffsetCallback((CommandHandler *)opaque_ptr, command_path, command_data); + } + return ESP_OK; +} + +static esp_err_t esp_matter_command_callback_set_default_ntp(const ConcreteCommandPath &command_path, TLVReader &tlv_data, + void *opaque_ptr) +{ + chip::app::Clusters::TimeSynchronization::Commands::SetDefaultNTP::DecodableType command_data; + CHIP_ERROR error = Decode(tlv_data, command_data); + if (error == CHIP_NO_ERROR) { + emberAfTimeSynchronizationClusterSetDefaultNTPCallback((CommandHandler *)opaque_ptr, command_path, command_data); + } + return ESP_OK; +} + namespace esp_matter { namespace cluster { @@ -3451,6 +3507,53 @@ command_t *create_reverse_open_commissioning_window(cluster_t *cluster) } /* command */ } /* commissioner_control */ +namespace time_synchronization { +namespace command { +constexpr const command_entry_t accepted_command_list[] = { + {TimeSynchronization::Commands::SetUTCTime::Id, COMMAND_FLAG_ACCEPTED, esp_matter_command_callback_set_utc_time}, +}; + +constexpr const command_entry_t generated_command_list[] = {}; + +command_t *create_set_utc_time(cluster_t *cluster) +{ + return esp_matter::command::create(cluster, TimeSynchronization::Commands::SetUTCTime::Id, COMMAND_FLAG_ACCEPTED, + esp_matter_command_callback_set_utc_time); +} + +command_t *create_set_trusted_time_source(cluster_t *cluster) +{ + return esp_matter::command::create(cluster, TimeSynchronization::Commands::SetTrustedTimeSource::Id, + COMMAND_FLAG_ACCEPTED, esp_matter_command_callback_set_trusted_time_source); +} + +command_t *create_set_time_zone(cluster_t *cluster) +{ + return esp_matter::command::create(cluster, TimeSynchronization::Commands::SetTimeZone::Id, COMMAND_FLAG_ACCEPTED, + esp_matter_command_callback_set_time_zone); +} + +command_t *create_set_time_zone_response(cluster_t *cluster) +{ + return esp_matter::command::create(cluster, TimeSynchronization::Commands::SetTimeZoneResponse::Id, + COMMAND_FLAG_GENERATED, nullptr); +} + +command_t *create_set_dst_offset(cluster_t *cluster) +{ + return esp_matter::command::create(cluster, TimeSynchronization::Commands::SetDSTOffset::Id, + COMMAND_FLAG_ACCEPTED, esp_matter_command_callback_set_dst_offset); +} + +command_t *create_set_default_ntp(cluster_t *cluster) +{ + return esp_matter::command::create(cluster, TimeSynchronization::Commands::SetDefaultNTP::Id, + COMMAND_FLAG_ACCEPTED, esp_matter_command_callback_set_default_ntp); +} + +} /* command */ +} /* time_synchronization */ + } /* cluster */ } /* esp_matter */ @@ -3499,6 +3602,7 @@ constexpr const cluster_command_t cluster_command_table[] = { {ThreadNetworkDirectory::Id, GET_COMMAND_COUNT_LIST(cluster::thread_network_directory)}, {WaterHeaterManagement::Id, GET_COMMAND_COUNT_LIST(cluster::water_heater_management)}, {CommissionerControl::Id, GET_COMMAND_COUNT_LIST(cluster::commissioner_control)}, + {TimeSynchronization::Id, GET_COMMAND_COUNT_LIST(cluster::time_synchronization)}, }; #if defined(CONFIG_ESP_MATTER_ENABLE_MATTER_SERVER) && defined(CONFIG_ESP_MATTER_ENABLE_DATA_MODEL) diff --git a/components/esp_matter/esp_matter_command.h b/components/esp_matter/esp_matter_command.h index 464dacbe6..036c3d8d3 100644 --- a/components/esp_matter/esp_matter_command.h +++ b/components/esp_matter/esp_matter_command.h @@ -503,5 +503,16 @@ command_t *create_reverse_open_commissioning_window(cluster_t *cluster); } /* command */ } /* commissioner_control */ +namespace time_synchronization { +namespace command { +command_t *create_set_utc_time(cluster_t *cluster); +command_t *create_set_trusted_time_source(cluster_t *cluster); +command_t *create_set_time_zone(cluster_t *cluster); +command_t *create_set_time_zone_response(cluster_t *cluster); +command_t *create_set_dst_offset(cluster_t *cluster); +command_t *create_set_default_ntp(cluster_t *cluster); +} /* command */ +} /* time_synchronization */ + } /* cluster */ } /* esp_matter */ diff --git a/components/esp_matter/esp_matter_feature.cpp b/components/esp_matter/esp_matter_feature.cpp index 98e81ec17..b6574995d 100644 --- a/components/esp_matter/esp_matter_feature.cpp +++ b/components/esp_matter/esp_matter_feature.cpp @@ -4941,5 +4941,123 @@ esp_err_t add(cluster_t *cluster) } /* feature */ } /* pump_configuration_and_control */ + +namespace time_synchronization { +namespace feature { + +namespace time_zone { +uint32_t get_id() +{ + return static_cast(TimeSynchronization::Feature::kTimeZone); +} + +esp_err_t add(cluster_t *cluster, config_t *config) +{ + if (!cluster || !config) { + ESP_LOGE(TAG, "Cluster or config cannot be NULL"); + return ESP_ERR_INVALID_ARG; + } + update_feature_map(cluster, get_id()); + + // Attributes managed internally + attribute::create_time_zone(cluster, nullptr, 0, 0); + attribute::create_dst_offset(cluster, nullptr, 0, 0); + attribute::create_local_time(cluster, nullable()); + attribute::create_time_zone_list_max_size(cluster, 0); + attribute::create_dst_offset_list_max_size(cluster, 0); + + // Attributes not managed internally + attribute::create_time_zone_database(cluster, config->time_zone_database); + + // Commands + command::create_set_time_zone(cluster); + command::create_set_dst_offset(cluster); + command::create_set_time_zone_response(cluster); + + // Events + event::create_dst_table_empty(cluster); + event::create_dst_status(cluster); + event::create_time_zone_status(cluster); + + return ESP_OK; +} +} /* time_zone */ + +namespace ntp_client { +uint32_t get_id() +{ + return static_cast(TimeSynchronization::Feature::kNTPClient); +} + +esp_err_t add(cluster_t *cluster, config_t *config) +{ + if (!cluster || !config) { + ESP_LOGE(TAG, "Cluster or config cannot be NULL"); + return ESP_ERR_INVALID_ARG; + } + update_feature_map(cluster, get_id()); + + // Attributes managed internally + attribute::create_default_ntp(cluster, nullptr, 0); + + // Attributes not managed internally + attribute::create_supports_dns_resolve(cluster, config->supports_dns_resolve); + + // Commands + command::create_set_default_ntp(cluster); + + return ESP_OK; +} +} /* ntp_client */ + +namespace ntp_server { + +uint32_t get_id() +{ + return static_cast(TimeSynchronization::Feature::kNTPServer); +} + +esp_err_t add(cluster_t *cluster, config_t *config) +{ + if (!cluster || !config) { + ESP_LOGE(TAG, "Cluster or config cannot be NULL"); + return ESP_ERR_INVALID_ARG; + } + update_feature_map(cluster, get_id()); + + // Attributes not managed internally + attribute::create_ntp_server_available(cluster, config->ntp_server_available); + + return ESP_OK; +} +} /* ntp_server */ + +namespace time_sync_client { +uint32_t get_id() +{ + return static_cast(TimeSynchronization::Feature::kTimeSyncClient); +} + +esp_err_t add(cluster_t *cluster) +{ + if (!cluster) { + ESP_LOGE(TAG, "Cluster cannot be NULL"); + return ESP_ERR_INVALID_ARG; + } + update_feature_map(cluster, get_id()); + + // Attributes managed internally + attribute::create_trusted_time_source(cluster, nullptr, 0, 0); + + // Commands + command::create_set_trusted_time_source(cluster); + + return ESP_OK; +} +} /* time_sync_client */ + +} /* feature */ +} /* time_synchronization */ + } /* cluster */ } /* esp_matter */ diff --git a/components/esp_matter/esp_matter_feature.h b/components/esp_matter/esp_matter_feature.h index 5094b480e..4aa397b89 100644 --- a/components/esp_matter/esp_matter_feature.h +++ b/components/esp_matter/esp_matter_feature.h @@ -2395,5 +2395,46 @@ esp_err_t add(cluster_t *cluster); } /* feature */ } /* pump_configuration_and_control */ +namespace time_synchronization { +namespace feature { + +namespace time_zone { +typedef struct config { + uint8_t time_zone_database; + config() : time_zone_database(2/* None */) {} +} config_t; + +uint32_t get_id(); +esp_err_t add(cluster_t *cluster, config_t *config); +} /* time_zone */ + +namespace ntp_client { +typedef struct config { + bool supports_dns_resolve; + config() : supports_dns_resolve(false) {} +} config_t; + +uint32_t get_id(); +esp_err_t add(cluster_t *cluster, config_t *config); +} /* ntp_client */ + +namespace ntp_server { +typedef struct config { + bool ntp_server_available; + config() : ntp_server_available(false) {} +} config_t; + +uint32_t get_id(); +esp_err_t add(cluster_t *cluster, config_t *config); +} /* ntp_server */ + +namespace time_sync_client { +uint32_t get_id(); +esp_err_t add(cluster_t *cluster); +} /* time_sync_client */ + +} /* feature */ +} /* time_synchronization */ + } /* cluster */ } /* esp_matter */ diff --git a/examples/light_switch/main/app_main.cpp b/examples/light_switch/main/app_main.cpp index 377973ea3..39324d317 100644 --- a/examples/light_switch/main/app_main.cpp +++ b/examples/light_switch/main/app_main.cpp @@ -24,6 +24,9 @@ #if CONFIG_DYNAMIC_PASSCODE_COMMISSIONABLE_DATA_PROVIDER #include #endif +#if CONFIG_ENABLE_SNTP_TIME_SYNC +#include +#endif static const char *TAG = "app_main"; uint16_t switch_endpoint_id = 0; @@ -109,6 +112,19 @@ extern "C" void app_main() node::config_t node_config; node_t *node = node::create(&node_config, app_attribute_update_cb, app_identification_cb); ABORT_APP_ON_FAILURE(node != nullptr, ESP_LOGE(TAG, "Failed to create Matter node")); +#ifdef CONFIG_ENABLE_SNTP_TIME_SYNC + endpoint_t *root_node_ep = endpoint::get_first(node); + ABORT_APP_ON_FAILURE(root_node_ep != nullptr, ESP_LOGE(TAG, "Failed to find root node endpoint")); + + cluster::time_synchronization::config_t time_sync_cfg; + static chip::app::Clusters::TimeSynchronization::DefaultTimeSyncDelegate time_sync_delegate; + time_sync_cfg.delegate = &time_sync_delegate; + cluster_t *time_sync_cluster = cluster::time_synchronization::create(root_node_ep, &time_sync_cfg, CLUSTER_FLAG_SERVER); + ABORT_APP_ON_FAILURE(time_sync_cluster != nullptr, ESP_LOGE(TAG, "Failed to create time_sync_cluster")); + + cluster::time_synchronization::feature::time_zone::config_t tz_cfg; + cluster::time_synchronization::feature::time_zone::add(time_sync_cluster, &tz_cfg); +#endif on_off_switch::config_t switch_config; endpoint_t *endpoint = on_off_switch::create(node, &switch_config, ENDPOINT_FLAG_NONE, switch_handle);