Learning clock for classrooms - Inky Frame - NTPtime retrival?

Hi everybody, I build a learning clock for my classroom. I teach second grade in Belgium and my childern (ages 7-8) learn the analoge clock to quarter of an hour precise. So, I used a Inky Frame 7.3 to accompany an analoge clock and explain the hands of the clock duing the day. So, its almost/now/beyond hour/quarter past/half hour/quarter to.

The clock works great, the data is shown nicely I think. But I always get an error from the NTP time module:

MPY: soft reboot
Syncing RTC with NTP server...
Failed to sync with NTP server: -6
Starting main loop...

On computer, the code then uses my clock of my laptop to sync. On battery, the Inky assumes 12.00. This is my full code:

import time
import ntptime
import inky_frame
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY  # 7.3"

# Set tz_offset to be the number of hours off of UTC for your local zone.
tz_offset = 0
tz_seconds = tz_offset * 3600

# Font sizes
TOP_TEXT_SCALE = 12  # Font size for the top 1/3 of the screen
BOTTOM_TEXT_SCALE = 12  # Font size for the bottom 2/3 of the screen

def sync_rtc_from_ntp():
    try:
        print("Syncing RTC with NTP server...")
        ntptime.settime()  # Sync time from NTP server
        print("RTC synced with NTP server.")
    except Exception as e:
        print(f"Failed to sync with NTP server: {e}")

def time_to_text(hour, minute):
    # Convert hour to 12-hour format
    hour_12 = hour % 12 or 12
    next_hour = (hour + 1) % 12 or 12

    if 58 <= minute or minute <= 2:
        return f"Het is nu {hour_12} uur.", (255, 255, 255), (255, 165, 0)  # white, orange
    elif 3 <= minute <= 7:
        return f"Het is voorbij {hour_12} uur.", (255, 0, 0), (255, 165, 0)  # red, orange
    elif 8 <= minute <= 12:
        return f"Het is bijna kwart over {hour_12}.", (0, 128, 0), (0, 128, 0)  # dark green, dark green
    elif 13 <= minute <= 17:
        return f"Het is nu kwart over {hour_12}.", (255, 255, 255), (0, 128, 0)  # white, dark green
    elif 18 <= minute <= 22:
        return f"Het is voorbij kwart over {hour_12}.", (255, 0, 0), (0, 128, 0)  # red, dark green
    elif 23 <= minute <= 27:
        return f"Het is bijna half {next_hour}.", (0, 128, 0), (255, 0, 0)  # dark green, red
    elif 28 <= minute <= 32:
        return f"Het is nu half {next_hour}.", (255, 255, 255), (255, 0, 0)  # white, red
    elif 33 <= minute <= 37:
        return f"Het is voorbij half {next_hour}.", (255, 0, 0), (255, 0, 0)  # red, red
    elif 38 <= minute <= 42:
        return f"Het is bijna kwart voor {next_hour}.", (0, 128, 0), (0, 0, 255)  # dark green, blue
    elif 43 <= minute <= 47:
        return f"Het is nu kwart voor {next_hour}.", (255, 255, 255), (0, 0, 255)  # white, blue
    elif 48 <= minute <= 52:
        return f"Het is voorbij kwart voor {next_hour}.", (255, 0, 0), (0, 0, 255)  # red, blue
    elif 53 <= minute <= 57:
        return f"Het is bijna {hour_12} uur.", (0, 128, 0), (255, 165, 0)  # dark green, orange
    return f"Het is nu {hour_12} uur.", (255, 255, 255), (255, 255, 255)  # default white

def split_time_text(time_text):
    parts = time_text.split(" ")
    if parts[2] in ["bijna", "voorbij", "nu"]:
        first_part = f"Het is {parts[2]}"
        second_part = " ".join(parts[3:])
    else:
        first_part = "Het is nu"
        second_part = " ".join(parts[2:])
    return first_part, second_part

