Have PICO get time from host over USB connection?

I have some code I’m running on my Tufty I will post at the end.
The plan was to have it show the battery voltage and % left when on battery across the top of the screen. This works via the usb_power function. And when on USB power and plugged into my desktop PC, show the Time and Date instead of the battery info. This sort of works.
Time and Date display, with the correct time and date if I run my file “from” Thonny. If I just turn on the tufty, while plugged in, I get 0:00~AM and 01:01:2021. It never syncs the time with the PC.
Question is, is there a quick and dirty way to have it get the time from the host PC?
I will likely add an RV3028 at some point. Still, I got to wondering if i could get this to work anyway?

import time
import machine
import picographics
from picographics import PicoGraphics, DISPLAY_TUFTY_2040, PEN_RGB332
display = PicoGraphics(display=DISPLAY_TUFTY_2040, rotate=180, pen_type=PEN_RGB332)
display.set_backlight(1.0)
display.set_font("bitmap8")

rtc = machine.RTC()
#rtc.set_time(second, minute, hour, weekday, day, month, year)

print(f"Getting time from Pico RTC/Thonny: {rtc.datetime()}")
year, month, day, weekday, hour, minute, second, microsecond = rtc.datetime()

from breakout_bme280 import BreakoutBME280
from pimoroni_i2c import PimoroniI2C
i2c = PimoroniI2C(sda=(4), scl=(5))
bme = BreakoutBME280(i2c)

from machine import ADC, Pin

# set up the ADCs for measuring battery voltage
vbat_adc = ADC(29)
vref_adc = ADC(28)
vref_en = Pin(27)
vref_en.init(Pin.OUT)
vref_en.value(0)
usb_power = Pin(24, Pin.IN)         # reading GP24 tells us whether or not USB power is connected

# Reference voltages for a full/empty battery, in volts
# the values could vary by battery size/manufacturer so you might need to adjust them
# Values for a Galleon 400mAh LiPo:
full_battery = 3.7
empty_battery = 2.5
         

from pimoroni import Button
button_a = Button(7, invert=False)
button_b = Button(8, invert=False)
button_c = Button(9, invert=False)
button_up = Button(22, invert=False)
button_down = Button(6, invert=False)
button_boot = Button(23, invert=True)

tempcolor = display.create_pen(0, 255, 0)  # this colour will get changed in a bit
humidcolor = display.create_pen(0, 255, 0)  # this colour will get changed in a bit
presscolor = display.create_pen(0, 255, 0)  # this colour will get changed in a bit
white = display.create_pen(255, 255, 255)
black = display.create_pen(0, 0, 0)
red = display.create_pen(255, 0, 0)
green = display.create_pen(0, 255, 0)
blue = display.create_pen(0, 0, 255)
yellow = display.create_pen(255, 255, 0)
orange = display.create_pen(255, 140, 0)
grey = display.create_pen(120, 120, 120)
violet = display.create_pen(255, 0, 255)
cyan = display.create_pen(0, 255, 255)

start_time = time.time()

display.set_pen(black)
display.clear()

# converts the temperature into a description and pen colour
def describe_temperature(temperature):
    global tempcolor
    if temperature <= 0:
        description = "Very Cold"
        tempcolor = blue
    elif 0 < temperature <= 12:
        description = "Cold"
        tempcolor = yellow
    elif 12 < temperature < 17:
        description = "Cool"
        tempcolor = green
    elif 17 <= temperature <= 24:
        description = "OK"
        tempcolor = green      
    elif 24 < temperature <= 27:
        description = "Hot"
        tempcolor = orange
    elif temperature > 27:
        description = "Very Hot"
        tempcolor = red
    else:
        description = ""
        tempcolor = black
    return description

# converts humidity into good/bad description and pen color
def describe_humidity(humidity):
    global humidcolor
    if humidity < 30:
        description = "Low - Dry"
        humidcolor = orange
    elif 30 <= humidity <= 60:
        description = "OK"
        humidcolor = green
    elif 60 < humidity < 80:
        description = "High"
        humidcolor = yellow
    elif humidity >= 80:
        description = "Very High"
        humidcolor = red        
    else:
        description = ""
        humidcolor = black        
    return description

