@@ -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."""
0 commit comments