esphome / esphome-core

🚨 No longer used 🚨 - The C++ framework behind ESPHome
https://esphome.io/
GNU General Public License v3.0
545 stars 113 forks source link

[Feature Request] SNTP Support #65

Closed brandond closed 6 years ago

brandond commented 6 years ago

Now that I start looking at using Lambda code for more things, I'm realizing it might be nice to have the local RTC set properly.

It appears that both ESP32 and ESP8266 have support for SNTP via the configTime() function:

As far as exposing this through esphomeyaml, it seems like there are only three things that can be configured:

It would probably be reasonable to have GMT and DST offset default to zero, which effectively puts the RTC at UTC. Similarly, it could default to three pool servers (n.pool.ntp.org)

OttoWinter commented 6 years ago

Oh... I thought NTP was super hard to get working on the ESP... Apparently I was wrong 😬

About the esphomelib side:

For the esphomeyaml side:

brandond commented 6 years ago

I'd be glad to take a shot at this. A few additional notes:

OttoWinter commented 6 years ago

Great! So about your responses:

# Other names 'time', 'real_time_clock', ...
local_time:
  # Required, on ESP32 would also apply DST
  timezone: Europe/Vienna
  # Let's give it an ID so that it has a familiar interface for lambda users
  id: my_local_time

  # Optional: 
  # Could also be a ntp_server_1, ntp_server_2, ...  ¯\_(ツ)_/¯, probably doesn't really matter
  ntp_server:
    - 1.pool.ntp.org
    - 2.pool.ntp.org
    - 3.pool.ntp.org

some_component:
  - platform: some_platform
    on_something:
      then:
        - lambda: >-
            # get_time() should return a struct that has the individual time components as
            # .year, .month, ...
            auto time = id(my_local_time).get_time();
            # Use plural for all these names?
            ESP_LOGD("main", "Year is %d", time.year);
            ESP_LOGD("main", "Month is %d", time.month); // 1-12
            ESP_LOGD("main", "Day is %d", time.day); // 1-31
            ESP_LOGD("main", "Hour is %d", time.hour); // 0-23 - no AM/PM bs :P
            ESP_LOGD("main", "Minute is %d", time.minute); // 0-59
            ESP_LOGD("main", "Second is %d", time.second); // 0-59
            ESP_LOGD("main", "Millisecond is %d", time.millisecond); // 0-999

Some of that could maybe be possible to implement... so above format is nothing near final.

brandond commented 6 years ago

It looks like it's using the glibc TZ stuff behind the scenes, and poorly. Check out the goofy munging it's doing here: https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-time.c#L18

Since all it's really doing is enabling LWIP's SNTP stuff and then setting a time zone, that bit of logic would be easy to replace with a properly formatted POSIX TZ string for the specified zone name, as resolved on the local host at build time.

I'm not sure that this increases the code size any since the SNTP stuff is built into LWIP and the rest of the time logic is built into newlib. It is in fact perfectly valid to configure the TZ and call time functions without setting up NTP, it's just that the ESPs do not have a battery-backed RTC so time resets to unix epoch when it reboots.

At some point someone's probably going to come along and ask for external RTC support, so it might be forward-thinking to handle the RTC as a modular platform, with the built-in RTC as the default and only currently supported option.

So, maybe something like this:

rtc:
  platform: (hardware|pcf8523|ds3231|etc)
  timezone: UTC
  id: local_clock
ntp:
  rtc: local_clock
  servers:
    - 0.pool.ntp.org
    - 1.pool.ntp.org
    - 2.pool.ntp.org
brandond commented 6 years ago

So, I did some searching around and it does not appear that there is any well-maintained mapping of tzinfo zone names to POSIX TZ strings. Boost kinda comes close, but even they use a mapping table that's woefully out of date: https://github.com/boostorg/date_time/blob/master/data/

Probably the best thing to do would be to document the limitation, and simply pass through whatever the user specifies. If they want to figure out a proper TZ string for the current rules in their region, they're welcome to do so.

brandond commented 6 years ago

Early results; initial PR incoming.

[08:32:22][D][debug:136]: Reset Reason: Power on
[08:32:22][D][debug:137]: Reset Info: flag: 0
[08:32:22][C][sntp:024]: Setting up SNTP...
[08:32:22][I][application:071]: Running through first loop()
[08:32:22][I][application:082]: First loop finished successfully!
[08:32:22][C][main:045]: RTC time is 0 (year=70 month=0 day=1)
[08:32:22][C][main:046]: Formatted time is 1970-01-01T00:00:00Z
[08:33:00][C][main:045]: RTC time is 1528561980 (year=118 month=5 day=9)
[08:33:00][C][main:046]: Formatted time is 2018-06-09T16:33:00Z
OttoWinter commented 6 years ago

OMG YES!!! 😺 Absolutely awesome! Also about the tzinfo to TZ mapping: If we provide a link to a table in the docs where people can look the TZ string up themselves that would already be fine.

brandond commented 6 years ago

Sorry for leaving this unfinished for so long - day job getting busy plus summer family vacation has reduced the time I've had to work on this over the last few months.