Старт идеи
Давно интересовался как можно объединить микроконтроллеры, Python и пк, и мне в голову приходила идея дополнительной клавиатуры для пользователя, которая будет заменять сочетания клавиш, всего лишь одной кнопкой. Сначала я пробовал объединить платы NodeMCU на базе ESP8266 с пк, с помощью Python. Знаний для написания скетча на ардуино у меня не было, и погуглив, нашел язык MicroPython. Он сильно мне подошел, так как я владел базовыми знаниями Python, да и умение правильно задавать вопрос гуглу.
Попытка №1
Написал скетч на MicroPython, реализовав создание точки Wi-Fi на микроконтроллере, для подключения микроконтроллера к сети к которой подключен ноутбук. Реализовал 6 кнопок на плате, приложение на Python на основе библиотеки Socket и Keyboard.
Реализация была таковая на микроконтроллере:
Создать Wi-Fi точку
Подключиться к сети, к которой подключен ноутбук
Создать точку соединения по UDP
Передать текст при успешном подключении
Передавать номер кнопки на которую нажали
Закрыть точку соединения при отключение слушателя
Реализация на Python:
Подключения к точке соединения
Получить ответ по UDP об успешном подключении
Прослушивать точку соединения на получения номера нажатой кнопки
Воспроизвести сочетание кнопок, которая привязана к данной кнопке
Всё работало, но проблема была после того как приложение на Python могла потерять соединение, и уходила бесконечный цикл по получения пустого сообщен��я, хотя была реализовано обычное условие на проверку получения сообщений по длине полученного сообщения. Подумал что это не подходит, так как хотелось еще добавить подсветку кнопок с помощью адресных светодиодов на ws2812, и возможно не залезая в код микроконтроллера менять цвет.
Попытка №2
Как то на распродаже на Али, купил аналог Arduino Nano Digispark Attiny88 и решил попробовать это все проделать с ней, но не по Wi-Fi, а по usb. На плате распаян разъем MicroUSB, и подумал что можно реализовать симуляцию клавиатуры на плате. И как же мне повезло, это было уже кем то продуманно и реализовано на микроконтроллерах Attiny, но оказалось не все так гладко. Библиотека была от самих разработчиков плат на базе микроконтроллера Attiny, и работала на микроконтроллерах Attiny85, а на Attiny88 нет. И спустя пару минут, нахожу библиотеку, которую переделал один из коллег ютубера AlexGyver, я точно не знаю кто они друг другу, коллеги, партнеры, извините если что то сказал не так. Я понимаю что я нашел то что мне нужно и все понятно по описанию. Вот ссылка на репозиторий на GitHub. К этому времени появилось понимание как можно реализовать это все в среде ArduinoIDE и начал реализовывать желаемое. Припаял 8 свитчей, от механической клавиатуры к плате, по принципу клавиатуры для ардуино 4 на 3, только в моём случае 4 на 2. И тут я уперся объем памяти Attiny88, которая не понятным мне причинам, заполнилась и ее не хватало для заливки скетча на плату. Подумал, тогда реализую так, у каждой кнопки свой пин, и общая земля. И все заработало. При нажатие на кнопке на пк отправлялось нажатие сочетание клавиш, которая была запрограммирована в скетче. Но снова я получил не то, мне пришлось бы заходить каждый раз в скетч и менять сочетание кнопок, хотя я этого не делал так много, но все равно, внутренний перфекционист говорил не то, но пока забудем о нем. И приступим к реализации подсвечивания кнопок с помощью ws2812. И тут я снова столкнулся с проблемой памяти, хотя я реализовал включения светодиода только одним цветом, но памяти уже не хватает. И понимаю, что надо бросать это делать и переходить на другой микроконтроллер.
Попытка №3
Покупал так же по распродаже Arduino Nano на Type-C, и решил все переделать на нем, но оказалось одно но, пришлось бы допаивать отдельный разъем, добавлять резисторы и тогда, а желания еще возиться с этим не было, я решил что можно все передавать через Com-порт, и получать команды от пк через него. Убрав часть кода с реализации симуляции клавиатуры, я начинаю писать отправку и нажатие кнопок по Com-порту. И ура, всё работает, микроконтроллер передает нажатие кнопки, светодиод светится, и можно еще реализовать другие функции, так как там по-любому есть память на всё остальное.
Приступаю написанию приложения на Python. Ну тут аналогично как перейти с Attiny88 на Arduino Nano, был уже готовый код, который надо было чуток отредактировать. Консольное приложение готово, но на этом нельзя останавливаться, я же хочу реализовать возможность изменять сочетание клавиш, в легкой форме, и изменения подсветки кнопок не редактируя скетч.
И пару месяцев назад, я делал маленькую программу на Tkinter, для управления музыкой, с помощью маленького окна, и не держа развернутым плеер. Начал с того что я хочу реализовать.
Выбор Com-порта
Включать на недлительный период подсветки кнопки
Задать сочетание клавиш
Задать цвет подсветки кнопки
Выбор Com-порта легко реализовать в выпадающей строке, которая получает список портов. Теперь надо реализовать остальные функции. Начал по порядку, сначала в Tkinter добавил 8 кнопок, и при нажатии кнопки вызывалась функция, которая передает какую-то команда по Com-порту на микроконтроллер, но как отправить атрибуты функции в Tkinter я не знал, и погуглив какой-то время нахожу, что можно реализовать через lambda: и сама функция и в скобках аргументы. Уже полдела сделано по этому функционалу, и при нажатие на кнопку в приложении на Tkinter, вызывалась нужна функция с нужным мне аргументом
command=lambda: self.jobs.check_led('LED_1')
def check_led(self, led):
if led == 'LED_1':
ser.write(b'led_1')
if led == 'LED_2':
ser.write(b'led_2')
if led == 'LED_3':
ser.write(b'led_3')
if led == 'LED_4':
ser.write(b'led_4')
if led == 'LED_5':
ser.write(b'led_5')
if led == 'LED_6':
ser.write(b'led_6')
if led == 'LED_7':
ser.write(b'led_7')
if led == 'LED_8':
ser.write(b'led_8')Теперь осталось научить Ардуину понимать что она получает, и реагировать так как мне нужно. через проверку длинны полученного сообщения запускалось проверка полученного сообщения по условиям, и реагирования включения нужного мне светодиода.
if (Serial.available()) {
data = Serial.readStringUntil('$');
int len_data = data.length();
if (len_data == 5) {
if (data == "led_1") {
led_on(1);
myTimer3 = millis();
}
if (data == "led_2") {
led_on(2);
myTimer3 = millis();
}
void led_on(int pins) {
pins -= 1;
pixels.setPixelColor(pins, pixels.Color(255, 198, 24));
pixels.show();Всё хорошо, работает, пора как то организовать понятный интерфейс в программирование сочетаний клавиш. Решил не делать много кнопок и реализовать сочетание максимум из 3 кнопок. У библиотеки Keyboard есть список кнопок которые он может воспроизвести, и теперь нужно создать словарь, который будет удобен для чтения мне, и понятный на Tkinter.
def key_libs():
list = {
'':'',
'Backspace': 'backspace',
'Tab': 'tab',
'Enter': 'enter',
'Shift': 'shift',
'Ctrl': 'ctrl',
'Alt': 'alt',
'Caps_Lock': 'caps lock',
'Esc': 'esc',
'Spacebar': 'spacebar',
'Page_Up': 'page up',
'Page_Down': 'page down',
'End': 'end',
'Home': 'home',
'Left': 'left',
'Up': 'up',
'Right': 'right',
'Down': 'down',
'Select': 'select',
'Print_Screen': 'print screen',
'Insert': 'insert',
'Delete': 'delete',
'0': '0',
'1': '1',
'2': '2',
'3': '3',
'4': '4',
'5': '5',
'6': '6',
'7': '7',
'8': '8',
'9': '9',
'a': 'a',
'b': 'b',
'c': 'c',
'd': 'd',
'e': 'e',
'f': 'f',
'g': 'g',
'h': 'h',
'i': 'i',
'j': 'j',
'k': 'k',
'l': 'l',
'm': 'm',
'n': 'n',
'o': 'o',
'p': 'p',
'q': 'q',
'r': 'r',
's': 's',
't': 't',
'u': 'u',
'v': 'v',
'w': 'w',
'x': 'x',
'y': 'y',
'z': 'z',
'Left_Windows': 'left windows',
'Right_Windows': 'right windows',
'*': '*',
'+': '+',
'-': '-',
'/': '/',
'F1': 'f1',
'F2': 'f2',
'F3': 'f3',
'F4': 'f4',
'F5': 'f5',
'F6': 'f6',
'F7': 'f7',
'F8': 'f8',
'F9': 'f9',
'F10': 'f10',
'F11': 'f11',
'F12': 'f12',
'Left_Shift': 'left shift',
'Right_Shift': 'right shift',
'Left_Ctrl': 'left ctrl',
'Right_Ctrl': 'right ctrl',
'Browser_Back': 'browser back',
'Browser_Forward': 'browser forward',
'Browser_Refresh': 'browser refresh',
'Browser_Stop': 'browser stop',
'Browser_Favorites': 'browser favorites',
'Volume_Mute': 'volume mute',
'Volume_Down': 'volume down',
'Volume_Up': 'volume up',
'Next_Track': 'next track',
'Previous_Track': 'previous track',
'Stop_Media': 'stop media',
'Play/Pause_Media': 'play/pause media',
}
return listРеализовать список кнопок решил снова через выпадающий список.
def generate_list(self):
self.key_list = []
libs = dict(lists())
for key in libs:
self.key_list.append(key)
self.key_box11 = ttk.Combobox(self, values=self.key_list, width=14, state="readonly")
self.key_box11.grid(row=0, column=1)Так но как реализовать запоминание какие кнопок нужно нажимать, а не заполнять их каждый раз. И я решил это сделать через БД. Это хороший опыт по работе с БД, не очень тяжелый и понятный, а еще и реализация по созданию БД, по поиску данных, и обновления данных. Перед созданием БД я начал изучать что и как можно реализовать, но в этот раз не в гугле, а у телеграмм бота с что-то типа ChatGPT и задавая вопросы получал нужный и понятный ответ. И создаю такую БД

Теперь надо чтобы подключался мой код на Python и отправлял SQL запросы в БД и получал ответ. И я создаю отдельный файл, с классом для таких команд.
И теперь к выпадающему списку добавляется новая переменная и дополнительная строка.
self.value11 = StringVar(value=self.btn_set.check_key_db(1, 1))
self.key_box11 = ttk.Combobox(self, textvariable=self.value11, values=self.key_list, width=14, state="readonly")
self.key_box11.grid(row=0, column=1)
def check_key_db(self, num, ordinal):
name = eval('"BTN_PIN_{}"'.format(num))
var = eval('"key{}"'.format(ordinal))
re = self.cursor.execute(f'SELECT {var} FROM key_settings WHERE btn_name = "{name}"').fetchone()[0]
inv_d = {value: key for key, value in libs.items()}
return inv_d[re]Так как список выпадал, который мне нужен и я реализовал передачу в БД сразу данные которые понимает Keyboard, пришлось инвертировать словарь, чтобы можно было с помощью данных которые понимаем Keyboard, выводить понятный мне текст. Теперь нужно добавить кнопку которая будет сохранять кнопки в БД. Ну логично я использовал обычную кнопку в Tkinter, а которой снова использовал функцию через lambda.
Теперь нужно реализовать работу прослушивания Com-порта в реальном времени, параллельно работе программы. Для этого я выбрал Thread, реализовав запуск программы в одном потоке, а во втором потоке прослушивание Com-порта при подключении к нужному Com-порту.
Так еще одна функция реализована, пора переходить за изменения цвета подсветки кнопок. Поискав нахожу готовое решение от Tkinter как модуль colorchooser, который при вызове выводит палитру цветов, и при выборе передавал HEX код цвета и цвет в RGB, как мне нужно.

Так как теперь сделать так чтобы после выбора я видел какой цвет я выбрал, и добавляю поле Label, у которой при выборе цвета меняется фоновый цвет, на тот который я выбрал в палитре, и в это мне помогло, то что при выборе я получаю HEX код цвета, и передаю снова через lambda код цвета. Но снова нужно хранить цвет, и снова тут приходит на помощь БД, которую создал раннее. Так теперь надо передать цвет на Ардуино по Com-порту, тут решил не ломать голову и решил передать такой текст в формате Номер кнопки, и цвет в палитре RGB/
self.btn_set.save_colors_db(num, user_color_background)
label = eval('self.colorlabel{}'.format(int(num)))
label["background"] = user_color_background
sends_commands = f'clr{num}r{r_clr}g{g_clr}b{b_clr}$'
jobs.sends_color(sends_commands)Но снова теперь надо обучить Ардуино понимать отправляемый текст. И тут я снова беру проверку длины сообщения и только потом разбивание текста на нужные мне переменные как номер кнопки, цвет в палитре RGB/
else if (len_data == 16) {
int led_pins = data.substring(3).toInt() - 1;
int r = data.substring(5, 8).toInt();
int g = data.substring(9, 12).toInt();
int b = data.substring(13, 16).toInt();
pixels.setPixelColor(led_pins, pixels.Color(r, g, b));
pixels.show();
}И всё работает, так теперь надо как то помнить цвет который был забит до этого и я решил реализовать это через EEPROM и теперь код уже выглядит так.
// запись цвета в память
else if (len_data == 16) {
int led_pins = data.substring(3).toInt() - 1;
EEPROM.write(led_pins * 12, data.substring(5, 8).toInt());
EEPROM.write(led_pins * 12 + 3, data.substring(9, 12).toInt());
EEPROM.write(led_pins * 12 + 6, data.substring(13, 16).toInt());
int r = EEPROM.read(led_pins * 12);
int g = EEPROM.read(led_pins * 12 + 3);
int b = EEPROM.read(led_pins * 12 + 6);
pixels.setPixelColor(led_pins, pixels.Color(r, g, b));
pixels.show();
}
// считывание из памяти при нажатие на кнопку
void led_on(int pins) {
pins -= 1;
int r = EEPROM.read(pins * 12);
int g = EEPROM.read(pins * 12 + 3);
int b = EEPROM.read(pins * 12 + 6);
pixels.setPixelColor(pins, pixels.Color(r, g, b));
pixels.show();
}И вот теперь остался внешний вид, тут уже каждый сам выбирает что как. Но я решил, что мне не нравятся стоковый кнопки Tkinter, шрифт текста, и я всё это изменил. Скачав с какого то сайт который мне понравился, нарисовав кнопки в Photoshop Online, получилось вот такая программа


Прикреплю ссылку на репозиторий на GitHub
Добавлена еще функция, что при закрытия программы, чтобы не мешала, появляется иконка в трее, в которой есть две функции разворота окна, и закрывания программы, но только она не закрывается полностью, так как не понял, как реализовать остановку второго потока.
