Browse Source

Maturita

master
Marek 2 months ago
commit
e34c373686
  1. 3
      .gitignore
  2. 6
      info/knihovny.txt
  3. 17
      info/popis_kodu
  4. 159
      src/Detect_roi.py
  5. 87
      src/calibrate_roi.py
  6. 24
      src/capture.py
  7. 119
      src/detect_face_colors.py
  8. 45
      src/kociemba_test.py
  9. 11
      src/main.py
  10. 209
      src/scan_one_face_3.py
  11. 176
      src/web_ui.py
  12. 99
      src/web_ui_old.py

3
.gitignore

@ -0,0 +1,3 @@
venv/
src/__pycache__/
images/

6
info/knihovny.txt

@ -0,0 +1,6 @@
Pouzite knihovny:
1. Flask
2. kociemba
3. opencv-python (cv2)
4. numpy
5. rpi_ws281x

17
info/popis_kodu

@ -0,0 +1,17 @@
Hlavni soubory:
1. Main.py - vstupni bod aplikace (entry point)
-Inicializuje system a spousti samotny webovy server.
2. Web_ui.py - sprava weboveho rozhrani
- Ridi stav skenovani (pamatuje si stav kostky a stranu)
- Spravuje uzivatelske rezimy
3. scan_one_face_3.py
- hlavni system pro digitalizaci kostky
- propojuje hardware (kameru, led kruh a dalsi.)
- funkce pro porieni snimku
Pomocne a kalibracni skripty (pouzite pri vyvoji)
calibrate_roi.py
detect_roi.py
capture.py
kociemba_test.py

159
src/Detect_roi.py

