diff --git a/python-stdlib/time/metadata.txt b/python-stdlib/time/metadata.txt new file mode 100644 index 000000000..a6207a237 --- /dev/null +++ b/python-stdlib/time/metadata.txt @@ -0,0 +1,4 @@ +srctype = micropython-lib +type = module +version = 0.1.0 +license = Python diff --git a/python-stdlib/time/setup.py b/python-stdlib/time/setup.py new file mode 100644 index 000000000..fddd2420d --- /dev/null +++ b/python-stdlib/time/setup.py @@ -0,0 +1,24 @@ +import sys + +# Remove current dir from sys.path, otherwise setuptools will peek up our +# module instead of system's. +sys.path.pop(0) +from setuptools import setup + +sys.path.append("..") +import sdist_upip + +setup( + name="micropython-time", + version="0.1.0", + description="MicroPython time module.", + long_description="MicroPython wrapper for utime, providing extra cpython compatible functions.", + url="https://github.com/micropython/micropython-lib", + author="micropython-lib Developers", + author_email="micro-python@googlegroups.com", + maintainer="micropython-lib Developers", + maintainer_email="micro-python@googlegroups.com", + license="Python", + cmdclass={"sdist": sdist_upip.sdist}, + py_modules=["time"], +) diff --git a/python-stdlib/time/test_monotonic.py b/python-stdlib/time/test_monotonic.py new file mode 100644 index 000000000..6f50db63a --- /dev/null +++ b/python-stdlib/time/test_monotonic.py @@ -0,0 +1,30 @@ +import unittest +from time import monotonic +import time + + +class TestMonotonic(unittest.TestCase): + def test_current(self): + now = monotonic() + time.sleep_ms(5) + then = monotonic() + assert now < int(now + then) + assert then < int(now + then) + + def test_timeout(self): + start = monotonic() + timeout = 0.01 + assert (monotonic() - start) < timeout + time.sleep_ms(12) + assert (monotonic() - start) > timeout + + def test_timeout_add(self): + start = monotonic() + timeout = start + 0.015 + assert monotonic() < timeout + time.sleep_ms(20) + assert monotonic() > timeout + + +if __name__ == "__main__": + unittest.main() diff --git a/python-stdlib/time/time.py b/python-stdlib/time/time.py new file mode 100644 index 000000000..ff735aa6b --- /dev/null +++ b/python-stdlib/time/time.py @@ -0,0 +1,67 @@ +from utime import * +from utime import ticks_ms, ticks_diff + + +class Monotonic: + """ + MicroPython replacement for time.monotonic suitable for timeouts & comparisons. + """ + + # CPython time.monotonic() → float returns the value (in fractional seconds) of a monotonic clock, + # i.e. a clock that cannot go backwards. The clock is not affected by system clock updates. + # The reference point of the returned value is undefined, so that only the difference between the + # results of two calls is valid. + + # Most micropython ports have single-precision float for size / efficiency reasons, and some do not have + # float support at all in hardware (so are very slow). + # To support measurements of difference between two time points, time.ticks_ms() and time.ticks.diff() + # are generally recommended, however this can complicate efforts to port existing libraries using + # time.monotonic. + + # This library is intended to support being used as a drop-in replacement for many/most use cases of + # time.monotonic. It will wrap the ticks functions and handle/hide the 32-bit rollover handling. + + # Note however if you convert the output of monotonic to int or float, eg `float(monotonic())` then + # comparisions between these value are not always valid becasuse they will wrap around back to zero + # after a certain length of time. In other words, always do comparisons against the object returned + # by monotonic() without type conversion. + + # See the test_monotonic.py unit test for examples of usage. + + def __init__(self, val_ms) -> None: + self.val_ms = val_ms + + def __int__(self): + return int(self.val_ms // 1000) + + def __float__(self): + return float(self.val_ms) / 1000.0 + + def __repr__(self): + return str(self.__float__()) + + @staticmethod + def _convert(other): + if isinstance(other, Monotonic): + return other.val_ms + elif isinstance(other, float): + return int(round(other * 1000)) + else: + return int(other * 1000) + + def __add__(self, other): + return Monotonic(self.val_ms + self._convert(other)) + + def __sub__(self, other): + """Returns relative difference in time in seconds""" + return float(ticks_diff(self.val_ms, self._convert(other))) / 1000.0 + + def __gt__(self, other): + return self.__sub__(other) > 0 + + def __lt__(self, other): + return self.__sub__(other) < 0 + + +def monotonic(): + return Monotonic(ticks_ms())