Feed component variable (list of strings) from global component

Hi,
I’ve tried hard to dynamically configure the list of ntp servers for a sntp time component - but without success.

I want to let the user configure the 3 possible ntp servers via a simple web form (realized with the ESPAsyncWebServer - not with the standard web_server component, to save resources).

For this I’ve tried to define a global with the three ntp servers (the global will be set from the ESPAsyncWebServer submitted html form - this was the easy part).

But I found no way to feed the values from the global component into the servers variable of the sntp timer component:

This is the simple setup with constants (and also with global substitutions)

time:
  - platform: sntp
    id: sntp_time
    timezone: America/Phoenix
    servers:
      - 0.us.pool.ntp.org
      - 1.us.pool.ntp.org
      - 2.us.pool.ntp.org

But as soon as I try to use a lambda to fill the list of servers from the global, it fails:

I’ve tried:

globals:
  - id: global_ntp_servers # time servers
    type: std::string[]
    restore_value: true # persistent
    initial_value: '{"0.us.pool.ntp.org", "1.us.pool.ntp.org", "2.us.pool.ntp.org"}'

but this results in compiler errors.

And this:

globals:
  - id: global_ntp_servers # time servers
    type: std::array<std::string, 3>
    restore_value: true # persistent
    initial_value: '{"0.us.pool.ntp.org", "1.us.pool.ntp.org", "2.us.pool.ntp.org"}'

but again compiler errors.

Using type

std::vector<std::string>

in the global also doesn’t work.

I’ve done a different test with just one server - just a string.

globals:
  - id: global_ntp_servers # time servers
    type: std::string
    restore_value: true # persistent
    initial_value: '"0.us.pool.ntp.org"'

this global definition is ok (as expected).

But when trying to use it to feed the sntp servers variable, I fail again.

time:
  - platform: sntp
    id: sntp_time
    timezone: America/Phoenix
    servers:
      - lambda: 'return id(global_ntp_servers);'

with the error:

string value cannot be dictionary or list.

When I try to convert this string to a list, it fails also.

time:
  - platform: sntp
    id: sntp_time
    timezone: America/Phoenix
    servers:
      - lambda: |-
          std::list<std::string> stringList = {id(global_ntp_servers)};
          return stringList;

I’m a bit lost and have two questions:

  • How do I define an array (or list or vector) of strings in a global?
  • How can I feed values defined in a global to a component variable in general - and in my case a list of strings?

Thanks in advance for your help

Maybe this is of some help.

globals:
  - id: HELLO_WORLD
    type: std::array<std::string, 4>
    initial_value: '{
      "hello", 
      "world",
      "foo", 
      "bar"
    }'

interval:
    - interval: 5sec
      then:
        - lambda: |-
            for (const auto& str : id(HELLO_WORLD)) {
              ESP_LOGD("global_array", "%s", str.c_str());
            }

Not sure if the servers can be set at runtime though?

https://esphome.io/api/sntp__component_8h_source

Thanks!

I’ve never looked to the sources of the sntp component before. The link you’ve provided showing that the ntp servers are constructor parameters.
I’m not an expert about the internals of ESPHome but I think you’re right and because of this, the servers could not be changed later and therefore could not be set dynamically. I suspect that the instances of the components are only created once at startup?!

This leads me to the following question:
How is it basically possible to allow a user to configure some basic settings for components from “outside” (e.g. via a web form) without changing, rebuilding and re-installing the program? I expect that other components beside the SNTP have the same restrictions (e.g. MQTT server settings, etc.). Also I found this hint about runtime configuration limits.

I couldn’t find something like a best practice or blue print about this use case. Maybe there is something similar like the substitutions mechanism used at build time but instead a “substitution at runtime” which may load the substitutions after a reboot from somewhere (NVM ?) and using these values as config variables for the components - without a program change… I don’t know if something like this exists.

The component HAS to be written to support that requirement. Looking at the code for sntp, it is not. It only does the configuration during setup, which occurs shortly after boot. It is a small matter of programming to change that behavior and allow stopping, reconfiguring, and restarting the service to support the new requirement you have.

Thanks for clarification.
I understand that there is no generic mechanism which would be able to substitute component variables at runtime. Instead a component has to be developed in a specific way to support this.

This would be a great and useful feature for the future… just thinking…

Would it be possible to add a feature in ESPHome like a generic runtime substitute syntax like e.g. starting with two dollar signs like $${variableName} and in case this variable name has to be e.g. a global id with “restore_value: true” to be able to load the value out of the NVM at startup (for sure the datatype has to match) - or maybe with a different mechanism. So the compiler could generate some stub-code for all occurrences of $${…} which loads the current value at startup from the NVM and put them in before the component gets instantiated… just an idea. This would not require that a component needs to be implemented to support this - for sure this would only work at reboot. But it’s a common use case that users may change some basic settings while the program is already running but these will only be activated after a reboot… just thinking…

Now take this with a grain of salt as I’m kind of beyond my depth but:

I think sometimes you can call underlying framework functions in lamda’s:
https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/system_time.html#_CPPv422esp_sntp_setservername4u8_tPKc

https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/system_time.html#_CPPv413esp_sntp_stopv

https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/system_time.html#_CPPv413esp_sntp_initv

You may be able to add some on_boot code to simulate some intialise code, a bit like Athom is doing here:

Could be wrong here but might be worth looking into…

Thanks for sharing these valuable details. I’ll try to take a deeper look into this.