Skip to content

stdlib/time: Add time package (wrapping utime) with monotonic function. #502

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions python-stdlib/time/metadata.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
srctype = micropython-lib
type = module
version = 0.1.0
license = Python
24 changes: 24 additions & 0 deletions python-stdlib/time/setup.py
Original file line number Diff line number Diff line change
@@ -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"],
)
30 changes: 30 additions & 0 deletions python-stdlib/time/test_monotonic.py
Original file line number Diff line number Diff line change
@@ -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()
67 changes: 67 additions & 0 deletions python-stdlib/time/time.py
Original file line number Diff line number Diff line change
@@ -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())