# converts pressure into barometer-type description and pen color
def describe_pressure(pressure):
    global presscolor
    if pressure < 982:
        description = "Very Low"
        presscolor = red
    elif 982 <= pressure < 1004:
        description = "Low"
        presscolor = yellow
    elif 1004 <= pressure < 1026:
        description = "OK"
        presscolor = green
    elif 1026 <= pressure < 1048:
        description = "High"
        presscolor = blue
    elif pressure >= 1048:
        description = "Very High"
        presscolor = orange
    else:
        description = ""
        presscolor = black
    return description
 

def draw_graph(temp_value, press_value, humid_value):
    scaled_temp = int((temperature * 5) + 160)
    scaled_humid = int((humidity * (320 / 100)) - 10) 
    scaled_press = int(((pressuremb - 960) * 3) - 10)

    describe_temperature(temperature)
    display.set_pen(tempcolor)
    display.rectangle(0, 80, (scaled_temp), 19)
    display.circle((scaled_temp), 89, 9)
    display.text("{:0.0f}c".format(temperature), 0, 40, scale=4)
    display.text(describe_temperature(temperature), 150, 40, scale=4)
    display.set_pen(black)
    display.circle((scaled_temp), 89, 5) 
    
    describe_humidity(humidity)
    display.set_pen(humidcolor)
    display.rectangle(0, 150, (scaled_humid), 19)
    display.circle((scaled_humid), 159, 9)
    display.text("{:0.0f}%".format(humidity), 0, 110, scale=4)
    display.text(describe_humidity(humidity), 150, 110, scale=4)
    display.set_pen(black)
    display.circle((scaled_humid), 159, 5)
    
    describe_pressure(pressuremb)
    display.set_pen(presscolor)
    display.rectangle(0, 220, (scaled_press), 19)
    display.circle((scaled_press), 229, 9)
    display.text("{:0.0f}mb".format(pressuremb), 0, 180, scale=4)
    display.text(describe_pressure(pressuremb), 150, 180, scale=4)
    display.set_pen(black)
    display.circle((scaled_press), 229, 5)
    
    rtc.datetime((year, month, day, 0, hour, minute, second, 0))


while True:
    
    if button_a.is_pressed:
        print ("Button A pressed")
        
    elif button_b.is_pressed:
        print ("Button B pressed")
        
    elif button_c.is_pressed:
        print ("Button C pressed")
        
    elif button_up.is_pressed:
        display.set_backlight(1.0)

    elif button_down.is_pressed:
        display.set_backlight(0.5)

    elif button_boot.is_pressed:
        print ("Button BOOT/USR pressed")
        
        time.sleep(1)

    
    time_elapsed = time.time() - start_time
    
    year, month, day, wd, hour, minute, second, _ = rtc.datetime()

    last_second = second
                   

            
    temperature, pressure, humidity = bme.read()
    pressuremb = pressure / 100      
    draw_graph(temperature, pressure, humidity)
    
    temperature = round(temperature)
    humidity = round(humidity)
    pressure = round(pressure)
    
    vref_en.value(1)

    
    vdd = 1.24 * (65535 / vref_adc.read_u16())
    vbat = (
        (vbat_adc.read_u16() / 65535) * 3 * vdd
    )

    vref_en.value(0)

    # Print out the voltage
    print("Battery Voltage = ", vbat, "V", sep="")

    # convert the raw ADC read into a voltage, and then a percentage
    percentage = 100 * ((vbat - empty_battery) / (full_battery - empty_battery))
    if percentage > 100:
        percentage = 100
    if percentage < 0:
        percentage = 0
    
    if usb_power.value() == 1:
        display.set_pen(cyan)
        display.text(f"{day:02}:{month:02}:{year:02}", 180, 0, scale=3)  
        #display.text("year, month, day,rtc_date", 170, 0, scale=3)
        if hour <10:
            display.text(f"{hour:1}:{minute:02}~AM", 0, 0, scale=3)
        elif 10 <= hour < 12:
            display.text(f"{hour:2}:{minute:02}~AM", 0, 0, scale=3)
        elif hour == 12:
            display.text(f"{hour:2}:{minute:02}~PM", 0, 0, scale=3)    
        elif hour >12:
            hour = hour - 12
            if hour <10:
                display.text(f"{hour:1}:{minute:02}~PM", 0, 0, scale=3)
            elif 10 <= hour < 12:
                display.text(f"{hour:2}:{minute:02}~PM", 0, 0, scale=3)
            elif hour == 12:
                display.text(f"{hour:2}:{minute:02}~AM", 0, 0, scale=3)    
    else:
        if percentage >= 50:
            display.set_pen(green)
            display.text("Battery", 0, 0, scale=3)
            display.text("{:0.2f}v".format(vbat), 150, 0, scale=3)
            display.text("{:0.0f}%".format(percentage), 255, 0, scale=3)
        elif 50 < percentage > 20:   
            display.set_pen(yellow)
            display.text("{:0.2f}v".format(vbat), 150, 0, scale=3)
            display.text("{:0.0f}%".format(percentage), 255, 0, scale=3)
        elif percentage <= 20:    
            display.set_pen(red)
            display.text("{:0.2f}v".format(vbat), 150, 0, scale=3)
            display.text("{:0.0f}%".format(percentage), 255, 0, scale=3)

    if time_elapsed < 0.5:
        temperature, pressure, humidity = bme.read()
        display.set_pen(black)
        display.clear()
        display.update()
        time.sleep(1)
    else:
        display.update()
        time.sleep(1)
        display.set_pen(black)
        display.clear()    
        time.sleep(1)

