'''
NOTE: this version of PiCube, PiCubePico, is made with a LOT of assistance from
GitHub Copilot, mainly because I could not find much documentation online, about
the various libraries in order for this project to work.
However, the Pi Zero2 W powered version (PiCubeZero) is NOT generated by and kind of
GenAI / LLM, but whether that version works is right now unknown. :(
'''
import time
import random
from machine import Pin, I2C
import neopixel
from sh1107 import SH1107_I2C
from tm1637 import TM1637
import os
# --- Hardware Setup ---
# SH1107 OLED 128x128 I2C (Grove)
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)
oled = SH1107_I2C(128, 128, i2c, address=0x3c)
oled.sleep(False)
# NeoPixel (WS2812B), GPIO3, 1 LED
led = neopixel.NeoPixel(Pin(3), 1)
# TM1637 7-seg, CLK=GP4, DIO=GP5
seg = TM1637(clk=Pin(4), dio=Pin(5))
# Touch sensor, GP2 (use a button in Wokwi or a TTP223 module)
touch = Pin(2, Pin.IN, Pin.PULL_UP)
# --- Scramble function ---
faces = ['U', 'D', 'L', 'R', 'F', 'B']
modifiers = ['', "'", '2']
def generate_scramble(moves=18):
scramble = []
prev_face = None
for _ in range(moves):
while True:
face = random.choice(faces)
if face != prev_face:
break
modifier = random.choice(modifiers)
scramble.append(face + modifier)
prev_face = face
return " ".join(scramble)
def set_led(r, g, b):
led[0] = (r, g, b)
led.write()
def display_oled(scramble, time_display, status, solves, ao5, ao12):
oled.fill(0)
y = 0
oled.text("Time: {}".format(time_display), 0, y, 1); y += 12
oled.text("Status: {}".format(status), 0, y, 1); y += 12
oled.text("Solves: {}".format(len(solves)), 0, y, 1); y += 12
if ao5 is not None:
oled.text("ao5: {:.2f}".format(ao5), 0, y, 1); y += 12
if ao12 is not None:
oled.text("ao12: {:.2f}".format(ao12), 0, y, 1); y += 12
y += 2
oled.text("Scramble:", 0, y, 1); y += 12
# Wrap scramble to fit width, 21 chars per line
scr = scramble
for i in range(0, len(scr), 21):
if y > 120: break
oled.text(scr[i:i+21], 0, y, 1)
y += 12
oled.show()
def calculate_averages(times):
ao5 = sum(times[-5:]) / 5 if len(times) >= 5 else None
ao12 = sum(times[-12:]) / 12 if len(times) >= 12 else None
return ao5, ao12
def display_7seg(seconds):
# Always show 4 digits, with colon ON for style, robust for any input
s = "{:04.2f}".format(max(0, seconds)).replace('.', '')
s = s[-4:]
s = '0' * (4 - len(s)) + s
seg.write([
seg.encode_digit(int(s[0])),
seg.encode_digit(int(s[1])) | 0x80, # colon ON
seg.encode_digit(int(s[2])),
seg.encode_digit(int(s[3]))
])
SOLVE_FILE = "solve_times.txt"
def load_solve_times():
try:
with open(SOLVE_FILE, "r") as f:
lines = f.readlines()
times = []
for line in lines:
try:
times.append(float(line.strip()))
except Exception:
pass
return times
except:
return []
def append_solve_time(solve_time):
try:
with open(SOLVE_FILE, "a") as f:
f.write("{:.2f}\n".format(solve_time))
except:
pass
def main():
solve_times = load_solve_times()
status = "Ready"
scramble = generate_scramble(18)
set_led(0, 80, 0) # Green for ready
while True:
ao5, ao12 = calculate_averages(solve_times)
display_oled(scramble, "0.0", status, solve_times, ao5, ao12)
seg.write([0, 0, 0, 0])
set_led(0, 80, 0) # Green
print("Touch and HOLD to start...")
# Wait for user to touch and hold (LOW)
while touch.value():
time.sleep(0.01)
# Flash LED while holding
status = "Hold"
display_oled(scramble, "0.0", status, solve_times, ao5, ao12)
while not touch.value():
set_led(255, 255, 255) # White on
time.sleep(0.15)
set_led(0, 0, 0) # Off
time.sleep(0.15)
# On release, start timer
status = "Timing"
set_led(0, 0, 255) # Blue for timing
display_oled(scramble, "0.0", status, solve_times, ao5, ao12)
start_time = time.ticks_ms()
# Show real-time timer
while touch.value():
elapsed = (time.ticks_ms() - start_time) / 1000
time_display = "{:.2f}".format(elapsed)
display_oled(scramble, time_display, status, solve_times, ao5, ao12)
display_7seg(elapsed)
time.sleep(0.05)
# On next touch (LOW), stop timer
end_time = time.ticks_ms()
solve_time = round((end_time - start_time) / 1000, 2)
solve_times.append(solve_time)
append_solve_time(solve_time)
set_led(255, 0, 0) # Red for finished
# Display result for ~4 seconds
status = "Solved"
ao5, ao12 = calculate_averages(solve_times)
for _ in range(40):
display_oled(scramble, "{:.2f}".format(solve_time), status, solve_times, ao5, ao12)
display_7seg(solve_time)
time.sleep(0.1)
# Wait for user to release the button before getting ready for next solve
while not touch.value():
time.sleep(0.01)
# Prepare for next solve
scramble = generate_scramble(18)
status = "Ready"
set_led(0, 80, 0)
if __name__ == "__main__":
main()
Loading
grove-oled-sh1107
grove-oled-sh1107