from machine import Pin, I2C
import framebuf
import time
# ======================== SSD1306 I2C Driver ========================
class SSD1306_I2C:
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width
self.height = height
self.i2c = i2c
self.addr = addr
self.buffer = bytearray(width * height // 8)
self.framebuf = framebuf.FrameBuffer(self.buffer, width, height, framebuf.MONO_VLSB)
self.init_display()
def init_display(self):
for cmd in (
0xAE, 0xD5, 0x80, 0xA8, 0x3F, 0xD3, 0x00, 0x40, 0x8D, 0x14,
0x20, 0x00, 0xA1, 0xC8, 0xDA, 0x12, 0x81, 0xCF, 0xD9, 0xF1,
0xDB, 0x40, 0xA4, 0xA6, 0x2E, 0xAF
):
self.write_cmd(cmd)
def write_cmd(self, cmd):
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
def write_data(self, data):
self.i2c.writeto(self.addr, bytearray([0x40] + list(data)))
def show(self):
for page in range(8):
self.write_cmd(0xB0 + page)
self.write_cmd(0x00)
self.write_cmd(0x10)
start = page * self.width
end = start + self.width
self.write_data(self.buffer[start:end])
def fill(self, color):
self.framebuf.fill(color)
def text(self, text, x, y, color=1):
self.framebuf.text(text, x, y, color)
# ======================== Hardware Setup ========================
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000)
oled = SSD1306_I2C(128, 64, i2c)
# 5 Inputs (buttons)
btn_new_ticket = Pin(13, Pin.IN, Pin.PULL_UP)
btn_call_next = Pin(12, Pin.IN, Pin.PULL_UP)
btn_reset = Pin(14, Pin.IN, Pin.PULL_UP)
btn_recall = Pin(27, Pin.IN, Pin.PULL_UP)
btn_cancel_last= Pin(26, Pin.IN, Pin.PULL_UP)
# 3 LEDs (outputs)
led_new_ticket = Pin(15, Pin.OUT)
led_call_next = Pin(2, Pin.OUT)
led_system_ok = Pin(16, Pin.OUT)
# Buzzer (output)
buzzer = Pin(18, Pin.OUT)
# ======================== Queue State ========================
queue = []
next_ticket_num = 1
current_serving = 0
# ======================== LED & Buzzer Management ========================
led_timers = {
led_new_ticket: 0,
led_call_next: 0,
led_system_ok: 0
}
buzzer_off_time = 0
def set_led_blink(led, duration_ms=200):
led.value(1)
led_timers[led] = time.ticks_add(time.ticks_ms(), duration_ms)
def beep(duration_ms=100):
global buzzer_off_time
buzzer.value(1)
buzzer_off_time = time.ticks_add(time.ticks_ms(), duration_ms)
def update_outputs():
global buzzer_off_time
now = time.ticks_ms()
# LEDs
for led, off_time in led_timers.items():
if off_time != 0 and time.ticks_diff(now, off_time) >= 0:
led.value(0)
led_timers[led] = 0
# Buzzer
if buzzer_off_time != 0 and time.ticks_diff(now, buzzer_off_time) >= 0:
buzzer.value(0)
buzzer_off_time = 0
# ======================== Simple Button Debouncing (Cooldown) ========================
last_press_time = {
btn_new_ticket: 0,
btn_call_next: 0,
btn_reset: 0,
btn_recall: 0,
btn_cancel_last: 0
}
PRESS_COOLDOWN_MS = 200 # ignore further presses for 200ms
def is_pressed(button):
now = time.ticks_ms()
# Button is active low (pressed = 0)
if button.value() == 0:
if time.ticks_diff(now, last_press_time[button]) > PRESS_COOLDOWN_MS:
last_press_time[button] = now
return True
return False
# ======================== Display Update ========================
def update_display():
oled.fill(0)
oled.text("Queue System", 0, 0)
# Now serving
if current_serving == 0:
oled.text("Now: ---", 0, 16)
else:
oled.text("Now: {:3d}".format(current_serving), 0, 16)
# Queue length and "empty" status shown on OLED
qlen = len(queue)
if qlen == 0:
oled.text("Queue: empty", 0, 32)
else:
oled.text("Queue: {:2d}".format(qlen), 0, 32)
preview = str(queue[:3])
if qlen > 3:
preview += "..."
oled.text(preview, 0, 48)
# Last ticket issued
last_ticket = next_ticket_num - 1 if next_ticket_num > 1 else 0
oled.text("Last: {:3d}".format(last_ticket), 80, 56)
oled.show()
# ======================== Actions ========================
def action_new_ticket():
global next_ticket_num, queue
new_num = next_ticket_num
queue.append(new_num)
next_ticket_num += 1
update_display()
set_led_blink(led_new_ticket)
beep(80)
print("New ticket: {}".format(new_num))
def action_call_next():
global queue, current_serving
if queue:
current_serving = queue.pop(0)
update_display()
set_led_blink(led_call_next)
beep(150)
print("Call next: {}".format(current_serving))
else:
# Queue empty – show message on OLED
oled.fill(0)
oled.text("Queue empty!", 0, 28)
oled.show()
beep(200)
time.sleep_ms(800)
update_display()
print("Queue empty")
def action_reset():
global queue, next_ticket_num, current_serving
queue.clear()
next_ticket_num = 1
current_serving = 0
update_display()
set_led_blink(led_system_ok)
beep(100)
# Show reset confirmation on OLED
oled.fill(0)
oled.text("System Reset", 0, 28)
oled.show()
time.sleep_ms(800)
update_display()
print("System reset")
def action_recall_last():
if current_serving != 0:
oled.fill(0)
oled.text("Recalling:", 0, 20)
oled.text("{}".format(current_serving), 30, 40)
oled.show()
set_led_blink(led_system_ok, 300)
beep(60)
time.sleep_ms(1000)
update_display()
print("Recall last: {}".format(current_serving))
else:
oled.text("No served yet", 0, 56)
oled.show()
beep(120)
time.sleep_ms(800)
update_display()
print("No previous number")
def action_cancel_last():
global queue
if queue:
removed = queue.pop()
update_display()
set_led_blink(led_system_ok) # reuse system LED for cancel feedback
beep(70)
# Show cancel confirmation on OLED
oled.fill(0)
oled.text("Cancelled:", 0, 28)
oled.text("Ticket {}".format(removed), 0, 40)
oled.show()
time.sleep_ms(800)
update_display()
print("Cancelled last ticket: {}".format(removed))
else:
oled.text("Queue empty", 0, 56)
oled.show()
beep(100)
time.sleep_ms(800)
update_display()
print("Nothing to cancel")
# ======================== Main Loop ========================
update_display()
set_led_blink(led_system_ok, 1000)
beep(200)
print("Queue System Ready")
while True:
if is_pressed(btn_new_ticket):
action_new_ticket()
if is_pressed(btn_call_next):
action_call_next()
if is_pressed(btn_reset):
action_reset()
if is_pressed(btn_recall):
action_recall_last()
if is_pressed(btn_cancel_last):
action_cancel_last()
update_outputs()
time.sleep_ms(20)