Создание пользовательской клавиатуры

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

В этой статье мы создадим «клавиатуру» на 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/


Интересные статьи

Интересные статьи

Как попасть в Реестр отечественного ПО Минцифра? Зачем? И причем тут пользовательская документация.
В данной статье мы рассмотрим систему аутентификации пользователей и внешних систем в личном кабинете через сервер аутентификации Blitz Identity Provider. Согласно требованиям проекта, который мы...
В этом материале речь пойдёт о том, как собрать часы из обычных микросхем. Схема часов (оригинал) Как собрать часы?
Многие разработчики начинают разработку многопользовательского онлайн сервера на основе библиотеки socket.io. Эта библиотека позволяет очень просто реализовать обмен данными между клиетом...
Сегодня я хотел бы показать вам, как создать свой собственный Publisher в новом фреймворке от Apple Combine.