主控板led灯的RGB远程调节的图形交互界面

主控板led灯的RGB远程调节的图形交互界面

1. 设计界面

下面这张图片是我最开始的设计稿
请添加图片描述
在设计界面的时候,首先分析自己的需求:

  • 我需要实现的功能有哪些
  • 我需要的插件有哪些

我需要的功能:

  1. 可以选择led灯的各种颜色
  2. 可以调整led灯的亮度
  3. 可以本地通过串口发送信息,也可以通过远程发布mqtt主题并使主控板进行订阅来发送信息
  4. 所有的调整都要实时通过串口或mqtt向主控板发送信息,使其板子上的led灯做出反应
  5. 根据参考文档实现真正的端口选择,以及写死波特率、数据位、停止位、校验位

我需要的插件:

  1. 一个调色盘可以帮助我选取各种我想要的颜色
  2. 一个亮度滑块帮我调节led灯的亮度
  3. 一个颜色滑块能让我快速的自己配置各种颜色
  4. 一个可以在本地和远程切换的按钮
  5. 加入一个端口选择按钮,使电脑有多个端口的时候,能准确的选择自己使用的那个
  6. 还可以加入一些文本显示插件,显示一些颜色信息,还有插件的信息,或者是串口的信息等等

因此我根据需求获得了我的第二版设计图,并根据设计图,利用tkinter进行图形交互界面的设计
在这里插入图片描述

2. 实现步骤

2.1 插件和库的使用

serialserial.tools.list_ports: 这些库用于串口通信。serial 用于打开和管理串口连接,serial.tools.list_ports 用于列出所有可用的串口端口。

tkinter: 这是Python的标准GUI库,用于创建图形用户界面。它提供了各种小部件,如按钮、标签和滑块。

colorchooser: tkinter 的一个子模块,用于提供一个颜色选择对话框,用户可以通过它选择颜色。

threading: 这个库用于创建和管理线程。在这个程序中,线程用于定时发送数据和运行MQTT事件循环。

time: 提供时间相关功能,这里用于线程的延迟操作。

paho.mqtt.client: 这是一个MQTT客户端库,用于通过MQTT协议与MQTT服务器进行通信。


2.2 端口选择——参考文档

1. 获取可用的串口列表
程序使用 serial.tools.list_ports.comports() 获取所有可用的串口,并将这些串口显示在下拉菜单中。

self.port_list = list(serial.tools.list_ports.comports())

self.port_list 是一个包含当前系统中可用的所有串口的列表。每个列表元素是一个 ListPortInfo 对象,包含串口的相关信息。

2. 初始化 Combobox 控件
接下来,程序使用 TkinterCombobox 控件来创建一个下拉列表,供用户选择可用的串口。

self.comvalue = StringVar()
self.comboxlist = ttk.Combobox(root, textvariable=self.comvalue, state='readonly')
  • self.comvalue 是一个 StringVar 对象,用于存储当前选择的串口值。
  • self.comboxlist 是一个 Combobox 控件,state='readonly' 表示该下拉列表是只读的,用户只能从预定义的选项中进行选择。

3. 设置下拉列表的选项
将获取到的串口信息赋值给 Combobox 的选项列表:

if len(self.port_list) <= 0:
    self.comstatus = "No COM Found"
    self.com1 = ()
else:
    self.com1 = tuple(self.port_list)
self.comboxlist["values"] = self.com1
  • 检查 self.port_list 列表的长度,判断是否有可用的串口设备。
  • self.com1 是一个包含所有可用串口的元组,它被赋值给 Comboboxvalues 属性。
  • self.port_list 列表转换为元组,并赋值给 self.com1
  • self.com1 中的串口设备设置为self.comboxlist组合框(下拉框)的可选项["values"]

4.绑定Combobox 的选择事件
为了在用户选择串口后自动更新所选串口,程序为 Combobox 绑定了一个选择事件:

self.comboxlist.bind("<<ComboboxSelected>>", self.comread)

当用户选择一个串口时,self.comread 函数将被调用。

