Files
2026-01-18 11:49:21 +00:00

257 lines
8.9 KiB
Python

from machine import I2C, Pin
from pico_i2c_lcd import I2cLcd
import network
import secrets
import time
import struct
import socket
import machine
from secrets import WIFI_SSID, WIFI_PASSWORD
# Constants
NTP_DELTA = 2208988800
ntp_host = ["0.uk.pool.ntp.org", "1.uk.pool.ntp.org", "2.uk.pool.ntp.org", "3.uk.pool.ntp.org"]
# Define time tuple indices
tm_year = 0
tm_mon = 1
tm_mday = 2
tm_hour = 3
tm_min = 4
tm_sec = 5
tm_wday = 6
tm_yday = 7
tm_isdst = 8
# Initialize I2C and LCD
i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
I2C_ADDR = i2c.scan()[0]
lcd = I2cLcd(i2c, I2C_ADDR, 2, 16)
# Define the custom bell character on one line
bell_char = [0x00, 0x04, 0x0E, 0x0E, 0x0E, 0x1F, 0x00, 0x04]
# Load the bell character into the LCD memory at position 0
lcd.custom_char(0, bell_char)
def format_time(t):
# Format time tuple into a string
return "{:04}-{:02}-{:02} {:02}:{:02}:{:02}".format(t[0], t[1], t[2], t[3], t[4], t[5])
def uk_time():
now = time.time() # Get current time in seconds since epoch
year = time.localtime(now)[0] # Get current year
# Calculate the timestamps for the last Sunday in March and October
HHMarch = time.mktime((year, 3, (31 - (int(5 * year / 4 + 4)) % 7), 1, 0, 0, 0, 0, 0)) # Last Sunday in March
HHOctober = time.mktime((year, 10, (31 - (int(5 * year / 4 + 1)) % 7), 1, 0, 0, 0, 0, 0)) # Last Sunday in October
# Determine if the current time is in BST or GMT
if HHMarch <= now < HHOctober:
# We are in BST (UTC+1)
return time.localtime(now + 3600) # Add 1 hour to convert from UTC to BST
else:
# We are in GMT (UTC+0)
return time.localtime(now) # No adjustment needed
def q_set_time():
NTP_QUERY = bytearray(48)
NTP_QUERY[0] = 0x1B
time_is_set = False
for host in ntp_host:
addr = socket.getaddrinfo(host, 123)[0][-1]
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.settimeout(1)
s.sendto(NTP_QUERY, addr)
msg = s.recv(48)
finally:
s.close()
val = struct.unpack("!I", msg[40:44])[0]
t = val - NTP_DELTA
tm = time.gmtime(t)
print(f"UTC Time from NTP: {format_time(tm)}")
# Set RTC time directly to UTC from NTP
machine.RTC().datetime((tm[0], tm[1], tm[2], tm[6] + 1, tm[3], tm[4], tm[5], 0))
time_is_set = True
except OSError as exc:
if exc.args[0] == 110: # ETIMEDOUT
print(f"ETIMEDOUT for the host {host}.")
time_is_set = False
if time_is_set:
break
if time_is_set:
# Fetch time again after synchronization
utc_time = time.localtime()
print("UTC time after synchronization:", format_time(utc_time))
t = uk_time()
print("UK time after synchronization:", format_time(t))
# Update RTC with correct UK time
machine.RTC().datetime((t[tm_year], t[tm_mon], t[tm_mday], t[tm_wday] + 1, t[tm_hour], t[tm_min], t[tm_sec], 0))
print("Local time after synchronization:", format_time(time.localtime()))
print("NTP sync successful.")
return True
else:
print("Could not sync time...")
return False
def req_attention():
for i in range(5):
lcd.backlight_off()
time.sleep(0.2)
lcd.backlight_on()
time.sleep(0.4)
def calculate_countdown(t):
# Ensure the time tuple has all 9 elements by filling missing elements with defaults
if len(t) < 9:
t = t + (0,) * (9 - len(t))
current_time = time.mktime(t)
# Calculate the target times for 09:00 and 17:00 today
target_time_9 = time.mktime((t[tm_year], t[tm_mon], t[tm_mday], 9, 0, 0, t[tm_wday], t[tm_yday], t[tm_isdst]))
target_time_17 = time.mktime((t[tm_year], t[tm_mon], t[tm_mday], 17, 0, 0, t[tm_wday], t[tm_yday], t[tm_isdst]))
# Calculate the target time for 09:00 on the next weekday (Monday if today is Friday)
if t[tm_wday] == 4: # Friday
target_time_9_next_weekday = time.mktime((t[tm_year], t[tm_mon], t[tm_mday] + 3, 9, 0, 0, 0, t[tm_yday] + 3, t[tm_isdst]))
else:
target_time_9_next_weekday = time.mktime((t[tm_year], t[tm_mon], t[tm_mday] + 1, 9, 0, 0, (t[tm_wday] + 1) % 7, t[tm_yday] + 1, t[tm_isdst]))
# Determine the appropriate countdown target
if t[tm_wday] >= 0 and t[tm_wday] <= 4: # Monday to Friday
if current_time < target_time_9:
# Before 09:00, count down to 09:00 today
countdown_seconds = target_time_9 - current_time
elif current_time < target_time_17:
# Between 09:00 and 17:00, count down to 17:00 today
countdown_seconds = target_time_17 - current_time
else:
# After 17:00, count down to 09:00 the next weekday
countdown_seconds = target_time_9_next_weekday - current_time
else: # Saturday and Sunday
# During the weekend, count down to 09:00 on Monday
target_time_monday_9 = time.mktime((t[tm_year], t[tm_mon], t[tm_mday] + (7 - t[tm_wday]), 9, 0, 0, 0, t[tm_yday] + (7 - t[tm_wday]), t[tm_isdst]))
countdown_seconds = target_time_monday_9 - current_time
# Convert seconds to hours and minutes
countdown_hours = int(countdown_seconds // 3600)
countdown_minutes = int((countdown_seconds % 3600) // 60)
return "{:02}:{:02}".format(countdown_hours, countdown_minutes)
def display_time_and_countdown():
last_date = ""
backlight_status = None # Track the current backlight status
while True:
t = time.localtime() # Fetch current local time from RTC
# Update the date display if it has changed
new_date = "{:02}/{:02}/{}".format(t[tm_mday], t[tm_mon], t[tm_year])
if new_date != last_date:
lcd.move_to(0, 0)
lcd.putstr(new_date)
last_date = new_date
# Time display
lcd.move_to(0, 1)
lcd.putstr("{:02}:{:02}:{:02}".format(t[tm_hour], t[tm_min], t[tm_sec]))
# Display space before bell
lcd.putstr(" ")
# Display the bell character
lcd.putchar(chr(0))
# Display the countdown
countdown = calculate_countdown(t)
lcd.putstr(countdown)
# Handle backlight based on time range
if (t[tm_hour] >= 23 and t[tm_min] >= 00) or (t[tm_hour] < 7) or (t[tm_hour] == 7 and t[tm_min] < 30):
# Time is between 22:30 and 07:30 (next day), turn off the backlight
if backlight_status != "off":
lcd.backlight_off()
backlight_status = "off"
else:
# Time is between 07:30 and 22:30, turn on the backlight
if backlight_status != "on":
lcd.backlight_on()
backlight_status = "on"
time.sleep(1)
# Initialize display and define the bell character
lcd.blink_cursor_off()
lcd.backlight_on()
lcd.clear()
lcd.custom_char(0, bell_char) # Load bell character into position 0
# Main loop to connect to WiFi and sync time, followed by the display logic
while True:
wifi_connected = False
lcd.blink_cursor_on()
lcd.show_cursor()
lcd.putstr("Connecting...")
# Connect to WiFi
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
time.sleep(5)
lcd.clear()
lcd.putstr("Wifi connection:\n")
wifi_connected = wlan.isconnected()
lcd.putstr("Success\n" if wifi_connected else "Fail\n")
if wifi_connected:
lcd.clear()
lcd.putstr("NTP sync...\n")
max_wait = 10
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
time.sleep(1)
if wlan.status() != 3:
lcd.clear()
lcd.putstr("WiFi error.\n")
lcd.putstr("Reconnect in 5s")
req_attention()
wlan.active(False)
time.sleep(5)
continue
else:
lcd.clear()
lcd.putstr("Syncing time...\n")
try:
time_set_status = q_set_time()
if time_set_status:
lcd.clear()
lcd.putstr("Synced.\n")
print("Sync really successful.")
else:
raise Exception("Sync error.")
except Exception as e:
print("Sync exception!")
print("Error message: " + str(e))
lcd.clear()
lcd.putstr("Sync error.\n")
req_attention()
wlan.active(False)
time.sleep(5)
continue
lcd.blink_cursor_off()
lcd.hide_cursor()
display_time_and_countdown()
# Wait until next WiFi connect iteration
wlan.active(False)
time.sleep(5)