Additional do-it-yourself keyboard

Idea start

I have long been interested in how microcontrollers, Python and PC can be combined, and the idea of ​​\u200b\u200ban additional keyboard for the user came to my mind, which would replace keyboard shortcuts with just one button. I first tried to pair ESP8266 based NodeMCU boards with a PC using Python. I didn’t have the knowledge to write a sketch on arduino, and by googling, I found the MicroPython language. It suited me a lot, since I had basic knowledge of Python, and the ability to correctly ask Google a question.

Attempt #1

I wrote a sketch in MicroPython, implementing the creation of a Wi-Fi point on the microcontroller to connect the microcontroller to the network to which the laptop is connected. Implemented 6 buttons on the board, a Python application based on the Socket and Keyboard libraries.

The implementation was as follows on the microcontroller:

  • Create Wi-Fi Hotspot

  • Connect to the network that the laptop is connected to

  • Create connection point over UDP

  • Send text on successful connection

  • Pass the number of the button that was clicked

  • Close connection point when listener is disconnected

Implementation in Python:

  • Junction Point Connections

  • Get a UDP response about a successful connection

  • Listen to the connection point for the number of the pressed button

  • Play the combination of buttons associated with the given button

Everything worked, but the problem was after the Python application could lose the connection, and an infinite loop left after receiving an empty message, although the usual condition was implemented to check for receiving messages by the length of the received message. I thought that this is not suitable, because I also wanted to add button illumination using addressable LEDs on the ws2812, and possibly change the color without getting into the microcontroller code.

Attempt #2

Somehow, at a sale on Ali, I bought an analogue of the Arduino Nano Digispark Attiny88 and decided to try to do it all with it, but not via Wi-Fi, but via usb. The microUSB connector is soldered on the board, and I thought that it was possible to implement keyboard simulation on the board. And how lucky I was, it was already thought out and implemented by someone on Attiny microcontrollers, but it turned out not to be so smooth. The library was from the developers of boards based on the Attiny microcontroller, and worked on Attiny85 microcontrollers, but not on Attiny88. And after a couple of minutes, I find the library, which was remade by one of the colleagues of the YouTuber AlexGyver, I don’t know exactly who they are to each other, colleagues, partners, sorry if I said something wrong. I understand that I found what I need and everything is clear from the description. Here is the link to the repository on GitHub. By this time, there was an understanding of how to implement all this in the ArduinoIDE environment and began to implement the desired. I soldered 8 switches, from the mechanical keyboard to the board, according to the principle of the keyboard for arduino 4 by 3, only in my case 4 by 2. And then I ran into the amount of Attiny88 memory, which, for reasons I do not understand, filled up and it was not enough to fill the sketch on fee. I thought, then I’ll implement it like this, each button has its own pin, and a common ground. And everything worked. When a button was pressed, a keystroke was sent to the PC, which was programmed in the sketch. But again, I got the wrong thing, I would have to go into the sketch every time and change the combination of buttons, although I didn’t do it that much, but still, the inner perfectionist said the wrong thing, but for now let’s forget about him. And let’s start implementing button highlighting using ws2812. And here I again ran into a memory problem, although I implemented the LED on with only one color, but there is no longer enough memory. And I understand that I need to quit doing this and switch to another microcontroller.

Attempt #3

I also bought an Arduino Nano on Type-C on sale, and decided to redo everything on it, but it turned out to be one thing, but I would have to solder a separate connector, add resistors and then, but there was still no desire to mess with it, I decided that everything could be transferred through the Com-port, and receive commands from the PC through it. Having removed part of the code from the implementation of the keyboard simulation, I start writing sending and pressing buttons on the Com port. And hooray, everything works, the microcontroller transmits a button press, the LED lights up, and you can still implement other functions, since there is anyway memory for everything else.