5. 更新所选串口
comread 函数中,程序获取用户选择的串口并将其存储在 self.com 中:

def comread(self, *args):
    self.com = list(self.port_list[self.comboxlist.current()])[0]
    self.comstatus = self.com
    self.stlable["fg"] = "green"
    self.stlable["text"] = self.com + " Select"
  • self 代表类的实例对象,用于访问类中的属性和方法。*args 是可变参数,用于接收多个不确定数量的参数。这种设计通常是为了兼容事件处理函数的调用,因为某些 GUI 框架在调用回调函数时会传递额外的参数(如事件对象)。这个方法将在用户从组合框中选择一个串口设备时被调用。
  • self.comboxlist.current() 返回用户在组合框中选择的选项的索引(整数值)。self.port_list 是包含所有可用串口设备信息的列表。则self.port_list[self.comboxlist.current()] 就是根据用户选择的索引,从 self.port_list 中获取相应的串口设备信息(通常是一个对象或字符串)。list() 将获取的串口设备信息转换为列表。[0] 获取该列表中的第一个元素,通常是串口设备的名称(如 “COM3” )。最终结果是将用户选择的串口设备名称存储到 self.com 变量中。

在这里插入图片描述

2.3 颜色展示区域

# 左侧:颜色展示区域
color_code = tk.StringVar()
color_label = tk.Label(root, textvariable=color_code, bg="white", width=20, height=2)
color_label.grid(row=0, column=0, padx=30, pady=5)
color_code.set("颜色为: #ffffff")
  • StringVar() 创建了一个字符串变量,color_code 用于存储颜色代码。
    set("颜色为: #ffffff") 设置初始值为 #ffffff(白色),并在界面上显示。
    color_code用于存储当前选中颜色的十六进制代码,并绑定到标签以显示当前颜色代码。

  • Label() 创建一个标签,绑定 textvariable=color_code 以动态显示颜色代码。
    bg="white" 设置初始背景颜色为白色。width=20, height=2 设置标签的宽度和高度。grid(row=0, column=0, padx=30, pady=5) 使用网格布局,将标签放置在第0行第0列,并添加内边距。
    color_label用于显示当前选定的颜色,背景颜色根据选择的RGB值动态更新。


在这里插入图片描述

2.4 调色盘

# 左侧:调色盘按钮
palette_button = tk.Button(root, text="调色盘", command=choose_color)
palette_button.grid(row=1, column=0, padx=30, pady=5)

调色盘按钮触发choose_color函数

def choose_color():
    color = colorchooser.askcolor()[1]
    if color:
        r, g, b = int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16)
        #将十六进制的颜色字符串解析为 RGB 三个颜色分量的整数值。
        red_slider.set(r)
        green_slider.set(g)
        blue_slider.set(b)
        update_color_from_sliders()
  • colorchooser.askcolor()Tkinter 提供的一个函数,用于打开一个颜色选择对话框。
    这个函数返回一个包含两个元素的元组:第一个元素是 RGB 值的元组 (R, G, B),第二个元素是选择的颜色的十六进制字符串表示,如 ‘#rrggbb’。[1] 从返回的元组中提取第二个元素,即用户选择的颜色的十六进制表示(‘#rrggbb’)。
    color 变量将保存用户选择的颜色的十六进制字符串表示。
  • 弹出颜色选择对话框,让用户选择颜色。如果选择了颜色,解析其RGB值并更新相应的滑块。
    并调用 update_color_from_sliders 更新颜色显示。
def update_color_from_sliders():
    r = red_slider.get()
    g = green_slider.get()
    b = blue_slider.get()
    brightness = brightness_slider.get()
    color = f'#{r:02x}{g:02x}{b:02x}'
    color_label.config(bg=color)
    color_code.set(f"颜色为: {color}")
    send_color_data(r, g, b, brightness)

从红色、绿色、蓝色和亮度滑块中获取当前值。计算颜色的十六进制代码,并更新颜色标签的背景色和颜色代码标签。
并调用 send_color_data 发送当前颜色和亮度数据。

