-
Notifications
You must be signed in to change notification settings - Fork 0
Replace worldtimeapi.org HTTP calls with NTP time sync #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,9 +4,8 @@ | |||||||||||
| from badgeware import screen, PixelFont, shapes, brushes, io, run, Matrix | ||||||||||||
|
|
||||||||||||
| try: | ||||||||||||
| from urllib.urequest import urlopen | ||||||||||||
| import json | ||||||||||||
| import network | ||||||||||||
| import ntptime | ||||||||||||
| NETWORK_AVAILABLE = True | ||||||||||||
| except ImportError: | ||||||||||||
| NETWORK_AVAILABLE = False | ||||||||||||
|
|
@@ -68,12 +67,11 @@ def draw(self): | |||||||||||
| MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", | ||||||||||||
| "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] | ||||||||||||
|
|
||||||||||||
| # Cache for fetched time data | ||||||||||||
| _cached_time = None | ||||||||||||
| _last_fetch_attempt = 0 | ||||||||||||
| _fetch_success = False | ||||||||||||
| FETCH_INTERVAL = 60 * 60 * 1000 # Try to fetch once per hour (in milliseconds) when successful | ||||||||||||
| RETRY_INTERVAL = 5 * 1000 # Retry every 5 seconds (in milliseconds) when failed | ||||||||||||
| # NTP sync state | ||||||||||||
| _ntp_synced = False | ||||||||||||
| _ntp_sync_attempt = 0 | ||||||||||||
| _last_sync_attempt = 0 | ||||||||||||
| SYNC_RETRY_INTERVAL = 5 * 1000 # Retry NTP sync every 5 seconds (in milliseconds) when failed | ||||||||||||
|
|
||||||||||||
| # Network connection state | ||||||||||||
| WIFI_TIMEOUT = 60 | ||||||||||||
|
|
@@ -180,67 +178,68 @@ def wlan_start(): | |||||||||||
| pass | ||||||||||||
| return False | ||||||||||||
|
|
||||||||||||
| def fetch_current_date(): | ||||||||||||
| def sync_time_via_ntp(): | ||||||||||||
| """ | ||||||||||||
| Fetch current date from worldtimeapi.org | ||||||||||||
| Returns (year, month, day) tuple or None if fetch fails | ||||||||||||
| Sync system time using NTP | ||||||||||||
| Returns True if sync was successful, False otherwise | ||||||||||||
| """ | ||||||||||||
| global _cached_time, _last_fetch_attempt, _fetch_success | ||||||||||||
| global _ntp_synced, _ntp_sync_attempt, _last_sync_attempt | ||||||||||||
|
|
||||||||||||
| if not NETWORK_AVAILABLE: | ||||||||||||
| return None | ||||||||||||
| return False | ||||||||||||
|
|
||||||||||||
| if not connected: | ||||||||||||
| return None | ||||||||||||
| return False | ||||||||||||
|
|
||||||||||||
| # Return cached time if still valid | ||||||||||||
| current_ticks = io.ticks | ||||||||||||
| if _cached_time and _fetch_success and (current_ticks - _last_fetch_attempt < FETCH_INTERVAL): | ||||||||||||
| return _cached_time | ||||||||||||
| # If already synced, don't sync again | ||||||||||||
| if _ntp_synced: | ||||||||||||
| return True | ||||||||||||
|
|
||||||||||||
| # Check if we should retry (use shorter interval when failed) | ||||||||||||
| if not _fetch_success and _last_fetch_attempt > 0: | ||||||||||||
| if current_ticks - _last_fetch_attempt < RETRY_INTERVAL: | ||||||||||||
| return None # Return None to indicate we're still waiting to retry | ||||||||||||
| # Check if we should retry | ||||||||||||
| current_ticks = io.ticks | ||||||||||||
| if _ntp_sync_attempt > 0: | ||||||||||||
| if current_ticks - _last_sync_attempt < SYNC_RETRY_INTERVAL: | ||||||||||||
| return False # Still waiting to retry | ||||||||||||
|
|
||||||||||||
| # Update last fetch attempt timestamp before trying | ||||||||||||
| _last_fetch_attempt = current_ticks | ||||||||||||
| # Update last sync attempt timestamp | ||||||||||||
| _last_sync_attempt = current_ticks | ||||||||||||
|
||||||||||||
| _ntp_sync_attempt += 1 | ||||||||||||
|
||||||||||||
|
|
||||||||||||
| # Attempt to fetch current date from the API | ||||||||||||
| try: | ||||||||||||
| # Use worldtimeapi.org - a free API that doesn't require authentication | ||||||||||||
| # Reduced timeout to 3 seconds to keep badge responsive | ||||||||||||
| response = urlopen("https://worldtimeapi.org/api/timezone/Etc/UTC", timeout=3) | ||||||||||||
| try: | ||||||||||||
| data = response.read() | ||||||||||||
| time_data = json.loads(data) | ||||||||||||
| # datetime format: "2025-10-30T01:23:45.123456+00:00" | ||||||||||||
| datetime_str = time_data.get("datetime", "") | ||||||||||||
|
|
||||||||||||
| if datetime_str: | ||||||||||||
| # Parse the date part (YYYY-MM-DD) with validation | ||||||||||||
| try: | ||||||||||||
| if "T" not in datetime_str: | ||||||||||||
| raise ValueError("datetime string missing 'T' separator") | ||||||||||||
| date_part = datetime_str.split("T")[0] | ||||||||||||
| parts = date_part.split("-") | ||||||||||||
| if len(parts) != 3: | ||||||||||||
| raise ValueError("date part does not have three components") | ||||||||||||
| year, month, day = parts | ||||||||||||
| _cached_time = (int(year), int(month), int(day)) | ||||||||||||
| _fetch_success = True | ||||||||||||
| return _cached_time | ||||||||||||
| except (ValueError, TypeError) as parse_err: | ||||||||||||
| print(f"Failed to parse date from API response: {parse_err}") | ||||||||||||
| _fetch_success = False | ||||||||||||
| finally: | ||||||||||||
| response.close() | ||||||||||||
| # Sync time via NTP | ||||||||||||
| ntptime.settime() | ||||||||||||
| _ntp_synced = True | ||||||||||||
| print("NTP time sync successful") | ||||||||||||
| return True | ||||||||||||
| except Exception as e: | ||||||||||||
| # Network request failed, will retry on next attempt | ||||||||||||
| print(f"Failed to fetch time from internet: {e}") | ||||||||||||
| _fetch_success = False | ||||||||||||
| print(f"NTP sync failed (attempt {_ntp_sync_attempt}): {e}") | ||||||||||||
| return False | ||||||||||||
|
|
||||||||||||
| def get_current_date(): | ||||||||||||
| """ | ||||||||||||
| Get current date from system time (after NTP sync) | ||||||||||||
| Returns (year, month, day) tuple or None if time is not synced | ||||||||||||
| """ | ||||||||||||
| if not _ntp_synced: | ||||||||||||
| return None | ||||||||||||
|
|
||||||||||||
| return None | ||||||||||||
| try: | ||||||||||||
| # Get current time from system | ||||||||||||
| # time.localtime() returns: (year, month, day, hour, minute, second, weekday, yearday) | ||||||||||||
| current_time = time.localtime() | ||||||||||||
| year = current_time[0] | ||||||||||||
| month = current_time[1] | ||||||||||||
| day = current_time[2] | ||||||||||||
|
|
||||||||||||
| # Sanity check: if year is unreasonable, NTP didn't actually work | ||||||||||||
| # Valid range: 2025-2100 (the badge was created in 2025) | ||||||||||||
| if year < 2025 or year > 2100: | ||||||||||||
|
Comment on lines
+235
to
+236
|
||||||||||||
| # Valid range: 2025-2100 (the badge was created in 2025) | |
| if year < 2025 or year > 2100: | |
| # Valid lower bound: badge was created in 2025, but allow margin | |
| MIN_VALID_YEAR = 2024 | |
| if year < MIN_VALID_YEAR: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once NTP sync succeeds, the function returns early and never attempts to re-sync. If the system time drifts significantly or the device runs for an extended period, the time could become inaccurate. Consider implementing periodic re-synchronization (e.g., once per day) to maintain accuracy over longer runtime periods.