mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +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") | ||||
| GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component) | ||||
| RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component) | ||||
| RestoringGlobalStringComponent = globals_ns.class_( | ||||
|     "RestoringGlobalStringComponent", cg.Component | ||||
| ) | ||||
| GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action) | ||||
|  | ||||
| CONF_MAX_RESTORE_DATA_LENGTH = "max_restore_data_length" | ||||
|  | ||||
|  | ||||
| MULTI_CONF = True | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
| @@ -24,6 +30,7 @@ CONFIG_SCHEMA = cv.Schema( | ||||
|         cv.Required(CONF_TYPE): cv.string_strict, | ||||
|         cv.Optional(CONF_INITIAL_VALUE): cv.string_strict, | ||||
|         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) | ||||
|  | ||||
| @@ -32,12 +39,19 @@ CONFIG_SCHEMA = cv.Schema( | ||||
| @coroutine_with_priority(-100.0) | ||||
| async def to_code(config): | ||||
|     type_ = cg.RawExpression(config[CONF_TYPE]) | ||||
|     template_args = cg.TemplateArguments(type_) | ||||
|     restore = config[CONF_RESTORE_VALUE] | ||||
|  | ||||
|     type = RestoringGlobalsComponent if restore else GlobalsComponent | ||||
|     res_type = type.template(template_args) | ||||
|     # 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 | ||||
|  | ||||
|     res_type = type.template(template_args) | ||||
|     initial_value = None | ||||
|     if CONF_INITIAL_VALUE in config: | ||||
|         initial_value = cg.RawExpression(config[CONF_INITIAL_VALUE]) | ||||
|   | ||||
| @@ -65,6 +65,64 @@ template<typename T> class RestoringGlobalsComponent : public Component { | ||||
|   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...> { | ||||
|  public: | ||||
|   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(RestoringGlobalsComponent<T> *value) { return value->value(); } | ||||
| template<typename T, uint8_t SZ> T &id(RestoringGlobalStringComponent<T, SZ> *value) { return value->value(); } | ||||
|  | ||||
| }  // namespace globals | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -663,7 +663,11 @@ async def process_lambda( | ||||
|     :param return_type: The return type of the lambda. | ||||
|     :return: The generated lambda expression. | ||||
|     """ | ||||
|     from esphome.components.globals import GlobalsComponent, RestoringGlobalsComponent | ||||
|     from esphome.components.globals import ( | ||||
|         GlobalsComponent, | ||||
|         RestoringGlobalsComponent, | ||||
|         RestoringGlobalStringComponent, | ||||
|     ) | ||||
|  | ||||
|     if value is None: | ||||
|         return | ||||
| @@ -676,6 +680,7 @@ async def process_lambda( | ||||
|             and ( | ||||
|                 full_id.type.inherits_from(GlobalsComponent) | ||||
|                 or full_id.type.inherits_from(RestoringGlobalsComponent) | ||||
|                 or full_id.type.inherits_from(RestoringGlobalStringComponent) | ||||
|             ) | ||||
|         ): | ||||
|             parts[i * 3 + 1] = var.value() | ||||
|   | ||||
| @@ -5,6 +5,13 @@ esphome: | ||||
|   board: nodemcu-32s | ||||
|   build_path: build/test2 | ||||
|  | ||||
| globals: | ||||
|   - id: my_global_string | ||||
|     type: std::string | ||||
|     restore_value: yes | ||||
|     max_restore_data_length: 70 | ||||
|     initial_value: '"DefaultValue"' | ||||
|  | ||||
| substitutions: | ||||
|   devicename: test2 | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user