Using your code with 199 onwards commented out on my Tufty I get

%Run -c $EDITOR_CONTENT
Getting time from Pico RTC/Thonny: (2023, 1, 23, 0, 19, 59, 28, 0)

which is correct. No bme fitted so not initialised. Windows 10 PC.

Best of luck

I get that too, it gets the time “if run from Thonny”.

>>> %Run -c $EDITOR_CONTENT
Getting time from Pico RTC/Thonny: (2023, 1, 23, 0, 16, 35, 19, 0)

If run on boot up as main.py, I get
0:00~AM and 01:01:2021 displayed on the LCD.
It never syncs the time with the PC. It’s plugged into and powered by the PC’s USB port.

I created a quick and dirty program:

import time
from time import sleep
from picographics import PicoGraphics, DISPLAY_TUFTY_2040, PEN_RGB332
display = PicoGraphics(display=DISPLAY_TUFTY_2040,pen_type=PEN_RGB332)
WIDTH, HEIGHT = display.get_bounds()
RED = display.create_pen(255, 0, 0)
BLACK = display.create_pen(0, 0, 0)

display.set_backlight(1.0)
display.set_font("bitmap8")

display.set_pen(RED)
st = time.time()
rtc = machine.RTC()
rtc.datetime((2024, 1, 23, 1, 13, 10, 32, 0))
year, month, day, weekday, hour, minute, second, microsecond = rtc.datetime()

while True:
    display.set_pen(RED)
    st = time.time()
    rtc = machine.RTC()
    display.text("TIME: ", 1, 1, 240, 5)
    display.text(f"{rtc.datetime()}",1,100,240,2)
    display.text(f"{st}",1,150,240,2)
    rtc = machine.RTC()
    display.update()
    sleep(1)
    display.set_pen(BLACK)
    display.clear()
    sleep (.25)

I had the same issue as you, @alphanumeric, so I had to define the values. Thonny seems to pass them to Tufty 2040 during execution. Tested even with an attached battery pack powered up. No matter it reverted back to the old time, unless I defined the initial values.

You might need to define them the same way a clock radio needs you to reset upon a power-outage.

Found the following:

time – time related functions — MicroPython latest documentation.

I’m just going to add an RV3028 RTC. Even when its set via Thonny, its drifting / gaining time. It’s not accurate. For now I’m just going to display the battery info.
I have very similar code running on a sort of Tufty Clone. Pico Lipo, Display pack V2, BME280 and an RV3028. I was building it right around the time the Tufty was announced.
I was trying to duplicate the code on the Tufty. Thought I might be able to sneak buy, for now, without the RV3028. On that setup I set the RV3028 once. Then update the PICO RTC from it on bootup. That one keeps accurate time when on battery, or when on a USB power supply.

import utime
import picographics
from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY_2, PEN_RGB332
display = PicoGraphics(display=DISPLAY_PICO_DISPLAY_2, rotate=0, pen_type=PEN_RGB332)
display.set_backlight(1.0)
display.set_font("bitmap8") 

