bzhou830-240598 发表于 昨天 07:04

《2025 DigiKey AI应用创意挑战赛》基于视觉的魔方快速还原机器人

# 项目名称

基于视觉的魔方快速还原机器人

## 项目概述

带小孩在小区玩,看到了几个小孩在玩魔方。估计跟我小时候一样,怎么弄也还原不了(当然,我现在还是还原不了!)。我凑近一看,有小孩问我能不能帮他还原。我试了几下,一群小孩惊叹原来连大人也是没办法还原魔方的啊!

后来细想,虽然我不能还原,但是我知道还原魔方是有固定的套路的。也就是公式!既然有固定的公式,那用机器来还原肯定就行得通,而且肯定比人要快。

随后学习了一些相关知识就准备开始做这个项目了。项目采用视觉方案来对魔方的每个面的色块进行识别。识别后使用固定的公式驱动舵机转动对魔方进行还原。

!(https://www.eefocus.com/forum/data/attachment/forum/202601/28/063518duxmmvl9zwet3xxm.png)

## 实物展示

驱动部分采用两个舵机,一个舵机控制水平方向上的旋转,另一个舵机实现魔方翻面和魔方旋转时候的夹持。

!(https://www.eefocus.com/forum/data/attachment/forum/202601/28/063901w9vqpbqp5lbtq922.png)

!(https://www.eefocus.com/forum/data/attachment/forum/202601/28/064051xvw5ziv5uyssn65n.png)

摄像头则安装在顶部的位置上。整机的结构如下:

!(https://www.eefocus.com/forum/data/attachment/forum/202601/28/064646tfohyf6zjj5yeaxa.png)

主要功能代码如下:

```
# 导入库
import tkinter as tk
from tkinter import ttk
import datetime as dt
import time
import os.path

# 关闭GUI时的处理函数
def on_closing():
    disp.show_cubotino()
    print("\n正在关闭GUI应用\n\n")
    camWindow.destroy()
    servoWindow.destroy()
    servo.servo_off()
    close_cam()
    time.sleep(0.5)
    root.destroy()

# 拍摄并显示图像
def take_image(refresh=1, widgets_freeze=True):
    global picamera_initialized, cv2, camera, width, height
   
    if widgets_freeze:
      labels = (crop_label, warp_label, cam_files_label)
      for label in labels:
            disable_widgets(label, relief="sunken")
      root.update()

    if not picamera_initialized:
      cv2, camera, width, height = camera_setup(display=disp, gui_debug=False)
      picamera_initialized = True
   
    servo.cam_led_On(1)
    frame, w, h = read_camera()
    servo.cam_led_Off()
   
    try:
      frame2, w2, h2 = crop(frame, width, height, x_l, x_r, y_u, y_b)
      frame2, w2, h2 = warp(frame2, w2, h2, warp_fraction, warp_slicing)
      
      for i in range(refresh):
            cv2.namedWindow('裁剪与校正后图像')
            cv2.moveWindow('裁剪与校正后图像', 0,40)
            cv2.imshow('裁剪与校正后图像', frame2)
            key = cv2.waitKey(100)
      
    except:
      print("裁剪或校正错误,请尝试较温和的参数")
   
    if widgets_freeze:
      for label in labels:
            enable_widgets(label, relief="raised")
      root.update()

# 从文件读取舵机设置
def read_servo_settings_file(fname=''):
    try:
      srv_settings = settings.get_servos_settings()
      return srv_settings
    except:
      print('读取舵机设置文件时出错')
      return {}

# 应用舵机设置到全局变量
def upload_servo_settings(srv_settings):
    global t_min_pulse_width, t_max_pulse_width, t_servo_close, t_servo_open
    global t_servo_read, t_servo_flip, t_servo_rel_delta
    global t_flip_to_close_time, t_close_to_flip_time, t_flip_open_time, t_open_close_time, t_rel_time
   
    global b_min_pulse_width, b_max_pulse_width, b_servo_CCW, b_servo_CW, b_home
    global b_rel_CCW, b_rel_CW, b_extra_home_CW, b_extra_home_CCW
    global b_spin_time, b_rotate_time, b_rel_time

    t_min_pulse_width = srv_settings['t_min_pulse_width']
    t_max_pulse_width = srv_settings['t_max_pulse_width']
    t_servo_close = srv_settings['t_servo_close']
    t_servo_open = srv_settings['t_servo_open']
    t_servo_read = srv_settings['t_servo_read']
    t_servo_flip = srv_settings['t_servo_flip']
    t_servo_rel_delta = srv_settings['t_servo_rel_delta']
    t_flip_to_close_time = srv_settings['t_flip_to_close_time']
    t_close_to_flip_time = srv_settings['t_close_to_flip_time']
    t_flip_open_time = srv_settings['t_flip_open_time']
    t_open_close_time = srv_settings['t_open_close_time']
    t_rel_time = srv_settings['t_rel_time']

    b_min_pulse_width = srv_settings['b_min_pulse_width']
    b_max_pulse_width = srv_settings['b_max_pulse_width']
    b_servo_CCW = srv_settings['b_servo_CCW']
    b_servo_CW = srv_settings['b_servo_CW']
    b_home = srv_settings['b_home']
    b_rel_CCW = srv_settings['b_rel_CCW']
    b_rel_CW = srv_settings['b_rel_CW']
    b_extra_home_CW = srv_settings['b_extra_home_CW']
    b_extra_home_CCW = srv_settings['b_extra_home_CCW']
    b_spin_time = srv_settings['b_spin_time']
    b_rotate_time = srv_settings['b_rotate_time']
    b_rel_time = srv_settings['b_rel_time']

# 更新舵机设置字典
def update_servo_settings_dict():
    srv_settings['t_min_pulse_width'] = str(t_min_pulse_width)
    srv_settings['t_max_pulse_width'] = str(t_max_pulse_width)
    srv_settings['t_servo_close'] = str(t_servo_close)
    srv_settings['t_servo_open'] = str(t_servo_open)
    srv_settings['t_servo_read'] = str(t_servo_read)
    srv_settings['t_servo_flip'] = str(t_servo_flip)
    srv_settings['t_servo_rel_delta'] = str(t_servo_rel_delta)
    srv_settings['t_flip_to_close_time'] = str(t_flip_to_close_time)
    srv_settings['t_close_to_flip_time'] = str(t_close_to_flip_time)
    srv_settings['t_flip_open_time'] = str(t_flip_open_time)
    srv_settings['t_open_close_time'] = str(t_open_close_time)
    srv_settings['t_rel_time'] = str(t_rel_time)
   
    srv_settings['b_min_pulse_width'] = str(b_min_pulse_width)
    srv_settings['b_max_pulse_width'] = str(b_max_pulse_width)
    srv_settings['b_servo_CCW'] = str(b_servo_CCW)
    srv_settings['b_servo_CW'] = str(b_servo_CW)
    srv_settings['b_home'] = str(b_home)
    srv_settings['b_rel_CCW'] = str(b_rel_CCW)
    srv_settings['b_rel_CW'] = str(b_rel_CW)
    srv_settings['b_extra_home_CW'] = str(b_extra_home_CW)
    srv_settings['b_extra_home_CCW'] = str(b_extra_home_CCW)
    srv_settings['b_spin_time'] = str(b_spin_time)
    srv_settings['b_rotate_time'] = str(b_rotate_time)
    srv_settings['b_rel_time'] = str(b_rel_time)
   
    return srv_settings

# 加载上一次舵机设置
def load_previous_servo_settings():
    srv_settings = settings.load_previous_settings(servo=True)
    upload_servo_settings(srv_settings)
    update_servo_sliders()

# 保存新舵机设置
def save_new_servo_settings():
    global srv_settings
   
    fname = settings.get_servo_settings_fname()
    if os.path.exists(fname):
      datetime = dt.datetime.now().strftime('%Y%m%d_%H%M%S')
      backup_fname = fname[:-4] + '_backup_' + datetime + '.txt'
      print("\n保存旧设置到备份文件:", backup_fname)
      settings.save_setting(backup_fname, srv_settings, debug=False)
      
      settings.backups_cleanup(fname, n=10)

      srv_settings = update_servo_settings_dict()
      print("保存当前设置到:", fname)
      settings.save_setting(fname, srv_settings, debug=False)
    else:
      print(f"文件 {fname} 不存在")

# 更新舵机滑块
def update_servo_sliders():
    s_top_srv_close.set(t_servo_close)
    s_top_srv_rel_delta.set(t_servo_rel_delta)
    s_top_srv_open.set(t_servo_open)
    s_top_srv_read.set(t_servo_read)
    s_top_srv_flip.set(t_servo_flip)
    s_top_srv_flip_to_close_time.set(t_flip_to_close_time)
    s_top_srv_close_to_flip_time.set(t_close_to_flip_time)
    s_top_srv_flip_open_time.set(t_flip_open_time)
    s_top_srv_open_close_time.set(t_open_close_time)
    s_top_srv_rel_time.set(t_rel_time)
   
    s_btn_srv_min_pulse.set(b_min_pulse_width)
    s_btn_srv_max_pulse.set(b_max_pulse_width)
    s_btn_srv_CCW.set(b_servo_CCW)
    s_btn_srv_home.set(b_home)
    s_btn_srv_CW.set(b_servo_CW)
    s_btn_srv_rel_CCW.set(b_rel_CCW)
    s_btn_srv_rel_CW.set(b_rel_CW)
    s_extra_home_CW.set(b_extra_home_CW)
    s_extra_home_CCW.set(b_extra_home_CCW)
    s_btn_srv_spin_time.set(b_spin_time)
    s_btn_srv_rotate_time.set(b_rotate_time)
    s_btn_srv_rel_time.set(b_rel_time)

# 获取底部舵机最后位置
def last_btn_srv_pos():
    global b_servo_pos
   
    if b_servo_pos == 'CCW':
      return b_servo_CCW
    elif b_servo_pos == 'CW':
      return b_servo_CW
    else:
      return b_home

# 读取摄像头设置文件
def read_cam_settings_file(fname=''):
    try:
      cam_settings = settings.get_settings()
      return cam_settings
    except:
      print('读取摄像头设置文件时出错')
      return {}

# 应用摄像头设置到全局变量
def upload_cam_settings(cam_settings):
    global x_l, x_r, y_u, y_b, warp_fraction, warp_slicing

    x_l = cam_settings['x_l']
    x_r = cam_settings['x_r']
    y_u = cam_settings['y_u']
    y_b = cam_settings['y_b']
    warp_fraction = cam_settings['warp_fraction']
    warp_slicing = cam_settings['warp_slicing']

# 加载上一次摄像头设置
def load_previous_cam_settings():
    cam_settings = settings.load_previous_settings(servo=False)
    upload_cam_settings(cam_settings)
    update_cam_sliders()

# 更新摄像头设置字典
def update_cam_settings_dict():
    cam_settings['x_l'] = str(x_l)
    cam_settings['x_r'] = str(x_r)
    cam_settings['y_u'] = str(y_u)
    cam_settings['y_b'] = str(y_b)
    cam_settings['warp_fraction'] = str(warp_fraction)
    cam_settings['warp_slicing'] = str(warp_slicing)
   
    return cam_settings

# 保存新摄像头设置
def save_new_cam_settings():
    global cam_settings
   
    fname = settings.get_settings_fname()
    if os.path.exists(fname):
      datetime = dt.datetime.now().strftime('%Y%m%d_%H%M%S')
      backup_fname = fname[:-4] + '_backup_' + datetime + '.txt'
      print("\n保存旧摄像头设置到备份文件:", backup_fname)
      settings.save_setting(backup_fname, cam_settings, debug=False)
      
      settings.backups_cleanup(fname, n=10)
      
      cam_settings = update_cam_settings_dict()
      print("保存当前摄像头设置到:", fname)
      settings.save_setting(fname, cam_settings, debug=False)
    else:
      print(f"文件 {fname} 不存在")

# 更新摄像头滑块
def update_cam_sliders():
    s_crop_l.set(x_l)
    s_crop_r.set(x_r)
    s_crop_u.set(y_u)
    s_crop_b.set(y_b)
    s_warp_f.set(warp_fraction)
    s_warp_s.set(warp_slicing)

# 滑块值处理函数
def servo_close(val):
    global t_servo_close, t_servo_rel_delta, t_servo_pos
    t_servo_close = round(float(s_top_srv_close.get()),3)
    t_servo_rel = round(t_servo_close - float(s_top_srv_rel_delta.get()),3)
    disp.show_on_display('顶部舵机 关闭', str(t_servo_close), fs1=30, y2=75, fs2=30)
    servo.servo_to_pos('top', t_servo_close)
    time.sleep(t_open_close_time)
    servo.servo_to_pos('top', t_servo_rel)
    time.sleep(t_rel_time)
    disp.show_on_display('顶部舵机 关闭', f'{t_servo_close} ({t_servo_rel})', fs1=30, y2=75, fs2=30)
    t_servo_pos = 'close'
    btm_srv_widgets_status()

def servo_release(val):
    global t_servo_rel_delta, t_servo_pos
    t_servo_rel_delta = round(float(s_top_srv_rel_delta.get()),3)
    disp.show_on_display('顶部舵机 释放', f'({t_servo_rel_delta})', fs1=25, y2=75, fs2=30)
    t_servo_pos = 'close'
    btm_srv_widgets_status()

def servo_open(val):
    global t_servo_open, t_servo_pos
    t_servo_open = round(float(s_top_srv_open.get()),3)
    disp.show_on_display('顶部舵机 打开', str(t_servo_open), fs1=30, y2=75, fs2=30)
    servo.servo_to_pos('top', t_servo_open)
    t_servo_pos = 'open'
    btm_srv_widgets_status()
   

def servo_read(val):
    global t_servo_read, t_servo_pos
    t_servo_read = round(float(s_top_srv_read.get()),3)
    disp.show_on_display('顶部舵机 读取', str(t_servo_read), fs1=30, y2=75, fs2=30)
    servo.servo_to_pos('top', t_servo_read)
    t_servo_pos = 'read'
    btm_srv_widgets_status()

def servo_flip(val):
    global t_servo_flip, t_servo_pos
    t_servo_flip = round(float(s_top_srv_flip.get()),3)
    disp.show_on_display('顶部舵机 翻转', str(t_servo_flip), fs1=30, y2=75, fs2=30)
    servo.servo_to_pos('top', t_servo_flip)
    t_servo_pos = 'flip'
    btm_srv_widgets_status()
   
def flip_to_close_time(val):
    global t_flip_to_close_time
    t_flip_to_close_time = round(float(s_top_srv_flip_to_close_time.get()),3)

def servo_rel_close(val):
    global t_rel_time
    t_rel_time = round(float(s_top_srv_rel_time.get()),3)

   
def close_to_flip_time(val):
    global t_close_to_flip_time
    t_close_to_flip_time = round(float(s_top_srv_close_to_flip_time.get()),3)

def flip_open_time(val):
    global t_flip_open_time
    t_flip_open_time = round(float(s_top_srv_flip_open_time.get()),3)

def open_close_time(val):
    global t_open_close_time
    t_open_close_time = round(float(s_top_srv_open_close_time.get()),3)
   
   
def btn_srv_min_pulse(val):
    global b_min_pulse_width, b_servo_pos
    b_min_pulse_width = round(float(s_btn_srv_min_pulse.get()),3)
    disp.show_on_display('底部舵机 最小脉宽', str(b_min_pulse_width), fs1=26, y2=75, fs2=30)
    pos = last_btn_srv_pos()
    servo.b_servo_create(b_min_pulse_width, b_max_pulse_width, pos)

def btn_srv_max_pulse(val):
    global b_max_pulse_width, b_servo_pos
    b_max_pulse_width = round(float(s_btn_srv_max_pulse.get()),3)
    disp.show_on_display('底部舵机 最大脉宽', str(b_max_pulse_width), fs1=26, y2=75, fs2=30)
    pos = last_btn_srv_pos()
    servo.b_servo_create(b_min_pulse_width, b_max_pulse_width, pos)

def servo_CCW(val):
    global b_servo_CCW, b_servo_pos
    if t_servo_pos in ('close', 'open'):
      b_servo_CCW = round(float(s_btn_srv_CCW.get()),3)
      disp.show_on_display('底部舵机 逆时针', str(b_servo_CCW), fs1=30, y2=75, fs2=30)
      servo.servo_to_pos('bottom', b_servo_CCW)
      b_servo_pos = 'CCW'
    else:
      disp.show_on_display('顶部舵机', '阻挡中', fs1=30, y2=75, fs2=30)
   

def servo_home(val):
    global b_home, b_servo_pos
    if t_servo_pos in ('close', 'open'):
      b_home = round(float(s_btn_srv_home.get()),3)
      disp.show_on_display('底部舵机 归位', str(b_home), fs1=30, y2=75, fs2=30)
      servo.servo_to_pos('bottom', b_home)
      b_servo_pos = 'home'
    else:
      disp.show_on_display('顶部舵机', '阻挡中', fs1=30, y2=75, fs2=30)

def servo_CW(val):
    global b_servo_CW, b_servo_pos
    if t_servo_pos in ('close', 'open'):
      b_servo_CW = round(float(s_btn_srv_CW.get()),3)
      disp.show_on_display('底部舵机 顺时针', str(b_servo_CW), fs1=30, y2=75, fs2=30)
      servo.servo_to_pos('bottom', b_servo_CW)
      b_servo_pos = 'CW'
    else:
      disp.show_on_display('顶部舵机', '阻挡中', fs1=30, y2=75, fs2=30)

def servo_rel_CCW(val):
    global b_rel_CCW
    b_rel_CCW = round(float(s_btn_srv_rel_CCW.get()),3)
    target = round(b_servo_CCW + b_rel_CCW,3)
    servo.servo_to_pos('bottom', target)
    disp.show_on_display('逆时针释放', str(b_rel_CCW), fs1=30, y2=75, fs2=30)

def servo_rel_CW(val):
    global b_rel_CW
    b_rel_CW = round(float(s_btn_srv_rel_CW.get()),3)
    disp.show_on_display('顺时针释放', str(b_rel_CW), fs1=30, y2=75, fs2=30)
    target = round(b_servo_CW - b_rel_CW,3)
    servo.servo_to_pos('bottom', target)
    b_servo_pos = 'CW'

def servo_extra_home_CCW(val):
    global b_extra_home_CCW, b_servo_pos
    b_extra_home_CCW = round(float(s_extra_home_CCW.get()),3)
    disp.show_on_display('归位额外逆时针', str(b_extra_home_CCW), fs1=30, y2=75, fs2=30)
    target = round(b_home + b_extra_home_CCW,3)
    servo.servo_to_pos('bottom', target)
    b_servo_pos = 'CW'

def servo_extra_home_CW(val):
    global b_extra_home_CW, b_servo_pos
    b_extra_home_CW = round(float(s_extra_home_CW.get()),3)
    disp.show_on_display('归位额外顺时针', str(b_extra_home_CW), fs1=30, y2=75, fs2=30)
    target = round(b_home - b_extra_home_CW,3)
    servo.servo_to_pos('bottom', target)
    b_servo_pos = 'CW'

def servo_rotate_time(val):
    global b_rotate_time
    b_rotate_time = round(float(s_btn_srv_rotate_time.get()),3)

def servo_rel_time(val):
    global b_rel_time
    b_rel_time = round(float(s_btn_srv_rel_time.get()),3)

def servo_spin_time(val):
    global b_spin_time
    b_spin_time = round(float(s_btn_srv_spin_time.get()),3)

def crop_u(val):
    global y_u
    y_u = int(s_crop_u.get())
    disp.show_on_display('裁剪 上边', str(y_u), fs1=27, y2=75, fs2=30)
    take_image(widgets_freeze=False)

def crop_l(val):
    global x_l
    x_l = int(s_crop_l.get())
    disp.show_on_display('裁剪 左边', str(x_l), fs1=28, y2=75, fs2=30)
    take_image(widgets_freeze=False)

def crop_r(val):
    global x_r
    x_r = int(s_crop_r.get())
    disp.show_on_display('裁剪 右边', str(x_r), fs1=28, y2=75, fs2=30)
    take_image(widgets_freeze=False)

def crop_b(val):
    global y_b
    y_b = int(s_crop_b.get())
    disp.show_on_display('裁剪 下边', str(y_b), fs1=26, y2=75, fs2=30)
    take_image(widgets_freeze=False)

def warp_f_get(val):
    global warp_fraction
    warp_fraction = float(round(s_warp_f.get(),2))
    disp.show_on_display('校正比例', str(warp_fraction), fs1=24, y2=75, fs2=30)
    take_image(widgets_freeze=False)

def warp_s_get(val):
    global warp_slicing
    warp_slicing = float(s_warp_s.get())
    disp.show_on_display('校正切片', str(warp_slicing), fs1=26, y2=75, fs2=30)
    take_image(widgets_freeze=False)

# 测试设置
def servo_init():
    global t_servo_pos, b_servo_pos
   
    servo_init=servo.servo_tune_via_gui(debug=False, start_pos = 'open')
    if servo_init:
      t_servo_pos = 'start'
      b_servo_pos = 'home'

def close_top_cover():
    global t_servo_pos
    servo.stop_release()
    if b_servo_pos in ('CCW', 'home', 'CW'):
      disp.show_on_display('顶部舵机 关闭', str(t_servo_close), fs1=30, y2=75, fs2=30)
      
      t_servo_pos = servo.close_cover(t_servo_close, t_servo_rel_delta, t_open_close_time, t_rel_time, test=True)
      disp.show_on_display('顶部舵机 关闭', f'{t_servo_close} ({t_servo_rel_delta})', fs1=30, y2=75, fs2=30)
      btm_srv_widgets_status()

def open_top_cover():
    global t_servo_pos
    servo.stop_release()
    if b_servo_pos in ('CCW', 'home', 'CW') and t_servo_pos != 'open':
      t_servo_pos = servo.open_cover(t_servo_open, test=True)
      disp.show_on_display('顶部舵机 打开', str(t_servo_open), fs1=30, y2=75, fs2=30)
      btm_srv_widgets_status()
      

def read_position():
    global t_servo_read, t_servo_pos
    servo.stop_release()
    if b_servo_pos in ('CCW', 'home', 'CW') and t_servo_pos != 'read':
      t_servo_pos = servo.servo_to_pos('top', t_servo_read)
      disp.show_on_display('顶部舵机 读取', str(t_servo_read), fs1=30, y2=75, fs2=30)
      btm_srv_widgets_status()

def flip_cube():
    global t_servo_pos
    servo.stop_release()
    if b_servo_pos in ('CCW', 'home', 'CW'):
      t_servo_pos = servo.flip_toggle(t_servo_pos, t_servo_read, t_servo_flip)
      if t_servo_pos == 'flip':
            disp.show_on_display('顶部舵机 翻转', str(t_servo_flip), fs1=30, y2=75, fs2=30)
      if t_servo_pos == 'read':
            disp.show_on_display('顶部舵机 读取', str(t_servo_read), fs1=30, y2=75, fs2=30)
      btm_srv_widgets_status()

def ccw():
    global b_servo_pos
    servo.stop_release()
   
    if t_servo_pos == 'open':
      timer1 = b_spin_time
      if b_servo_pos == 'CW':
            timer1+=timer1
      if t_servo_pos in ('close', 'open') and b_servo_pos != 'CCW':
            target = round(b_servo_CCW + b_rel_CCW,3)
            b_servo_pos = servo.spin_out('CCW', target, 0, timer1, test=True )
            disp.show_on_display('底部舵机 逆时针', f'({target})' , fs1=30, y2=75, fs2=30)
    elif t_servo_pos == 'close':
      timer1 = b_rotate_time
      if b_servo_pos == 'CW':
            timer1+=timer1
      if t_servo_pos in ('close', 'open') and b_servo_pos != 'CCW':
            b_servo_pos = servo.spin_out('CCW', b_servo_CCW, b_rel_CCW, timer1, test=True )
            disp.show_on_display('底部舵机 逆时针', f'{b_servo_CCW} ({round(b_servo_CCW+b_rel_CCW,3)})', fs1=30, y2=75, fs2=30)

   
def home():
    global b_servo_pos
   
    if t_servo_pos in ('close', 'open') and b_servo_pos != 'home':
      servo.stop_release()
      if t_servo_pos == 'open':
            timer1 = b_spin_time
            if b_servo_pos == 'CCW':
                b_servo_pos = servo.rotate_home('CW', b_home, 0, timer1, test=True)
                disp.show_on_display('顶部舵机 归位', str(b_home), fs1=30, y2=75, fs2=30)
            elif b_servo_pos == 'CW':
                b_servo_pos = servo.rotate_home('CCW', b_home, 0, timer1, test=True)
                disp.show_on_display('顶部舵机 归位', str(b_home), fs1=30, y2=75, fs2=30)
   
      elif t_servo_pos == 'close':
            timer1 = b_rotate_time
            if b_servo_pos == 'CCW':
                b_servo_pos = servo.rotate_home('CW', b_home, b_extra_home_CCW, timer1, test=True)
                disp.show_on_display('顶部舵机 归位', f'{round(b_home+b_extra_home_CCW,3)} ({b_home})', fs1=30, y2=75, fs2=30)
            elif b_servo_pos == 'CW':
                b_servo_pos = servo.rotate_home('CCW', b_home, b_extra_home_CW, timer1, test=True)
                disp.show_on_display('顶部舵机 归位', f'{round(b_home-b_extra_home_CW,3)} ({b_home})', fs1=30, y2=75, fs2=30)

def cw():
    global b_servo_pos
   
    if t_servo_pos in ('close', 'open') and b_servo_pos != 'CW':
      servo.stop_release()
      if t_servo_pos == 'open':
            timer1 = b_spin_time
            if b_servo_pos == 'CCW':
                timer1+=timer1
            target = round(b_servo_CW - b_rel_CW, 3)
            b_servo_pos = servo.spin_out('CW', target, 0, timer1, test=True)
            disp.show_on_display('底部舵机 逆时针', f'({target})', fs1=30, y2=75, fs2=30)   
      elif t_servo_pos == 'close':
            timer1 = b_rotate_time
            if b_servo_pos == 'CCW':
                timer1+=timer1
            b_servo_pos = servo.spin_out('CW', b_servo_CW, b_rel_CW, timer1, test=True)
            disp.show_on_display('底部舵机 逆时针', f'{b_servo_CW} ({round(b_servo_CW-b_rel_CW,3)})', fs1=30, y2=75, fs2=30)
            print("b_servo_CW, b_rel_CW, timer1:", b_servo_CW, b_rel_CW, timer1)

def test():
    for label in (top_srv_label, btn_srv_label, test_label, large_test_label, files_label):
      disable_widgets(label, relief="sunken")
    root.update()
   
    servo.stop_release()
    read_position()
    result = servo.test_set_of_movements()

    if result == 'stopped':
      servo_init()
    elif result == 'completed':
      open_top_cover()
      time.sleep(0.5)
      home()
      
    for label in (top_srv_label, btn_srv_label):
      enable_widgets(label, relief="raised")
   
    for label in (test_label, large_test_label, files_label):
      enable_widgets(label, relief="raised")
   
    btm_srv_widgets_status()

def btm_srv_widgets_status():
    global t_servo_pos
   
    if t_servo_pos == 'close' or t_servo_pos == 'open':
      ccw_btn["relief"] = "raised"
      ccw_btn["state"] = "normal"
      home_btn["relief"] = "raised"
      home_btn["state"] = "normal"
      cw_btn["relief"] = "raised"
      cw_btn["state"] = "normal"
      enable_widgets(btn_srv_label, relief="raised")
   
    else:
      ccw_btn["relief"] = "sunken"
      ccw_btn["state"] = "disabled"
      home_btn["relief"] = "sunken"
      home_btn["state"] = "disabled"
      cw_btn["relief"] = "sunken"
      cw_btn["state"] = "disabled"
      disable_widgets(btn_srv_label, relief="sunken")

# GUI窗口管理
def goto_camWindow():
    show_window(camWindow)

def goto_servoWindow():
    show_window(servoWindow)

def disable_widgets(parent, relief):
    for child in parent.winfo_children():
            child["relief"] = relief
            child["state"] = "disabled"

def enable_widgets(parent, relief):
    for child in parent.winfo_children():
            child["relief"] = relief
            child["state"] = "normal"

def show_window(window, startup=False):
    global servoWindow_ontop, t_servo_pos
    if window==servoWindow:
      if not startup:
            try:
                open_top_cover()
                t_servo_pos = ''
                btm_srv_widgets_status()
               
            except:
                pass
      window.tkraise()
      servoWindow_ontop=True
      root.geometry('+0+40')
      if not startup:
            root.resizable(1,1)
            root.geometry(f'{srv_w_w}x{srv_w_h}')
            cv2.destroyAllWindows()
      disp.show_on_display('舵机', '调校中', fs1=38, y2=75, fs2=38)
      disp.set_backlight(1)
      return

    elif window==camWindow:
      read_position()
      window.tkraise()
      servoWindow_ontop=False
      w = 1100
      h = 650
      root.resizable(1,1)
      root.geometry(f'{w}x{h}+{int(ws-w)}+40')
      root.update()
      update_cam_sliders()
      root.update()
      disp.show_on_display('摄像头', '调校中', fs1=38, y2=75, fs2=38)
      disp.set_backlight(1)
      take_image(refresh=10)

# 初始变量
printout = False
servoWindow_ontop=True
picamera_initialized = False
startup = True

srv_settings= read_servo_settings_file()
upload_servo_settings(srv_settings)
servo_init()

cam_settings= read_cam_settings_file()
upload_cam_settings(cam_settings)

# GUI高层部分
root = tk.Tk()
root.title("CUBOTino 舵机设置")
root.geometry('+0+40')

root.rowconfigure(0, weight=1)
root.columnconfigure(0,weight=1)
root.resizable(1,1)

servoWindow=tk.Frame(root)
camWindow=tk.Frame(root)
for window in (servoWindow, camWindow):
    window.grid(row=0,column=0,sticky='nsew')

show_window(servoWindow, startup)

# 顶部舵机相关部件
top_srv_label = tk.LabelFrame(servoWindow, text="顶部盖板 - 舵机设置", labelanchor="nw", font=("Arial", "
```

!(https://www.eefocus.com/forum/data/attachment/forum/202601/28/065556dg6nx4xy8ne6x6ts.png)

## 后记

本次分享项目主要参考资源为CUBOTino_micro的外形结构和这个开源项目的魔方解法代码。
最后感谢eefocus, 感谢Digkey, 让我成为最酷的孩子王。



页: [1]
查看完整版本: 《2025 DigiKey AI应用创意挑战赛》基于视觉的魔方快速还原机器人