Sync time in MicroPython using NTP
What if we want to blink an LED to remind us on the day of a friend's birthday? Or what if we want a lamp to automatically turn on, like an alarm, every morning? Learn how to sync your ESP32 with network time!
All of our ESP32 projects so far have dealt with durations of time. This is really useful for blinking an LED every X seconds, or for changing a Neopixel's colour every Y milliseconds. But what if we instead want to blink that LED to remind us on the day of a friend's birthday? Or what if we want the Neopixels to automatically turn on, like an alarm, every morning at 10 AM? (I'm not exactly an early riser). We need a way for the microcontroller to understand date and time.
Do you wonder why your microwave's clock uselessly blinks 00:00 after a power outage? It's because it only knows the duration of time that has elapsed since it was last powered on. It has no clue what time of day it actually is. So it has no option but to revert back to zero and make you set it again. For it to know the time of day, it would need to have additional hardware built in.
Usually, microcontroller projects achieve this by adding on an external battery-powered module known as a realtime clock, and querying it to get the current date and time. But since we are using an internet-connected ESP32, we can actually just query the time from the interwebz! For free! No extra hardware needed. To learn how, keep reading or watch the video.
NTP, or Network Time Protocol
NTP is a standardized way for devices to tell each other the time. In fact, this is how our computers and phones keep in sync with everyone else on the internet! But how does your phone know whose clock to trust? Society's got you covered - there's a trusted pool of NTP servers that's responsible for providing accurate clock time all over the world! A lot of those servers actually use signals from GPS satellites to get very precise 'measurements' of time, which is really cool because it means our MicroPython project will run on...space-time. Ba-dum tishh.
Luckily for us, we don't actually need to learn the guts of the network time protocol, or how to connect to an NTP server - there's a library for all of that. To sweeten the deal, the ntptime library is actually already built-in to the ESP32 port of MicroPython. We don't even need to install anything.
How MicroPython represents time
To get started, use WebREPL or a serial terminal to connect to your ESP32 so you can access a REPL. Type the following to see its current estimate of the time:
> time.localtime() (2000, 1, 1, 0, 4, 10, 5, 1) # yours will be different
The format of this tuple is (year, month, month-day, hour, min, second, weekday, year-day). As you can see, this means that my ESP32 currently thinks that it's the 1st of January in the year 2000...the dawn of a new millennium. Just like Morpheus did to Neo in The Matrix, I'm gonna shatter that illusion and give it a red pill from the NTP server.
The 'red pill' in this case is the settime function:
> import ntptime > ntptime.settime() # this queries the time from an NTP server > time.localtime() (2022, 4, 3, 23, 12, 28, 6, 93)
Much better! As of the time of this writing, it is indeed 2022-04-03: the 3rd of April, 2022. But it's definitely not past 23 hrs, i.e. 11PM (I swear). It's actually about 7:12PM right now. So why is the ESP32's time 4 hours off from mine? It's because of a fun little thing called timezones. The NTP server reported UTC/GMT time, which I always imagine is synchronized to the Queen of England's personal clock in Buckingham Palace. But I am in Toronto, and for half the year our clocks are 4 hours behind London time. (During the other half of the year, it's 5 hours behind due to daylight savings...but we won't deal with that today).
How do we account for this 4 hour discrepancy? Luckily for us, the time.localtime() function takes an optional parameter: time in seconds since the epoch. But beware! This is not the unix epoch we know and love. MicroPython counts time since 1st Jan 2000 instead of 1st Jan 1970. The astute among you may have noticed that this was the exact date the ESP32 reported when we first ran time.localtime(), because the default value of this function's parameter is zero.
OK so how do we get the number of seconds since the epoch? It's simple: use time.time(). But again, this function spits out the number of seconds in UTC time so we just need to add our timezone's offset to it:
> UTC_OFFSET = -4 * 60 * 60 # change the '-4' according to your timezone > actual_time = time.localtime(time.time() + UTC_OFFSET) > actual_time (2022, 4, 3, 19, 13, 47, 6, 93)
Aha! It's actually 1913 hrs a.k.a 7:13PM in Toronto. Now you know. That's all you have to do to get the clock time on your ESP32.
As a bonus, if you live in a daylight-savings timezone like me: try writing a simple function to automatically change the UTC_OFFSET based on the day of the year. Hint: as I mentioned earlier, the last field in that tuple is the year-day, i.e. in the code above that means it's the 93rd day of the year. It should be simpler to use this value rather than checking the month and the date. And don't forget to account for leap years! Second hint: if it's divisible by 400, or divisible by 4 and not divisible by 100, it's a leap year. In other words: (year % 400 == 0) or ((year % 4 == 0) and (year % 100 != 0))
If you learned something cool today and would like me to email you the next time I release a MicroPython tutorial, just hit Follow/Subscribe below. I'll deliver it to your inbox hot off the press. I post every 1-2 weeks so don't stress about getting spammed. If you want to chat or show me your solution to the bonus question above, message me on twitter. I look forward to all your messages 😃 Till next time, Sayonara!