import time
import rtc
import board
import busio
import digitalio
import displayio
import terminalio
import pwmio
from adafruit_display_text import label
from adafruit_debouncer import Debouncer
import adafruit_displayio_ssd1306
# SEGÉDFÜGGVÉNYEK
# =========================================================
def is_leap_year(year):
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
def days_in_month(year, month):
if month == 2:
return 29 if is_leap_year(year) else 28
if month in (1, 3, 5, 7, 8, 10, 12):
return 31
return 30
def clamp_day(state):
max_day = days_in_month(state["year"], state["month"])
if state["day"] > max_day:
state["day"] = max_day
if state["day"] < 1:
state["day"] = 1
def day_of_year(year, month, day):
total = 0
for m in range(1, month):
total += days_in_month(year, m)
total += day
return total
def weekday_monday0(year, month, day):
# 0 = hétfő, 6 = vasárnap
t = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]
y = year
if month < 3:
y -= 1
w = (y + y // 4 - y // 100 + y // 400 + t[month - 1] + day) % 7
return (w - 1) % 7
def rtc_struct_time_from_state(state):
year = state["year"]
month = state["month"]
day = state["day"]
hour = state["hour"]
minute = state["minute"]
second = 0 # másodpercet nem állítunk
wday = weekday_monday0(year, month, day)
yday = day_of_year(year, month, day)
return time.struct_time((
year, month, day,
hour, minute, second,
wday, yday, -1
))
def apply_state_to_rtc(state):
rtc.RTC().datetime = rtc_struct_time_from_state(state)
def change_clock_field(state, field_name, delta):
if field_name == "year":
state["year"] += delta
if state["year"] < 2020:
state["year"] = 2020
if state["year"] > 2050:
state["year"] = 2050
clamp_day(state)
elif field_name == "month":
state["month"] += delta
if state["month"] > 12:
state["month"] = 1
elif state["month"] < 1:
state["month"] = 12
clamp_day(state)
elif field_name == "day":
max_day = days_in_month(state["year"], state["month"])
state["day"] += delta
if state["day"] > max_day:
state["day"] = 1
elif state["day"] < 1:
state["day"] = max_day
elif field_name == "hour":
state["hour"] = (state["hour"] + delta) % 24
elif field_name == "minute":
state["minute"] = (state["minute"] + delta) % 60
apply_state_to_rtc(state)
def change_alarm_field(alarm_state, field_name, delta):
if field_name == "hour":
alarm_state["hour"] = (alarm_state["hour"] + delta) % 24
elif field_name == "minute":
alarm_state["minute"] = (alarm_state["minute"] + delta) % 60
# OLED
# =========================================================
displayio.release_displays()
i2c = busio.I2C(scl=board.GP1, sda=board.GP0)
display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=64)
group = displayio.Group()
title_label = label.Label(terminalio.FONT, text="RTC + ebresztes", color=0xFFFFFF, x=0, y=4)
line1_label = label.Label(terminalio.FONT, text="", color=0xFFFFFF, x=0, y=16)
line2_label = label.Label(terminalio.FONT, text="", color=0xFFFFFF, x=0, y=27)
line3_label = label.Label(terminalio.FONT, text="", color=0xFFFFFF, x=0, y=49)
line4_label = label.Label(terminalio.FONT, text="", color=0xFFFFFF, x=0, y=60)
group.append(title_label)
group.append(line1_label)
group.append(line2_label)
group.append(line3_label)
group.append(line4_label)
display.root_group = group
# GOMBOK
# =========================================================
pin_select = digitalio.DigitalInOut(board.GP18)
pin_select.direction = digitalio.Direction.INPUT
pin_plus = digitalio.DigitalInOut(board.GP19)
pin_plus.direction = digitalio.Direction.INPUT
pin_minus = digitalio.DigitalInOut(board.GP20)
pin_minus.direction = digitalio.Direction.INPUT
btn_select = Debouncer(pin_select)
btn_plus = Debouncer(pin_plus)
btn_minus = Debouncer(pin_minus)
# BUZZER
# =========================================================
buzzer = pwmio.PWMOut(board.GP2, frequency=1000, duty_cycle=0, variable_frequency=True)
BUZZER_DUTY_50 = 32768 # kb. 50%
alarm_sound_on = False
def buzzer_on(freq):
buzzer.frequency = freq
buzzer.duty_cycle = BUZZER_DUTY_50
def buzzer_off():
buzzer.duty_cycle = 0
# KEZDŐ ADATOK
# =========================================================
clock_state = {
"year": 2026,
"month": 3,
"day": 26,
"hour": 14,
"minute": 30
}
apply_state_to_rtc(clock_state)
alarm_state = {
"hour": 6,
"minute": 0,
"enabled": True
}
# ÜZEMMÓDOK
# 0 = idő/dátum állítás
# 1 = ébresztés állítás
#
# Rövid SELECT: mezőváltás
# Hosszú SELECT: módváltás
# =========================================================
MODE_CLOCK = 0
MODE_ALARM = 1
mode = MODE_CLOCK
clock_fields = ["year", "month", "day", "hour", "minute"]
clock_field_names_hu = {
"year": "EV",
"month": "HONAP",
"day": "NAP",
"hour": "ORA",
"minute": "PERC"
}
clock_selected_index = 0
alarm_fields = ["hour", "minute"]
alarm_field_names_hu = {
"hour": "ORA",
"minute": "PERC"
}
alarm_selected_index = 0
# HOSSZÚ NYOMÁS ISMÉTLÉS
# =========================================================
FIRST_REPEAT_DELAY = 0.6
REPEAT_INTERVAL = 0.12
plus_pressed_since = None
minus_pressed_since = None
plus_last_repeat = None
minus_last_repeat = None
SELECT_LONG_PRESS_TIME = 1.0
select_pressed_since = None
select_long_handled = False
def handle_repeat(button, change_function, target_state, field_name, delta,
pressed_since, last_repeat):
now = time.monotonic()
if button.fell:
change_function(target_state, field_name, delta)
pressed_since = now
last_repeat = now
if button.rose:
pressed_since = None
last_repeat = None
if pressed_since is not None and not button.value:
if (now - pressed_since) >= FIRST_REPEAT_DELAY:
if (now - last_repeat) >= REPEAT_INTERVAL:
change_function(target_state, field_name, delta)
last_repeat = now
return pressed_since, last_repeat
# ÉBRESZTÉS
# =========================================================
alarm_ringing = False
last_alarm_date = None
last_blink_toggle = 0
current_tone_index = 0
tone_list = [880, 660]
# SZUNDI
snooze_active = False
snooze_until = 0
def current_date_key():
now = time.localtime()
return (now.tm_year, now.tm_mon, now.tm_mday)
def check_alarm_start():
global alarm_ringing, last_alarm_date, snooze_active
if not alarm_state["enabled"]:
return
now = time.localtime()
now_sec = time.time()
today = (now.tm_year, now.tm_mon, now.tm_mday)
# Szundi lejárt -> újra csörög
if snooze_active and now_sec >= snooze_until:
alarm_ringing = True
snooze_active = False
return
# Normál napi ébresztés csak egyszer induljon
if last_alarm_date == today:
return
if now.tm_hour == alarm_state["hour"] and now.tm_min == alarm_state["minute"]:
alarm_ringing = True
last_alarm_date = today
def update_alarm_sound():
global last_blink_toggle, alarm_sound_on, current_tone_index
if not alarm_ringing:
buzzer_off()
alarm_sound_on = False
return
now_mono = time.monotonic()
# 0.3 s be, 0.3 s ki -> szaggatott
if now_mono - last_blink_toggle >= 0.3:
last_blink_toggle = now_mono
alarm_sound_on = not alarm_sound_on
if alarm_sound_on:
current_tone_index = (current_tone_index + 1) % len(tone_list)
buzzer_on(tone_list[current_tone_index])
else:
buzzer_off()
def stop_alarm():
global alarm_ringing, alarm_sound_on, snooze_active
alarm_ringing = False
alarm_sound_on = False
snooze_active = False
buzzer_off()
def start_snooze():
global alarm_ringing, alarm_sound_on, snooze_active, snooze_until
alarm_ringing = False
alarm_sound_on = False
snooze_active = True
snooze_until = time.time() + 10 # <- A szundi időtartama
buzzer_off()
# KIJELZŐ
# =========================================================
def update_display():
now = time.localtime()
line1_label.text = "{:04d}-{:02d}-{:02d}".format(now.tm_year, now.tm_mon, now.tm_mday)
line2_label.text = "{:02d}:{:02d}:{:02d}".format(now.tm_hour, now.tm_min, now.tm_sec)
if mode == MODE_CLOCK:
current_field = clock_fields[clock_selected_index]
line3_label.text = "Mod: ORA Mezo: " + clock_field_names_hu[current_field]
else:
current_field = alarm_fields[alarm_selected_index]
line3_label.text = "Mod: EBR Mezo: " + alarm_field_names_hu[current_field]
alarm_text = "Ebr: {:02d}:{:02d}".format(alarm_state["hour"], alarm_state["minute"])
if alarm_state["enabled"]:
alarm_text += " ON"
else:
alarm_text += " OFF"
if snooze_active:
alarm_text = "SZUNDI 10 mp"
elif alarm_ringing:
alarm_text = "+:szundi -:stop"
line4_label.text = alarm_text
# FŐCIKLUS
# =========================================================
last_display_refresh = 0
while True:
btn_select.update()
btn_plus.update()
btn_minus.update()
# Ha épp riasztás:
# PLUS = szundi 10 mp
# MINUS = végleges leállítás
# SELECT = leállítás
if alarm_ringing:
if btn_plus.fell:
start_snooze()
elif btn_minus.fell:
stop_alarm()
elif btn_select.fell:
stop_alarm()
else:
# SELECT: rövid = mezőváltás, hosszú = módváltás
if btn_select.fell:
select_pressed_since = time.monotonic()
select_long_handled = False
if select_pressed_since is not None and not btn_select.value:
if (time.monotonic() - select_pressed_since) >= SELECT_LONG_PRESS_TIME:
if not select_long_handled:
if mode == MODE_CLOCK:
mode = MODE_ALARM
else:
mode = MODE_CLOCK
select_long_handled = True
if btn_select.rose:
if not select_long_handled:
if mode == MODE_CLOCK:
clock_selected_index = (clock_selected_index + 1) % len(clock_fields)
else:
alarm_selected_index = (alarm_selected_index + 1) % len(alarm_fields)
select_pressed_since = None
select_long_handled = False
# PLUS / MINUS csak akkor állítson, ha nincs aktív riasztás
if mode == MODE_CLOCK:
current_field = clock_fields[clock_selected_index]
plus_pressed_since, plus_last_repeat = handle_repeat(
btn_plus,
change_clock_field,
clock_state,
current_field,
+1,
plus_pressed_since,
plus_last_repeat
)
minus_pressed_since, minus_last_repeat = handle_repeat(
btn_minus,
change_clock_field,
clock_state,
current_field,
-1,
minus_pressed_since,
minus_last_repeat
)
else:
current_field = alarm_fields[alarm_selected_index]
plus_pressed_since, plus_last_repeat = handle_repeat(
btn_plus,
change_alarm_field,
alarm_state,
current_field,
+1,
plus_pressed_since,
plus_last_repeat
)
minus_pressed_since, minus_last_repeat = handle_repeat(
btn_minus,
change_alarm_field,
alarm_state,
current_field,
-1,
minus_pressed_since,
minus_last_repeat
)
# Ébresztés ellenőrzés
check_alarm_start()
update_alarm_sound()
# Kijelző frissítés
now_mono = time.monotonic()
if now_mono - last_display_refresh > 0.1:
update_display()
last_display_refresh = now_mono
time.sleep(0.01)SELECT
+
-