def send_color_data(r, g, b, brightness):
    global ser, mqttc 	#定义串口和mqtt的全局变量
    mode = mode_button.config('text')[-1]
    data = f"R{r:03d}G{g:03d}B{b:03d}B{brightness:03d}\n"
    if mode == "本地串口":
        if ser and ser.is_open:
            ser.write(data.encode('utf-8'))
            print(f"通过串口发送数据: {data}")  # 打印发送的数据到控制台
    elif mode == "mqtt远程":
        if mqttc:
            mqttc.publish('jason', data, qos=1)  # 发布主题到MQTT服务器
            print(f"通过MQTT发送数据: {data}")  # 打印发送的数据到控制台

2.5 RGB滑块

# 左侧:红色滑块和标签
tk.Label(root, text="Red", fg='red', bg='#ffffce').grid(row=3, column=0, sticky='w', padx=30)
red_slider = tk.Scale(root, from_=0, to=255, orient="horizontal", command=lambda x: update_color_from_sliders())
red_slider.grid(row=3, column=0, padx=(70, 10))

# 左侧:绿色滑块和标签
tk.Label(root, text="Green", fg='green', bg='#ffffce').grid(row=4, column=0, sticky='w', padx=30)
green_slider = tk.Scale(root, from_=0, to=255, orient="horizontal", command=lambda x: update_color_from_sliders())
green_slider.grid(row=4, column=0, padx=(70, 10))

# 左侧:蓝色滑块和标签
tk.Label(root, text="Blue", fg='blue', bg='#ffffce').grid(row=5, column=0, sticky='w', padx=30)
blue_slider = tk.Scale(root, from_=0, to=255, orient="horizontal", command=lambda x: update_color_from_sliders())
blue_slider.grid(row=5, column=0, padx=(70, 10))

Scale() 创建滑块,用于选择RGB颜色值。from_=0, to=255 设置滑块的范围,从0到255,对应颜色的强度。orient="horizontal" 设置滑块为水平布局。
command=lambda x: update_color_from_sliders() 设置滑块变化时调用 update_color_from_sliders() 函数,更新颜色。
grid() 将滑块放置在相应的行和列,并添加适当的边距。
RGB滑块用于用于调整红色、绿色和蓝色的值,用户可以通过拖动滑块来选择所需的颜色。


2.6 模式切换按钮

# 右侧:本地与线上选择按钮
mode_button = tk.Button(root, text="本地串口", command=toggle_mode)
mode_button.grid(row=0, column=1, padx=30, pady=5)

模式切换按钮触发toggle_mode函数

def toggle_mode():
    current_mode = mode_button.config('text')[-1]
    new_mode = "本地串口" if current_mode == "mqtt远程" else "mqtt远程"
    mode_button.config(text=new_mode)
  • mode_button.config('text') 获取 mode_button 按钮的当前配置项中与 'text' 相关的值。
    config() 函数在这里是用来获取按钮的配置,可以返回一个包含所有配置项的字典。如果只传入 'text' 参数,它会返回与 'text' 配置项相关的值(通常是一个包含按钮文本的元组)。[-1] 从返回的元组中提取最后一个元素,即按钮的当前文本内容。在此处,它要么是 “本地串口”,要么是 “mqtt远程”。current_mode 变量保存按钮的当前文本内容的最后一个字符,即 “口” 或 “远”。
  • 通过三元运算符表达式,根据当前模式确定新的模式,并将其存储在 new_mode 变量中。

2.7 端口选择下拉框

# 右侧:端口选择下拉按钮
port_list = list(serial.tools.list_ports.comports())
port_options = [port.device for port in port_list]
port_var = tk.StringVar()
port_dropdown = ttk.Combobox(root, textvariable=port_var, values=port_options, state='readonly')
port_dropdown.grid(row=1, column=1, padx=30, pady=5)
port_dropdown.bind("<<ComboboxSelected>>", comread)