def update_display():
    graphics = PicoGraphics(DISPLAY)
    WIDTH, HEIGHT = graphics.get_bounds()
    last_update_minute = -1  # Initialized to -1 to ensure the first update occurs

    while True:
        current_time = time.localtime()
        year, month, day, hour, minute, second, dow, _ = current_time

        # Adjust for time zone offset
        adjusted_hour = (hour + tz_offset) % 24

        # Update only at specific minute intervals
        if minute in [58, 3, 8, 13, 18, 23, 28, 33, 38, 43, 48, 53] and minute != last_update_minute:
            last_update_minute = minute
            # Determine background color for the top 1/3 of the screen
            time_text, top_color, bottom_color = time_to_text(adjusted_hour, minute)
            first_part, second_part = split_time_text(time_text)

            # Set the top part background color
            graphics.set_pen(graphics.create_pen(*top_color))
            graphics.rectangle(0, 0, WIDTH, HEIGHT // 3)

            # Set the line color
            graphics.set_pen(graphics.create_pen(0, 0, 0))
            graphics.rectangle(0, HEIGHT // 3 - 1, WIDTH, 1)  # Black line between top and bottom parts

            # Set the background color for the bottom 2/3 part
            graphics.set_pen(graphics.create_pen(*bottom_color))
            graphics.rectangle(0, HEIGHT // 3, WIDTH, 2 * HEIGHT // 3)

            # Convert text for both parts
            graphics.set_font("bitmap8")

            # Calculate positions for the two parts
            top_text_height = 8 * TOP_TEXT_SCALE
            bottom_text_height = 8 * BOTTOM_TEXT_SCALE

            first_part_width = graphics.measure_text(first_part, scale=TOP_TEXT_SCALE)
            second_part_width = graphics.measure_text(second_part, scale=BOTTOM_TEXT_SCALE)

            offset_left_first = (WIDTH - first_part_width) // 2
            offset_top_first = (HEIGHT // 3 - top_text_height) // 2

            offset_left_second = (WIDTH - second_part_width) // 2
            offset_top_second = (2 * HEIGHT // 3 - bottom_text_height) // 2 + HEIGHT // 3

            # Draw the text
            graphics.set_pen(graphics.create_pen(0, 0, 0))  # Set text color to black for readability
            graphics.text(first_part, offset_left_first, offset_top_first, scale=TOP_TEXT_SCALE)
            graphics.text(second_part, offset_left_second, offset_top_second, scale=BOTTOM_TEXT_SCALE)

            graphics.update()

        # Wait for a while before checking the time again
        time.sleep(5)

def sync_rtc_daily():
    while True:
        current_time = time.localtime()
        year, month, day, hour, minute, second, dow, _ = current_time

        # Adjust for time zone offset
        adjusted_hour = (hour + tz_offset) % 24

        if adjusted_hour == 4 and minute == 0:
            sync_rtc_from_ntp()
            print("RTC synced with NTP server at 4 AM.")
            print("Local time after syncing RTC:", time.localtime())

        # Wait for a while before checking the time again
        time.sleep(60)

def main():
    sync_rtc_from_ntp()  # Initial sync with NTP server
    print("Starting main loop...")

    while True:
        update_display()
        sync_rtc_daily()

# Run the main function
main()


I got it. I was doing stupid things

import time
import uasyncio
import WIFI_CONFIG
import inky_frame
from network_manager import NetworkManager
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY  # 7.3"

# Set tz_offset to be the number of hours off of UTC for your local zone.
tz_offset = 2
tz_seconds = tz_offset * 3600

# Font sizes
TOP_TEXT_SCALE = 12  # Font size for the top 1/3 of the screen
BOTTOM_TEXT_SCALE = 12  # Font size for the bottom 2/3 of the screen

def time_to_text(hour, minute):
    # Convert hour to 12-hour format
    hour_12 = hour % 12 or 12
    next_hour = (hour + 1) % 12 or 12

    if 58 <= minute or minute <= 2:
        return f"Het is nu {hour_12} uur", (255, 255, 255), (255, 165, 0)  # white, orange
    elif 3 <= minute <= 7:
        return f"Het is voorbij {hour_12} uur", (255, 0, 0), (255, 165, 0)  # red, orange
    elif 8 <= minute <= 12:
        return f"Het is bijna kwart over {hour_12}", (34, 139, 34), (34, 139, 34)  # light green, light green
    elif 13 <= minute <= 17:
        return f"Het is nu kwart over {hour_12}", (255, 255, 255), (34, 139, 34)  # white, light green
    elif 18 <= minute <= 22:
        return f"Het is voorbij kwart over {hour_12}", (255, 0, 0), (34, 139, 34)  # red, light green
    elif 23 <= minute <= 27:
        return f"Het is bijna half {next_hour}", (34, 139, 34), (255, 0, 0)  # light green, red
    elif 28 <= minute <= 32:
        return f"Het is nu half {next_hour}", (255, 255, 255), (255, 0, 0)  # white, red
    elif 33 <= minute <= 37:
        return f"Het is voorbij half {next_hour}", (255, 0, 0), (255, 0, 0)  # red, red
    elif 38 <= minute <= 42:
        return f"Het is bijna kwart voor {next_hour}", (34, 139, 34), (0, 0, 255)  # light green, blue
    elif 43 <= minute <= 47:
        return f"Het is nu kwart voor {next_hour}", (255, 255, 255), (0, 0, 255)  # white, blue
    elif 48 <= minute <= 52:
        return f"Het is voorbij kwart voor {next_hour}", (255, 0, 0), (0, 0, 255)  # red, blue
    elif 53 <= minute <= 57:
        return f"Het is bijna {hour_12} uur", (34, 139, 34), (255, 165, 0)  # light green, orange
    return f"Het is nu {hour_12} uur", (255, 255, 255), (255, 255, 255)  # default white

def split_time_text(time_text):
    parts = time_text.split(" ")
    if parts[2] in ["bijna", "voorbij", "nu"]:
        first_part = f"Het is {parts[2]}"
        second_part = " ".join(parts[3:])
    else:
        first_part = "Het is nu"
        second_part = " ".join(parts[2:])
    return first_part, second_part

def update_display():
    graphics = PicoGraphics(DISPLAY)
    WIDTH, HEIGHT = graphics.get_bounds()
    last_update_minute = -1  # Initialized to -1 to ensure the first update occurs

    while True:
        current_time = time.localtime()
        year, month, day, hour, minute, second, dow, _ = current_time

        # Adjust for time zone offset
        adjusted_hour = (hour + tz_offset) % 24

        # Update only at specific minute intervals
        if minute in [58, 3, 8, 13, 18, 23, 28, 33, 38, 43, 48, 53] and minute != last_update_minute:
            last_update_minute = minute
            print(f"Updating display at {hour:02}:{minute:02}:{second:02}")

            # Determine background color for the top 1/3 of the screen
            time_text, top_color, bottom_color = time_to_text(adjusted_hour, minute)
            print(f"Time text: {time_text}, Top color: {top_color}, Bottom color: {bottom_color}")

            first_part, second_part = split_time_text(time_text)
            print(f"First part: {first_part}, Second part: {second_part}")

            # Set the top part background color
            graphics.set_pen(graphics.create_pen(*top_color))
            graphics.rectangle(0, 0, WIDTH, HEIGHT // 3)

            # Set the line color
            graphics.set_pen(graphics.create_pen(0, 0, 0))
            graphics.rectangle(0, HEIGHT // 3 - 1, WIDTH, 1)  # Black line between top and bottom parts

            # Set the background color for the bottom 2/3 part
            graphics.set_pen(graphics.create_pen(*bottom_color))
            graphics.rectangle(0, HEIGHT // 3, WIDTH, 2 * HEIGHT // 3)

            # Convert text for both parts
            graphics.set_font("bitmap8")

            # Calculate positions for the two parts
            top_text_height = 8 * TOP_TEXT_SCALE
            bottom_text_height = 8 * BOTTOM_TEXT_SCALE

            first_part_width = graphics.measure_text(first_part, scale=TOP_TEXT_SCALE)
            second_part_width = graphics.measure_text(second_part, scale=BOTTOM_TEXT_SCALE)

            offset_left_first = (WIDTH - first_part_width) // 2
            offset_top_first = (HEIGHT // 3 - top_text_height) // 2

            offset_left_second = (WIDTH - second_part_width) // 2
            offset_top_second = (2 * HEIGHT // 3 - bottom_text_height) // 2 + HEIGHT // 3

            # Draw the text
            graphics.set_pen(graphics.create_pen(0, 0, 0))  # Set text color to black for readability
            graphics.text(first_part, offset_left_first, offset_top_first, scale=TOP_TEXT_SCALE)
            graphics.text(second_part, offset_left_second, offset_top_second, scale=BOTTOM_TEXT_SCALE)

            graphics.update()
            print("Display updated.")

        # Wait for a while before checking the time again
        time.sleep(5)
        print("Sleeping for 5 seconds...")

# Sync the Inky (always on) RTC to the Pico W so that "time.localtime()" works.
print("Syncing RTC...")
inky_frame.pcf_to_pico_rtc()
print("RTC synced.")

# Avoid running code unless we've been triggered by an event
if inky_frame.woken_by_rtc() or inky_frame.woken_by_button():
    print("Device woken up by RTC or button.")
    def status_handler(mode, status, ip):
        print(f"Network status: Mode={mode}, Status={status}, IP={ip}")

    year, month, day, hour, minute, second, dow, _ = time.localtime(time.time() + tz_seconds)
    print(f"Current local time: {year:04}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}")

    # Connect to the network and get the time if it's not set
    if year < 2023:
        print("Year is less than 2023, attempting to connect to network...")
        connected = False
        network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler, client_timeout=60)

        t_start = time.time()
        try:
            uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
            connected = True
        except RuntimeError as e:
            print(f"Runtime error: {str(e)}")
        except Exception as e:
            print(f"Error: {str(e)}")
        t_end = time.time()

        if connected:
            inky_frame.set_time()
            print("Setting time from network...")
            print(f"Connection took: {t_end - t_start}s")
        else:
            print("Failed to connect to network.")

    # Start updating the display with current time
    update_display()
else:
    print("Device not woken up by RTC or button.")
1 Like