from breakout_bme280 import BreakoutBME280
from pimoroni_i2c import PimoroniI2C
i2c = PimoroniI2C(sda=(4), scl=(5))
bme = BreakoutBME280(i2c,0x77)

from breakout_rtc import BreakoutRTC
from machine import RTC
RV3028 = BreakoutRTC(i2c)

rtc = BreakoutRTC(i2c)
if rtc.is_12_hour:
    rtc.set_24_hour()

from machine import ADC, Pin
vsys = ADC(29)
charging = Pin(24, Pin.IN)
conversion_factor = 3 * 3.3 / 65535

full_battery = 4.2    
empty_battery = 2.8          

from pimoroni import Button
button_a = Button(12)
button_b = Button(13)
button_x = Button(14)
button_y = Button(15)

from pimoroni import RGBLED
led = RGBLED(6, 7, 8)
led.set_rgb(0, 0, 0) 

tempcolor = display.create_pen(0, 255, 0)  # this colour will get changed in a bit
humidcolor = display.create_pen(0, 255, 0)  # this colour will get changed in a bit
presscolor = display.create_pen(0, 255, 0)  # this colour will get changed in a bit
white = display.create_pen(255, 255, 255)
black = display.create_pen(0, 0, 0)
red = display.create_pen(255, 0, 0)
green = display.create_pen(0, 255, 0)
blue = display.create_pen(0, 0, 255)
yellow = display.create_pen(255, 255, 0)
orange = display.create_pen(255, 140, 0)
grey = display.create_pen(120, 120, 120)
violet = display.create_pen(255, 0, 255)
cyan = display.create_pen(0, 255, 255)

start_time = utime.time()

display.set_pen(black)
display.clear()

# converts the temperature into a description and pen colour
def describe_temperature(temperature):
    global tempcolor
    if temperature <= 0:
        description = "Very Cold"
        tempcolor = blue
    elif 0 < temperature <= 12:
        description = "Cold"
        tempcolor = yellow
    elif 12 < temperature < 17:
        description = "Cool"
        tempcolor = green
    elif 17 <= temperature <= 24:
        description = "OK"
        tempcolor = green      
    elif 24 < temperature <= 27:
        description = "Hot"
        tempcolor = orange
    elif temperature > 27:
        description = "Very Hot"
        tempcolor = red
    else:
        description = ""
        tempcolor = black
    return description

# converts humidity into good/bad description and pen color
def describe_humidity(humidity):
    global humidcolor
    if humidity < 30:
        description = "Low - Dry"
        humidcolor = orange
    elif 30 <= humidity <= 60:
        description = "OK"
        humidcolor = green
    elif 60 < humidity < 80:
        description = "High"
        humidcolor = yellow
    elif humidity >= 80:
        description = "Very High"
        humidcolor = red        
    else:
        description = ""
        humidcolor = black        
    return description

# converts pressure into barometer-type description and pen color
def describe_pressure(pressure):
    global presscolor
    if pressure < 982:
        description = "Very Low"
        presscolor = red
    elif 982 <= pressure < 1004:
        description = "Low"
        presscolor = yellow
    elif 1004 <= pressure < 1026:
        description = "OK"
        presscolor = green
    elif 1026 <= pressure < 1048:
        description = "High"
        presscolor = blue
    elif pressure >= 1048:
        description = "Very High"
        presscolor = orange
    else:
        description = ""
        presscolor = black
    return description


def draw_graph(temp_value, press_value, humid_value):
    scaled_temp = int((temperature * 5) + 160)
    scaled_humid = int((humidity * (320 / 100)) - 10) 
    scaled_press = int(((pressuremb - 960) * 3) - 10)

    describe_temperature(temperature)
    display.set_pen(tempcolor)
    display.rectangle(0, 80, (scaled_temp), 19)
    display.circle((scaled_temp), 89, 9)
    display.text("{:0.0f}c".format(temperature), 0, 40, scale=4)
    display.text(describe_temperature(temperature), 150, 40, scale=4)
    display.set_pen(black)
    display.circle((scaled_temp), 89, 5) 
    
    describe_humidity(humidity)
    display.set_pen(humidcolor)
    display.rectangle(0, 150, (scaled_humid), 19)
    display.circle((scaled_humid), 159, 9)
    display.text("{:0.0f}%".format(humidity), 0, 110, scale=4)
    display.text(describe_humidity(humidity), 150, 110, scale=4)
    display.set_pen(black)
    display.circle((scaled_humid), 159, 5)
    
    describe_pressure(pressuremb)
    display.set_pen(presscolor)
    display.rectangle(0, 220, (scaled_press), 19)
    display.circle((scaled_press), 229, 9)
    display.text("{:0.0f}mb".format(pressuremb), 0, 180, scale=4)
    display.text(describe_pressure(pressuremb), 150, 180, scale=4)
    display.set_pen(black)
    display.circle((scaled_press), 229, 5)
    
