mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Make string globals persist-able using fixed size allocations (#5296)
Co-authored-by: Daniel Dunn <dannydunn@eternityforest.com>
This commit is contained in:
		| @@ -15,8 +15,14 @@ CODEOWNERS = ["@esphome/core"] | |||||||
| globals_ns = cg.esphome_ns.namespace("globals") | globals_ns = cg.esphome_ns.namespace("globals") | ||||||
| GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component) | GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component) | ||||||
| RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component) | RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component) | ||||||
|  | RestoringGlobalStringComponent = globals_ns.class_( | ||||||
|  |     "RestoringGlobalStringComponent", cg.Component | ||||||
|  | ) | ||||||
| GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action) | GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action) | ||||||
|  |  | ||||||
|  | CONF_MAX_RESTORE_DATA_LENGTH = "max_restore_data_length" | ||||||
|  |  | ||||||
|  |  | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
| CONFIG_SCHEMA = cv.Schema( | CONFIG_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
| @@ -24,6 +30,7 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|         cv.Required(CONF_TYPE): cv.string_strict, |         cv.Required(CONF_TYPE): cv.string_strict, | ||||||
|         cv.Optional(CONF_INITIAL_VALUE): cv.string_strict, |         cv.Optional(CONF_INITIAL_VALUE): cv.string_strict, | ||||||
|         cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, |         cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, | ||||||
|  |         cv.Optional(CONF_MAX_RESTORE_DATA_LENGTH): cv.int_range(0, 254), | ||||||
|     } |     } | ||||||
| ).extend(cv.COMPONENT_SCHEMA) | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
| @@ -32,12 +39,19 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
| @coroutine_with_priority(-100.0) | @coroutine_with_priority(-100.0) | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     type_ = cg.RawExpression(config[CONF_TYPE]) |     type_ = cg.RawExpression(config[CONF_TYPE]) | ||||||
|     template_args = cg.TemplateArguments(type_) |  | ||||||
|     restore = config[CONF_RESTORE_VALUE] |     restore = config[CONF_RESTORE_VALUE] | ||||||
|  |  | ||||||
|  |     # Special casing the strings to their own class with a different save/restore mechanism | ||||||
|  |     if str(type_) == "std::string" and restore: | ||||||
|  |         template_args = cg.TemplateArguments( | ||||||
|  |             type_, config.get(CONF_MAX_RESTORE_DATA_LENGTH, 63) + 1 | ||||||
|  |         ) | ||||||
|  |         type = RestoringGlobalStringComponent | ||||||
|  |     else: | ||||||
|  |         template_args = cg.TemplateArguments(type_) | ||||||
|         type = RestoringGlobalsComponent if restore else GlobalsComponent |         type = RestoringGlobalsComponent if restore else GlobalsComponent | ||||||
|     res_type = type.template(template_args) |  | ||||||
|  |  | ||||||
|  |     res_type = type.template(template_args) | ||||||
|     initial_value = None |     initial_value = None | ||||||
|     if CONF_INITIAL_VALUE in config: |     if CONF_INITIAL_VALUE in config: | ||||||
|         initial_value = cg.RawExpression(config[CONF_INITIAL_VALUE]) |         initial_value = cg.RawExpression(config[CONF_INITIAL_VALUE]) | ||||||
|   | |||||||
| @@ -65,6 +65,64 @@ template<typename T> class RestoringGlobalsComponent : public Component { | |||||||
|   ESPPreferenceObject rtc_; |   ESPPreferenceObject rtc_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | // Use with string or subclasses of strings | ||||||
|  | template<typename T, uint8_t SZ> class RestoringGlobalStringComponent : public Component { | ||||||
|  |  public: | ||||||
|  |   using value_type = T; | ||||||
|  |   explicit RestoringGlobalStringComponent() = default; | ||||||
|  |   explicit RestoringGlobalStringComponent(T initial_value) { this->value_ = initial_value; } | ||||||
|  |   explicit RestoringGlobalStringComponent( | ||||||
|  |       std::array<typename std::remove_extent<T>::type, std::extent<T>::value> initial_value) { | ||||||
|  |     memcpy(this->value_, initial_value.data(), sizeof(T)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   T &value() { return this->value_; } | ||||||
|  |  | ||||||
|  |   void setup() override { | ||||||
|  |     char temp[SZ]; | ||||||
|  |     this->rtc_ = global_preferences->make_preference<uint8_t[SZ]>(1944399030U ^ this->name_hash_); | ||||||
|  |     bool hasdata = this->rtc_.load(&temp); | ||||||
|  |     if (hasdata) { | ||||||
|  |       this->value_.assign(temp + 1, temp[0]); | ||||||
|  |     } | ||||||
|  |     this->prev_value_.assign(this->value_); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||||
|  |  | ||||||
|  |   void loop() override { store_value_(); } | ||||||
|  |  | ||||||
|  |   void on_shutdown() override { store_value_(); } | ||||||
|  |  | ||||||
|  |   void set_name_hash(uint32_t name_hash) { this->name_hash_ = name_hash; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void store_value_() { | ||||||
|  |     int diff = this->value_.compare(this->prev_value_); | ||||||
|  |     if (diff != 0) { | ||||||
|  |       // Make it into a length prefixed thing | ||||||
|  |       unsigned char temp[SZ]; | ||||||
|  |  | ||||||
|  |       // If string is bigger than the allocation, do not save it. | ||||||
|  |       // We don't need to waste ram setting prev_value either. | ||||||
|  |       int size = this->value_.size(); | ||||||
|  |       // Less than, not less than or equal, SZ includes the length byte. | ||||||
|  |       if (size < SZ) { | ||||||
|  |         memcpy(temp + 1, this->value_.c_str(), size); | ||||||
|  |         // SZ should be pre checked at the schema level, it can't go past the char range. | ||||||
|  |         temp[0] = ((unsigned char) size); | ||||||
|  |         this->rtc_.save(&temp); | ||||||
|  |         this->prev_value_.assign(this->value_); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   T value_{}; | ||||||
|  |   T prev_value_{}; | ||||||
|  |   uint32_t name_hash_{}; | ||||||
|  |   ESPPreferenceObject rtc_; | ||||||
|  | }; | ||||||
|  |  | ||||||
| template<class C, typename... Ts> class GlobalVarSetAction : public Action<Ts...> { | template<class C, typename... Ts> class GlobalVarSetAction : public Action<Ts...> { | ||||||
|  public: |  public: | ||||||
|   explicit GlobalVarSetAction(C *parent) : parent_(parent) {} |   explicit GlobalVarSetAction(C *parent) : parent_(parent) {} | ||||||
| @@ -81,6 +139,7 @@ template<class C, typename... Ts> class GlobalVarSetAction : public Action<Ts... | |||||||
|  |  | ||||||
| template<typename T> T &id(GlobalsComponent<T> *value) { return value->value(); } | template<typename T> T &id(GlobalsComponent<T> *value) { return value->value(); } | ||||||
| template<typename T> T &id(RestoringGlobalsComponent<T> *value) { return value->value(); } | template<typename T> T &id(RestoringGlobalsComponent<T> *value) { return value->value(); } | ||||||
|  | template<typename T, uint8_t SZ> T &id(RestoringGlobalStringComponent<T, SZ> *value) { return value->value(); } | ||||||
|  |  | ||||||
| }  // namespace globals | }  // namespace globals | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -663,7 +663,11 @@ async def process_lambda( | |||||||
|     :param return_type: The return type of the lambda. |     :param return_type: The return type of the lambda. | ||||||
|     :return: The generated lambda expression. |     :return: The generated lambda expression. | ||||||
|     """ |     """ | ||||||
|     from esphome.components.globals import GlobalsComponent, RestoringGlobalsComponent |     from esphome.components.globals import ( | ||||||
|  |         GlobalsComponent, | ||||||
|  |         RestoringGlobalsComponent, | ||||||
|  |         RestoringGlobalStringComponent, | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     if value is None: |     if value is None: | ||||||
|         return |         return | ||||||
| @@ -676,6 +680,7 @@ async def process_lambda( | |||||||
|             and ( |             and ( | ||||||
|                 full_id.type.inherits_from(GlobalsComponent) |                 full_id.type.inherits_from(GlobalsComponent) | ||||||
|                 or full_id.type.inherits_from(RestoringGlobalsComponent) |                 or full_id.type.inherits_from(RestoringGlobalsComponent) | ||||||
|  |                 or full_id.type.inherits_from(RestoringGlobalStringComponent) | ||||||
|             ) |             ) | ||||||
|         ): |         ): | ||||||
|             parts[i * 3 + 1] = var.value() |             parts[i * 3 + 1] = var.value() | ||||||
|   | |||||||
| @@ -5,6 +5,13 @@ esphome: | |||||||
|   board: nodemcu-32s |   board: nodemcu-32s | ||||||
|   build_path: build/test2 |   build_path: build/test2 | ||||||
|  |  | ||||||
|  | globals: | ||||||
|  |   - id: my_global_string | ||||||
|  |     type: std::string | ||||||
|  |     restore_value: yes | ||||||
|  |     max_restore_data_length: 70 | ||||||
|  |     initial_value: '"DefaultValue"' | ||||||
|  |  | ||||||
| substitutions: | substitutions: | ||||||
|   devicename: test2 |   devicename: test2 | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user