import base64 import time from threading import Timer import tkinter as tk from tkinter import ttk import controller.controller_reifined as controller controller = controller.ControllerInterface(controller.ControllerRefined(0xFFFFFFFFFFFF, 0x112233445542)) # import controller.controller as controller # controller = controller.ControllerInterface(controller.Controller(0xFFFFFFFFFFFF, 0x112233445542)) win = tk.Tk() device_choice = ttk.Combobox() selected_port = tk.StringVar() dmx_address_field = ttk.Entry() personality_combobox = ttk.Combobox(win) failed_to_read_style = ttk.Style(win) failed_to_read_style.configure('failedToRead.TEntry', foreground="red") status = tk.StringVar() status.set('Пожалуйста, выберите COM порт') saved_status = '' current_entries = list() modes = [] connected = False last_click_time = time.time() check = tk.IntVar() def setStatus(new_status: str): status.set(new_status) win.update() win.update_idletasks() def resetStatus() -> None: global saved_status setStatus(saved_status) def setTempStatus(new_status: str, reset_time: float): global saved_status if status.get() != new_status: saved_status = status.get() setStatus(new_status) Timer(reset_time, resetStatus).start() def resetAllStyles() -> None: for e in current_entries: e.configure(style='TEntry') dmx_address_field.configure(style='TEntry') personality_combobox.configure(style='TCombobox') def clearAllFields() -> None: dmx_address_field.delete(0, tk.END) personality_combobox.set('') personality_combobox.configure(values=[]) for e in current_entries: e.delete(0, tk.END) def comPortSelected(event) -> None: global connected connected = False resetAllStyles() clearAllFields() setStatus(f'Подключение к {selected_port.get()}...') controller.close() controller.connect(selected_port.get()) print(selected_port.get()) if readControllerCurrentsAndData(): setStatus(f'Выбранный порт: {selected_port.get()}') connected = True else: setStatus(f'Не удалось подключиться к {selected_port.get()}') device_choice.set('') connected = False controller.close() def refreshComPorts(event) -> None: device_choice['values'] = controller.readComPorts() def connectionLost() -> None: controller.close() setStatus('Пожалуйста, выберите порт') setTempStatus(f'Подключение к {controller.controller.ser.port} потеряно!', 2) resetAllStyles() device_choice.set('') clearAllFields() def readControllerCurrentsAndData() -> bool: setStatus(f'Чтение токов {controller.controller.ser.port}...') values = controller.readCurrents() if not values: for e in current_entries: e.configure(style='failedToRead.TEntry') else: index = 0 for e in current_entries: e.configure(style='TEntry') e.delete(0, tk.END) e.insert(tk.END, values[index] * 10) index = index + 1 setStatus(f'Чтение адреса {controller.controller.ser.port}...') address = controller.readAddress() if address == -1: dmx_address_field.configure(style='failedToRead.TEntry') else: dmx_address_field.configure(style='TEntry') dmx_address_field.delete(0, tk.END) dmx_address_field.insert(tk.END, str(address)) global modes modes = [] setStatus(f'Чтение режимов {controller.controller.ser.port}...') dev_info = controller.readDeviceInfo() if dev_info: mode_count = dev_info[2] for index in range(mode_count): setStatus(f'Чтение режима {index + 1} {controller.controller.ser.port}...') mode = controller.readMode(index) if not mode: modes = [] break modes.append(mode) time.sleep(0.05) if not modes: personality_combobox.set('ERROR') else: personality_combobox.configure(style='TCombobox') personality_combobox.configure(values=modes) mode_names = [] for index, m in enumerate(modes): mode_names.append(f'{index + 1}:{m[1]}') personality_combobox.set(mode_names[dev_info[1] - 1]) personality_combobox.configure(values=mode_names) return values or address != -1 or modes def writeCurrentsAddressAndMode() -> None: toWrite = [] for e in current_entries: x = e.get() if x != "": toWrite.append(int(x) // 10) else: toWrite.append(0) setStatus(f'Запись токов в {controller.controller.ser.port}...') print(f'Writing... {toWrite}') if not controller.writeCurrents(toWrite): connectionLost() return setStatus(f'Запись адреса в {controller.controller.ser.port}...') if dmx_address_field.get() != "" and not controller.writeAddress(int(dmx_address_field.get())): connectionLost() return setStatus(f'Запись режима в {controller.controller.ser.port}...') if not controller.writeMode(personality_combobox.current() + 1): connectionLost() return if check.get() == 0: setStatus(f'Выбранный порт: {controller.controller.ser.port}') setTempStatus(f'Данные были записаны в {controller.controller.ser.port}', 1) else: time.sleep(0.5) dmx_save = dmx_address_field.get() pers_save = personality_combobox.get() currents_save = [] for entry in current_entries: currents_save.append(entry.get()) global modes setStatus(f'Чтение адреса: {controller.controller.ser.port}...') dmx_real = controller.readAddress() setStatus(f'Чтение режима: {controller.controller.ser.port}...') dev_info = controller.readDeviceInfo() setStatus(f'Чтение токов: {controller.controller.ser.port}...') time.sleep(1) currents_real = controller.readCurrents() if dmx_real == -1 or not dev_info or not currents_real: connectionLost() return pers_real = f'{dev_info[1]}:{modes[dev_info[1] - 1][1]}' failed = False if dmx_save == "" or int(dmx_save) != dmx_real: failed = True dmx_address_field.configure(style='failedToRead.TEntry') else: dmx_address_field.configure(style='TEntry') if pers_save != pers_real: failed = True personality_combobox.set('ERROR') else: personality_combobox.configure(style='TCombobox') for index in range(4): if currents_save[index] == "" or int(currents_save[index]) != currents_real[index] * 10: failed = True current_entries[index].configure(style='failedToRead.TEntry') else: current_entries[index].configure(style='TEntry') setStatus(f'Выбранный порт: {controller.controller.ser.port}') if failed: setTempStatus('Обнаружены ошибки, повторите запись', 1) else: setTempStatus('Проверка прошла успешно', 1) def show_personality_callback() -> None: popup = tk.Toplevel(win) popup.grab_set() popup.resizable(False, False) popup.title('Информация о режимах') columns = ( ("Номер", 50), ("Название", 100), ("Каналов", 65), ("Плавность", 80), ("Поведение без DMX", 160), ("Описание", 200), ) for index, c in enumerate(columns): label = tk.Label(popup, text=f'{c[0]}', width=c[1] // 8, borderwidth=1, relief="solid") label.grid(row=0, column=index, sticky="nsew") mode_info = [ (1, "1CH MAX FST", 'нет', 'максимальная яркость'), (2, "1CH MAX SLO", 'есть', 'максимальная яркость'), (3, "1CH KEEP FST", 'нет', 'сохранение кадра'), (4, "1CH KEEP SLO", 'есть', 'сохранение кадра'), (5, "1CH DARK FST", 'нет', 'гашение'), (6, "1CH DARK SLO", 'есть', 'гашение'), (7, "2CH MAX FST", 'нет', 'максимальная яркость'), (8, "2CH MAX SLO", 'есть', 'максимальная яркость'), (9, "2CH KEEP FST", 'нет', 'сохранение кадра'), (10, "2CH KEEP SLO", 'есть', 'сохранение кадра'), (11, "2CH DARK FST", 'нет', 'гашение'), (12, "2CH DARK FST", 'есть', 'гашение'), (13, "3 COLOR CHG", 'нет', 'сохранение кадра'), (14, "4 COLOR CHG", 'нет', 'сохранение кадра'), (15, "4CH MAXA FST", 'нет', 'максимальная яркость всех'), (16, "4CH MAXA SLO", 'есть', 'максимальная яркость всех'), (17, "4CH MAXW FST", 'нет', 'максимальная яркость белый'), (18, "4CH MAXW SLO", 'есть', 'максимальная яркость.белый'), (19, "4CH KEEP FST", 'нет', 'сохранение кадра'), (20, "4CH KEEP SLO", 'есть', 'сохранение кадра'), (21, "4CH DARK FST", 'нет', 'гашение'), (22, "4CH DARK SLO", 'есть', 'гашение'), (23, "4CH RB3S FST", 'нет', 'RGB перелив синхронизированный'), (24, "4CH RB3S SLO", 'есть', 'RGB перелив синхронизированный'), (25, "4CH RB3A FST", 'нет', 'RGB перелив индивидуальный'), (26, "4CH RB3A SLO", 'есть', 'RGB перелив индивидуальный'), (27, "4CH RB4S FST", 'нет', 'RGBW перелив синхронизированный'), (28, "4CH RB4S SLO", 'есть', 'RGBW перелив синхронизированный'), (29, "4CH RB4A FST", 'нет', ' RGBW перелив индивидуальный '), (30, "4CH RB4A SLO", 'есть', 'RGBW перелив индивидуальный '), ] for index in range(len(mode_info)): for j in range(2): label = tk.Label(popup, text=f'{mode_info[index][j]}', borderwidth=1, relief="solid") label.grid(row=index + 1, column=j, sticky="nsew") for index in range(len(mode_info)): for j in range(2, 4): label = tk.Label(popup, text=f'{mode_info[index][j]}', borderwidth=1, relief="solid") label.grid(row=index + 1, column=j + 1, sticky="nsew") descr = [ ('Режим одноканального диммера. \nВсе выходные каналы работают параллельно', 6), ('Режим двухканального диммера. \nВыходные каналы 1,2 и 3,4 работают параллельно', 6), ( 'Режим RGB перелива с изменением яркости каждого канала. \nW не управляется. Последний канал – скорость перелива. ', 1), ( 'Режим RGB перелива с изменением яркости каждого канала. \nW управляется отдельно по DMX. Последний канал – скорость перелива', 1), ('Режим четырехканального диммера. \nВсе выходные каналы работают независимо', 16) ] delta = 0 for index, c in enumerate(descr): label = tk.Label(popup, text=f'{c[0]}', borderwidth=1, relief="solid") label.grid(row=delta + 1, column=5, sticky="nsew", rowspan=c[1]) delta += c[1] chan = [(1, 6), (2, 6), (4, 1), (5, 1), (4, 16)] delta = 0 for index, c in enumerate(chan): label = tk.Label(popup, text=f'{c[0]}', borderwidth=1, relief="solid") label.grid(row=delta + 1, column=2, sticky="nsew", rowspan=c[1]) delta += c[1] def readCallback() -> None: global last_click_time if time.time() - last_click_time < 1: return last_click_time = time.time() if connected: if not readControllerCurrentsAndData(): connectionLost() return setStatus(f'Выбранный порт: {controller.controller.ser.port}') setTempStatus(f'Токи прочитаны, порт: {controller.controller.ser.port}', 1) else: setTempStatus('Порт не выбран!', 1) def writeCallback() -> None: global last_click_time if time.time() - last_click_time < 2: return last_click_time = time.time() if connected: writeCurrentsAddressAndMode() else: setTempStatus('Порт не выбран!', 1) def validate_dmx_address_entry(entry_data: str) -> bool: if entry_data.isdigit(): return 1 <= int(entry_data) <= 512 if not entry_data: return True return False def validate_current_entry(entry_data: str) -> bool: if entry_data.isdigit(): return int(entry_data) <= 2500 if not entry_data: return True return False def finish(): win.destroy() controller.close() if __name__ == "__main__": controller.writePowers([100, 200, 300, 400], [300, 2000, 2341, 10], 49, [100, 240, 30, 239], 52) win.configure(width=270, height=350) win.resizable(False, False) win.title("Controllers") win.protocol("WM_DELETE_WINDOW", finish) com_port_label = ttk.Label(win) com_port_label.configure(font="{Arial} 12 {}", text='COM порт:') com_port_label.place(anchor="nw", relx=0.05, rely=0.05) device_choice = ttk.Combobox(win, name="device_choice", textvariable=selected_port) device_choice.configure(justify="center") device_choice.place(anchor="nw", relx=0.40, rely=0.04, relwidth=0.4, relheight=0.08) device_choice['values'] = controller.readComPorts() device_choice['state'] = 'readonly' device_choice.bind('<>', comPortSelected) device_choice.bind('', refreshComPorts) dmx_address_label = ttk.Label(win) dmx_address_label.configure(font="{Arial} 12 {}", text='DMX Адрес:') dmx_address_label.place(anchor="nw", relx=0.05, rely=0.15) validate_callback = win.register(validate_dmx_address_entry) dmx_address_field = ttk.Entry(win) dmx_address_field.configure(font="{Arial} 12 {}", justify="center", validate='key', validatecommand=(validate_callback, '%P')) dmx_address_field.place(anchor="nw", relx=0.40, rely=0.15, relwidth=0.2, relheight=0.06) personality_label = ttk.Label(win) personality_label.configure(font="{Arial} 12 {}", text='Personality:') personality_label.place(anchor="nw", relx=0.05, rely=0.26) image = base64.b64decode( b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAnxJREFUOE+lk01IVUEUx/9zZ+Z+Pg2DsIWiSQaBYrgoREqkyFUEQWihqyQR+6KtywrbVERUhkQSCG1q1aJNkYmiqSUUQQuJapFZfvR8+nz3Y07cufLSPlYd7r0wc87/N/9zZ4bhP4P9S3/wzAgFyoCKFAgBXtxu+GvtH5NN5yZpMcsRKQD5LIPgBBVm8bKvboMmPzh0fpgWcx6yOQIzGOqrCrGzzMGqrzD8LoMPX/3ErAow3lu7Dr3Ww96uKYrFnstxrasc1RUuokhBKUIUEXoezODpm2VtS0U+xm/u0hD9iW1/zxgAY/GDnvZScM7wZCKNw3WFqCqz8fmbj/ZbMzofR255Dq9665Phno4pinumeMQYXJvDV0BEDNXlNq6eKMaX+RCtN2JAsgiiABPXqxiL//Zc1knMGAzEGHIhIYgYGGe4cHwL9td4GBjKoH8ok3cAAiavVDLWeGqM0jlLr7ziK2R9giEMCJOjs6kIbY2FePvJR/fDNCK9etIDEWH04jbGGjpHaCl0sbAUIlTQQmEK3fedzmLkAoWT/T+wFDJwYegdSnaD8Ly7hLF9HYP0cd5FEIslh7SS90BNCs31KUzPRrg75kPGYMlh8AQgoPD47NbET+mRMVIG10UaYAuYtkCBJ0BSwHQETEvofOwiDmM1g0enSxPAjqODtMK8NUAirq2wcflYEZ69D3D/tYLlSj2vWzSAgRbv1zmIIRXNo0SmDdPiunB3pYNLLUUYng7RNx7B9kxYnoTjSrD0LO61lWwExJDtrRPEbUsDTFuieLNEzhCQjtQAr0DC9Bfy4vxJXH8jK9uGyE5tguk6MB0JyxVIeQKmEeief7+9PwEI7uIRQxNy9wAAAABJRU5ErkJgggAA') personality_button = ttk.Button(win, command=show_personality_callback) question_image = tk.PhotoImage(data=image, format='png') personality_button.configure(compound="right", default="normal", image=question_image) personality_button.place(anchor="nw", relx=0.87, rely=0.246, relwidth=0.1) personality_combobox_font = ('TkDefaultFont', 9) personality_combobox = ttk.Combobox(win, font=personality_combobox_font) personality_combobox.configure( exportselection=True, justify="left", state="readonly", takefocus=False) personality_combobox.place(anchor="nw", relx=0.40, rely=0.246, relwidth=0.45, relheight=0.085) for i in range(4): channel_label = ttk.Label(win) channel_label.configure(font="{Arial} 12 {}", text=f'Ток канал {i + 1}:') channel_label.place(anchor="nw", relx=0.05, rely=0.38 + 0.08 * i) validate_callback = win.register(validate_current_entry) ent = ttk.Entry(win) ent.configure(font="{Arial} 12 {}", justify="center", validate='key', validatecommand=(validate_callback, '%P')) ent.place(anchor="nw", relx=0.40, rely=0.38 + 0.08 * i, relwidth=0.2, relheight=0.06) current_entries.append(ent) ma_label = ttk.Label(win) ma_label.configure(font="{Arial} 12 {}", text='mA') ma_label.place(anchor="nw", relx=0.65, rely=0.38 + 0.08 * i) read_button = ttk.Button(win) read_button.configure(text='Чтение', command=readCallback) read_button.place(anchor="nw", relx=0.05, rely=0.7, relwidth=0.37) write_button = ttk.Button(win) write_button.configure(text='Запись', command=writeCallback) write_button.place(anchor="nw", relx=0.6, rely=0.7, relwidth=0.37) check_checkbox = ttk.Checkbutton(win, variable=check) check_checkbox.configure(text='Проверить') check_checkbox.place(anchor="n", relx=0.5, rely=0.8) status_label = ttk.Label(win, textvariable=status) status_label.place(anchor="center", relx=0.5, rely=0.91) win.mainloop()