def set_pico_time():
    # set the Pico's RTC from the RTC breakout
    RV3028.update_time()
    RTC().datetime([RV3028.get_year(), RV3028.get_month(), RV3028.get_date(),
                    RV3028.get_weekday(), RV3028.get_hours(), RV3028.get_minutes(),
                    RV3028.get_seconds(), 0])
    print(f"Pico RTC set to breakout time: {RV3028.string_date()} {RV3028.string_time()}")
     
 
set_pico_time() 

start_time = utime.time()
    
while True:

    time_elapsed = utime.time() - start_time
    
    display.set_pen(cyan)
    
    hours = rtc.get_hours()
    minutes = rtc.get_minutes()
    
    if rtc.read_periodic_update_interrupt_flag():
        rtc.clear_periodic_update_interrupt_flag()

        if rtc.update_time():
            rtc_date = rtc.string_date()
            rtc_time = rtc.string_time()
   
    if hours <10:
        display.text(f"{hours:1}:{minutes:02}~AM", 0, 0, scale=3)
    elif 10 <= hours < 12:
        display.text(f"{hours:2}:{minutes:02}~AM", 0, 0, scale=3)
    elif hours == 12:
        display.text(f"{hours:2}:{minutes:02}~PM", 0, 0, scale=3)    
    elif hours >12:
        hours = hours - 12
        if hours <10:
            display.text(f"{hours:1}:{minutes:02}~PM", 0, 0, scale=3)
        elif 10 <= hours < 12:
            display.text(f"{hours:2}:{minutes:02}~PM", 0, 0, scale=3)
        elif hours == 12:
            display.text(f"{hours:2}:{minutes:02}~AM", 0, 0, scale=3)    

        
    temperature, pressure, humidity = bme.read()
    pressuremb = pressure / 100      
    draw_graph(temperature, pressure, humidity)
    
    temperature = round(temperature)
    humidity = round(humidity)
    pressure = round(pressure)
    
    voltage = vsys.read_u16() * conversion_factor
    percentage = 100 * ((voltage - empty_battery) / (full_battery - empty_battery))
    if percentage > 100:
        percentage = 100
        
    if charging.value() == 1:
        display.set_pen(cyan)
        display.text(rtc_date, 170, 0, scale=3)
        led.set_rgb(0, 0, 0)
        
    else:
        if percentage >= 50:
            led.set_rgb(0, 15, 0)
            display.set_pen(green)
            display.text("{:0.2f}v".format(voltage), 150, 0, scale=3)
            display.text("{:0.0f}%".format(percentage), 255, 0, scale=3)
        elif 50 < percentage > 20:   
            led.set_rgb(15, 15, 0)
            display.set_pen(yellow)
            display.text("{:0.2f}v".format(voltage), 150, 0, scale=3)
            display.text("{:0.0f}%".format(percentage), 255, 0, scale=3)
        elif percentage <= 20:    
            led.set_rgb(15, 0, 0)
            display.set_pen(red)
            display.text("{:0.2f}v".format(voltage), 150, 0, scale=3)
            display.text("{:0.0f}%".format(percentage), 255, 0, scale=3)

    if button_x.is_pressed:
        display.set_backlight(1.0)
    elif button_y.is_pressed:
        display.set_backlight(0.4) 
            
    if time_elapsed < 0.5:
        temperature, pressure, humidity = bme.read()
        display.set_pen(black)
        display.clear()
        display.update()
        utime.sleep(1)
    else:
        display.update()
        utime.sleep(1)
        display.set_pen(black)
        display.clear()    
        utime.sleep(1)