{"id":20429460,"url":"https://github.com/ekondayan/micropython-ntp","last_synced_at":"2025-05-08T17:32:24.820Z","repository":{"id":200643738,"uuid":"324154163","full_name":"ekondayan/micropython-ntp","owner":"ekondayan","description":"Robust NTP library for micropython.","archived":false,"fork":false,"pushed_at":"2024-01-08T17:46:53.000Z","size":1439,"stargazers_count":11,"open_issues_count":0,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-23T06:59:29.188Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ekondayan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-12-24T12:43:48.000Z","updated_at":"2025-03-12T08:06:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"4cb6f055-00fe-4617-bbd2-3c63c9d17be3","html_url":"https://github.com/ekondayan/micropython-ntp","commit_stats":null,"previous_names":["ekondayan/micropython-ntp"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekondayan%2Fmicropython-ntp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekondayan%2Fmicropython-ntp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekondayan%2Fmicropython-ntp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekondayan%2Fmicropython-ntp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ekondayan","download_url":"https://codeload.github.com/ekondayan/micropython-ntp/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253114695,"owners_count":21856550,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-15T08:00:55.647Z","updated_at":"2025-05-08T17:32:24.805Z","avatar_url":"https://github.com/ekondayan.png","language":"Python","readme":"# \u003cu\u003emicropython-ntp\u003c/u\u003e\n\n---\n\n# \u003cu\u003eDescription\u003c/u\u003e\n\nA robust MicroPython **Time library** for manipulating the **RTC** and and syncing it from a list of **NTP** servers.\n\n\u003cu\u003eFeatures:\u003c/u\u003e\n\n1. Sync the RTC from a NTP host\n\n2. Multiple NTP hosts\n\n3. Microsecond precision\n\n4. RTC chip-agnostic\n\n5. Calculate and compensate RTC drift\n\n6. Timezones\n\n7. Epochs\n\n8. Day Light Saving Time\n\n9. Get time in sec, ms and us\n\n10. Custom Logger with callback function\n\n***!!!At this point all the implemented features are robustly tested and they seem stable enough for production, BUT I do not recommended to use it in a production environment until the API stabilization phase is finished and some unit tests are developed!!!***\n\n**Quick Guide**\n\nBefore using the library there are a few thing that need to be done. \n\nThe first and the most important one is setting a callback for manipulating the RTC. The second and of the same importance is setting a list of NTP hosts/IPs.\n\nNext things to configure are the Timezone and Daylight Saving Time but they are not mandatory if do not need them.\n\nOther things that can be configured are: \n\n- Network timeout\n\n- Default epoch - if you need to get the time in other epoch other than the default for the device. Setting a default epoch allows you the convenience of not passing the epoch parameter each time you want to read the time. Each micropython port is compiled with a default epoch. For most of the ports but not all, it is 2000. For example the Unix port uses an epoch of 1970\n\n- The drift value of the RTC if it is know in advance. \n\n- The library logging output can be directed trough a callback. If not needed it can be disabled entirely\n\n**RTC access callback**\n\nThe first thing to do when using the library is to set a callback function for accessing the RTC chip. The idea behind this strategy is that the library can manipulate multiple RTC chips(internal, external or combination of both) and is chip agnostic. Providing this function is your responsibility. It's declaration is:\n\n```python\ndef func(datetime: tuple = None) -\u003e tuple\n```\n\nWith no arguments, this method acts as a getter and returns an 8-tuple with the current date and time. With 1 argument (being an 8-tuple) it sets the date and time. The 8-tuple has the following format:\n\n```python\n(year, month, day, weekday, hour, minute, second, subsecond)\n\n# year is the year including the century part\n# month is in (Ntp.MONTH_JAN ... Ntp.MONTH_DEC)\n# day is in (1 ... 31)\n# weekday is in (Ntp.WEEKDAY_MON ... Ntp.WEEKDAY_SUN)\n# hour is in (0 ... 23)\n# minute is in (0 ... 59)\n# second is in (0 ... 59)\n# subsecond is in (0 ... 999999)\n```\n\nThe `RTC` class in the `machine` module, provides a drop-in alternative for a callback:\n\n```python\nfrom machine import RTC\nfrom ntp import Ntp\n\n_rtc = RTC()\nNtp.set_datetime_callback(_rtc.datetime)\n```\n\n**RTC sync**\n\nTo be able to synchronize the RTC with NTP servers you have to set a list of hosts:\n\n```python\nNtp.set_hosts(('0.pool.ntp.org', '1.pool.ntp.org', '2.pool.ntp.org'))\n```\n\nYou can pass a valid hostname or an IP. A basic validation is run when saving each host/ip. If the value is neither a valid hostname or IP address, it is skipped WITHOUT an error being thrown. It is your responsibility to pass the correct values.\n\nAfter setting a list of NTP hosts, you can synchronize the RTC:\n\n```python\nNtp.rtc_sync()\n```\n\nThis function will loop trough all the hostnames in the list and will try to read the time from each one. The first with a valid response will be used to sync the RTC. The RTC is always synchronized in UTC.\n\nA network timeout in seconds can be set to prevent hanging\n\n```python\nNtp.set_ntp_timeout(timeout_s: int = 1)\n```\n\n**Reading the time**\n\nThere are two types of functions that read the time:\n\n- ones that return the time as a timestamp\n\n- ones that return the time in a date time tuple\n\nThe set of functions that return a timestamp is:\n\n```python\nNtp.time_s(epoch: int = None, utc: bool = False) -\u003e int\nNtp.time_ms(epoch: int = None, utc: bool = False) -\u003e int\nNtp.time_us(epoch: int = None, utc: bool = False) -\u003e int\n```\n\nThe suffix of each function shows the timestamp representation:\n\n- **_s** - seconds\n\n- **_ms** - milliseconds\n\n- **_us** - microseconds\n\nIf you want to get the time relative to an epoch, you can pass one of the following constants:\n\n```python\nNtp.EPOCH_1900\nNtp.EPOCH_1970\nNtp.EPOCH_2000\n```\n\nIf epoch parameter is `None`, the default epoch will be used. Otherwise the parameter will have a higher precedence.\n\nIf `utc = True` the returned timestamp will be in UTC format which excludes the Daylight Saving Time and the Timezone offsets.\n\nTo get the date and time in tuple format:\n\n```python\nNtp.time(utc: bool = False) -\u003e tuple\n\n# 9-tuple(year, month, day, hour, minute, second, weekday, yearday, us)\n# year is the year including the century part\n# month is in (Ntp.MONTH_JAN ... Ntp.MONTH_DEC)\n# day is in (1 ... 31)\n# hour is in (0 ... 23)\n# minutes is in (0 ... 59)\n# seconds is in (0 ... 59)\n# weekday is in (Ntp.WEEKDAY_MON ... Ntp.WEEKDAY_SUN)\n# yearday is in (1 ... 366)\n# us is in (0 ... 999999)\n```\n\n\u003cu\u003e!!! Both types of function read the time from the RTC!!!\u003c/u\u003e\n\nTo read the time directly from the NTP:\n\n```python\nNtp.ntp_time(epoch: int = None) -\u003e tuple\n# 2-tuple(ntp_time, timestamp)\n# * ntp_time contains the accurate time(UTC) from the NTP server\n#    in nanoseconds since the selected epoch. \n# * timestamp contains timestamp in microseconds taken at the time the\n#     request to the server was sent. This timestamp can be used later to\n#   compensate for the time difference between the request was sent\n#    and the later moment the time is used. The timestamp is the output\n#    of time.ticks_us()\n```\n\nGet the accurate time from the first valid NTP server in the list with microsecond precision. When the server does not respond within the timeout period, the next server in the list is used. The default timeout is 1 sec.  The timeout can be changed with `set_ntp_timeout()`. When none of the servers respond, throw an Exception. The epoch parameter serves the same purpose as with the other time functions.\n\n**Epochs**\n\nIn micropython every port has it's own epoch configured during the compilation. Most of the ports use the epoch of `2000-01-01 00:00:00 UTC`, but some like the Unix port use a different. All of the micropython's build in time functions work according to this epoch. In this library I refer to this compiled in epoch as a **device's epoch**. It is not possible to change it during run-time. \n\nWhy is this important? There are multiple occasions where you need the time in different epochs. One example is that NTP uses an epoch of 1900. Another example is if you want to store a timestamp in a database. Some of the databases use an epoch of 1970, others use an epoch of 1900. The list can go on and on.\n\nAll time functions that return a timestamp supports an `epoch` parameter as described  in above section.\n\nPassing an epoch parameter every time is cumbersome, that is why there is a convenience functions that allows you  to set a default epoch that all time functions will use.\n\n```python\n# Set a default epoch\nNtp.set_epoch(epoch: int = None):\n```\n\nIf `None` - the device's epoch will be used. Setting the epoch is not mandatory, the device's epoch will be used as default.\n\nTo get epoch of the device:\n\n```python\nNtp.device_epoch()\n```\n\nThe `'time()` function does not have an epoch parameter, because it returns a structured tuple.\n\nA helper function that is available for calculating the delta between two epochs:\n\n```python\nepoch_delta(from_epoch: int, to_epoch: int) -\u003e int\n```\n\nIf you want to convert a timestamp from an earlier epoch to a latter,  you will have to subtract the seconds between the two epochs. If you want to convert a timestamp from a latter epoch to an earlier,  you will have to add the seconds between the two epochs. The function takes that into account and returns a positive or negative value.\n\n**RTC drift**\n\nAll RTC are prone to drifting over time. This is due to manufacturing tolerances of the  crystal oscillator, PCB, passive components, system aging, temperature excursions, etc. Every chip manufacturer states in the datasheet the clock accuracy of their chip. The unit of measure is **ppm**(parts per million). By knowing the frequency and ppm of the crystal, you can calculate how much the RTC will deviate from the real time. For example if you have a 40MHz clock which is stated +-10ppm.\n\n```python\n# 1 part is equal 1 tick\n\nfrequency = 40_000_000\nppm  = 10 # 10 ticks for every 1_000_000 ticks\nticks_drift_per_sec = (frequency / 1_000_000) * ppm = 400\n\n# The duration of one tick in seconds\ntick_time = 1 / frequency = 0.000000025\n\n# Calculate how many seconds will be the drift\n# of the RTC every second\ndrift_every_sec = tick_time * ticks_drift_per_sec = 0.000_01\n```\n\nFrom the calculation above we know that the RTC can drift +-10us every second. If we know the exact drift, we can calculate the exact deviation from the real time. Unfortunately the exact ppm of every oscillator in unknown and has to be determined per chip manually.\n\nTo calculate the drift, the library uses a simpler approach. Every time the RTC is synchronized from NTP, the response is stored in a class variable. When you want to calculate the drift by calling `Ntp.drift_calculate()`, the function reads the current time from NTP and compares it with the stored from the last RTC sync. By knowing the RTC microsecond ticks and the real delta between the NTP queries, calculating the ppm is a trivial task. The longer the period between `Ntp.rtc_sync()` and `Ntp.drift_calculate()` the more accurate result you will get. Empirically I found that in order to get results that vaguely come close to the real, calculating the drift shall be called at least 15 minutes after syncing the RTC.\n\nTo calculate the drift:\n\n```python\nNtp.drift_calculate(new_time = None) -\u003e float\n```\n\nThe return value is a float where positive values represent a RTC that is speeding, negative values represent RTC that is lagging, and zero means the RTC hasn't drifted at all.\n\nTo get the current drift of the RTC in microseconds:\n\n```python\nNtp.drift_us(ppm_drift: float = None)\n```\n\nThis function does not read the time from the NTP server(no internet connection is required), instead it uses the previously calculated ppm.\n\nTo manually set the drift:\n\n```python\nNtp.set_drift_ppm(ppm: float)\n```\n\nThe `ppm` parameter can be positive or negative. Positive values represent a RTC that is speeding, negative values represent RTC that is lagging. This is useful if you have in advance the ppm of the current chip, for example if you have previously calculated and stored the ppm.\n\nThe function `Ntp.rtc_sync()` is a pretty costly operation since it requires a network access. For an embedded IoT device this is unfeasible. Instead, you can compensate for the drift at regular and much shorter intervals by:\n\n```python\nNtp.drift_compensate(Ntp.drift_us())\n```\n\nA NTP sync can be performed at much longer intervals, like a day or week, depending on your device stability. If your device uses a TXCO(Temperature Compensated Crystal Oscillator), the period between NTP syncs can be much longer.\n\nHere is a list of all the functions that are managing the drift:\n\n```python\nNtp.drift_calculate(cls)\nNtp.drift_last_compensate()\nNtp.drift_last_calculate()\nNtp.drift_ppm()\nNtp.set_drift_ppm(ppm: float)\nNtp.drift_us(ppm_drift: float = None)\nNtp.drift_compensate(compensate_us: int)\n```\n\n**Timezones**\n\nThe library has support for timezones. When setting the timezone ensures basic validity check.\n\n```python\nNtp.set_timezone(hour: int, minute: int = 0)\n```\n\n**!!! NOTE: When syncing or drift compensating the RTC, the time will be set in UTC**\n\nFunctions that support the `utc` argument can be instructed to return the time with the Timezone and DST calculated or the UTC time:\n\n```python\nNtp.time(utc: bool = False) -\u003e tuple\nNtp.time_s(epoch: int = None, utc: bool = False) -\u003e int\nNtp.time_ms(epoch: int = None, utc: bool = False) -\u003e int\nNtp.time_us(epoch: int = None, utc: bool = False) -\u003e int\n\nNtp.rtc_last_sync(epoch: int = None, utc: bool = False) -\u003e int\nNtp.drift_last_compensate(epoch: int = None, utc: bool = False) -\u003e int\nNtp.drift_last_calculate(epoch: int = None, utc: bool = False) -\u003e int\n```\n\n**Daylight Saving Time**\n\nThe library supports calculating the time according to the Daylight Saving Time. To start using the DST functionality you have to set three things first:\n\n- DST start date and time\n\n- DST end date and time\n\n- DST bias\n\nThese parameters can be set with just one function `set_dst(start: tuple, end: tuple, bias: int)`  for convenience or you can set each parameter separately with a dedicated function. Example:\n\n```python\n# Set DST data in one pass\n# start (tuple): 4-tuple(month, week, weekday, hour) start of DST\n# end (tuple) :4-tuple(month, week, weekday, hour) end of DST\n# bias (int): Daylight Saving Time bias expressed in minutes\nNtp.set_dst(cls, start: tuple = None, end: tuple = None, bias: int = 0)\n\n# Set the start date and time of the DST\n# month (int): number in range 1(Jan) - 12(Dec)\n# week (int): integer in range 1 - 6. Sometimes there are months when they can spread over a 6 weeks ex. 05.2021\n# weekday (int): integer in range 0(Mon) - 6(Sun)\n# hour (int): integer in range 0 - 23\nNtp.set_dst_start(month: int, week: int, weekday: int, hour: int)\n\n# Set the end date and time of the DST\n# month (int): number in range 1(Jan) - 12(Dec)\n# week (int): number in range 1 - 6. Sometimes there are months when they can spread over 6 weeks.\n# weekday (int): number in range 0(Mon) - 6(Sun)\n# hour (int): number in range 0 - 23\nNtp.set_dst_end(cls, month: int, week: int, weekday: int, hour: int)\n\n# Set Daylight Saving Time bias expressed in minutes.\n# bias (int): minutes of the DST bias. Correct values are 30, 60, 90 and 120\nNtp.set_dst_time_bias(cls, bias: int)\n```\n\nYou can disable DST functionality by setting any of the start or end date time to `None`\n\n```python\n# Default values are `None` which disables the DST\nNtp.set_dst()\n```\n\nTo calculate if DST is currently in effect:\n\n```python\nNtp.dst() -\u003e int\n```\n\nReturns the bias in seconds. A value of `0` means no DST is in effect or it is disabled.\n\nTo get a boolean value:\n\n```python\nbool(Ntp.dst())\n```\n\n**Logger**\n\nThe library support setting a custom logger. If you want to redirect the error messages to another destination, set your logger\n\n```python\nNtp.set_logger(callback = print)\n```\n\nThe default logger is `print()` and to set it just call the method without any parameters.  To disable logging, set the callback to \"None\"\n\n# \u003cu\u003eExample\u003c/u\u003e\n\n```python\nfrom machine import RTC\nfrom ntp import Ntp\nimport time\n\ndef ntp_log_callback(msg: str):\n    print(msg)\n\n_rtc = RTC()\n\n# Initializing\nNtp.set_datetime_callback(_rtc.datetime)\nNtp.set_logger_callback(ntp_log_callback)\n\n# Set a list of valid hostnames/IPs\nNtp.set_hosts(('0.pool.ntp.org', '1.pool.ntp.org', '2.pool.ntp.org'))\n# Network timeout set to 1 second\nNtp.set_ntp_timeout(1)\n# Set timezone to 2 hours and 0 minutes\nNtp.set_timezone(2, 0)\n# If you know the RTC drift in advance, set it manually to -4.6ppm\nNtp.set_drift_ppm(-4.6)\n# Set epoch to 1970. All time calculations will be according to this epoch\nNtp.set_epoch(Ntp.EPOCH_1970)\n# Set the DST start and end date time and the bias in one go\nNtp.set_dst((Ntp.MONTH_MAR, Ntp.WEEK_LAST, Ntp.WEEKDAY_SUN, 3),\n            (Ntp.MONTH_OCT, Ntp.WEEK_LAST, Ntp.WEEKDAY_SUN, 4),\n            60)\n\n\n# Syncing the RTC with the time from the NTP servers\nNtp.rtc_sync()\n\n# Let the RTC drift for 1 minute\ntime.sleep(60)\n\n# Calculate the RTC drift\nNtp.drift_calculate()\n\n# Let the RTC drift for 1 minute\ntime.sleep(60)\n\n# Compensate the RTC drift\nNtp.drift_compensate(Ntp.drift_us())\n\n# Get the last timestamp the RTC was synchronized\nNtp.rtc_last_sync()\n\n# Get the last timestamp the RTC was compensated\nNtp.drift_last_compensate()\n\n# Get the last timestamp the RTC drift was calculated\nNtp.drift_last_calculate()\n\n# Get the calculated drift in ppm\nNtp.drift_ppm()\n\n# Get the calculated drift in us\nNtp.drift_us()\n```\n\n# \u003cu\u003eDependencies\u003c/u\u003e\n\n* Module sockets\n\n* Module struct\n\n* Module time\n\n* Module re\n\n# \u003cu\u003eContributions\u003c/u\u003e\n\nIf you want to help me improve this library, you can open Pull Requests. Only very well-documented PRs will be accepted. Please use clear and meaningful commit messages to explain what you've done.\n\nA strong emphasis is placed on documentation. Ensure that all methods and classes have clear and concise docstrings. In addition to docstrings, it's desirable to include comments within your code to provide context and explain the logic, especially in complex sections. If you introduce a new feature, consider updating or creating a corresponding documentation section.\n\nBefore submitting your PR, ensure that you've tested your changes thoroughly. If possible, add unit tests for any new functionality you've added. This will not only improve the reliability of the project but will also increase the chances of your PR being accepted.\n\nThank you for your interest in contributing! Every effort, big or small, is highly valued.\n\n# \u003cu\u003eDownload\u003c/u\u003e\n\nYou can download the project from GitHub:\n\n```bash\ngit clone https://github.com/ekondayan/micropython-ntp.git micropython-ntp\n```\n\n# \u003cu\u003eLicense\u003c/u\u003e\n\nThis Source Code Form is subject to the BSD 3-Clause license. You can find it under  the LICENSE.md file in the projects' directory or here: [The 3-Clause BSD License](https://opensource.org/licenses/BSD-3-Clause)\n","funding_links":[],"categories":["Libraries"],"sub_categories":["Communications"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fekondayan%2Fmicropython-ntp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fekondayan%2Fmicropython-ntp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fekondayan%2Fmicropython-ntp/lists"}