from machine import Pin, I2C
from time import sleep
import utime
# === LCD Setup ===
try:
from lcd_api import LcdApi
from i2c_lcd import I2cLcd
except ImportError:
print("LCD libraries not found. Please install lcd_api and i2c_lcd.")
raise
# Helper to pad/trim strings safely (MicroPython lacks str.ljust)
def pad16(text):
"""Return string padded/truncated to exactly 16 chars"""
return (text + " " * 16)[:16]
# I²C at 100kHz (more reliable with PCF8574 backpacks)
i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=100000)
def init_lcd():
sleep(0.05) # 50 ms startup delay
try:
return I2cLcd(i2c, 0x27, 2, 16) # Adjust I2C address if needed
except Exception as e:
print("LCD initialization failed. Check I2C address/wiring.")
raise e
lcd = init_lcd()
# === Keypad Setup ===
ROWS = [Pin(i, Pin.OUT) for i in (2, 3, 4, 5)]
COLS = [Pin(i, Pin.IN, Pin.PULL_DOWN) for i in (6, 7, 8, 9)]
KEYS = [
['1', '2', '3', '+'],
['4', '5', '6', '-'],
['7', '8', '9', '*'],
['=', '0', '.', '/']
]
def scan_keypad():
"""Scan matrix and return key or None"""
for row_num, row in enumerate(ROWS):
row.high()
for col_num, col in enumerate(COLS):
if col.value():
row.low()
return KEYS[row_num][col_num]
row.low()
return None
# === Calculator State ===
first, second, operator, result = "", "", "", None
mode = "input_first" # input_first → input_second → result_shown
def update_display():
"""Refresh LCD with current state"""
lcd.move_to(0, 0)
line = first
if operator:
line += f" {operator}"
if second:
line += f" {second}"
lcd.putstr(pad16(line))
if mode == "result_shown" and result is not None:
lcd.move_to(0, 1)
lcd.putstr(pad16(f"= {result}"))
def reset_state():
"""Reset calculator to idle"""
global first, second, operator, result, mode
first, second, operator, result = "", "", "", None
mode = "input_first"
lcd.move_to(0, 0)
lcd.putstr(pad16("Goose Calc Ready"))
lcd.move_to(0, 1)
lcd.putstr(pad16(""))
sleep(0.5)
def safe_calculate(first, operator, second):
"""Perform math operation safely"""
try:
a, b = float(first), float(second)
if operator == '+': return a + b
if operator == '-': return a - b
if operator == '*': return a * b
if operator == '/': return "Div by 0" if b == 0 else a / b
except ValueError:
return "Error"
def calculate():
"""Wrapper for safe calculation and display update"""
global result, mode
if not first or not operator or not second:
result, mode = "Error", "result_shown"
update_display()
return
value = safe_calculate(first, operator, second)
if isinstance(value, (int, float)):
if isinstance(value, float) and value % 1 == 0:
result = str(int(value)) # Show 5.0 as "5"
else:
result = str(round(value, 8)) # Keep precision, strip junk
else:
result = str(value)
mode = "result_shown"
update_display()
def handle_key(key, press_duration):
"""Process key input with long-press clear"""
global first, second, operator, result, mode
# Long press '=' for clear
if key == '=' and press_duration > 1000:
reset_state()
return
# Numbers & decimal point
if key.isdigit() or key == ".":
if mode == "result_shown":
first, second, operator, result, mode = "", "", "", None, "input_first"
target = first if mode == "input_first" else second
if key == ".":
if "." in target: return
if not target: target = "0"
if len(target) < 10:
target += key
if mode == "input_first":
first = target
else:
second = target
update_display()
# Operators
elif key in "+-*/":
if mode == "result_shown" and result not in ("Error", "Div by 0"):
first, second, result, mode = result, "", None, "input_second"
elif first and mode != "input_second":
mode = "input_second"
else:
return
operator = key
update_display()
# Equals
elif key == "=" and operator and second:
calculate()
# === Main Loop ===
reset_state()
last_key, last_key_time, press_start = None, 0, 0
debounce_ms = 150 # slightly faster than 200ms
while True:
key = scan_keypad()
current_time = utime.ticks_ms()
if key and key != last_key and (current_time - last_key_time) > debounce_ms:
press_start, last_key, last_key_time = current_time, key, current_time
elif not key and last_key:
press_duration = current_time - press_start
handle_key(last_key, press_duration)
last_key, last_key_time = None, current_time
sleep(0.05)