forked from micropython/micropython
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Open
Labels
Description
Ideally setting up NTP on an integrated AirLift enabled board like Fruit Jam, PyPortal or MatrixPortal M4 would be as easy as :
import adafruit_airlift_ntp as ntp
ntp.sync() # done
It actually takes quite a bit of setup currently. @b-blake pointed this out and we put together a little helper library. Would it make sense for me to test and submit an adafruit_airlift_ntp library to the bundle or is there a better way to do this?
working example tested on Fruit Jam only.
settings.toml
# Wi-Fi credentials
CIRCUITPY_WIFI_SSID = "YourNetworkName"
CIRCUITPY_WIFI_PASSWORD = "YourPassword"
# NTP settings
# Host to query (default pool.ntp.org if not set)
CIRCUITPY_NTP_HOST = "pool.ntp.org"
# Time zone offset in hours relative to UTC
# Use -8 for Pacific Standard Time, -7 for Pacific Daylight Time, etc.
CIRCUITPY_NTP_TZ_OFFSET = -7
# How often to re-sync, in seconds (example: every 6 hours)
CIRCUITPY_NTP_INTERVAL = 21600
code.py
import time
import fruitjam_ntp
tz, host = fruitjam_ntp.sync()
print("NTP OK:", host, "tz:", tz, "->", time.localtime())
fruitjam_ntp.release()
lib/fruitjam_ntp.py
# fruitjam_ntp.py — robust NTP sync for Fruit Jam (ESP32SPI/AirLift)
# - Creates DigitalInOut pins ONCE and reuses them (avoids "ESP_CS in use")
# - Reads Wi-Fi + NTP prefs from settings.toml
# - Adds simple retry / reset on transient ESP errors
import os, time, rtc, board
from digitalio import DigitalInOut
from adafruit_esp32spi import adafruit_esp32spi
import adafruit_connection_manager
import adafruit_ntp
# Module-singletons so multiple sync() calls don't re-grab pins
_SPI = None
_CS = _RDY = _RST = None
_ESP = None
_POOL = None
def _ensure_radio():
"""Create and cache SPI + ESP32SPI/SocketPool once."""
global _SPI, _CS, _RDY, _RST, _ESP, _POOL
if _ESP and _POOL:
return _ESP, _POOL
if _SPI is None:
_SPI = board.SPI() # shared board SPI is fine here
if _CS is None:
_CS = DigitalInOut(board.ESP_CS)
if _RDY is None:
_RDY = DigitalInOut(board.ESP_BUSY)
if _RST is None:
_RST = DigitalInOut(board.ESP_RESET)
if _ESP is None:
_ESP = adafruit_esp32spi.ESP_SPIcontrol(_SPI, _CS, _RDY, _RST)
if _POOL is None:
_POOL = adafruit_connection_manager.get_radio_socketpool(_ESP)
return _ESP, _POOL
def sync(default_tz=0, retries=2, retry_delay=1.0):
"""
Sync RTC from NTP using settings.toml.
Returns (tz, host).
"""
ssid = os.getenv("CIRCUITPY_WIFI_SSID")
pwd = os.getenv("CIRCUITPY_WIFI_PASSWORD")
if not ssid or not pwd:
raise RuntimeError("Add CIRCUITPY_WIFI_SSID/PASSWORD to settings.toml")
tz = int(os.getenv("CIRCUITPY_NTP_TZ_OFFSET", str(default_tz)))
host = os.getenv("CIRCUITPY_NTP_HOST", "pool.ntp.org")
esp, pool = _ensure_radio()
# Connect / reconnect with light retries
for attempt in range(retries + 1):
try:
if not esp.is_connected:
esp.connect_AP(ssid, pwd)
break
except Exception as e:
if attempt >= retries:
raise
try:
esp.reset() # hardware toggle via RST pin
except Exception:
pass
time.sleep(retry_delay)
ntp = adafruit_ntp.NTP(pool, tz_offset=tz, server=host, cache_seconds=3600)
rtc.RTC().datetime = ntp.datetime
return tz, host
def release():
"""
Explicitly release pins if you want to force-free them before a soft reload.
Not required for normal operation, but handy if you’re experimenting.
"""
global _SPI, _CS, _RDY, _RST, _ESP, _POOL
try:
# ESP_SPIcontrol doesn't expose deinit; releasing pins is enough.
if _CS:
_CS.deinit()
if _RDY:
_RDY.deinit()
if _RST:
_RST.deinit()
finally:
_SPI = _CS = _RDY = _RST = _ESP = _POOL = None
b-blake