Skip to content

Commit d26e232

Browse files
authored
Merge pull request #115 from mikeysklar/portalbase-ntp
NTP support for PortalBase
2 parents c87f120 + 491c215 commit d26e232

File tree

3 files changed

+133
-1
lines changed

3 files changed

+133
-1
lines changed

adafruit_portalbase/network.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,108 @@ def process_json(self, json_data, json_path):
735735
values = json.dumps(json_data)
736736
return values
737737

738+
def _tz_from_env(self, default: float = 0.0) -> float:
739+
tz = default
740+
v = os.getenv("NTP_TZ")
741+
if v:
742+
try:
743+
tz = float(v)
744+
except ValueError:
745+
pass
746+
v = os.getenv("NTP_DST")
747+
if v:
748+
try:
749+
tz += float(v)
750+
except ValueError:
751+
pass
752+
return tz
753+
754+
def _socketpool_for_wifi(self):
755+
"""Return a SocketPool for whichever Wi-Fi backend is available.
756+
Works with native ESP32-S2/S3/C6 (wifi.radio) and ESP32SPI coprocessors.
757+
Some CP10 board wrappers may not expose .radio/.esp; in that case,
758+
try the native wifi.radio directly.
759+
"""
760+
wm = getattr(self, "_wifi", None)
761+
radio = getattr(wm, "radio", None)
762+
esp = getattr(wm, "esp", None) or getattr(wm, "_esp", None)
763+
764+
# CP10/MagTag fallback: wrapper didn't expose .radio/.esp -> use native radio
765+
if (radio is None) and (esp is None):
766+
try:
767+
import wifi as _wifi_mod # type: ignore
768+
769+
radio = getattr(_wifi_mod, "radio", None)
770+
except Exception:
771+
radio = None
772+
773+
target = radio if (radio is not None) else esp
774+
if target is None:
775+
raise RuntimeError("No WiFi radio/esp found")
776+
777+
# Prefer connection_manager helper, else direct SocketPool fallback
778+
try:
779+
from adafruit_connection_manager import get_radio_socketpool # lazy import
780+
781+
return get_radio_socketpool(target)
782+
except Exception:
783+
import socketpool # type: ignore
784+
785+
return socketpool.SocketPool(target)
786+
787+
def _wait_for_ready_optional(self, timeout: float = 10.0, poll: float = 0.05) -> None:
788+
wm = getattr(self, "_wifi", None)
789+
ready = getattr(wm, "esp32_ready", None)
790+
if ready is None:
791+
return
792+
start = time.monotonic()
793+
while time.monotonic() - start < timeout:
794+
if not ready.value:
795+
return
796+
time.sleep(poll)
797+
raise TimeoutError("ESP32 not responding")
798+
799+
def time_sync(self, server=None, timeout=None, retries=None, tz=None):
800+
server = server or os.getenv("NTP_SERVER", "0.adafruit.pool.ntp.org")
801+
retries = int(os.getenv("NTP_RETRIES", "8")) if retries is None else int(retries)
802+
timeout = float(os.getenv("NTP_TIMEOUT", "5.0")) if timeout is None else float(timeout)
803+
tz = self._tz_from_env() if tz is None else float(tz)
804+
805+
if not self.is_connected:
806+
self.connect()
807+
808+
try:
809+
self._wait_for_ready_optional()
810+
except Exception:
811+
pass
812+
813+
pool = self._socketpool_for_wifi()
814+
815+
last_exc = None
816+
for _ in range(max(1, retries)):
817+
try:
818+
from adafruit_ntp import NTP # lazy import for CI/tests
819+
820+
try:
821+
ntp = NTP(pool, server=server, tz=tz, socket_timeout=timeout)
822+
except TypeError:
823+
ntp = NTP(pool, server=server, tz_offset=tz, socket_timeout=timeout)
824+
825+
setter = getattr(ntp, "set_time", None)
826+
if setter:
827+
setter()
828+
return time.localtime()
829+
830+
now = ntp.datetime
831+
if rtc:
832+
rtc.RTC().datetime = now
833+
return now
834+
except Exception as ex:
835+
last_exc = ex
836+
time.sleep(0.5)
837+
838+
raise last_exc or RuntimeError("NTP sync failed")
839+
738840
@property
739841
def is_connected(self):
740842
"""Return whether we are connected."""

adafruit_portalbase/wifi_coprocessor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import adafruit_connection_manager
2626
import adafruit_requests
2727
import board
28-
from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
28+
from adafruit_esp32spi import adafruit_esp32spi
2929
from digitalio import DigitalInOut
3030

3131
__version__ = "0.0.0+auto.0"

examples/portalbase_time.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# SPDX-FileCopyrightText: 2025 Mikey Sklar for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
# Uncomment ONE board setup:
6+
7+
# --- PyPortal ---
8+
# from adafruit_pyportal import PyPortal
9+
# portal = PyPortal(status_neopixel=None)
10+
# net = portal.network
11+
12+
# --- MatrixPortal M4 ---
13+
# from adafruit_matrixportal.matrixportal import MatrixPortal
14+
# portal = MatrixPortal(status_neopixel=None)
15+
# net = portal.network
16+
17+
# --- MagTag (ESP32-S2 native WiFi) ---
18+
# from adafruit_magtag.magtag import MagTag
19+
# magtag = MagTag(status_neopixel=None)
20+
# net = magtag.network
21+
22+
# --- Fruit Jam (wrap its WiFi) ---
23+
from adafruit_fruitjam import FruitJam
24+
25+
jam = FruitJam(status_neopixel=None)
26+
net = jam.network
27+
28+
29+
# --- shared output ---
30+
print(net.time_sync())

0 commit comments

Comments
 (0)