В этой статье мы создадим «клавиатуру» на Arduino и Python.
Я решил создать клавиатуру, которая поможет мне писать код быстрее. В интернете много таких статьей, но при этом большая часть опирается на программируемые клавиатуры, которые можно купить и запрограммировать с помощью специальных программ.
Я не буду использовать клавиатуру, только обычные кнопки.
Код на Arduino
int pins[] = {13, 12, 11, 10, 9} // Пины, к которым подключены кнопки char coms[] = {'i', 'w', 'f', 'v', 's'} // Команды, которые мы отправляем /* * i - if(){} * w - while(){} * f - for(){} * v - void func(){} * s - Для Windows 10, выполняет Win+Shift+S, открывает возможность скриншота части экрана */ unsigned long long mls[] = {0, 0, 0, 0, 0} // Времена последних нажатий кнопок void setup(){ // Код выполняется 1 раз, при запуске (или перезагрузке) платы for(int i = 0; i < sizeof(pins)/sizeof(int); i++){ // sizeof(pins)/sizeof(int) - длина массива pins pinMode(pins[i], INPUT_PULLUP); // Ставим выход №i в pins входом с подтяжкой (для избавления от помех на уровне платы) } Serial.begin(9600); // Открываем последовательный порт на скорости 9600 } void loop(){ for(int i = 0; i < sizeof(pins)/sizeof(int); i++){ if(!digitalRead(pins[i]) && millis() - mls[i] > 500){ // Если кнопка на входе pins[i] нажата и с момента его последнего нажатия прошло больше 500 мс (0.5 с), то... Serial.println(coms[i]); // выводим символ coms[i] в последовательный порт (а затем перевод строки) mls[i] = millis(); // Время последнего нажатия = Время сейчас } } }
Функция millis() возвращает количество миллисекунд с момента запуска (или перезагрузки) платы. На некоторых версиях Arduino IDE строчка №23 может выдать ошибку компиляции. Тогда можно реализовать функцию string, которая преобразует символ в строку:
String string(char c){ // Тип «строка» начинается с большой буквы: String String res = " "; // Создаем строку длиной в 2 символа (на самом деле символ один, но откуда взялся второй объясню позже) res[0] = c; // Ставим первый символ строки в переданный return res; // Возвращаем результат }
И вместо 23 строки написать следующее:
Serial.println(string(coms[i]))
Схема подключения

Python. Необходимые библиотеки.
Для начала через pip установим необходимые нам библиотеки. Это PyQt5 и pyautogui. Думаю, что объяснять установку библиотек бессмысленно.
Ещё понадобится программа Qt Designer, если вы захотите внести изменения в GUI программы. Но о ней, может быть, я расскажу в другой статье.
Пишем код на Python
Импортируем необходимые библиотеки:
from PyQt5 import QtWidgets, uic from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo # Для работы с последовательным портом from PyQt5.QtCore import QIODevice import pyautogui # Для нажатия клавиш на клавиатуре и горячих клавиш import time # Задержки
Создаем окно и подготавливаемся к открытию последовательного порта (далее просто порт)
app = QtWidgets.QApplication([]) ui = uic.loadUi("design.ui") # Загружаем дизайн из файла design.ui, созданного в QT Designer serial = QSerialPort() serial.setBaudRate(9600) # Частота должна совпадать с частотой из скетча для Arduino (строка 17)
Создаем функции для работы с портом:
def updatePins(close=True): if close: serial.close() #Если порт нужно закрыть, закрываем ui.pinsAvailable.clear() #Очищаем поле для доступных портов port_list = [] #Список портов ports = QSerialPortInfo().availablePorts() #Получаем список портов for port in ports: port_list.append(port.portName()) #Добавляем название порта в список ui.pinsAvailable.addItems(port_list) #Добавляем элементы в Combobox ui.sost.setText("Closed and updated" if close else "Updated") # Ставим соответствующий текст в текстовое поле def onOpen(): serial.setPortName(ui.pinsAvailable.currentText()) # Подготавливаемся к открытию порта, выбранного в Combobox serial.open(QIODevice.ReadWrite) # Открываем порт ui.sost.setText("Opened") # Ставим соответствующий текст в текстовое поле def onClose(): serial.close() # Закрываем порт ui.sost.setText("Closed") # Обновляем текст updatePins(False) #Обновляем список портов, но не закрываем порт
Создаем функцию для чтения порта:
def onRead(): try: if not serial.canReadLine(): # Если нечего читать, выходим return rx = serial.readLine() # Читаем строку rxs = str(rx, 'utf-8')[:-2] # Обрезаем последние 2 символа. Последний - перенос строки, о предпоследнем - ниже (в разделе «Неизвестный символ») if rxs == "i": pyautogui.write("if(){}") elif rxs == "w": pyautogui.write("while(){}") elif rxs == "f": pyautogui.write("for(){}") elif rxs == "v": pyautogui.write("void func(){}") elif rxs == "s": pyautogui.hotkey("Win", "Shift", "S") ui.sost.setText("Pressed") # Ставим в текстовое поле соответствующий текст except Exception as e: print("Exception:", e) # Если ошибка, выводим в консоль
Устанавливаем события и запускаем окно:
serial.readyRead.connect(onRead) # Если в порте есть данные - читаем ui.openB.clicked.connect(onOpen) # Если нажаты кнопки - обрабатываем ui.closeB.clicked.connect(onClose) ui.updateB.clicked.connect(updatePins) # Запускаем интерфейс ui.show() app.exec()
Неизвестный символ
Как я сказал в строке 6 одного из кусков кода, нужно удалять последние 2 символа. И о предпоследнем символе я не знал. Однако когда я стал тестировать свою программу, я выяснил, что длина строки rxs равна 2, хотя должна быть равна 1. После его удаления программа стала работать корректно. Я предполагаю, что дело в самом хранении строк в языках C, C++ и Arduino. Любая строка (даже std::string в C++ и String в Arduino) представляют собой const char*[]. Так как массивы имеют постоянную длину, то в конце строки используется символ ‘\0’, показывающий, что строка закончилась, даже если еще осталось место в массиве. К слову, поэтому если нам нужна строка на 20 символов, нужно делать массив на 21 символ. Я думаю, что этот символ и передается при конвертации символа в строку для передачи в порт. Если кто-то знает, какой там символ на самом деле, буду рад узнать. Пишите в комментарии.
Итоговый код
Python:
from PyQt5 import QtWidgets, uic from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo # Для работы с последовательным портом from PyQt5.QtCore import QIODevice import pyautogui # Для нажатия клавиш на клавиатуре и горячих клавиш import time # Задержки app = QtWidgets.QApplication([]) ui = uic.loadUi("design.ui") # Загружаем дизайн из файла design.ui, созданного в QT Designer serial = QSerialPort() serial.setBaudRate(9600) # Частота должна совпадать с частотой из скетча для Arduino (строка 17) def updatePins(close=True): if close: serial.close() #Если порт нужно закрыть, закрываем ui.pinsAvailable.clear() #Очищаем поле для доступных портов port_list = [] #Список портов ports = QSerialPortInfo().availablePorts() #Получаем список портов for port in ports: port_list.append(port.portName()) #Добавляем название порта в список ui.pinsAvailable.addItems(port_list) #Добавляем элементы в Combobox ui.sost.setText("Closed and updated" if close else "Updated") # Ставим соответствующий текст в текстовое поле def onOpen(): serial.setPortName(ui.pinsAvailable.currentText()) # Подготавливаемся к открытию порта, выбранного в Combobox serial.open(QIODevice.ReadWrite) # Открываем порт ui.sost.setText("Opened") # Ставим соответствующий текст в текстовое поле def onClose(): serial.close() # Закрываем порт ui.sost.setText("Closed") # Обновляем текст updatePins(False) #Обновляем список портов, но не закрываем порт def onRead(): try: if not serial.canReadLine(): # Если нечего читать, выходим return rx = serial.readLine() # Читаем строку rxs = str(rx, 'utf-8')[:-2] # Обрезаем последние 2 символа. Последний - перенос строки, о предпоследнем - ниже (в разделе «Неизвестный символ») if rxs == "i": pyautogui.write("if(){}") elif rxs == "w": pyautogui.write("while(){}") elif rxs == "f": pyautogui.write("for(){}") elif rxs == "v": pyautogui.write("void func(){}") elif rxs == "s": pyautogui.hotkey("Win", "Shift", "S") ui.sost.setText("Pressed") # Ставим в текстовое поле соответствующий текст except Exception as e: print("Exception:", e) # Если ошибка, выводим в консоль serial.readyRead.connect(onRead) # Если в порте есть данные - читаем ui.openB.clicked.connect(onOpen) # Если нажаты кнопки - обрабатываем ui.closeB.clicked.connect(onClose) ui.updateB.clicked.connect(updatePins) # Запускаем интерфейс ui.show() app.exec()
Видео, по материалам которого создан данный проект: видео на YouTube.
В случае ошибок в коде, пишите в комментарии. Сам файл design.ui, в котором содержится дизайн окна PyQt5 можно скачать здесь.
ссылка на оригинал статьи https://habr.com/ru/post/700788/
Добавить комментарий