257 lines
8.9 KiB
Python
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)
|
|
|