StringVar() 创建字符串变量 port_var,用于存储当前选中的串口端口。
Combobox() 创建下拉框,绑定 port_var 变量,并设置选项为 port_options
state='readonly' 设置下拉框为只读状态,用户只能选择列表中的端口。
bind("<<ComboboxSelected>>", comread) 绑定事件,当用户选择端口时,调用 comread 函数处理。

def comread(*args):
    selected_port = port_var.get()
    if selected_port:
        port_status.set(f"所选端口: {selected_port}")
        setup_serial(selected_port)

从下拉框中获取选中的串口端口。如果选择了端口,更新端口状态标签并调用 setup_serial 函数设置串口连接。

def setup_serial(port):
    global ser
    if ser and ser.is_open:
        ser.close()  # 关闭之前的串口连接
    ser = serial.Serial(port, 9600)

配置和打开指定的串口端口。如果已有串口连接,先关闭它,然后重新打开新的连接。


2.8 亮度滑块

# 右侧:亮度滑块
brightness_value = tk.StringVar()
brightness_value.set("Brightness: 50")
brightness_slider = tk.Scale(root, from_=0, to=100, orient="horizontal", command=update_brightness)
brightness_slider.set(50)
brightness_slider.grid(row=4, column=1, padx=30, pady=5)

StringVar() 创建字符串变量 brightness_value,用于存储和显示亮度值。
set("Brightness: 50") 设置初始值为“Brightness: 50”
Scale() 创建亮度滑块,范围从0到100。
orient="horizontal" 设置滑块为水平布局。
command=update_brightness 绑定 update_brightness 函数,滑块值变化时调用此函数更新亮度显示。
grid(row=4, column=1, padx=30, pady=5) 使用网格布局,将滑块放置在第4行第1列。

def update_brightness(value):
    brightness_value.set(f"Brightness: {value}")

更新亮度值标签,显示当前的亮度值。


2.9 本地串口模式与MQTT远程模式

本地串口模式
在本地串口模式下,程序将通过串口向主控板发送数据:

  • 模式切换: 通过点击界面上的“本地串口”按钮来切换到本地串口模式。按钮的文本将更改为“mqtt远程”,表明当前模式为本地串口。
  • 串口选择: 用户通过下拉菜单选择串口。comread 函数会在选择串口后调用 setup_serial 函数来打开该串口。
  • 数据发送: send_color_data 函数会根据当前模式来决定如何发送数据。当模式为“本地串口”时,它会通过串口发送数据。数据格式为 RrrrGgggBbbbBxxx,其中 RrrrGgggBbbb 分别表示红色、绿色和蓝色的值,Bxxx 表示亮度值。发送的数据以 UTF-8 编码,并通过 ser.write() 发送。
  • 数据定时发送: periodic_send 函数在后台线程中运行,每秒钟读取滑块的值并调用 send_color_data 函数来发送数据。

MQTT远程模式
在MQTT远程模式下,程序将通过MQTT协议将数据发送到MQTT服务器:

  • 模式切换: 通过点击界面上的“mqtt远程”按钮来切换到MQTT远程模式。按钮的文本将更改为“本地串口”,表明当前模式为MQTT远程。
  • MQTT客户端设置: setup_mqtt 函数用于初始化MQTT客户端。它创建了一个 mqttc 实例,设置了连接回调函数 on_connect,并连接到MQTT服务器(broker.emqx.io)。这个服务器是公开的MQTT代理,你可以用它进行测试。
  • 数据发送: send_color_data 函数会根据当前模式来决定如何发送数据。当模式为“mqtt远程”时,它会通过MQTT客户端发布数据到主题 jason。数据格式与本地串口模式相同。
  • MQTT事件循环: mqtt_loop 函数启动MQTT客户端的事件循环,以便持续接收和处理MQTT消息。这个函数在后台线程中运行。

3. 完整代码

import serial
import serial.tools.list_ports
import tkinter as tk
from tkinter import ttk, colorchooser
import threading
import time
import paho.mqtt.client as mqtt  # 导入paho.mqtt.client库用于MQTT通信