I’m starting to write an application in Python. Well, here it’s similar to how to switch from Attiny88 to Arduino Nano, there was already a ready-made code that needed to be slightly edited. The console application is ready, but we can’t stop there, but I want to implement the ability to change the keyboard shortcut, in an easy way, and change the backlight of the buttons without editing the sketch.

And a couple of months ago, I was making a little Tkinter program to control music from a small window, without having the player maximized. I started with what I want to implement.

  • Com port selection

  • Turn on the button backlight for a short period

  • Set keyboard shortcut

  • Set button highlight color

Com port selection is easy to implement in the drop down box that gets the list of ports. Now we need to implement the rest of the functions. I started in order, first I added 8 buttons to Tkinter, and when the button was pressed, a function was called that sends some command via Com-port to the microcontroller, but I didn’t know how to send function attributes to Tkinter, and after googling for some time I find that can be implemented through lambda: both the function itself and the arguments in brackets. Already half the work has been done on this functionality, and when you click on the button in the application on Tkinter, the function with the argument I need was called

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')

Now it remains to teach Arduino to understand what it receives, and react as I need. through checking the length of the received message, a check of the received message was started according to the conditions, and the reaction of turning on the LED I needed was started.

    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();

Everything is fine, it works, it’s time to somehow organize a clear interface into programming keyboard shortcuts. I decided not to make many buttons and implement a combination of a maximum of 3 buttons. The Keyboard library has a list of buttons that it can play, and now I need to create a dictionary that is easy for me to read and understandable in 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

I decided to implement a list of buttons again through a drop-down 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)

So, how to implement remembering which buttons you need to press, and not fill them in every time. And I decided to do it through the database. This is a good experience for working with the database, not very difficult and understandable, but also an implementation for creating a database, searching for data, and updating data. Before creating the database, I began to study what and how to implement, but this time not in Google, but in the telegrams of a bot with something like ChatGPT and asking questions I received the necessary and understandable answer. And I create such a database

Now I need to connect my Python code and send SQL queries to the database and get a response. And I create a separate file, with a class for such commands.

And now a new variable and an additional line is added to the dropdown list.

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]

Since the list fell out, which I needed and I implemented the transfer to the database immediately of the data that the Keyboard understands, I had to invert the dictionary so that I could display text that I understand using the data that the Keyboard understands. Now you need to add a button that will save the buttons in the database. Well, logically, I used a regular button in Tkinter, and in which I again used a function through lambda.

Now you need to implement the work of listening to the Com-port in real time, in parallel with the program. To do this, I chose Thread, implementing the launch of the program in one thread, and in the second thread listening to the Com port when connected to the desired Com port.

So one more function is implemented, it’s time to move on to changing the color of the button backlight. After searching, I find a ready-made solution from Tkinter as the colorchooser module, which, when called, displays a color palette, and when selected, passes the HEX code of the color and the color in RGB, as I need.

So how now to make it so that after selection I see what color I have chosen, and add the Label field, in which, when choosing a color, the background color changes to the one I selected in the palette, and it helped me that when I select I get HEX color code, and pass the color code again through lambda. But again, you need to store the color, and again the database that you created earlier comes to the rescue. So now we need to transfer the color to the Arduino via the Com-port, then I decided not to rack my brains and decided to transfer such a text in the format Button number, and the color in the RGB palette /

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)

But again, now we need to train Arduino to understand the text being sent. And here I again take the message length check and only then split the text into the variables I need, like the button number, the color in the RGB palette /

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();
}

And everything works, so now I need to somehow remember the color that was scored before and I decided to implement it through EEPROM and now the code already looks like this.

// запись цвета в память
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();
}

And now the appearance remains, here everyone chooses what he likes. But I decided that I didn’t like the stock Tkinter buttons, text font, and I changed it all. After downloading from some site that I liked, drawing buttons in Photoshop Online, I got this program

I will attach a link to the repository on GitHub

Another function has been added that when the program is closed, so as not to interfere, a tray icon appears in which there are two functions for opening the window and closing the program, but only it does not close completely, because I did not understand how to stop the second thread.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *