Skip to content

Commit

Permalink
Add adjtime patch and adjust documentation
Browse files Browse the repository at this point in the history
The pull requests for the firmware features, needed
for the RTC skew, are no longer available on github.
Add an equivalent patch that applies to current HEAD
and adjust documentation.
  • Loading branch information
wieck committed Jul 11, 2021
1 parent cdce8ea commit 134c9c0
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 37 deletions.
79 changes: 42 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,21 @@ micropython-ntpclient

A uasyncio based NTP client for ESP32/ESP8266 boards running micropython.

**This is a proof of concept. Do not use in production at this point!**

**At this moment this code only works on boards with a
custom build and the new uasyncio module!**
**At this moment this code only works on boards with a custom build
micropython firmware.**

Please go to https://forum.micropython.org/viewtopic.php?f=15&t=7567
for discussion and questions.

Required commit to be cherry-picked for ESP32:
https://github.com/wieck/micropython/commit/cd80a9aba99a68af7e295067fa7d35383ccef640
**The following commits were never accepted by the upstream project.
In order to make the utime.adjtime() available please apply the
included patch _esp32_adjtime.diff_ and build a custom micropython image.**

Required commit to be cherry-picked for ESP8266:
https://github.com/wieck/micropython/commit/97e58630e74b024b58aeb5964d104973b361cad5
~~Required commit to be cherry-picked for ESP32:
https://github.com/wieck/micropython/commit/cd80a9aba99a68af7e295067fa7d35383ccef640~~

Required uasyncio:
https://github.com/dpgeorge/micropython/blob/extmod-uasyncio/extmod/uasyncio.py
~~Required commit to be cherry-picked for ESP8266:
https://github.com/wieck/micropython/commit/97e58630e74b024b58aeb5964d104973b361cad5~~


Installation and testing
Expand All @@ -30,8 +29,7 @@ function that uses the adjtime(3) function of the ESP32 SDK.

Once your board is flashed with that custom build, upload the
ntpclient directory and ntpclient_test[12].py scripts. You also need to upload
a ```boot.py``` that enables WiFi and connects to your WLAN as well as
the new uasyncio.
a ```boot.py``` that enables WiFi and connects to your WLAN.

Then use a REPL prompt and
```
Expand Down Expand Up @@ -61,10 +59,41 @@ ntpclient_test2.run(pps = 17, host = 'my.local.ntp.host.addr', scl = 22, sda = 2

Congratulations, you now have an NTP based clock that displays UTC.

Syntax
------

```
ntpclient.ntpclient(**kwargs)
kwargs:
host=HOSTNAME Hostname of the NTP server to use (default pool.ntp.org).
poll=SECONDS Maximum poll interval (default 1024). ntpclient will
dynamically increase/decrease the polling interval based
on current instability between 64 and this maximum
number of seconds.
max_startup_delta=SECONDS
Number of seconds of clock difference at which
ntpclient will perform a hard set of the clock on
startup instead of slewing the clock (default 1).
debug=BOOL Flag to make ntpclient emit debug messages on stdout
for diagnostics.
```

Example
-------
```
include ntpclient
ntpclient.ntpclient(host = 'ntpserver.localdomain')
```

Saving Drift information
------------------------

On the ESP32 port an optional argument to the ntpclient instance is
On the ESP32 port an optional keyword argument to the ntpclient instance is
the path for a "drift_file". In this file ntpclient will periodically
save drift information to speed up synchronization on subsequent
reboots.
Expand All @@ -73,33 +102,9 @@ reboots.
Implementation Notes
--------------------

The mechanism to adjust the RTC is very different for each platform.

* On the ESP32 the RTC is running on the main XTAL while under full power.
The algorithm tries to calculate the current "drift" of that oscillator.
From this drift, measured in microseconds per adjustment interval, it
calculates a number of microseconds by which to "slew" the RTC every
two seconds using the new adjtime() function. This is basically a
simplified version of what ntpd does on a Unix system.

* On the ESP8266 the RTC is running on an internal 150kHz oscillator.
This oscillator has some severe jitter and changes its speed by up to
+/- 5%, mostly caused by temperature changes. The above commit adds a
function machine.RTC.calibrate() which uses system_rtc_clock_cali_proc()
to recalibrate the clock based on the current main XTAL. It also
takes an optional argument by which the calibration value can be offset
in order to slew the clock. The function also adjusts the internal
"delta" value, that all time based functions use to calculate the
actual time since epoch.

Both implementations make the RTC appear monotonic and keep it more or
less in sync. Because the ESP8266 is not using a crystal based RTC, it
will wander off between server polls by up to 50ms even when using a
local NTP server (yes, that much). By default those server polls
will eventually happen only every 17 minutes, once the system has
settled in. The ntpclient class does take an optional "poll" argument,
that lets you override the maximum poll interval, but please only use
that if you also provide a local "host" as your NTP server. 'pool.ntp.org'
are public servers that other people pay for. Squandering those
resources because your microcontroller has a bad RTC implementation is
not fair.
77 changes: 77 additions & 0 deletions esp32_adjtime.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
diff --git a/docs/library/utime.rst b/docs/library/utime.rst
index b7c604dc7..b2c34740c 100644
--- a/docs/library/utime.rst
+++ b/docs/library/utime.rst
@@ -235,7 +235,17 @@ Functions
since last power-up or from other relative, hardware-specific point
(e.g. reset).

+
.. function:: time_ns()

Similar to `time()` but returns nanoseconds since the Epoch, as an integer (usually
a big integer, so will allocate on the heap).
+
+
+.. function:: adjtime([(secs, usecs)])
+
+ This function is only available in the esp32 port. It uses the API's
+ adjtime(3) function to "slew" the RTC by the given offset over some
+ time. If no offset is given the remaining offset from a previous call
+ is returned. During the adjustment the RTC remains monotonic but
+ appears to run slightly faster/slower.
diff --git a/ports/esp32/modutime.c b/ports/esp32/modutime.c
index cf7178e0b..5b3e36d48 100644
--- a/ports/esp32/modutime.c
+++ b/ports/esp32/modutime.c
@@ -31,6 +31,7 @@
#include <sys/time.h>

#include "py/runtime.h"
+#include "py/mperrno.h"
#include "lib/timeutils/timeutils.h"
#include "extmod/utime_mphal.h"

@@ -82,6 +83,34 @@ STATIC mp_obj_t time_time(void) {
}
MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time);

+STATIC mp_obj_t time_adjtime(size_t n_args, const mp_obj_t *args) {
+ struct timeval dout_tv;
+ mp_obj_t dout_tup[2];
+
+ if (n_args == 1 && args[0] != mp_const_none) {
+ // Set a new adjustment value from struct tv delta
+ mp_obj_t *delta_tup;
+ struct timeval delta_tv;
+
+ mp_obj_get_array_fixed_n(args[0], 2, &delta_tup);
+ delta_tv.tv_sec = mp_obj_get_int(delta_tup[0]);
+ delta_tv.tv_usec = mp_obj_get_int(delta_tup[1]);
+
+ if (adjtime(&delta_tv, &dout_tv) != 0)
+ mp_raise_OSError(MP_EINVAL);
+ } else {
+ // Just query the remaining adjustment
+ if (adjtime(NULL, &dout_tv) != 0)
+ mp_raise_OSError(MP_EINVAL);
+ }
+
+ // Build the return struct tv tuple
+ dout_tup[0] = mp_obj_new_int(dout_tv.tv_sec);
+ dout_tup[1] = mp_obj_new_int(dout_tv.tv_usec);
+ return mp_obj_new_tuple(2, dout_tup);
+}
+MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(time_adjtime_obj, 0, 1, time_adjtime);
+
STATIC const mp_rom_map_elem_t time_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) },

@@ -89,6 +118,7 @@ STATIC const mp_rom_map_elem_t time_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) },
{ MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&time_mktime_obj) },
{ MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) },
+ { MP_ROM_QSTR(MP_QSTR_adjtime), MP_ROM_PTR(&time_adjtime_obj) },
{ MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&mp_utime_sleep_obj) },
{ MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mp_utime_sleep_ms_obj) },
{ MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mp_utime_sleep_us_obj) },

0 comments on commit 134c9c0

Please sign in to comment.