ser = None  # 在全局范围内定义 ser 变量
mqttc = None  # 在全局范围内定义 mqttc 变量

def update_color_from_sliders():
    r = red_slider.get()
    g = green_slider.get()
    b = blue_slider.get()
    brightness = brightness_slider.get()
    color = f'#{r:02x}{g:02x}{b:02x}'
    color_label.config(bg=color)
    color_code.set(f"颜色为: {color}")
    send_color_data(r, g, b, brightness)

def choose_color():
    color = colorchooser.askcolor()[1]
    if color:
        r, g, b = int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16)
        red_slider.set(r)
        green_slider.set(g)
        blue_slider.set(b)
        update_color_from_sliders()

def toggle_mode():
    current_mode = mode_button.config('text')[-1]
    new_mode = "本地串口" if current_mode == "mqtt远程" else "mqtt远程"
    mode_button.config(text=new_mode)

def update_brightness(value):
    brightness_value.set(f"Brightness: {value}")

def comread(*args):
    selected_port = port_var.get()
    if selected_port:
        port_status.set(f"所选端口: {selected_port}")
        setup_serial(selected_port)

def setup_serial(port):
    global ser
    if ser and ser.is_open:
        ser.close()  # 关闭之前的串口连接
    ser = serial.Serial(port, 9600)

def setup_mqtt():
    global mqttc
    mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
    mqttc.on_connect = on_connect
    mqttc.connect("broker.emqx.io", 1883, 60)  # 连接到MQTT服务器

def send_color_data(r, g, b, brightness):
    global ser, mqttc
    mode = mode_button.config('text')[-1]
    data = f"R{r:03d}G{g:03d}B{b:03d}B{brightness:03d}\n"
    if mode == "本地串口":
        if ser and ser.is_open:
            ser.write(data.encode('utf-8'))
            print(f"通过串口发送数据: {data}")  # 打印发送的数据到控制台
    elif mode == "mqtt远程":
        if mqttc:
            mqttc.publish('jason', data, qos=1)  # 发布主题到MQTT服务器
            print(f"通过MQTT发送数据: {data}")  # 打印发送的数据到控制台

def periodic_send():
    while True:
        r = red_slider.get()
        g = green_slider.get()
        b = blue_slider.get()
        brightness = brightness_slider.get()
        send_color_data(r, g, b, brightness)
        time.sleep(1)

# 链接到mqtt服务器
def on_connect(client, userdata, flags, reason_code, properties):
    print(f"Connected with result code {reason_code}")

def mqtt_loop():
    mqttc.loop_forever()

root = tk.Tk()
root.title("RGB adjustment")
root.geometry("420x270")
root.configure(bg='#ffffce')
root.resizable(False, False)

# 左侧:颜色展示区域
color_code = tk.StringVar()
color_label = tk.Label(root, textvariable=color_code, bg="white", width=20, height=2)
color_label.grid(row=0, column=0, padx=30, pady=5)
color_code.set("颜色为: #ffffff")

# 左侧:调色盘按钮
palette_button = tk.Button(root, text="调色盘", command=choose_color)
palette_button.grid(row=1, column=0, padx=30, pady=5)

# 左侧:颜色滑块标签
tk.Label(root, text="三原色调色滑块:", bg='#ffffce').grid(row=2, column=0, padx=30, pady=2)

# 左侧:红色滑块和标签
tk.Label(root, text="Red", fg='red', bg='#ffffce').grid(row=3, column=0, sticky='w', padx=30)
red_slider = tk.Scale(root, from_=0, to=255, orient="horizontal", command=lambda x: update_color_from_sliders())
red_slider.grid(row=3, column=0, padx=(70, 10))

# 左侧:绿色滑块和标签
tk.Label(root, text="Green", fg='green', bg='#ffffce').grid(row=4, column=0, sticky='w', padx=30)
green_slider = tk.Scale(root, from_=0, to=255, orient="horizontal", command=lambda x: update_color_from_sliders())
green_slider.grid(row=4, column=0, padx=(70, 10))

