Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
20 changes: 20 additions & 0 deletions adafruit_fruitjam/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,26 @@ def __init__( # noqa: PLR0912,PLR0913,Too many branches,Too many arguments in f

gc.collect()

def sync_time(self, **kwargs):
"""Set the system RTC via NTP using this FruitJam's Network.

This is a convenience wrapper for ``self.network.sync_time(...)``.

:param str server: Override NTP host (defaults to ``NTP_SERVER`` or
``"pool.ntp.org"`` if unset). (Pass via ``server=...`` in kwargs.)
:param float tz_offset: Override hours from UTC (defaults to ``NTP_TZ``;
``NTP_DST`` is still added). (Pass via ``tz_offset=...``.)
:param dict tuning: Advanced options dict (optional). Supported keys:
``timeout`` (float, socket timeout seconds; defaults to ``NTP_TIMEOUT`` or 5.0),
``cache_seconds`` (int; defaults to ``NTP_CACHE_SECONDS`` or 0),
``require_year`` (int; defaults to ``NTP_REQUIRE_YEAR`` or 2022).
(Pass via ``tuning={...}``.)

:returns: Synced time
:rtype: time.struct_time
"""
return self.network.sync_time(**kwargs)

def set_caption(self, caption_text, caption_position, caption_color):
"""A caption. Requires setting ``caption_font`` in init!

Expand Down
106 changes: 106 additions & 0 deletions adafruit_fruitjam/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@
"""

import gc
import os

import adafruit_connection_manager as acm
import adafruit_ntp
import microcontroller
import neopixel
import rtc
from adafruit_portalbase.network import (
CONTENT_IMAGE,
CONTENT_JSON,
Expand Down Expand Up @@ -209,3 +213,105 @@ def process_image(self, json_data, sd_card=False): # noqa: PLR0912 Too many bra
gc.collect()

return filename, position

def sync_time(self, server=None, tz_offset=None, tuning=None):
"""
Set the system RTC via NTP using this Network's Wi-Fi connection.

Reads optional settings from settings.toml:
NTP_SERVER (default "pool.ntp.org")
NTP_TZ (float hours from UTC, default 0)
NTP_DST (additional offset, usually 0 or 1)
NTP_TIMEOUT (seconds, default 5.0)
NTP_CACHE_SECONDS (default 0 = always fetch fresh)
NTP_REQUIRE_YEAR (minimum acceptable year, default 2022)

Keyword args:
server (str) – override NTP_SERVER
tz_offset (float) – override NTP_TZ (+ NTP_DST still applied)
tuning (dict) – override other knobs:
{"timeout": 5.0,
"cache_seconds": 0,
"require_year": 2022}

Returns:
time.struct_time
"""
# Bring up Wi-Fi using the existing flow.
self.connect()

# Build a socket pool from the existing ESP interface.
pool = acm.get_radio_socketpool(self._wifi.esp)

# Settings with environment fallbacks.
server = server or os.getenv("NTP_SERVER") or "pool.ntp.org"

if tz_offset is None:
tz_env = os.getenv("NTP_TZ")
try:
tz_offset = float(tz_env) if tz_env not in {None, ""} else 0.0
except Exception:
tz_offset = 0.0

# Simple DST additive offset (no IANA time zone logic).
try:
dst = float(os.getenv("NTP_DST") or 0)
except Exception:
dst = 0.0
tz_offset += dst

# Optional tuning (env can override passed defaults).
t = tuning or {}

def _f(name, default):
v = os.getenv(name)
try:
return float(v) if v not in {None, ""} else float(default)
except Exception:
return float(default)

def _i(name, default):
v = os.getenv(name)
try:
return int(v) if v not in {None, ""} else int(default)
except Exception:
return int(default)

timeout = float(t.get("timeout", _f("NTP_TIMEOUT", 5.0)))
cache_seconds = int(t.get("cache_seconds", _i("NTP_CACHE_SECONDS", 0)))
require_year = int(t.get("require_year", _i("NTP_REQUIRE_YEAR", 2022)))

# Query NTP and set the system RTC.
ntp = adafruit_ntp.NTP(
pool,
server=server,
tz_offset=tz_offset,
socket_timeout=timeout,
cache_seconds=cache_seconds,
)

# Query NTP and set the system RTC.
ntp = adafruit_ntp.NTP(
pool,
server=server,
tz_offset=tz_offset,
socket_timeout=timeout,
cache_seconds=cache_seconds,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this ntp initialization need to be duplicated?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also import rtc is in lines 34 and 316


try:
now = ntp.datetime # struct_time
except OSError as e:
# Retry once in case of transient ETIMEDOUT, after forcing reconnect.
if getattr(e, "errno", None) == 116 or "ETIMEDOUT" in str(e):
# Ensure radio is up again
self.connect()
now = ntp.datetime
else:
raise

if now.tm_year < require_year:
raise RuntimeError("NTP returned an unexpected year; not setting RTC")

rtc.RTC().datetime = now
return now
3 changes: 3 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
"audiocore",
"storage",
"terminalio",
"adafruit_connection_manager",
"adafruit_ntp",
"rtc",
]

autodoc_preserve_defaults = True
Expand Down
29 changes: 29 additions & 0 deletions examples/fruitjam_ntp_settings.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also just noticed all the copyright notices are set to my name. You can set these to your name or handle if you want.

#
# SPDX-License-Identifier: MIT
# Wi-Fi credentials
CIRCUITPY_WIFI_SSID = "YourSSID"
CIRCUITPY_WIFI_PASSWORD = "YourPassword"

# NTP settings
# Common UTC offsets (hours):
# 0 UTC / Zulu
# 1 CET (Central Europe)
# 2 EET (Eastern Europe)
# 3 FET (Further Eastern Europe)
# -5 EST (Eastern US)
# -6 CST (Central US)
# -7 MST (Mountain US)
# -8 PST (Pacific US)
# -9 AKST (Alaska)
# -10 HST (Hawaii, no DST)

NTP_SERVER = "pool.ntp.org" # NTP host (default pool.ntp.org)
NTP_TZ = -5 # timezone offset in hours
NTP_DST = 1 # daylight saving (0=no, 1=yes)
NTP_INTERVAL = 3600 # re-sync interval (seconds)

# Optional tuning
NTP_TIMEOUT = 5 # socket timeout in seconds
NTP_CACHE_SECONDS = 0 # cache results (0 = always fetch)
NTP_REQUIRE_YEAR = 2022 # sanity check minimum year
11 changes: 11 additions & 0 deletions examples/fruitjam_time_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import time

from adafruit_fruitjam import FruitJam

fj = FruitJam()
now = fj.sync_time()
print("RTC set:", now)
print("Localtime:", time.localtime())
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ adafruit-circuitpython-requests
adafruit-circuitpython-bitmap-font
adafruit-circuitpython-display-text
adafruit-circuitpython-sd
adafruit-circuitpython-ntp
adafruit-circuitpython-connectionmanager