@ -0,0 +1,159 @@
import os
import cv2
import numpy as np
IMG_PATH = "/home/kluci/rubik_images/face_1.jpg"
DEBUG_FULL = "/home/kluci/rubik_images/face_1_debug_full.jpg"
DEBUG_CROP = "/home/kluci/rubik_images/face_1_debug_crop.jpg"
ROI_X = 750
ROI_Y = 215
ROI_W = 580
ROI_H = 580
ROI_MARGIN = 20
SAMPLE_SIZE = 30
def clamp(v, lo, hi):
if v < lo:
return lo
if v > hi:
return hi
return v
def classify_hsv(h, s, v):
if s < 45 and v > 120:
return "W"
if v < 50:
return "?"
if 18 <= h <= 35 and s > 70 and v > 80:
return "Y"
if 36 <= h <= 85 and s > 60 and v > 60:
return "G"
if 86 <= h <= 130 and s > 60 and v > 50:
return "B"
if (h <= 10 or h >= 170) and s > 70 and v > 60:
return "R"
if 11 <= h <= 17 and s > 70 and v > 60:
return "O"
return "?"
def main():
if not os.path.exists(IMG_PATH):
print("ERROR: image not found:", IMG_PATH)
return
img = cv2.imread(IMG_PATH)
if img is None:
print("ERROR: failed to read:", IMG_PATH)
return
H_img, W_img = img.shape[:2]
x = clamp(ROI_X, 0, W_img - 2)
y = clamp(ROI_Y, 0, H_img - 2)
w = clamp(ROI_W, 2, W_img - x)
h = clamp(ROI_H, 2, H_img - y)
x2 = clamp(x + ROI_MARGIN, 0, W_img - 2)
y2 = clamp(y + ROI_MARGIN, 0, H_img - 2)
w2 = clamp(w - 2 * ROI_MARGIN, 2, W_img - x2)
h2 = clamp(h - 2 * ROI_MARGIN, 2, H_img - y2)
crop = img[y2:y2+h2, x2:x2+w2].copy()
hsv = cv2.cvtColor(crop, cv2.COLOR_BGR2HSV)
ch, cw = crop.shape[:2]
cell_w = cw // 3
cell_h = ch // 3
debug_full = img.copy()
debug_crop = crop.copy()
cv2.rectangle(debug_full, (x, y), (x + w, y + h), (0, 0, 255), 3)
cv2.rectangle(debug_full, (x2, y2), (x2 + w2, y2 + h2), (0, 255, 255), 2)
grid = []
idx = 1
print("ROI used: x=%d y=%d w=%d h=%d (inner x=%d y=%d w=%d h=%d)" % (x, y, w, h, x2, y2, w2, h2))
print("Detected colors (3x3):")
for r in range(3):
row = []
for c in range(3):
rx0 = c * cell_w
ry0 = r * cell_h
rx1 = (c + 1) * cell_w
ry1 = (r + 1) * cell_h
cx = (rx0 + rx1) // 2
cy = (ry0 + ry1) // 2
half = SAMPLE_SIZE // 2
sx0 = clamp(cx - half, 0, cw - 1)
sy0 = clamp(cy - half, 0, ch - 1)
sx1 = clamp(cx + half, 0, cw)
sy1 = clamp(cy + half, 0, ch)
patch = hsv[sy0:sy1, sx0:sx1]
mean = patch.reshape(-1, 3).mean(axis=0)
Hm, Sm, Vm = float(mean[0]), float(mean[1]), float(mean[2])
color = classify_hsv(Hm, Sm, Vm)
row.append(color)
cv2.rectangle(debug_crop, (rx0, ry0), (rx1, ry1), (0, 255, 0), 2)
cv2.rectangle(debug_crop, (sx0, sy0), (sx1, sy1), (255, 0, 0), 2)
cv2.putText(debug_crop, color, (rx0 + 10, ry0 + 55),
cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), 3)
cv2.putText(debug_crop, "H%d S%d V%d" % (int(Hm), int(Sm), int(Vm)),
(rx0 + 10, ry1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7,
(0, 255, 255), 2)
fx0 = x2 + rx0
fy0 = y2 + ry0
fx1 = x2 + rx1
fy1 = y2 + ry1
fsx0 = x2 + sx0
fsy0 = y2 + sy0
fsx1 = x2 + sx1
fsy1 = y2 + sy1
cv2.rectangle(debug_full, (fx0, fy0), (fx1, fy1), (0, 255, 0), 2)
cv2.rectangle(debug_full, (fsx0, fsy0), (fsx1, fsy1), (255, 0, 0), 2)
cv2.putText(debug_full, color, (fx0 + 5, fy0 + 35),
cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2)
print("%d: cell[%d,%d] H=%.1f S=%.1f V=%.1f => %s" % (idx, r, c, Hm, Sm, Vm, color))
idx += 1
grid.append(row)
cv2.imwrite(DEBUG_FULL, debug_full)
cv2.imwrite(DEBUG_CROP, debug_crop)
print("")
print("Grid:")
for r in grid:
print(" ".join(r))
print("")
print("Saved:", DEBUG_FULL)
print("Saved:", DEBUG_CROP)
if __name__ == "__main__":
main()

87
src/calibrate_roi.py

@ -0,0 +1,87 @@
#!/usr/bin/env python3
import os
import json
import cv2
IMG_PATH = "/home/kluci/rubik_images/face_1.jpg"
ROI_PATH = "/home/kluci/rubik_images/roi.json"
points = []
def on_mouse(event, x, y, flags, param):
global points
if event == cv2.EVENT_LBUTTONDOWN:
points.append((x, y))
print("Clicked:", x, y)
def main():
if not os.path.exists(IMG_PATH):
print("ERROR: image not found:", IMG_PATH)
print("Run capture script first to create face_1.jpg")
return
img = cv2.imread(IMG_PATH)
if img is None:
print("ERROR: failed to read image:", IMG_PATH)
return
cv2.namedWindow("calibrate_roi", cv2.WINDOW_NORMAL)
cv2.setMouseCallback("calibrate_roi", on_mouse)
while True:
vis = img.copy()
# draw clicked points
for p in points:
cv2.circle(vis, p, 8, (0, 0, 255), -1)
# if 2 points, draw rectangle
if len(points) >= 2:
x1, y1 = points[0]
x2, y2 = points[1]
x_min, x_max = (min(x1, x2), max(x1, x2))
y_min, y_max = (min(y1, y2), max(y1, y2))
cv2.rectangle(vis, (x_min, y_min), (x_max, y_max), (0, 255, 0), 3)
cv2.imshow("calibrate_roi", vis)
key = cv2.waitKey(20) & 0xFF
# ESC = exit without save
if key == 27:
print("Exit without saving.")
break
# S = save if we have 2 points
if key in (ord('s'), ord('S')):
if len(points) < 2:
print("Need 2 clicks: top-left and bottom-right.")
continue
x1, y1 = points[0]
x2, y2 = points[1]
x_min, x_max = (min(x1, x2), max(x1, x2))
y_min, y_max = (min(y1, y2), max(y1, y2))
roi = {
"x": int(x_min),
"y": int(y_min),
"w": int(x_max - x_min),
"h": int(y_max - y_min)
}
with open(ROI_PATH, "w") as f:
json.dump(roi, f)
print("Saved ROI to:", ROI_PATH)
print(roi)
break
# R = reset points
if key in (ord('r'), ord('R')):
points = []
print("Reset points.")
cv2.destroyAllWindows()
if __name__ == "__main__":
main()

24
src/capture.py

@ -0,0 +1,24 @@
import os
import time
from picamera2 import Picamera2
OUTDIR = "/home/kluci/rubik_images"
OUTFILE = os.path.join(OUTDIR, "face_1.jpg")
def main():
os.makedirs(OUTDIR, exist_ok=True)
picam = Picamera2()
config = picam.create_still_configuration(main={"size": (1920, 1080)})
picam.configure(config)
picam.start()
time.sleep(1.5)
picam.capture_file(OUTFILE)
picam.stop()
print("Saved:", OUTFILE)
if __name__ == "__main__":
main()

119
src/detect_face_colors.py

@ -0,0 +1,119 @@
import os
import cv2
import numpy as np
IMG_PATH = "/home/kluci/rubik_images/face_1.jpg"
DEBUG_PATH = "/home/kluci/rubik_images/face_1_debug.jpg"
SAMPLE_SIZE = 50
def clamp(v, lo, hi):
if v < lo:
return lo
if v > hi:
return hi
return v
def classify_hsv(h, s, v):
"""
Velmi jednoducha klasifikace barvy z HSV.
HSV v OpenCV:
H: 0-179
S: 0-255
V: 0-255
Tohle je START nastaveni. Budeme to ladit podle tvych fotek.
Vraci jedno pismeno: W Y R O G B
"""
if s < 40 and v > 120:
return "W"
if v < 50:
return "?"
if 18 <= h <= 35 and s > 70 and v > 80:
return "Y"
if 36 <= h <= 85 and s > 60 and v > 60:
return "G"
if 86 <= h <= 130 and s > 60 and v > 50:
return "B"
if (h <= 10 or h >= 170) and s > 70 and v > 60:
return "R"
if 11 <= h <= 17 and s > 70 and v > 60:
return "O"
return "?"
def main():
if not os.path.exists(IMG_PATH):
print("ERROR: image not found:", IMG_PATH)
print("Run capture_face.py first.")
return
img = cv2.imread(IMG_PATH)
if img is None:
print("ERROR: failed to read image:", IMG_PATH)
return
h_img, w_img = img.shape[:2]
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
cell_w = w_img // 3
cell_h = h_img // 3
debug = img.copy()
grid = []
idx = 1
print("Detected 3x3 colors:")
for r in range(3):
row = []
for c in range(3):
x0 = c * cell_w
y0 = r * cell_h
x1 = (c + 1) * cell_w
y1 = (r + 1) * cell_h
cx = (x0 + x1) // 2
cy = (y0 + y1) // 2
half = SAMPLE_SIZE // 2
sx0 = clamp(cx - half, 0, w_img - 1)
sy0 = clamp(cy - half, 0, h_img - 1)
sx1 = clamp(cx + half, 0, w_img)
sy1 = clamp(cy + half, 0, h_img)
patch = hsv[sy0:sy1, sx0:sx1]
mean = patch.reshape(-1, 3).mean(axis=0)
H, S, V = float(mean[0]), float(mean[1]), float(mean[2])
color = classify_hsv(H, S, V)
row.append(color)
cv2.rectangle(debug, (x0, y0), (x1, y1), (0, 255, 0), 2) # mrizka
cv2.rectangle(debug, (sx0, sy0), (sx1, sy1), (255, 0, 0), 2) # vzorek
cv2.putText(debug, f"{color}", (x0 + 20, y0 + 60),
cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), 3)
cv2.putText(debug, f"H{int(H)} S{int(S)} V{int(V)}", (x0 + 10, y1 - 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
print(f"{idx}: cell[{r},{c}] -> H={H:.1f} S={S:.1f} V={V:.1f} => {color}")
idx += 1
grid.append(row)
cv2.imwrite(DEBUG_PATH, debug)
print("Saved debug image:", DEBUG_PATH)
print("")
print("Grid (top row first):")
for r in grid:
print(" ".join(r))
if __name__ == "__main__":
main()

45
src/kociemba_test.py

@ -0,0 +1,45 @@
import kociemba
faces = [
"Horni (U - stredova Bila)",
"Prava (R - stredova Cervena)",
"Predni (F - stredova Zelena)",
"Spodni (D - stredova Zluta)",
"Leva (L - stredova Oranzova)",
"Zadni (B - stredova Modra)"
]
color_to_face = {
'W': 'U',
'Y': 'D',
'R': 'R',
'O': 'L',
'G': 'F',
'B': 'B'
}
kociemba_string = ""
for face in faces:
while True:
user_input = input(f"Zadej 9 barev pro stenu {face} zleva doprava (W, Y, R, O, G, B): ").strip().upper()
if len(user_input) == 9 and all(char in color_to_face for char in user_input):
translated_face = "".join(color_to_face[char] for char in user_input)
kociemba_string += translated_face
break
else:
print("Neplatny vstup. Zadej presne 9 pismen z povolenych barev.")
print("\n--- KONTROLA ---")
print(f"Vygenerovany string: {kociemba_string}")
confirmation = input("Chces vygenerovat reseni? (ano/ne): ").strip().lower()
if confirmation == "ano":
try:
solution = kociemba.solve(kociemba_string)
print("\n--- RESENI ---")
print(solution)
except Exception as e:
print(f"\nNelze najit reseni, zkontroluj zadane barvy. Detail chyby: {e}")

11
src/main.py

@ -0,0 +1,11 @@
from web_ui import start_server
def main():
print("=====================================")
print(" Rubik's Cube Solver - Startuje... ")
print("=====================================")
start_server()
if __name__ == "__main__":
main()

209
src/scan_one_face_3.py

@ -0,0 +1,209 @@
import os
import re
import time
import cv2
import numpy as np
from picamera2 import Picamera2
from rpi_ws281x import PixelStrip, Color
OUTDIR = "/home/kluci/rubiks_solver/images"
ROI_X = 600
ROI_Y = 190
ROI_W = 600
ROI_H = 600
LED_COUNT = 24
LED_PIN = 18
LED_BRIGHTNESS = 3
ROI_MARGIN = 20
SAMPLE_SIZE = 55
RES_W = 1920
RES_H = 1080
REF_COLORS = {
"W": (148, 30, 230),
"R": (172, 130, 250),
"O": (6, 130, 250),
"Y": (27, 130, 250),
"G": (68, 200, 185),
"B": (110, 200, 185)
}
_global_strip = None
def get_strip():
global _global_strip
if _global_strip is None:
_global_strip = PixelStrip(LED_COUNT, LED_PIN, brightness=LED_BRIGHTNESS)
_global_strip.begin()
return _global_strip
def led_on(strip):
strip.setBrightness(LED_BRIGHTNESS)
for i in range(strip.numPixels()):
strip.setPixelColor(i, Color(255, 220, 120))
strip.show()
def led_off(strip):
for i in range(strip.numPixels()):
strip.setPixelColor(i, Color(0, 0, 0))
strip.show()
def clamp(v, lo, hi):
if v < lo: return lo
if v > hi: return hi
return v
def next_face_index(folder):
pat = re.compile(r"^face_(\d+)\.jpg$")
max_i = 0
for name in os.listdir(folder):
m = pat.match(name)
if m:
i = int(m.group(1))
if i > max_i:
max_i = i
return max_i + 1
def hue_dist(a, b):
d = abs(a - b)
return min(d, 180 - d)
def classify_hsv(h, s, v):
if s < 60 and v > 150:
return "W"
best_name = "?"
best_score = 999999
for name, ref in REF_COLORS.items():
rh, rs, rv = ref
dh = hue_dist(h, rh)
ds = abs(s - rs)
dv = abs(v - rv)
if name == "W":
score = dh * 0.5 + ds * 3.0 + dv * 1.0
elif name in ("R", "O"):
score = dh * 4.0 + ds * 1.0 + dv * 0.6
else:
score = dh * 3.0 + ds * 0.8 + dv * 0.5
if score < best_score:
best_score = score
best_name = name
return best_name
def capture_image(filepath):
picam = Picamera2()
try:
config = picam.create_still_configuration(main={"size": (RES_W, RES_H)})
picam.configure(config)
picam.start()
time.sleep(1.5)
picam.capture_file(filepath)
finally:
picam.stop()
picam.close()
def analyze_image(img):
H_img, W_img = img.shape[:2]
x = clamp(ROI_X, 0, W_img - 2)
y = clamp(ROI_Y, 0, H_img - 2)
w = clamp(ROI_W, 2, W_img - x)
h = clamp(ROI_H, 2, H_img - y)
x2 = clamp(x + ROI_MARGIN, 0, W_img - 2)
y2 = clamp(y + ROI_MARGIN, 0, H_img - 2)
w2 = clamp(w - 2 * ROI_MARGIN, 2, W_img - x2)
h2 = clamp(h - 2 * ROI_MARGIN, 2, H_img - y2)
crop = img[y2:y2+h2, x2:x2+w2].copy()
hsv = cv2.cvtColor(crop, cv2.COLOR_BGR2HSV)
ch, cw = crop.shape[:2]
cell_w = cw // 3
cell_h = ch // 3
grid = []
debug_full = img.copy()
debug_crop = crop.copy()
cv2.rectangle(debug_full, (x, y), (x + w, y + h), (0, 0, 255), 3)
cv2.rectangle(debug_full, (x2, y2), (x2 + w2, y2 + h2), (0, 255, 255), 2)
for r in range(3):
row = []
for c in range(3):
rx0 = c * cell_w
ry0 = r * cell_h
rx1 = (c + 1) * cell_w
ry1 = (r + 1) * cell_h
cx = (rx0 + rx1) // 2
cy = (ry0 + ry1) // 2
half = SAMPLE_SIZE // 2
sx0 = clamp(cx - half, 0, cw - 1)
sy0 = clamp(cy - half, 0, ch - 1)
sx1 = clamp(cx + half, 0, cw)
sy1 = clamp(cy + half, 0, ch)
patch = hsv[sy0:sy1, sx0:sx1]
flat = patch.reshape(-1, 3)
good = flat[(flat[:, 2] < 245)]
if len(good) > 20: flat = good
med = np.median(flat, axis=0)
Hm, Sm, Vm = float(med[0]), float(med[1]), float(med[2])
color = classify_hsv(Hm, Sm, Vm)
row.append(color)
cv2.rectangle(debug_crop, (rx0, ry0), (rx1, ry1), (0, 255, 0), 2)
cv2.rectangle(debug_crop, (sx0, sy0), (sx1, sy1), (255, 0, 0), 2)
cv2.putText(debug_crop, color, (rx0 + 10, ry0 + 55), cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), 3)
cv2.putText(debug_crop, "H%d S%d V%d" % (int(Hm), int(Sm), int(Vm)), (rx0 + 10, ry1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
fx0, fy0 = x2 + rx0, y2 + ry0
fx1, fy1 = x2 + rx1, y2 + ry1
fsx0, fsy0 = x2 + sx0, y2 + sy0
fsx1, fsy1 = x2 + sx1, y2 + sy1
cv2.rectangle(debug_full, (fx0, fy0), (fx1, fy1), (0, 255, 0), 2)
cv2.rectangle(debug_full, (fsx0, fsy0), (fsx1, fsy1), (255, 0, 0), 2)
cv2.putText(debug_full, color, (fx0 + 5, fy0 + 35), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2)
grid.append(row)
return grid, debug_full, debug_crop
def scan_single_face():
os.makedirs(OUTDIR, exist_ok=True)
strip = get_strip()
led_off(strip)
idx = next_face_index(OUTDIR)
img_path = os.path.join(OUTDIR, "face_%d.jpg" % idx)
debug_full_path = os.path.join(OUTDIR, "debug_full_%d.jpg" % idx)
debug_crop_path = os.path.join(OUTDIR, "debug_crop_%d.jpg" % idx)
led_on(strip)
time.sleep(0.3)
capture_image(img_path)
led_off(strip)
img = cv2.imread(img_path)
if img is None:
return ""
img = cv2.rotate(img, cv2.ROTATE_180)
grid, debug_full, debug_crop = analyze_image(img)
cv2.imwrite(debug_full_path, debug_full)
cv2.imwrite(debug_crop_path, debug_crop)
result_string = "".join(color for row in grid for color in row)
led_off(strip)
return result_string
if __name__ == "__main__":
test_result = scan_single_face()
print("Test skenu:", test_result)

176
src/web_ui.py

@ -0,0 +1,176 @@
from flask import Flask, request, redirect, url_for
import kociemba
import os
from scan_one_face_3 import scan_single_face
app = Flask(__name__)
app_state = {
"kociemba_string": "",
"current_face_idx": 0,
"last_scan": "",
"mode": "advanced", # Pamatuje si rezim
"faces": [
"(Bila strana) Polozte kostku zlutou nahoru modrou k sobe",
"(Cervena strana) Polozte kostku oranzovou nahoru bilou k sobe",
"(Zelena strana) Polozte kostku modrou nahoru bilou k sobe",
"(Zluta strana) Polozte kostku bilou nahoru zelenou k sobe",
"(Oranzova strana) Polozte kostku cervenou nahoru bilou k sobe",
"(Modra strana) Polozte kostku zelenou nahoru bilou k sobe"
],
"color_to_face": {'W': 'U', 'Y': 'D', 'R': 'R', 'O': 'L', 'G': 'F', 'B': 'B'}
}
# 1. HLAVNI MENU
@app.route('/')
def index():
return """
<body style="font-family: Arial; text-align: center; margin-top: 30px;">
<h1>Rubik's Cube Solver</h1>
<p style="color: gray;">Zvolte rezim skenovani</p>
<div style="margin-top: 30px;">
<form action="/start/normal" method="post">
<button style="padding: 15px 30px; margin: 10px; background-color: #3498db; color: white; font-size: 18px; width: 85%; border-radius: 8px; border: none;">1. Normal Mode<br><small>(Automaticke potvrzovani)</small></button>
</form>
<form action="/start/advanced" method="post">
<button style="padding: 15px 30px; margin: 10px; background-color: #9b59b6; color: white; font-size: 18px; width: 85%; border-radius: 8px; border: none;">2. Advanced Mode<br><small>(Rucni oprava barev)</small></button>
</form>
<form action="/tutorial" method="get">
<button style="padding: 15px 30px; margin: 10px; background-color: #2ecc71; color: white; font-size: 18px; width: 85%; border-radius: 8px; border: none;">3. Tutorial / Navod</button>
</form>
</div>
<hr style="margin-top: 60px;">
<details>
<summary style="color: gray; cursor: pointer;">Nastaveni pro vyvojare</summary>
<form action="/dev_mode" method="post" style="margin-top: 15px;">
Heslo: <input type="password" name="password" style="width: 100px;">
<button type="submit" style="padding: 5px; background-color: #e74c3c; color: white; border: none; border-radius: 4px;">Prepnout na VNC (Domaci Wi-Fi)</button>
</form>
</details>
</body>
"""
# TLACITKA Z MENU
@app.route('/start/<mode>', methods=['POST'])
def start_mode(mode):
app_state["kociemba_string"] = ""
app_state["current_face_idx"] = 0
app_state["last_scan"] = ""
app_state["mode"] = mode
return redirect(url_for('step'))
@app.route('/tutorial')
def tutorial():
return """
<body style="font-family: Arial; padding: 20px;">
<h1 style="text-align: center;">Jak skenovat kostku</h1>
<p>1. Kostku skladame postupne v tomto poradi barev stran: <b>Bila, Cervena, Zelena, Zluta, Oranzova, Modra</b>.</p>
<p>2. Vzdy dodrzujte spravnou orientaci kostky (ktera barva je nahore a ktera k vam).</p>
<p>3. V <b>Normal modu</b> program barvy rovnou prelozi. V <b>Advanced modu</b> si je muzete zkontrolovat a prepsat pismena (W, R, G, Y, O, B).</p>
<br>
<div style="text-align: center;">
<a href="/" style="padding: 15px 30px; background-color: #34495e; color: white; text-decoration: none; border-radius: 8px; font-size: 18px;">Zpet do menu</a>
</div>
</body>
"""
@app.route('/dev_mode', methods=['POST'])
def dev_mode():
password = request.form.get('password')
if password == "12345":
print("Prepinam do vyvojarskeho modu...")
os.system('sudo nmcli con up "Dobi"')
return "<body style='text-align:center; margin-top:50px;'><h2>Prepinam site...</h2><p>Raspberry nyni vysila signal do domaci Wi-Fi. Pripojte se z PC pres VNC.</p></body>"
else:
return "<h2 style='color:red; text-align:center;'>Spatne heslo!</h2><a href='/'>Zpet</a>"
# 2. SAMOTNE SKENOVANI
@app.route('/step')
def step():
if app_state["current_face_idx"] >= 6:
return f"""
<body style="font-family: Arial; text-align: center; margin-top: 50px;">
<h2>Vse naskenovano!</h2>
<p>String: <b>{app_state['kociemba_string']}</b></p>
<form action="/solve" method="post"><button style="padding: 20px; font-size: 20px; background-color: #3498db; color: white; border: none; border-radius: 8px;">Vygenerovat reseni</button></form>
<br><br><a href="/" style="color: red;">Zpet do hlavniho menu</a>
</body>
"""
face_name = app_state["faces"][app_state["current_face_idx"]]
html = f"""
<body style="font-family: Arial; text-align: center; margin-top: 50px;">
<h3>Krok {app_state["current_face_idx"] + 1} ze 6</h3>
<h2>{face_name}</h2>
<form action="/scan" method="post"><button style="padding: 20px 40px; background-color: #f1c40f; font-size: 20px; border: none; border-radius: 8px;">Skenovat stranu</button></form>
"""
if app_state["last_scan"]:
if "CHYBA" in app_state["last_scan"]:
html += f"<p style='color: red;'><b>{app_state['last_scan']}</b> Zkuste to znovu.</p>"
elif app_state["mode"] == "advanced":
html += f"""
<hr style="margin-top: 30px;">
<h3 style="color: green;">Naskenovano: {app_state["last_scan"]}</h3>
<p>Zkontroluj pismena a potvrd:</p>
<form action="/confirm" method="post">
<input type="text" name="colors" value="{app_state['last_scan']}" style="font-size: 24px; text-align: center;" required><br><br>
<button style="padding: 15px; background-color: #2ecc71; color: white; border: none; border-radius: 8px; font-size: 18px;">Potvrdit</button>
</form>
"""
html += "<br><br><a href='/'>Zrusit a zpet do menu</a></body>"
return html
@app.route('/scan', methods=['POST'])
def scan():
scanned = scan_single_face()
if scanned and len(scanned) == 9:
if app_state["mode"] == "normal":
if all(c in app_state['color_to_face'] for c in scanned):
translated = "".join(app_state['color_to_face'][c] for c in scanned)
app_state["kociemba_string"] += translated
app_state["current_face_idx"] += 1
app_state["last_scan"] = ""
else:
app_state["last_scan"] = "CHYBA: Neznama barva ve skenu."
else:
app_state["last_scan"] = scanned
else:
app_state["last_scan"] = "CHYBA_KAMERY"
return redirect(url_for('step'))
@app.route('/confirm', methods=['POST'])
def confirm():
colors = request.form.get('colors').strip().upper()
if len(colors) == 9 and all(c in app_state['color_to_face'] for c in colors):
translated = "".join(app_state['color_to_face'][c] for c in colors)
app_state["kociemba_string"] += translated
app_state["current_face_idx"] += 1
app_state["last_scan"] = ""
return redirect(url_for('step'))
@app.route('/solve', methods=['POST'])
def solve():
k_string = app_state["kociemba_string"]
if k_string == "UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB":
return "<h1 style='text-align:center; margin-top:50px;'>Kostka uz je slozena!</h1><br><div style='text-align:center;'><a href='/'>Zpet do menu</a></div>"
try:
solution = kociemba.solve(k_string)
return f"<body style='text-align:center; margin-top:50px;'><h1>Reseni:</h1><h2 style='color:blue;'>{solution}</h2><br><a href='/' style='padding: 10px; background: #2ecc71; color: white; text-decoration:none; border-radius: 5px;'>Skladat novou</a></body>"
except Exception as e:
return f"<h2 style='color:red; text-align:center; margin-top:50px;'>Nelze najit reseni: {e}</h2><div style='text-align:center;'><a href='/'>Zpet do menu</a></div>"
def start_server():
print("Spoustim webove rozhrani... Otevri prohlizec")
app.run(host='0.0.0.0', port=80)

99
src/web_ui_old.py

@ -0,0 +1,99 @@
from flask import Flask, request, redirect, url_for
import kociemba
from scan_one_face_3 import scan_single_face
app = Flask(__name__)
app_state = {
"kociemba_string": "",
"current_face_idx": 0,
"last_scan": "",
"faces": [
"(Bila strana) Polozte kostku zlutou nahoru modrou k sobe",
"(Cervena strana) Polozte kostku oranzovou nahoru bilou k sobe",
"(Zelena strana) Polozte kostku modrou nahoru bilou k sobe",
"(Zluta strana) Polozte kostku bilou nahoru zelenou k sobe",
"(Oranzova strana) Polozte kostku cervenou nahoru bilou k sobe",
"(Modra strana) Polozte kostku zelenou nahoru bilou k sobe"
],
"color_to_face": {'W': 'U', 'Y': 'D', 'R': 'R', 'O': 'L', 'G': 'F', 'B': 'B'}
}
@app.route('/')
def index():
if app_state["current_face_idx"] >= 6:
return f"""
<body style="font-family: Arial; text-align: center; margin-top: 50px;">
<h2>Vse naskenovano!</h2>
<p>Vygenerovany string: <b>{app_state['kociemba_string']}</b></p>
<form action="/solve" method="post"><button style="padding: 20px; font-size: 20px; background-color: #3498db; color: white;">Vygenerovat reseni</button></form>
<br><a href="/reset">Zacit znovu</a>
</body>
"""
face_name = app_state["faces"][app_state["current_face_idx"]]
html = f"""
<body style="font-family: Arial; text-align: center; margin-top: 50px;">
<h2>Krok {app_state["current_face_idx"] + 1} ze 6</h2>
<h1>{face_name}</h1>
<form action="/scan" method="post"><button style="padding: 20px 40px; background-color: #f1c40f; font-size: 20px;">Skenovat stranu</button></form>
"""
if app_state["last_scan"]:
html += f"""
<hr>
<h3 style="color: green;">Naskenovano: {app_state["last_scan"]}</h3>
<p>Pokud je to spravne, potvrd. Pokud je chyba, prepis znaky:</p>
<form action="/confirm" method="post">
<input type="text" name="colors" value="{app_state['last_scan']}" style="font-size: 24px; text-align: center;" required><br><br>
<button style="padding: 15px; background-color: #2ecc71; color: white;">Potvrdit</button>
</form>
"""
html += "</body>"
return html
@app.route('/scan', methods=['POST'])
def scan():
try:
scanned = scan_single_face()
if scanned and len(scanned) == 9:
app_state["last_scan"] = scanned
else:
app_state["last_scan"] = "CHYBA_FORMATU"
except Exception as e:
app_state["last_scan"] = f"CHYBA_KODU: {str(e)}"
return redirect(url_for('index'))
@app.route('/confirm', methods=['POST'])
def confirm():
colors = request.form.get('colors').strip().upper()
if len(colors) == 9 and all(c in app_state['color_to_face'] for c in colors):
translated = "".join(app_state['color_to_face'][c] for c in colors)
app_state["kociemba_string"] += translated
app_state["current_face_idx"] += 1
app_state["last_scan"] = ""
return redirect(url_for('index'))
@app.route('/solve', methods=['POST'])
def solve():
k_string = app_state["kociemba_string"]
if k_string == "UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB":
return "<h1 style='text-align:center;'>Kostka uz je slozena!</h1><a href='/reset'>Zpet</a>"
try:
solution = kociemba.solve(k_string)
return f"<body style='text-align:center;'><h1>Reseni:</h1><h2 style='color:blue;'>{solution}</h2><br><a href='/reset'>Skladat novou</a></body>"
except Exception as e:
return f"<h2 style='color:red;'>Nelze najit reseni: {e}</h2><a href='/reset'>Zpet</a>"
@app.route('/reset')
def reset():
app_state["kociemba_string"] = ""
app_state["current_face_idx"] = 0
app_state["last_scan"] = ""
return redirect(url_for('index'))
def start_server():
print("Spoustim webove rozhrani... Otevri prohlizec na portu 5000")
app.run(host='0.0.0.0', port=5000)
Loading…
Cancel
Save