# 左侧:蓝色滑块和标签
tk.Label(root, text="Blue", fg='blue', bg='#ffffce').grid(row=5, column=0, sticky='w', padx=30)
blue_slider = tk.Scale(root, from_=0, to=255, orient="horizontal", command=lambda x: update_color_from_sliders())
blue_slider.grid(row=5, column=0, padx=(70, 10))

# 右侧:本地与线上选择按钮
mode_button = tk.Button(root, text="本地串口", command=toggle_mode)
mode_button.grid(row=0, column=1, padx=30, pady=5)

# 右侧:端口选择下拉按钮
port_list = list(serial.tools.list_ports.comports())
port_options = [port.device for port in port_list]
port_var = tk.StringVar()
port_dropdown = ttk.Combobox(root, textvariable=port_var, values=port_options, state='readonly')
port_dropdown.grid(row=1, column=1, padx=30, pady=5)
port_dropdown.bind("<<ComboboxSelected>>", comread)

# 右侧:端口选择状态
port_status = tk.StringVar()
port_status.set("所选端口: 无")
port_status_label = tk.Label(root, textvariable=port_status, bg='#ffffce')
port_status_label.grid(row=2, column=1, padx=30, pady=5)
#Label() 创建标签,绑定 textvariable=port_status 以动态显示端口状态。

# 右侧:亮度滑块标签
tk.Label(root, text="亮度调节滑块:", bg='#ffffce').grid(row=3, column=1, padx=30, pady=5)

# 右侧:亮度滑块
brightness_value = tk.StringVar()
brightness_value.set("Brightness: 50")
brightness_slider = tk.Scale(root, from_=0, to=100, orient="horizontal", command=update_brightness)
brightness_slider.set(50)
brightness_slider.grid(row=4, column=1, padx=30, pady=5)

# 添加右下角标签
footer_label = tk.Label(root, text="by jason", bg='#ffffce', fg='gray')
footer_label.grid(row=5, column=1, padx=30, pady=5, sticky='e')

# 启动定时发送线程
send_thread = threading.Thread(target=periodic_send, daemon=True)
send_thread.start()

# 启动MQTT循环的线程
setup_mqtt()  # 初始化MQTT客户端
mqtt_thread = threading.Thread(target=mqtt_loop, daemon=True)  # daemon=True代表主线程结束子线程也会结束
mqtt_thread.start()

root.mainloop()


4. 要实现主机端所需了解的参数

def setup_mqtt():
    global mqttc
    mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
    mqttc.on_connect = on_connect
    mqttc.connect("broker.emqx.io", 1883, 60)  # 连接到MQTT服务器
def setup_serial(port):
    global ser
    if ser and ser.is_open:
        ser.close()  # 关闭之前的串口连接
    ser = serial.Serial(port, 9600)

数据格式:
R255G255B255B100R255的255是红色滑块的参数、G255的255是绿色滑块的参数、B255的255是蓝色滑块的参数、B100的100是亮度参数。


在文章的最后教大家一个小技巧——将python程序打包成exe文件

首先我们可以用pyinstaller模块实现将python程序打包成exe文件。操作步骤如下:

  1. 安装pyinstaller模块
    在pycharm中操作:file --> setting --> Project: 你的py文件名 --> Python Interpreter --> 点击+ --> 搜索pyinstaller --> 点击左下角安装即可
    在这里插入图片描述
  2. 在pycharm的Terminal终端输入以下指令:
    pyinstaller -F xxxx.py
    在这里插入图片描述
  • 【注】相关参数如下:
    --icon=图标路径 例:pyinstaller -F --icon=my.ico xxxx.py
    -F打包成一个exe文件
    -w使用窗口,无控制台
    -c使用控制台,无窗口
    -D创建一个目录,里面包含exe以及其他一些依赖性文件
  1. 在项目的dist目录下可以看到生成了exe文件,直接在windows系统上就可以运行。
    【注】exe文件本质上是将python解释器和程序打包到了一起,这样我们执行程序是就不用管windows系统是不是有python解释器了。
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值