Skip to content

Final Project

SpacePad by Areg Khalatyan is licensed under CC BY-NC-SA 4.0

This week I worked on defining my final project idea and started to getting used to the documentation process.

Project Management

Exploded View

Here is the exploded view of the rough design work.

System Diagram

With this diagram I want to show how I divided the work into parts and in the following graph you will see how they relate to each other and how I will further develop my project.

Task List

  • Research


    • Sketching

    • Testing
  • At this point I will have to work on the concept and decide what my final project will be.

    The first task is to make a sketch.

    The second tests the viability of the idea and determines how feasible it is to implement it within a certain period of time.

    At this point I had some ideas, from which I settled on a laptop stand with a built-in macro keyboard and Spacemouse .

  • Design


    • CAD modeling / 3D

      • Packaging Design for Electronics
      • Industrial Design

    • 2D Desgn
      • Board Shape
      • Icones design

  • Second Stage

    • So the second stage is probably the most difficult because in this stage I will have to test my idea. I divided this part into 3 subsections

    • Substage 1

    • I do 3D and 2D modeling to decide where and what will happen.
  • Electrnics Desgn/Prodution

    • Component Selection
    • Schematic Design
    • PCB Design
    • PCB Fabrication
    • Assembly
    • Testing
  • Substage 2


    • I am engaged in the production of electronics and printed circuit boards.

  • Software Development


    • Programing
      • SpaceMouse Programing
      • Testing
      • Keyboard Programing
      • Testing
      • LED Programing
      • Testing
      • Networcing
      • Testing

    • Apication & Interface
      • APP for setting macros
      • Networcing
      • Testing
  • Substage 3.


    • I will start programming and creating applications that I will use in the project.

    This part of the design will most likely be repeated in a circle several times.

  • The final Stage of production/assembly

    • CNC 3D/2D
    • Laser Cutting
    • Molding
    • Vacuum Forming
  • The third stage is already the final one.


    • At this point I will do everything else, fabrication of desk, PCB housing, etc.

Schedule

Task Details Deadline
1 CAD modeling / 3D Initial development 10.05.24
2 Packaging for electronics Spacemouse & Touch sensors 15.05.24
3 Electrnics Desgn Spacemouse & Touch sensors 20.05.24
4 Electrnics Prodution Spacemouse & Touch sensors 24.05.24
5 Icones design Macros iconesfor Touch sensors 25.05.24
6 Testing Hardware testing 26.05.24
7 Programing ALL 29.05.24
8 Apication & Interface Development of an application for setting up macros and Spacemouse 01.06.24
9 Testing Software testing 01.06.24
11 CAD modeling Final development of the whole project 02.06.24
12 CNC Milling & Laser Cutting Making an adjustable desk, base and mechanical elements from plywood. 03.06.24
13 Vacuum Forming Storage compartment- Mold making and vacuum forming 04.06.24
14 3D Printing & PCB Prodution 3D printing of all elements 04.06.24
15 Assembling Final assembly 05.06.24
16 Testing Final Testing 05.06.24

Research

Before starting my project, I did a lot of research, but here I wrote about what I actually used.

So in the research stage i found this 3d model for joysticks and use in my design. Joystick HW-504 CAD
I also find this Touch element. There is lot of information about ESP’S touch sensors and I learned lots about touch sensors.
In my PCB design i also use this ESP32-DEVKIT-V1 footprint
The first attempt to connect the esp32 to the computer failed and then I found out that this was due to the fact that I did not have the appropriate drivers, and i find this Diver.

Design

I started the process of designing SpaceMouse in the System Integration week here are some slides. Besides SpaceMouse I also started designing a keyboard. In System Integration week you can find more information about system integration and first attempts .

Keyboard Design

And so after I made a preliminary design of my project, namely part of the keyboard, I began to design the board on Kikad. So at this point I was thinking about how I would use the pins available to me since I already had 8 GPIOs used for the joystick. I also had to use 6 Touch enabled GPIOs for the keyboard, and also wanted the sensors to light up when touched, but there weren’t enough pins for individual LEDs, so I decided to use addressable LEDs, namely WS 2812 B, which can be connected to a single pin and can be used via separate programming. First, as usual, I designed the individual LED components with a capacitor.

But after I went to an electronics store located in my city, it turned out that the individual components cost much more than just buying an LED strip. And so I decided to use the same tape, simply cutting off the number of LEDs I needed.

Since the capacitors were already soldered on the tape, all I had to do was design the foot prints and connect them correctly. At first I wanted to draw a footprint on Illustrator, then I found a way to do it correctly and I created a footprint and symbol for my component using the footprint and symbol editor.

You can download it from here.

And this is what my diagram looks like now.

I also added a 470 Ohm resistor to the date in the contact, because the LED was 5 V, and the microcontroller itself was 3.3 V, and after searching a little bit more on the Internet, I realized that for protection you need to add a small resistor. I also added a 10uF capacitor to smooth out noise in all circuits.

Design

I want to note that all the work was done in parallel with the design of the case and I think this is the best option for such matters.

From this I found the shape of my PCB and also developed the touch buttons. After developing in SolidWorks, I exported these buttons to dxf, and then exported them using Illustrator and to an svg file so that I could edit them in Kicad in the future.

I generated g-code using mods and configured it for milling on the Roland SRM20 machine.

This is what I got as a result.

Source svg file

PCB Design

And so let’s continue about designing in the KiCad program.

This is my circuit in the central part of the ESP 32 Devkit V1 microcontroller, on the left is the joystick connection diagram, in the upper right corner are addressable LEDs, to the right of the microcontroller are the touch sensor electrodes, and in the lower right corner are all the remaining pins so that there is room for further development of the project.

After drawing the schematic, I started designing the board and started with the LEDs and pins to connect the Space Mouse. I would like to note that I wanted to design a double-sided board and this is my first time doing this, but I really liked the process because there are many opportunities to make it beautiful and functional.

So, I first exported the PCB shape I created, found in the CAD modeling stage, then, as I said, I created the circuits for the LEDs and wired them in series, which was easier than programming them. In the second step, I started designing the second layer, the bottom one, where my microcontroller would be located and connected to the joysticks. I also added a capacitor for the LEDs.

Here’s what I came up with: I added a hole to connect the two layers of the board and added my logo.

And finally it’s time to make the board. And for this I exported the board into svg files separately, each layer with Traces, Interior and Drills.

You can download them here.

Traces Drills Interior

So, as for the production of double-sided printed circuit boards, this is a rather difficult task. Because once we’ve made the first layer, we need to position it very clearly for the second layer so that the holes don’t diverge.

And for this you need to follow the necessary sequence. First you need to isolate the traces, then drill a hole in it, and then cut out the outline. After this, the most interesting thing is that I will now tell you how I solved this problem.

Having completed the first stage, I carefully remove the board from the table, and in the second stage I drill a hole directly on the table. I only need three holes for this, so I stop drilling halfway through.

After that, I position the board using pin connectors and glue it with double-sided tape. After this, you can safely isolate traces of the second layer.

And the result is simply beautiful, and all the holes are in place.

So while I was milling, I was simultaneously printing the keyboard body and printing it in clear PLA so that light could shine through.

Then it was time to solder. I soldered everything, everything looked great and the footprints that I designed were accurate as well.

Here you can see that I soldered the pin headers for the touch electrodes to make everything easier to assemble and disassemble.

And on the electrodes themselves, I made holes in the corners and soldered them to the wires on the back side. And to be honest, I didn’t want to spoil the appearance, but I had no other choice; In the future I am going to cover the electrodes with vinyl or veneer.

System Integration week you can find more information about system integration and first attempts .

Testing

I started testing with LEDs and for this I used the fastled library and its built-in examples.

/// @file    ColorPalette.ino
/// @brief   Demonstrates how to use @ref ColorPalettes
/// @example ColorPalette.ino

#include <FastLED.h>

#define LED_PIN     4
#define NUM_LEDS    6
#define BRIGHTNESS  64
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];

#define UPDATES_PER_SECOND 100


CRGBPalette16 currentPalette;
TBlendType    currentBlending;

extern CRGBPalette16 myRedWhiteBluePalette;
extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM;


void setup() {
    delay( 3000 ); // power-up safety delay
    FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
    FastLED.setBrightness(  BRIGHTNESS );

    currentPalette = RainbowColors_p;
    currentBlending = LINEARBLEND;
}

void loop()
{
    ChangePalettePeriodically();

    static uint8_t startIndex = 0;
    startIndex = startIndex + 1; /* motion speed */

    FillLEDsFromPaletteColors( startIndex);

    FastLED.show();
    FastLED.delay(1000 / UPDATES_PER_SECOND);
}

void FillLEDsFromPaletteColors( uint8_t colorIndex)
{
    uint8_t brightness = 255;

    for( int i = 0; i < NUM_LEDS; ++i) {
        leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
        colorIndex += 3;
    }
}
void ChangePalettePeriodically()
{
    uint8_t secondHand = (millis() / 1000) % 60;
    static uint8_t lastSecond = 99;
    if( lastSecond != secondHand) {
        lastSecond = secondHand;
        if( secondHand ==  0)  { currentPalette = RainbowColors_p;         currentBlending = LINEARBLEND; }
        if( secondHand == 10)  { currentPalette = RainbowStripeColors_p;   currentBlending = NOBLEND;  }
        if( secondHand == 15)  { currentPalette = RainbowStripeColors_p;   currentBlending = LINEARBLEND; }
        if( secondHand == 20)  { SetupPurpleAndGreenPalette();             currentBlending = LINEARBLEND; }
        if( secondHand == 25)  { SetupTotallyRandomPalette();              currentBlending = LINEARBLEND; }
        if( secondHand == 30)  { SetupBlackAndWhiteStripedPalette();       currentBlending = NOBLEND; }
        if( secondHand == 35)  { SetupBlackAndWhiteStripedPalette();       currentBlending = LINEARBLEND; }
        if( secondHand == 40)  { currentPalette = CloudColors_p;           currentBlending = LINEARBLEND; }
        if( secondHand == 45)  { currentPalette = PartyColors_p;           currentBlending = LINEARBLEND; }
        if( secondHand == 50)  { currentPalette = myRedWhiteBluePalette_p; currentBlending = NOBLEND;  }
        if( secondHand == 55)  { currentPalette = myRedWhiteBluePalette_p; currentBlending = LINEARBLEND; }
    }
}
// This function fills the palette with totally random colors.
void SetupTotallyRandomPalette()
{
    for( int i = 0; i < 16; ++i) {
        currentPalette[i] = CHSV( random8(), 255, random8());
    }
}

// This function sets up a palette of black and white stripes,
// using code.  Since the palette is effectively an array of
// sixteen CRGB colors, the various fill_* functions can be used
// to set them up.
void SetupBlackAndWhiteStripedPalette()
{
    // 'black out' all 16 palette entries...
    fill_solid( currentPalette, 16, CRGB::Black);
    // and set every fourth one to white.
    currentPalette[0] = CRGB::White;
    currentPalette[4] = CRGB::White;
    currentPalette[8] = CRGB::White;
    currentPalette[12] = CRGB::White;
}
// This function sets up a palette of purple and green stripes.
void SetupPurpleAndGreenPalette()
{
    CRGB purple = CHSV( HUE_PURPLE, 255, 255);
    CRGB green  = CHSV( HUE_GREEN, 255, 255);
    CRGB black  = CRGB::Black;

    currentPalette = CRGBPalette16(
                                  green,  green,  black,  black,
                                  purple, purple, black,  black,
                                  green,  green,  black,  black,
                                  purple, purple, black,  black );
}
// This example shows how to set up a static color palette
// which is stored in PROGMEM (flash), which is almost always more
// plentiful than RAM.  A static PROGMEM palette like this
// takes up 64 bytes of flash.
const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM =
{
    CRGB::Red,
    CRGB::Gray, // 'white' is too bright compared to red and blue
    CRGB::Blue,
    CRGB::Black,
    CRGB::Red,
    CRGB::Gray,
    CRGB::Blue,
    CRGB::Black,
    CRGB::Red,
    CRGB::Red,
    CRGB::Gray,
    CRGB::Gray,
    CRGB::Blue,
    CRGB::Blue,
    CRGB::Black,
    CRGB::Black
};

After testing, it turned out that one LED was poorly soldered. So I fixed it.

After that, I started testing my touch sensors with LEDs; I wanted each LED to light up individually when I press the electrode. And for this I changed the code and added touch pins to it. With the help of which I determined that one sensor was also poorly soldered and corrected this and this is the result.

#include <FastLED.h>

#define NUM_LEDS 6
#define DATA_PIN 4
CRGB leds[NUM_LEDS];
#define TOUCH_PIN_4  T4
#define TOUCH_PIN_5  T5
#define TOUCH_PIN_6  T6
#define TOUCH_PIN_7  T7
#define TOUCH_PIN_8  T8
#define TOUCH_PIN_9  T9
#define TOUCH_THRESHOLD  20
int touchPins[6] = {T9, T8, T7, T6, T5, T4};
// This function sets up the ledsand tells the controller about them
void setup() {
  // sanity check delay - allows reprogramming if accidently blowing power w/leds
    delay(2000);

    FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS);  // GRB ordering is typical

}

// This function runs over and over, and is where you do the magic to light
// your leds.
void loop() {
  for(int i = 0; i < 6; i++) {
      int touchValue = touchRead(touchPins[i]);
  // Move a single white led 
  if (touchValue < TOUCH_THRESHOLD) {

      leds[i] = CRGB::White;
      FastLED.show();

      // Wait a little bit
      delay(500);

      // Turn our current led back to black for the next loop around
      leds[i] = CRGB::Black;
      FastLED.show();
    }
  }

  delay(50);
}

Finally I tested sending messages using serial port.

The next step was to test my SpaceMouse and keyboard together.

And at this stage I discovered many problems and the first of them was that I was using pins for one of the joysticks that were not intended for analog reading. And to fix this problem, I first connected the other pins using jumpers. At first I used pins 2 and 15, but it turned out that a built-in LED was connected to pin 2 and this interfered with the operation of the joystick.

In general, I solved this problem and replaced the pin 4 that was connected to the addressable LED with the pin 2. And 4 was used for joysticks.

Redesign

So I had no choice, I had to design the boards again, and since everything would have to be done anew, I also changed the connectors to make everything much more compact. I also changed the case - connecting both cases for the joystick and for the keyboard together and, in general, this is what i did.

Since I needed to redesign the board, I decided to first change the SolidWorks design to have the outline of the future board, and connected the housing for the joysticks and keyboard together. I also increased the distance between the joysticks to allow the joystick to operate more freely. I also slightly increased the diameter of the handle to make it more comfortable.

In addition, I added a place for attaching the case to the table and made places in the table so that they fit snugly against each other.

PCB Design

I exported outline from Solidworks to Kicad and started redesigning the board. And before exporting from SolidWorks, I made a hole in the place of the connectors so that I would know where to put them. Here I changed the connector type for joysticks from horizontal SMD to vertical THT. I also changed the connectors for connecting the two boards from horizontal single-row to vertical double-row.

On the board for the keyboard I also changed the type of connectors, but the main thing I did was change the connection to the microcontroller pins. As I said earlier, in the first board I used pins that did not support analog signals for joysticks.

Here’s how I did it in the left picture- the previous wrong one, on the right, the changed ones are already correct.

I also changed the Pin for addressable LEDs since I used 4 pins to connect the joystick. For LEDs I replaced 4 with 2 because 2 had a built-in LED and I could not use it for joysticks because this LED interfered with the signals and gave incorrect results.

In this picture you can see the already corrected connection diagram.

PCB Production

So I exported and milled all the files on Roland machines as before.

Here you can download and repeat it. SpacePad KiCad

Corected version for Spacemouse.

Traces Drills Interior

Corected version for keyboard.

Traces Drills Interior

Soldering & Assembling

During the process of milling and soldering , I also 3D printed the PCB body and handle for the Spacemouse and started assembling.

Post-processing

After assembly, I began post-processing the Spacemouse handle. I planned to do this in advance and for this purpose I printed this part using ABS plastic, since it lends itself well to mechanical and chemical processing.

First I made a thread for the fasteners, then I sanded the surface with sandpaper, then with acetone. After this process I paint it with matte spray.

Desk Design

After that I made a design with a ratchet mechanism to adjust the angle of the desk.

And for this I needed to use laser cutting of flat parts, 3D printing of bearing supports, and I also needed a metal pipe with a diameter of 10 mm.

I found a pipe in Fablab and cut it with a grinder.

And I printed the mount for the bearings on a 3D printer and placed the bearings inside during the printing process.

The rest of the details were laser cut.

Laser cuting

I wanted to make the desk itself using CNC, but at that moment the machine was not working, so I made the upper parts using a laser. I did the cutting and cut the adjustment table itself from 6 mm plywood, and I also cut the facing of my desk from 3 mm plywood and the parts of rachet also from 3mm.

I exported the contours from Solidworks and Change them in Adobe Illustrator You can download this file here. Laser & CNC eps

CNC Milling

So that time our CNC machine was fixed and I decided to CNC the rest of the parts so that the final product would be durable.

I decided to make the parts from 12 mm thick plywood. I export contours for CNC in the same way. These files can be found here. Laser & CNC files eps

Using VCarve I generated toolpaths for the desk base.

First I added the dog bones. Then generate toolpaths for the drills.

First I had to drill. Because I don’t want to add tabs so as not to spoil the appearance.

I did this using 1/8 drill.

Then I make toolpaths for pocket & outline using a 6mm router bit.

This is what I got as a result.

Then I glued all the pieces.

Vacuum Forming

During wild card week I made some storage space for laptop items .

You can find more information here. Wildcard week

Final Assembling

It’s finally time for final assembly.

Programming

This stage was probably the most difficult and during the programming process there were many errors that I eliminated step by step, but, unfortunately, I did not take screenshots or videos. But I saved the code at different stages. Now I’ll roughly demonstrate the development of the code, where I started and where I ended.

So fist to use the ESP32 you need to instal this Diver.

So before starting I tested As you read at the top I tested TOUCH sensors LEDs And also the EEPROM library. And so in this code I Joined all parts of code.

In short, this code is used for the touch keys so that when each key is pressed, an LED will light up as an indication. And with the help of the eprom library, we can change the key combination that will be pressed when touching the sensor and save it in flash memory.

#include <FastLED.h>
#include <BleKeyboard.h>
#include <EEPROM.h>

BleKeyboard bleKeyboard;

#define NUM_LEDS 6
#define DATA_PIN 4
CRGB leds[NUM_LEDS];
#define TOUCH_PIN_4  T4
#define TOUCH_PIN_5  T5
#define TOUCH_PIN_6  T6
#define TOUCH_PIN_7  T7
#define TOUCH_PIN_8  T8
#define TOUCH_THRESHOLD  20
int touchPins[6] = {T9, T8, T7, T6, T5, T4};

struct MacroSettings {
  String keys[6]; // Keys for macros
};

MacroSettings savedSettings;

void saveSettings() {
  EEPROM.put(0, savedSettings); // Writing settings to EEPROM
  EEPROM.commit(); // Committing the write
}

void loadSettings() {
  EEPROM.get(0, savedSettings); // Loading settings from EEPROM
}

void setup() {
  delay(2000); // Sanity check delay - allows reprogramming if accidentally blowing power with LEDs
  Serial.begin(115200);
  Serial.println("Starting BLE work!");
  bleKeyboard.begin();

  FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS);  // GRB ordering is typical
  loadSettings(); // Loading settings at startup
}

void loop() {
  for(int i = 0; i < 6; i++) {
    int touchValue = touchRead(touchPins[i]);
    if (touchValue < TOUCH_THRESHOLD) {
      if (bleKeyboard.isConnected()) {
        Serial.println("Keyboard is connected.");
        sendMacro(savedSettings.keys[i]);
      }
      leds[i] = CRGB::White;
      FastLED.show();

      delay(500);

      leds[i] = CRGB::Black;
      FastLED.show();
    }
  }
  delay(50);
}

void sendMacro(String keys) {
  String key;
  int prevIndex = 0;
  for (int i = 0; i <= keys.length(); i++) {
    if (i == keys.length() || keys.charAt(i) == ' ') {
      key = keys.substring(prevIndex, i);
      prevIndex = i + 1;

      if (key == "Ctrl") {
        bleKeyboard.press(KEY_LEFT_CTRL);
      } else if (key == "Alt") {
        bleKeyboard.press(KEY_LEFT_ALT);
      } else if (key == "Shift") {
        bleKeyboard.press(KEY_LEFT_SHIFT);
      } else {
        bleKeyboard.press(key[0]); // Handling a single character as a key
      }
    }
  }
  delay(100);  // Delay to simulate key hold
  bleKeyboard.releaseAll(); // Releasing all keys
}

void serialEvent() {
  while (Serial.available()) {
    char input = Serial.read();
    if (input >= '0' && input <= '5') {
      int index = input - '0';
      String newKeys = "";

      while (Serial.available()) {
        char ch = Serial.read();
        if (ch == '\n') {
          break;
        }
        newKeys += ch;
      }

      savedSettings.keys[index] = newKeys;
      saveSettings();

      Serial.print("Keys for sensor ");
      Serial.print(index);
      Serial.print(" updated to: ");
      Serial.println(newKeys);
    }
  }
}

Main Difference I added a clearEEPROM function which clears the EEPROM and sets default values ​​for macros and then saves these settings to the EEPROM. And serialEvent added a “reset” command that calls the clearEEPROM function. I also added flag - waitForString , because now the device must read not only single characters but also entire strings.

And this is also very important, I changed the touch threshold. #define TOUCH_THRESHOLD from 20 to 50, because when using a bluetooth connection with a power bank or other charger, the sensors stopped working, as it turned out the problem was precisely the touch threshold (probably the case was that when connecting the microcontroller to the computer, the computer created some a magnetic field).

#include <FastLED.h>
#include <BleKeyboard.h>
#include <EEPROM.h>

BleKeyboard bleKeyboard;

#define NUM_LEDS 6
#define DATA_PIN 4
CRGB leds[NUM_LEDS];
#define TOUCH_PIN_4 T4
#define TOUCH_PIN_5 T5
#define TOUCH_PIN_6 T6
#define TOUCH_PIN_7 T7
#define TOUCH_PIN_8 T8
#define TOUCH_THRESHOLD 50
int touchPins[6] = { T9, T8, T7, T6, T5, T4 };

bool waitForString = true;

struct MacroSettings {
  String keys[6];  // Keys for macros
};

MacroSettings savedSettings;

void saveSettings() {
  EEPROM.put(0, savedSettings);  // Writing settings to EEPROM
  EEPROM.commit();               // Committing the write
  Serial.println("Settings saved.");
}

void loadSettings() {
  EEPROM.get(0, savedSettings);  // Loading settings from EEPROM
}

void clearEEPROM() {
  for (int i = 0; i < 120; i++) {  // Assuming 120 bytes of EEPROM
    EEPROM.write(i, 0);
  }
  EEPROM.commit();  // Applying changes
  Serial.println("EEPROM cleared.");
  savedSettings.keys[0] = "Ctrl x";
  savedSettings.keys[1] = "Ctrl v";
  savedSettings.keys[2] = "Ctrl c";
  savedSettings.keys[3] = "Ctrl z";
  savedSettings.keys[4] = "Ctrl Shift z";
  savedSettings.keys[5] = "Ctrl Alt Del";
  saveSettings();
}

void setup() {
  EEPROM.begin(120);
  delay(2000);  // Sanity check delay - allows reprogramming if accidentally blowing power with LEDs
  Serial.begin(115200);
  Serial.println("Starting BLE work!");
  bleKeyboard.begin();
  FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS);  // GRB ordering is typical

  loadSettings();  // Loading settings from EEPROM at startup
  for (int i = 0; i < 6; i++) {
    Serial.println(savedSettings.keys[i]);
  }
}

void loop() {
  for (int i = 0; i < 6; i++) {
    int touchValue = touchRead(touchPins[i]);
    if (touchValue < TOUCH_THRESHOLD) {
      if (bleKeyboard.isConnected()) {
        Serial.println("Keyboard is connected.");
        sendMacro(savedSettings.keys[i]);
      }
      leds[i] = CRGB::White;
      FastLED.show();
      delay(100);
      leds[i] = CRGB::Black;
      FastLED.show();
    }
  }
  delay(50);
}

void sendMacro(String keys) {
  String key;
  int prevIndex = 0;
  for (int i = 0; i <= keys.length(); i++) {
    if (i == keys.length() || keys.charAt(i) == ' ') {
      key = keys.substring(prevIndex, i);
      prevIndex = i + 1;

      if (key == "Ctrl") {
        bleKeyboard.press(KEY_LEFT_CTRL);
      } else if (key == "Shift") {
        bleKeyboard.press(KEY_LEFT_SHIFT);
      } else if (key == "Alt") {
        bleKeyboard.press(KEY_LEFT_ALT);
      } else if (key == "Del") {
        bleKeyboard.press(KEY_DELETE);
      } else if (key == "Esc") {
        bleKeyboard.press(KEY_ESC);
      } else {
        bleKeyboard.press(key[0]);  // Handling a single character as a key
      }
    }
  }
  delay(30);                 // Delay to simulate key hold
  bleKeyboard.releaseAll();  // Releasing all keys
}

void serialEvent() {
  while (Serial.available()) {
    if (waitForString) {
      String input = Serial.readStringUntil('\n');
      input.trim();  // Removing any leading/trailing spaces

      if (input == "reset") {
        clearEEPROM();
      } else {
        waitForString = false;  // Switching the flag, now waiting for a character
      }
    } else {
      char input = Serial.read();
      if (input >= '0' && input <= '5') {
        int index = input - '0';
        String newKeys = "";

        while (Serial.available()) {
          char ch = Serial.read();
          if (ch == '\n') {
            break;
          }
          newKeys += ch;
        }

        savedSettings.keys[index] = newKeys;
        saveSettings();
        for (int i = 0; i < 6; i++) {
          Serial.println(savedSettings.keys[i]);
        }
        Serial.print("Keys for sensor ");
        Serial.print(index);
        Serial.print(" updated to: ");
        Serial.println(newKeys);
        waitForString = true;  // Resetting the flag
      }
    }
  }
}

In this code I changed the pin to control the LED strip. Now it uses pin 2 instead of pin 4 because as I said earlier I redesigned the board.

I need to say that ome portions of this code related only to joysticks, including calculations and ideas, have been taken and modified from TeachingTech. So I mixed the joysticks and macrokeyboard codes. And also added backlight switch in the default settings for Macro 1. In addition, I also made Spacemouse keys programmable for the other programs.

/*
* This code is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) License.
* You are free to share, copy, and redistribute the material in any medium or format, and adapt, remix, transform, and build upon the material
* as long as you give appropriate credit, do not use the material for commercial purposes, and distribute your contributions under the same license.
* 
* Note: Some portions of this code related only to joysticks, including calculations and ideas, have been taken and modified from Teaching Tech's code.
* Any changes and modifications made to the original code parts have been done to fit the specific requirements of this project.
* 
* Created by Areg Khalatyan in 2024 as part of a project in Fab Academy.
* 
* License details: https://creativecommons.org/licenses/by-nc-sa/4.0/
*/

#include <FastLED.h>
#include <BleKeyboard.h>
#include <EEPROM.h>

BleKeyboard bleKeyboard;

#define NUM_LEDS 6
#define DATA_PIN 2
CRGB leds[NUM_LEDS];
#define TOUCH_PIN_4 T4
#define TOUCH_PIN_5 T5
#define TOUCH_PIN_6 T6
#define TOUCH_PIN_7 T7
#define TOUCH_PIN_8 T8
#define TOUCH_THRESHOLD 50
int touchPins[6] = { T9, T8, T7, T6, T5, T4 };

int paletteIndex = 0;
bool lightshow = false;

//Light Show
#define UPDATES_PER_SECOND 100
CRGBPalette16 currentPalette;
int wholePaletteIndex = 0;
TBlendType currentBlending;
extern CRGBPalette16 myRedWhiteBluePalette;
extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM;
CRGBPalette16 pallettes[] = { RainbowColors_p, RainbowStripeColors_p, CloudColors_p, PartyColors_p, myRedWhiteBluePalette_p };

bool waitForString = true;

//This part is for debugging written by Teaching Tech. And these are his original comments.
//Joystick
// Debugging
// 0: Debugging off. Set to this once everything is working.
// 1: Output raw joystick values. 0-1023 raw ADC 10-bit values
// 2: Output centered joystick values. Values should be approx -500 to +500, jitter around 0 at idle.
// 3: Output centered joystick values. Filtered for deadzone. Approx -500 to +500, locked to zero at idle.
// 4: Output translation and rotation values. Approx -800 to 800 depending on the parameter.
// 5: Output debug 4 and 5 side by side for direct cause and effect reference.
int debug = 0;

//This part is for directions written by Teaching Tech. And these are his original comments.
//Here he only modify directions for his device.
// Direction
// Modify the direction of translation/rotation depending on preference. This can also be done per application in the 3DConnexion software.
// Switch between true/false as desired.
bool invX = false;   // pan left/right
bool invY = false;   // pan up/down
bool invZ = true;    // zoom in/out
bool invRX = true;   // Rotate around X axis (tilt front/back)
bool invRY = false;  // Rotate around Y axis (tilt left/right)
bool invRZ = true;   // Rotate around Z axis (twist left/right)

int threshold = 500;


int PINLIST[8] = {
   //This stroke also written by Teaching Tech for The positions of the reads
  36,  // X-axis A
  39,  // Y-axis A
  34,  // X-axis B
  35,  // Y-axis B
  26,  // X-axis C
  25,  // Y-axis C
  4,  // X-axis D
  15,  // Y-axis D
};//I just changed the pins for my microcontroller.

//This stroke also written by Teaching Tech for deadzone to filter out unintended movements. Recommended to have this as small as possible for  to allow smaller knob range of motion.
int DEADZONE = 200;  //I changed this value from 20 - 200 because I don't need a very precise value. On the contrary, I don’t need false positives.

struct MacroSettings {
  String keys[18];  // Keys for macros
};

bool keysPressed[12] = {false,false,false,false,false,false,false,false,false,false,false,false};

// This stroke also written by Teaching Tech for define axes are matched to pin order.
#define AX 0
#define AY 1
#define BX 2
#define BY 3
#define CX 4
#define CY 5
#define DX 6
#define DY 7

//This stroke also writen by Teaching Tech for Centerpoint variable to be populated during setup routine.
int centerPoints[8];

//Here starts the code that Teaching Tech wrote for Function to read and store analogue voltages for each joystick axis.
void readAllFromJoystick(int *rawReads) {
  for (int i = 0; i < 8; i++) {
    rawReads[i] = analogRead(PINLIST[i]);
  }
}//Here ends the code that Teaching Tech wrote.

MacroSettings savedSettings;

void saveSettings() {
  EEPROM.put(0, savedSettings);  // Writing settings to EEPROM
  EEPROM.commit();               // Committing the write
  Serial.println("Settings saved.");
}

void loadSettings() {
  EEPROM.get(0, savedSettings);  // Loading settings from EEPROM
}

void clearEEPROM() {
  for (int i = 0; i < 120; i++) {  // Assuming 120 bytes of EEPROM
    EEPROM.write(i, 0);
  }
  EEPROM.commit();  // Applying changes
  Serial.println("EEPROM cleared.");
  savedSettings.keys[0] = "Ctrl x";
  savedSettings.keys[1] = "Ctrl v";
  savedSettings.keys[2] = "Ctrl c";
  savedSettings.keys[3] = "Ctrl z";
  savedSettings.keys[4] = "Ctrl Shift z";
  savedSettings.keys[5] = "lightshow";
  savedSettings.keys[6] = "Ctrl right";
  savedSettings.keys[7] = "Ctrl left";
  savedSettings.keys[8] = "Ctrl down";
  savedSettings.keys[9] = "Ctrl up";
  savedSettings.keys[10] = "Shift z";
  savedSettings.keys[11] = "z";
  savedSettings.keys[12] = "up";
  savedSettings.keys[13] = "down";
  savedSettings.keys[14] = "right";
  savedSettings.keys[15] = "down";
  savedSettings.keys[16] = "Alt right";
  savedSettings.keys[17] = "Alt left";
  saveSettings();
}

void setup() {
  EEPROM.begin(120);
  delay(2000);  // Delay check - allows reprogramming on accidental over power with LED
  Serial.begin(115200);
  Serial.println("Starting BLE operation!");
  bleKeyboard.begin();
  FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS);  // Typical order GRB

  currentPalette = RainbowColors_p;
  currentBlending = LINEARBLEND;

  // Read centre positions for joysticks.
  readAllFromJoystick(centerPoints);
  readAllFromJoystick(centerPoints);

  clearEEPROM();
  loadSettings();
  for (int i = 0; i < 18; i++) {
    Serial.println(savedSettings.keys[i]);
  }
}

void loop() {
  if (lightshow) {
    ChangePalettePeriodically();
    FillLEDsFromPaletteColors(paletteIndex);
    paletteIndex++;
    FastLED.show();
    FastLED.delay(1000 / UPDATES_PER_SECOND);
  }
  for (int i = 0; i < 6; i++) {
    int touchValue = touchRead(touchPins[i]);
    if (touchValue < TOUCH_THRESHOLD) {
      if (bleKeyboard.isConnected()) {
        Serial.println("Keyboard connected.");
        if (i != 5) {
          sendMacro(savedSettings.keys[i]);
        }
      }
      if (i == 5) {
          sendMacro(savedSettings.keys[i]);
        }
      for (int j = 0; j < 6; j++) {
        if (j != i) {
          leds[j] = CRGB::Black;
        }
      }
      leds[i] = CRGB::White;
      FastLED.show();
      delay(100);
      leds[i] = CRGB::Black;
      FastLED.show();

    }
  }

  //Here begins the code that Teaching Tech wrote for debugging.
  int rawReads[8], centered[8];
  // Joystick values are read. 0-1023
  readAllFromJoystick(rawReads);
  // Report back 0-1023 raw ADC 10-bit values if enabled
  if (debug == 1) {
    Serial.print("AX:");
    Serial.print(rawReads[0]);
    Serial.print(",");
    Serial.print("AY:");
    Serial.print(rawReads[1]);
    Serial.print(",");
    Serial.print("BX:");
    Serial.print(rawReads[2]);
    Serial.print(",");
    Serial.print("BY:");
    Serial.print(rawReads[3]);
    Serial.print(",");
    Serial.print("CX:");
    Serial.print(rawReads[4]);
    Serial.print(",");
    Serial.print("CY:");
    Serial.print(rawReads[5]);
    Serial.print(",");
    Serial.print("DX:");
    Serial.print(rawReads[6]);
    Serial.print(",");
    Serial.print("DY:");
    Serial.println(rawReads[7]);
  }

  // Subtract centre position from measured position to determine movement.
  for (int i = 0; i < 8; i++) centered[i] = rawReads[i] - centerPoints[i];  //
  // Report centered joystick values if enabled. Values should be approx -500 to +500, jitter around 0 at idle.
  if (debug == 2) {
    Serial.print("AX:");
    Serial.print(centered[0]);
    Serial.print(",");
    Serial.print("AY:");
    Serial.print(centered[1]);
    Serial.print(",");
    Serial.print("BX:");
    Serial.print(centered[2]);
    Serial.print(",");
    Serial.print("BY:");
    Serial.print(centered[3]);
    Serial.print(",");
    Serial.print("CX:");
    Serial.print(centered[4]);
    Serial.print(",");
    Serial.print("CY:");
    Serial.print(centered[5]);
    Serial.print(",");
    Serial.print("DX:");
    Serial.print(centered[6]);
    Serial.print(",");
    Serial.print("DY:");
    Serial.println(centered[7]);
  }
  // Filter movement values. Set to zero if movement is below deadzone threshold.
  for (int i = 0; i < 8; i++) {
    if (centered[i] < DEADZONE && centered[i] > -DEADZONE) centered[i] = 0;
  }
  // Report centered joystick values. Filtered for deadzone. Approx -500 to +500, locked to zero at idle
  if (debug == 3) {
    Serial.print("AX:");
    Serial.print(centered[0]);
    Serial.print(",");
    Serial.print("AY:");
    Serial.print(centered[1]);
    Serial.print(",");
    Serial.print("BX:");
    Serial.print(centered[2]);
    Serial.print(",");
    Serial.print("BY:");
    Serial.print(centered[3]);
    Serial.print(",");
    Serial.print("CX:");
    Serial.print(centered[4]);
    Serial.print(",");
    Serial.print("CY:");
    Serial.print(centered[5]);
    Serial.print(",");
    Serial.print("DX:");
    Serial.print(centered[6]);
    Serial.print(",");
    Serial.print("DY:");
    Serial.println(centered[7]);
  }

  // Doing all through arithmetic contribution by fdmakara
  // Integer has been changed to 16 bit int16_t to match what the HID protocol expects.
  int16_t transX, transY, transZ, rotX, rotY, rotZ;  // Declare movement variables at 16 bit integers
  // Original fdmakara calculations
  //transX = (-centered[AX] +centered[CX])/1;
  //transY = (-centered[BX] +centered[DX])/1;
  //transZ = (-centered[AY] -centered[BY] -centered[CY] -centered[DY])/2;
  //rotX = (-centered[AY] +centered[CY])/2;
  //rotY = (+centered[BY] -centered[DY])/2;
  //rotZ = (+centered[AX] +centered[BX] +centered[CX] +centered[DX])/4;
  // My altered calculations based on debug output. Final divisor can be changed to alter sensitivity for each axis.
  transX = -(-centered[CY] + centered[AY]) / 1;
  transY = (-centered[BY] + centered[DY]) / 1;
  if ((abs(centered[AX]) > DEADZONE) && (abs(centered[BX]) > DEADZONE) && (abs(centered[CX]) > DEADZONE) && (abs(centered[DX]) > DEADZONE)) {
    transZ = (-centered[AX] - centered[BX] - centered[CX] - centered[DX]) / 1;
    transX = 0;
    transY = 0;
  } else {
    transZ = 0;
  }
  rotX = (-centered[AX] + centered[CX]) / 1;
  rotY = (+centered[BX] - centered[DX]) / 1;
  if ((abs(centered[AY]) > DEADZONE) && (abs(centered[BY]) > DEADZONE) && (abs(centered[CY]) > DEADZONE) && (abs(centered[DY]) > DEADZONE)) {
    rotZ = (+centered[AY] + centered[BY] + centered[CY] + centered[DY]) / 2;
    rotX = 0;
    rotY = 0;
  } else {
    rotZ = 0;
  }

  // Alter speed to suit user preference - Use 3DConnexion slider instead for V2.
  //transX = transX/100*speed;
  //transY = transY/100*speed;
  //transZ = transZ/100*speed;
  //rotX = rotX/100*speed;
  //rotY = rotY/100*speed;
  //rotZ = rotZ/100*speed;
  // Invert directions if needed
  if (invX == true) { transX = transX * -1; };
  if (invY == true) { transY = transY * -1; };
  if (invZ == true) { transZ = transZ * -1; };
  if (invRX == true) { rotX = rotX * -1; };
  if (invRY == true) { rotY = rotY * -1; };
  if (invRZ == true) { rotZ = rotZ * -1; };

  // Report translation and rotation values if enabled. Approx -800 to 800 depending on the parameter.
  if (debug == 4) {
    Serial.print("TX:");
    Serial.print(transX);
    Serial.print(",");
    Serial.print("TY:");
    Serial.print(transY);
    Serial.print(",");
    Serial.print("TZ:");
    Serial.print(transZ);
    Serial.print(",");
    Serial.print("RX:");
    Serial.print(rotX);
    Serial.print(",");
    Serial.print("RY:");
    Serial.print(rotY);
    Serial.print(",");
    Serial.print("RZ:");
    Serial.println(rotZ);
  }//This ends the code that Teaching Tech wrote for debugging. didn't change anything here.

  //This part of code i wrote for using joystick values to press and release the keys.
  //Press the corresponding keys
  int axes[6] = {0,0,0,0,0,0};

  if (transX > threshold) {
    axes[0] = 1;
  } else if (transX < -threshold) {
    axes[0] = -1;
  }
  if (transY > threshold) {
    axes[1] = 1;
  } else if (transY < -threshold) {
    axes[1] = -1;
  }
  if (transZ > threshold) {
    axes[2] = 1;
  } else if (transZ < -threshold) {
    axes[2] = -1;
  }
  if (rotX > threshold) {
    axes[3] = 1;
  } else if (rotX < -threshold) {
    axes[3] = -1;
  }
  if (rotY > threshold) {
    axes[4] = 1;
  } else if (rotY < -threshold) {
    axes[4] = -1;
  }
  if (rotZ > threshold) {
    axes[5] = 1;
  } else if (rotZ < -threshold) {
    axes[5] = -1;
  }

  for (int i = 0; i < 6; i++) {
    if (axes[i] == 0) {
      if (keysPressed[i*2]) {
        releaseKeys(savedSettings.keys[i*2]);
        keysPressed[i*2] = false;
      }
      if (keysPressed[i*2+1]) {
        releaseKeys(savedSettings.keys[i*2+1]);
        keysPressed[i*2+1] = false;
      }
    }
  }
  for (int i = 0; i < 6; i++) {
    if (axes[i] == 1 && !keysPressed[i*2]) {
      pressKeys(savedSettings.keys[6+i*2]);
      keysPressed[i*2] = true;
    }
    if (axes[i] == -1 && !keysPressed[i*2+1]) {
      pressKeys(savedSettings.keys[6+i*2+1]);
      keysPressed[i*2+1] = true;
    }
  }
  //Here begins the code that Teaching Tech wrote for debugging.
  // Report debug 4 and 5 info side by side for direct reference if enabled. Very useful if you need to alter which inputs are used in the arithmatic above.
  if (debug == 5) {
    Serial.print("AX:");
    Serial.print(centered[0]);
    Serial.print(",");
    Serial.print("AY:");
    Serial.print(centered[1]);
    Serial.print(",");
    Serial.print("BX:");
    Serial.print(centered[2]);
    Serial.print(",");
    Serial.print("BY:");
    Serial.print(centered[3]);
    Serial.print(",");
    Serial.print("CX:");
    Serial.print(centered[4]);
    Serial.print(",");
    Serial.print("CY:");
    Serial.print(centered[5]);
    Serial.print(",");
    Serial.print("DX:");
    Serial.print(centered[6]);
    Serial.print(",");
    Serial.print("DY:");
    Serial.print(centered[7]);
    Serial.print("||");
    Serial.print("TX:");
    Serial.print(transX);
    Serial.print(",");
    Serial.print("TY:");
    Serial.print(transY);
    Serial.print(",");
    Serial.print("TZ:");
    Serial.print(transZ);
    Serial.print(",");
    Serial.print("RX:");
    Serial.print(rotX);
    Serial.print(",");
    Serial.print("RY:");
    Serial.print(rotY);
    Serial.print(",");
    Serial.print("RZ:");
    Serial.println(rotZ);
  }

  delay(50);
}

//This ends the code that Teaching Tech wrote for debugging. Here I just changed the delay value from 400 to 50.
void sendMacro(String keys) {
  Serial.println(keys);
  String key;
  int prevIndex = 0;
  for (int i = 0; i <= keys.length(); i++) {
    if (i == keys.length() || keys.charAt(i) == ' ') {
      key = keys.substring(prevIndex, i);
      prevIndex = i + 1;
      lightshow = false;
      if (key == "Ctrl") {
        bleKeyboard.press(KEY_LEFT_CTRL);
      } else if (key == "Shift") {
        bleKeyboard.press(KEY_LEFT_SHIFT);
      } else if (key == "Alt") {
        bleKeyboard.press(KEY_LEFT_ALT);
      } else if (key == "Del") {
        bleKeyboard.press(KEY_DELETE);
      } else if (key == "Esc") {
        bleKeyboard.press(KEY_ESC);
      } else if (key == "lightshow") {
        Serial.println("lightshow");
        paletteIndex += 300;
        wholePaletteIndex++;
        if (wholePaletteIndex > 4) {
          lightshow = false;
          wholePaletteIndex = 0;
          for (int j = 0; j < 6; j++) {
            leds[j] = CRGB::Black;
          }
        } else {
          lightshow = true;
        }
      } else {
        bleKeyboard.press(key[0]);  //  Releasing a single character as a key
      }
    }
  }
  delay(30);                 
  bleKeyboard.releaseAll();  // Release all keys
}

void pressKeys(String keys) {
  Serial.println(keys);
  String key;
  int prevIndex = 0;
  for (int i = 0; i <= keys.length(); i++) {
    if (i == keys.length() || keys.charAt(i) == ' ') {
      key = keys.substring(prevIndex, i);
      prevIndex = i + 1;

      if (key == "Ctrl") {
        bleKeyboard.press(KEY_LEFT_CTRL);
      } else if (key == "Shift") {
        bleKeyboard.press(KEY_LEFT_SHIFT);
      } else if (key == "Alt") {
        bleKeyboard.press(KEY_LEFT_ALT);
      } else if (key == "Del") {
        bleKeyboard.press(KEY_DELETE);
      } else if (key == "Esc") {
        bleKeyboard.press(KEY_ESC);
      } else if (key == "left") {
        bleKeyboard.press(KEY_LEFT_ARROW);
      } else if (key == "right") {
        bleKeyboard.press(KEY_RIGHT_ARROW);
      } else if (key == "up") {
        bleKeyboard.press(KEY_UP_ARROW);
      } else if (key == "down") {
        bleKeyboard.press(KEY_DOWN_ARROW);
      } else {
        bleKeyboard.press(key[0]);  //  Releasing a single character as a key
      }
    }
  }
}

void releaseKeys(String keys) {
  String key;
  int prevIndex = 0;
  for (int i = 0; i <= keys.length(); i++) {
    if (i == keys.length() || keys.charAt(i) == ' ') {
      key = keys.substring(prevIndex, i);
      prevIndex = i + 1;

      if (key == "Ctrl") {
        bleKeyboard.release(KEY_LEFT_CTRL);
      } else if (key == "Shift") {
        bleKeyboard.release(KEY_LEFT_SHIFT);
      } else if (key == "Alt") {
        bleKeyboard.release(KEY_LEFT_ALT);
      } else if (key == "Del") {
        bleKeyboard.release(KEY_DELETE);
      } else if (key == "Esc") {
        bleKeyboard.release(KEY_ESC);
      }  else if (key == "left") {
        bleKeyboard.release(KEY_LEFT_ARROW);
      } else if (key == "right") {
        bleKeyboard.release(KEY_RIGHT_ARROW);
      } else if (key == "up") {
        bleKeyboard.release(KEY_UP_ARROW);
      } else if (key == "down") {
        bleKeyboard.release(KEY_DOWN_ARROW);
      } else {
        bleKeyboard.release(key[0]);  //  Releasing a single character as a key
      }
    }
  }
}

void serialEvent() {
  // Handle serial events
  while (Serial.available()) {
    if (waitForString) {
      // Read incoming serial data until a newline character is encountered
      String input = Serial.readStringUntil('\n');
      input.trim();  // Trim leading and trailing whitespace

      // Check if input is "reset" to clear EEPROM settings
      if (input == "reset") {
        clearEEPROM();
      } else {
        waitForString = false;  // Switch flag, now expecting a character
      }
    } else {
      char input = Serial.read();
      // Process numeric input (0-5) to update sensor key mappings
      if (input >= '0' && input <= '5') {
        int index = input - '0';
        String newKeys = "";

        // Read new keys until newline character
        while (Serial.available()) {
          char ch = Serial.read();
          if (ch == '\n') {
            break;
          }
          newKeys += ch;
        }

        // Update saved settings and save to EEPROM
        savedSettings.keys[index] = newKeys;
        saveSettings();

        // Print updated keys to serial monitor
        for (int i = 0; i < 6; i++) {
          Serial.println(savedSettings.keys[i]);
        }
        Serial.print("Keys for sensor ");
        Serial.print(index);
        Serial.print(" updated to: ");
        Serial.println(newKeys);
        waitForString = true;  // Reset flag
      }
    }
  }
}

void FillLEDsFromPaletteColors(uint8_t colorIndex) {
  uint8_t brightness = 255;

  for (int i = 0; i < NUM_LEDS; ++i) {
    leds[i] = ColorFromPalette(currentPalette, colorIndex, brightness, currentBlending);
    colorIndex += 3;
  }
}

void ChangePalettePeriodically() {
  uint8_t secondHand = (millis() / 1000) % 60;
  static uint8_t lastSecond = 99;

  if (lastSecond != secondHand) {
    lastSecond = secondHand;
    if (secondHand == 0) {
      currentPalette = RainbowColors_p;
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 10) {
      currentPalette = RainbowStripeColors_p;
      currentBlending = NOBLEND;
    }
    if (secondHand == 15) {
      currentPalette = RainbowStripeColors_p;
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 20) {
      SetupPurpleAndGreenPalette();
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 25) {
      SetupTotallyRandomPalette();
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 30) {
      SetupBlackAndWhiteStripedPalette();
      currentBlending = NOBLEND;
    }
    if (secondHand == 35) {
      SetupBlackAndWhiteStripedPalette();
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 40) {
      currentPalette = CloudColors_p;
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 45) {
      currentPalette = PartyColors_p;
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 50) {
      currentPalette = myRedWhiteBluePalette_p;
      currentBlending = NOBLEND;
    }
    if (secondHand == 55) {
      currentPalette = myRedWhiteBluePalette_p;
      currentBlending = LINEARBLEND;
    }
  }
}

// This function fills the palette with totally random colors.
void SetupTotallyRandomPalette() {
  for (int i = 0; i < 16; ++i) {
    currentPalette[i] = CHSV(random8(), 255, random8());
  }
}

// This function sets up a palette of black and white stripes,
// using code.  Since the palette is effectively an array of
// sixteen CRGB colors, the various fill_* functions can be used
// to set them up.
void SetupBlackAndWhiteStripedPalette() {
  // 'black out' all 16 palette entries...
  fill_solid(currentPalette, 16, CRGB::Black);
  // and set every fourth one to white.
  currentPalette[0] = CRGB::White;
  currentPalette[4] = CRGB::White;
  currentPalette[8] = CRGB::White;
  currentPalette[12] = CRGB::White;
}

// This function sets up a palette of purple and green stripes.
void SetupPurpleAndGreenPalette() {
  CRGB purple = CHSV(HUE_PURPLE, 255, 255);
  CRGB green = CHSV(HUE_GREEN, 255, 255);
  CRGB black = CRGB::Black;

  currentPalette = CRGBPalette16(
    green, green, black, black,
    purple, purple, black, black,
    green, green, black, black,
    purple, purple, black, black);
}


// This example shows how to set up a static color palette
// which is stored in PROGMEM (flash), which is almost always more
// plentiful than RAM.  A static PROGMEM palette like this
// takes up 64 bytes of flash.
const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM = {
  CRGB::Red,
  CRGB::Gray,  // 'white' is too bright compared to red and blue
  CRGB::Blue,
  CRGB::Black,

  CRGB::Red,
  CRGB::Gray,
  CRGB::Blue,
  CRGB::Black,

  CRGB::Red,
  CRGB::Red,
  CRGB::Gray,
  CRGB::Gray,
  CRGB::Blue,
  CRGB::Blue,
  CRGB::Black,
  CRGB::Black
};

APP & Interface Design

I used C# to develop the Application. I created the interface in Visual Studio and slightly modified my code in Arduino to interact with the application.
You can read more in Application Week.
In the last tabs you will also find C# and Arduino code.

Here you see that when turned on, everything works as intended with the factory settings and all commands are executed, only the top button does not work, which according to the default settings nothing is setuped.

In this frame First I turn on the backlight and then program the free button to Shift + M (hot key - shape builder tool in Adobe Illustrator)

Then I program another button to Shift + O (hot key - artboard tool in Adobe Illustrator)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace Space_Pad
{
    public partial class Form1 : Form
    {
        private SerialPort ESP2;

        private bool isDragging = false;
        private Point lastCursor;
        private Point lastForm;

        public Form1()
        {

            InitializeComponent();
            InitializeComboBoxes();


            string the_com = "";

            foreach (string mysps in SerialPort.GetPortNames())
            {
                if (mysps != "COM1") { the_com = mysps; break; }
            }
            ESP2 = new SerialPort(the_com, 115200);
            ESP2.Open();
            Console.WriteLine(the_com);

        }

        public void SaveData()
        {

        }

        private void button1_Click(object sender, EventArgs e)

        {

            panel2.Visible = false;
            panel3.Visible = false;
            panel4.Visible = false;
            panel5.Visible = false;
            panel6.Visible = false;

            panel1.Visible = !panel1.Visible;

            if (panel1.Visible)
            {
                comboBox1.Visible = true;
                comboBox2.Visible = true;
                comboBox3.Visible = true;
            }
            else
            {

            }

        }
        private void InitializeComboBoxes()
        {
            string[] keys = {
                "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
                "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
                "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
                "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
                "Up", "Down", "Left", "Right",
                "Enter", "Space", "Backspace", "Tab", "Escape", "Shift", "Ctrl", "Alt",
                "Home", "End", "Page Up", "Page Down", "Insert", "Delete",
                "-", "=", "[", "]", "\\", ";", "'", ",", ".", "/", "`"
            };
            comboBox1.Items.AddRange(keys);
            comboBox2.Items.AddRange(keys);
            comboBox3.Items.AddRange(keys);
            comboBox4.Items.AddRange(keys);
            comboBox5.Items.AddRange(keys);
            comboBox6.Items.AddRange(keys);
            comboBox7.Items.AddRange(keys);
            comboBox8.Items.AddRange(keys);
            comboBox9.Items.AddRange(keys);
            comboBox10.Items.AddRange(keys);
            comboBox11.Items.AddRange(keys);
            comboBox12.Items.AddRange(keys);
            comboBox13.Items.AddRange(keys);
            comboBox14.Items.AddRange(keys);
            comboBox15.Items.AddRange(keys);
            comboBox16.Items.AddRange(keys);
            comboBox17.Items.AddRange(keys);
            comboBox18.Items.AddRange(keys);
        }

        private void button2_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void Form1_MouseDown_1(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                isDragging = true;
                lastCursor = Cursor.Position;
                lastForm = this.Location;
            }
        }

        private void Form1_MouseMove_1(object sender, MouseEventArgs e)
        {
            if (isDragging)
            {
                int deltaX = Cursor.Position.X - lastCursor.X;
                int deltaY = Cursor.Position.Y - lastCursor.Y;
                this.Location = new Point(lastForm.X + deltaX, lastForm.Y + deltaY);
            }
        }

        private void Form1_MouseUp_1(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                isDragging = false;
            }
        }

        private void button3_Click_1(object sender, EventArgs e)
        {

            {

                panel1.Visible = false;
                panel3.Visible = false;
                panel4.Visible = false;
                panel5.Visible = false;
                panel6.Visible = false;




                panel2.Visible = !panel2.Visible;


                if (panel2.Visible)
                {
                    comboBox4.Visible = true;
                    comboBox5.Visible = true;
                    comboBox6.Visible = true;
                }
                else
                {

                }


            }
        }

        private void button4_Click(object sender, EventArgs e)
        {
            {


                panel1.Visible = false;
                panel2.Visible = false;
                panel4.Visible = false;
                panel5.Visible = false;
                panel6.Visible = false;



                panel3.Visible = !panel3.Visible;


                if (panel3.Visible)
                {
                    comboBox7.Visible = true;
                    comboBox8.Visible = true;
                    comboBox9.Visible = true;
                }
                else
                {

                }


            }
        }

        private void button6_Click(object sender, EventArgs e)
        {
            {
                panel1.Visible = false;
                panel3.Visible = false;
                panel4.Visible = false;
                panel5.Visible = false;
                panel2.Visible = false;



                panel6.Visible = !panel6.Visible;



                if (panel6.Visible)
                {
                    comboBox16.Visible = true;
                    comboBox17.Visible = true;
                    comboBox18.Visible = true;
                }
                else
                {

                }


            }
        }

        private void button7_Click(object sender, EventArgs e)
        {
            {
                panel1.Visible = false;
                panel3.Visible = false;
                panel4.Visible = false;
                panel2.Visible = false;
                panel6.Visible = false;



                panel5.Visible = !panel5.Visible;



                if (panel5.Visible)
                {
                    comboBox13.Visible = true;
                    comboBox14.Visible = true;
                    comboBox15.Visible = true;
                }
                else
                {

                }


            }
        }

        private void button5_Click_1(object sender, EventArgs e)
        {
            {


                panel1.Visible = false;
                panel3.Visible = false;
                panel2.Visible = false;
                panel5.Visible = false;
                panel6.Visible = false;



                panel4.Visible = !panel4.Visible;



                if (panel4.Visible)
                {
                    comboBox10.Visible = true;
                    comboBox11.Visible = true;
                    comboBox12.Visible = true;
                }
                else
                {

                }


            }
        }

        private void Reset_Click(object sender, EventArgs e)
        {
            ESP2.Write("reset");

        }

        private void Send_Click(object sender, EventArgs e)
        {
            ESP2.Write("2 Ctrl Shift");
            Console.WriteLine("trying upload data");
        }


        private void Collor(object sender, EventArgs e)
        {
            ESP2.Write("lightshow");
            Console.WriteLine("toggle light");
        }

        private void button11_Click(object sender, EventArgs e)
        {
            string textToSend = comboBox16.SelectedItem?.ToString() ?? string.Empty;
            textToSend += " " + (comboBox17.SelectedItem?.ToString() ?? string.Empty);
            textToSend += " " + (comboBox18.SelectedItem?.ToString() ?? string.Empty);

            if (ESP2.IsOpen)
            {
                ESP2.Write("0 " + textToSend + "\n");
                Console.WriteLine("Sent: " + "0 " + textToSend);
            }
            else
            {
                Console.WriteLine("Serial port is not open.");
            }
        }

        private void Save_Clickcpy(object sender, EventArgs e)
        {
            string textToSend = comboBox1.SelectedItem?.ToString() ?? string.Empty;
            textToSend += " " + (comboBox2.SelectedItem?.ToString() ?? string.Empty);
            textToSend += " " + (comboBox3.SelectedItem?.ToString() ?? string.Empty);

            if (ESP2.IsOpen)
            {
                ESP2.Write("2 " + textToSend + "\n");
                Console.WriteLine("Sent: " + "2 " + textToSend);
            }
            else
            {
                Console.WriteLine("Serial port is not open.");
            }

        }

        private void button1_Click_past(object sender, EventArgs e)
        {
            string textToSend = comboBox4.SelectedItem?.ToString() ?? string.Empty;
            textToSend += " " + (comboBox5.SelectedItem?.ToString() ?? string.Empty);
            textToSend += " " + (comboBox6.SelectedItem?.ToString() ?? string.Empty);

            if (ESP2.IsOpen)
            {
                ESP2.Write("1 " + textToSend + "\n");
                Console.WriteLine("Sent: " + "1 " + textToSend);
            }
            else
            {
                Console.WriteLine("Serial port is not open.");
            }
        }

        private void button8_ClickM1(object sender, EventArgs e)
        {
            string textToSend = comboBox7.SelectedItem?.ToString() ?? string.Empty;
            textToSend += " " + (comboBox8.SelectedItem?.ToString() ?? string.Empty);
            textToSend += " " + (comboBox9.SelectedItem?.ToString() ?? string.Empty);

            if (ESP2.IsOpen)
            {
                ESP2.Write("5 " + textToSend + "\n");
                Console.WriteLine("Sent: " + "5 " + textToSend);
            }
            else
            {
                Console.WriteLine("Serial port is not open.");
            }

        }

        private void button9_ClickUNDO(object sender, EventArgs e)
        {
            string textToSend = comboBox10.SelectedItem?.ToString() ?? string.Empty;
            textToSend += " " + (comboBox11.SelectedItem?.ToString() ?? string.Empty);
            textToSend += " " + (comboBox12.SelectedItem?.ToString() ?? string.Empty);

            if (ESP2.IsOpen)
            {
                ESP2.Write("3 " + textToSend + "\n");
                Console.WriteLine("Sent: " + "3 " + textToSend);
            }
            else
            {
                Console.WriteLine("Serial port is not open.");
            }
        }

        private void button10_ClickREDO(object sender, EventArgs e)
        {
            string textToSend = comboBox13.SelectedItem?.ToString() ?? string.Empty;
            textToSend += " " + (comboBox14.SelectedItem?.ToString() ?? string.Empty);
            textToSend += " " + (comboBox15.SelectedItem?.ToString() ?? string.Empty);

            if (ESP2.IsOpen)
            {
                ESP2.Write("4 " + textToSend + "\n");
                Console.WriteLine("Sent: " + "4 " + textToSend);
            }
            else
            {
                Console.WriteLine("Serial port is not open.");
            }
        }
    }
}
/*
* This code is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) License.
* You are free to share, copy, and redistribute the material in any medium or format, and adapt, remix, transform, and build upon the material
* as long as you give appropriate credit, do not use the material for commercial purposes, and distribute your contributions under the same license.
* 
* Note: Some portions of this code related only to joysticks, including calculations and ideas, have been taken and modified from Teaching Tech's code.
* Any changes and modifications made to the original code parts have been done to fit the specific requirements of this project.
* 
* Created by Areg Khalatyan in 2024 as part of a project in Fab Academy.
* 
* License details: https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
#include <FastLED.h>
#include <BleKeyboard.h>
#include <EEPROM.h>

BleKeyboard bleKeyboard;

#define NUM_LEDS 6
#define DATA_PIN 2
CRGB leds[NUM_LEDS];
#define TOUCH_PIN_4 T4
#define TOUCH_PIN_5 T5
#define TOUCH_PIN_6 T6
#define TOUCH_PIN_7 T7
#define TOUCH_PIN_8 T8
#define TOUCH_THRESHOLD 50
int touchPins[6] = { T9, T8, T7, T6, T5, T4 };

int paletteIndex = 0;
bool lightshow = false;

//Light Show
#define UPDATES_PER_SECOND 100
CRGBPalette16 currentPalette;
int wholePaletteIndex = 0;
TBlendType currentBlending;
extern CRGBPalette16 myRedWhiteBluePalette;
extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM;
CRGBPalette16 pallettes[] = { RainbowColors_p, RainbowStripeColors_p, CloudColors_p, PartyColors_p, myRedWhiteBluePalette_p };

bool waitForString = true;

//This part is for debugging written by Teaching Tech. And these are his original comments.
//Joystick
// Debugging
// 0: Debugging off. Set to this once everything is working.
// 1: Output raw joystick values. 0-1023 raw ADC 10-bit values
// 2: Output centered joystick values. Values should be approx -500 to +500, jitter around 0 at idle.
// 3: Output centered joystick values. Filtered for deadzone. Approx -500 to +500, locked to zero at idle.
// 4: Output translation and rotation values. Approx -800 to 800 depending on the parameter.
// 5: Output debug 4 and 5 side by side for direct cause and effect reference.
int debug = 0;

//This part is for directions written by Teaching Tech. And these are his original comments.
//Here he only modify directions for his device.
// Direction
// Modify the direction of translation/rotation depending on preference. This can also be done per application in the 3DConnexion software.
// Switch between true/false as desired.
bool invX = false;   // pan left/right
bool invY = false;   // pan up/down
bool invZ = true;    // zoom in/out
bool invRX = true;   // Rotate around X axis (tilt front/back)
bool invRY = false;  // Rotate around Y axis (tilt left/right)
bool invRZ = true;   // Rotate around Z axis (twist left/right)

int threshold = 500;


int PINLIST[8] = {
  //This stroke also written by Teaching Tech for The positions of the reads
  36,  // X-axis A
  39,  // Y-axis A
  34,  // X-axis B
  35,  // Y-axis B
  26,  // X-axis C
  25,  // Y-axis C
  4,  // X-axis D
  15,  // Y-axis D
};//I just changed the pins for my microcontroller.
//This stroke also written by Teaching Tech for deadzone to filter out unintended movements. Recommended to have this as small as possible for  to allow smaller knob range of motion.
int DEADZONE = 200;  //I changed this value from 20 - 200 because I don't need a very precise value. On the contrary, I don’t need false positives.


struct MacroSettings {
  char keys[19][20];  // Keys for Macross
};

bool keysPressed[12] = { false, false, false, false, false, false, false, false, false, false, false, false };

// This stroke also written by Teaching Tech for define axes are matched to pin order.
#define AX 0
#define AY 1
#define BX 2
#define BY 3
#define CX 4
#define CY 5
#define DX 6
#define DY 7

//This stroke also writen by Teaching Tech for Centerpoint variable to be populated during setup routine.
int centerPoints[8];
int centerPoints[8];

//Here starts the code that Teaching Tech wrote for Function to read and store analogue voltages for each joystick axis.
void readAllFromJoystick(int *rawReads) {
  for (int i = 0; i < 8; i++) {
    rawReads[i] = analogRead(PINLIST[i]);
  }
}

MacroSettings savedSettings;

void saveSettings() {
  EEPROM.put(0, savedSettings);  // Writing settings to EEPROM
  EEPROM.commit();               // Committing the write
  Serial.println("Настройки сохранены.");
}

void loadSettings() {
  EEPROM.get(0, savedSettings);  // Loading settings from EEPROM
}

void clearEEPROM() {
  for (int i = 0; i < sizeof(savedSettings); i++) {  // Clear only the size of the structure
    EEPROM.write(i, 0);
  }
  EEPROM.commit();  
  Serial.println("EEPROM очищена.");
  strcpy(savedSettings.keys[0], "Ctrl x");
  strcpy(savedSettings.keys[1], "Ctrl v");
  strcpy(savedSettings.keys[2], "Ctrl c");
  strcpy(savedSettings.keys[3], "Ctrl z");
  strcpy(savedSettings.keys[4], "Ctrl Shift z");
  strcpy(savedSettings.keys[5], "");
  strcpy(savedSettings.keys[6], "Ctrl right");
  strcpy(savedSettings.keys[7], "Ctrl left");
  strcpy(savedSettings.keys[8], "Ctrl down");
  strcpy(savedSettings.keys[9], "Ctrl up");
  strcpy(savedSettings.keys[10], "Shift z");
  strcpy(savedSettings.keys[11], "z");
  strcpy(savedSettings.keys[12], "up");
  strcpy(savedSettings.keys[13], "down");
  strcpy(savedSettings.keys[14], "right");
  strcpy(savedSettings.keys[15], "down");
  strcpy(savedSettings.keys[16], "Alt right");
  strcpy(savedSettings.keys[17], "Alt left");
  strcpy(savedSettings.keys[18], "lighton");
  saveSettings();

}

void setup() {
  EEPROM.begin(sizeof(savedSettings));  // Initialize EEPROM with the size of the structure
  delay(2000);                          // Delay check - allows reprogramming on accidental over power with LED
  Serial.begin(115200);
  Serial.println("Starting BLE operation!");
  bleKeyboard.begin();
  FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS);  // Typical order GRB

  currentPalette = RainbowColors_p;
  currentBlending = LINEARBLEND;

  // Read idle/centre positions for joysticks.
  readAllFromJoystick(centerPoints);
  readAllFromJoystick(centerPoints);


  loadSettings();
  Serial.print("keys 18:");
  Serial.println(savedSettings.keys[18]);
  if (strcmp(savedSettings.keys[18], "lighton") == 0) {
    sendMacro("lightshow");
    Serial.println("turn on light show");
  }
  for (int i = 0; i < 19; i++) {
    Serial.println(savedSettings.keys[i]);
  }

}

void loop() {
  if (lightshow) {
    //ChangePalettePeriodically();
    currentPalette = RainbowStripeColors_p;
    currentBlending = LINEARBLEND;
    FillLEDsFromPaletteColors(paletteIndex);
    paletteIndex++;
    FastLED.show();
    FastLED.delay(1000 / UPDATES_PER_SECOND);
  }
  for (int i = 0; i < 6; i++) {
    int touchValue = touchRead(touchPins[i]);
    if (touchValue < TOUCH_THRESHOLD) {
      if (bleKeyboard.isConnected()) {
        Serial.println("Keyboard connected.");
        if (i != 5) {
          sendMacro(savedSettings.keys[i]);
        }
      }
      if (i == 5) {
        sendMacro(savedSettings.keys[i]);
      }
      for (int j = 0; j < 6; j++) {
        if (j != i) {
          leds[j] = CRGB::Black;
        }
      }
      leds[i] = CRGB::White;
      FastLED.show();
      delay(100);
      leds[i] = CRGB::Black;
      FastLED.show();
    }
  }
  //Here begins the code that Teaching Tech wrote for debugging.
  int rawReads[8], centered[8];
  // Joystick values are read. 0-1023
  readAllFromJoystick(rawReads);
  // Report back 0-1023 raw ADC 10-bit values if enabled
  if (debug == 1) {
    Serial.print("AX:");
    Serial.print(rawReads[0]);
    Serial.print(",");
    Serial.print("AY:");
    Serial.print(rawReads[1]);
    Serial.print(",");
    Serial.print("BX:");
    Serial.print(rawReads[2]);
    Serial.print(",");
    Serial.print("BY:");
    Serial.print(rawReads[3]);
    Serial.print(",");
    Serial.print("CX:");
    Serial.print(rawReads[4]);
    Serial.print(",");
    Serial.print("CY:");
    Serial.print(rawReads[5]);
    Serial.print(",");
    Serial.print("DX:");
    Serial.print(rawReads[6]);
    Serial.print(",");
    Serial.print("DY:");
    Serial.println(rawReads[7]);
  }

  // Subtract centre position from measured position to determine movement.
  for (int i = 0; i < 8; i++) centered[i] = rawReads[i] - centerPoints[i];  //
  // Report centered joystick values if enabled. Values should be approx -500 to +500, jitter around 0 at idle.
  if (debug == 2) {
    Serial.print("AX:");
    Serial.print(centered[0]);
    Serial.print(",");
    Serial.print("AY:");
    Serial.print(centered[1]);
    Serial.print(",");
    Serial.print("BX:");
    Serial.print(centered[2]);
    Serial.print(",");
    Serial.print("BY:");
    Serial.print(centered[3]);
    Serial.print(",");
    Serial.print("CX:");
    Serial.print(centered[4]);
    Serial.print(",");
    Serial.print("CY:");
    Serial.print(centered[5]);
    Serial.print(",");
    Serial.print("DX:");
    Serial.print(centered[6]);
    Serial.print(",");
    Serial.print("DY:");
    Serial.println(centered[7]);
  }
  // Filter movement values. Set to zero if movement is below deadzone threshold.
  for (int i = 0; i < 8; i++) {
    if (centered[i] < DEADZONE && centered[i] > -DEADZONE) centered[i] = 0;
  }
  // Report centered joystick values. Filtered for deadzone. Approx -500 to +500, locked to zero at idle
  if (debug == 3) {
    Serial.print("AX:");
    Serial.print(centered[0]);
    Serial.print(",");
    Serial.print("AY:");
    Serial.print(centered[1]);
    Serial.print(",");
    Serial.print("BX:");
    Serial.print(centered[2]);
    Serial.print(",");
    Serial.print("BY:");
    Serial.print(centered[3]);
    Serial.print(",");
    Serial.print("CX:");
    Serial.print(centered[4]);
    Serial.print(",");
    Serial.print("CY:");
    Serial.print(centered[5]);
    Serial.print(",");
    Serial.print("DX:");
    Serial.print(centered[6]);
    Serial.print(",");
    Serial.print("DY:");
    Serial.println(centered[7]);
  }


  int16_t transX, transY, transZ, rotX, rotY, rotZ;  // Declare movement 
  transX = -(-centered[CY] + centered[AY]) / 1;
  transY = (-centered[BY] + centered[DY]) / 1;
  if ((abs(centered[AX]) > DEADZONE) && (abs(centered[BX]) > DEADZONE) && (abs(centered[CX]) > DEADZONE) && (abs(centered[DX]) > DEADZONE)) {
    transZ = (-centered[AX] - centered[BX] - centered[CX] - centered[DX]) / 1;
    transX = 0;
    transY = 0;
  } else {
    transZ = 0;
  }
  rotX = (-centered[AX] + centered[CX]) / 1;
  rotY = (+centered[BX] - centered[DX]) / 1;
  if ((abs(centered[AY]) > DEADZONE) && (abs(centered[BY]) > DEADZONE) && (abs(centered[CY]) > DEADZONE) && (abs(centered[DY]) > DEADZONE)) {
    rotZ = (+centered[AY] + centered[BY] + centered[CY] + centered[DY]) / 2;
    rotX = 0;
    rotY = 0;
  } else {
    rotZ = 0;
  }

  // Alter speed to suit user preference - Use 3DConnexion slider instead for V2.
  //transX = transX/100*speed;
  //transY = transY/100*speed;
  //transZ = transZ/100*speed;
  //rotX = rotX/100*speed;
  //rotY = rotY/100*speed;
  //rotZ = rotZ/100*speed;
  // Invert directions if needed
  if (invX == true) { transX = transX * -1; };
  if (invY == true) { transY = transY * -1; };
  if (invZ == true) { transZ = transZ * -1; };
  if (invRX == true) { rotX = rotX * -1; };
  if (invRY == true) { rotY = rotY * -1; };
  if (invRZ == true) { rotZ = rotZ * -1; };

  // Report translation and rotation values if enabled. Approx -800 to 800 depending on the parameter.
  if (debug == 4) {
    Serial.print("TX:");
    Serial.print(transX);
    Serial.print(",");
    Serial.print("TY:");
    Serial.print(transY);
    Serial.print(",");
    Serial.print("TZ:");
    Serial.print(transZ);
    Serial.print(",");
    Serial.print("RX:");
    Serial.print(rotX);
    Serial.print(",");
    Serial.print("RY:");
    Serial.print(rotY);
    Serial.print(",");
    Serial.print("RZ:");
    Serial.println(rotZ);
  }//This ends the code that Teaching Tech wrote for debugging. didn't change anything here.

        //This part of code i wrote for using joystick values to press and release the keys.
        //Press the corresponding keys
  int axes[6] = { 0, 0, 0, 0, 0, 0 };

  if (transX > threshold) {
    axes[0] = 1;
  } else if (transX < -threshold) {
    axes[0] = -1;
  }
  if (transY > threshold) {
    axes[1] = 1;
  } else if (transY < -threshold) {
    axes[1] = -1;
  }
  if (transZ > threshold) {
    axes[2] = 1;
  } else if (transZ < -threshold) {
    axes[2] = -1;
  }
  if (rotX > threshold) {
    axes[3] = 1;
  } else if (rotX < -threshold) {
    axes[3] = -1;
  }
  if (rotY > threshold) {
    axes[4] = 1;
  } else if (rotY < -threshold) {
    axes[4] = -1;
  }
  if (rotZ > threshold) {
    axes[5] = 1;
  } else if (rotZ < -threshold) {
    axes[5] = -1;
  }

  for (int i = 0; i < 6; i++) {
    if (axes[i] == 0) {
      if (keysPressed[i * 2]) {
        releaseKeys(savedSettings.keys[i * 2]);
        keysPressed[i * 2] = false;
      }
      if (keysPressed[i * 2 + 1]) {
        releaseKeys(savedSettings.keys[i * 2 + 1]);
        keysPressed[i * 2 + 1] = false;
      }
    }
  }
  for (int i = 0; i < 6; i++) {
    if (axes[i] == 1 && !keysPressed[i * 2]) {
      pressKeys(savedSettings.keys[6 + i * 2]);
      keysPressed[i * 2] = true;
    }
    if (axes[i] == -1 && !keysPressed[i * 2 + 1]) {
      pressKeys(savedSettings.keys[6 + i * 2 + 1]);
      keysPressed[i * 2 + 1] = true;
    }
  }

  //Here begins the code that Teaching Tech wrote for debugging.
        // Report debug 4 and 5 info side by side for direct reference if enabled. Very useful if you need to alter which inputs are used in the arithmatic above.
  if (debug == 5) {
    Serial.print("AX:");
    Serial.print(centered[0]);
    Serial.print(",");
    Serial.print("AY:");
    Serial.print(centered[1]);
    Serial.print(",");
    Serial.print("BX:");
    Serial.print(centered[2]);
    Serial.print(",");
    Serial.print("BY:");
    Serial.print(centered[3]);
    Serial.print(",");
    Serial.print("CX:");
    Serial.print(centered[4]);
    Serial.print(",");
    Serial.print("CY:");
    Serial.print(centered[5]);
    Serial.print(",");
    Serial.print("DX:");
    Serial.print(centered[6]);
    Serial.print(",");
    Serial.print("DY:");
    Serial.print(centered[7]);
    Serial.print("||");
    Serial.print("TX:");
    Serial.print(transX);
    Serial.print(",");
    Serial.print("TY:");
    Serial.print(transY);
    Serial.print(",");
    Serial.print("TZ:");
    Serial.print(transZ);
    Serial.print(",");
    Serial.print("RX:");
    Serial.print(rotX);
    Serial.print(",");
    Serial.print("RY:");
    Serial.print(rotY);
    Serial.print(",");
    Serial.print("RZ:");
    Serial.println(rotZ);
  }

  delay(50);
}// ends the code that Teaching Tech wrote for debugging. Here I just changed the delay value from 400 to 50.

void sendMacro(String keys) {
  Serial.println(keys);
  String key;
  int prevIndex = 0;
  for (int i = 0; i <= keys.length(); i++) {
    if (i == keys.length() || keys.charAt(i) == ' ') {
      key = keys.substring(prevIndex, i);
      prevIndex = i + 1;
      //lightshow = false;
      if (key == "Ctrl") {
        bleKeyboard.press(KEY_LEFT_CTRL);
      } else if (key == "Shift") {
        bleKeyboard.press(KEY_LEFT_SHIFT);
      } else if (key == "Alt") {
        bleKeyboard.press(KEY_LEFT_ALT);
      } else if (key == "Del") {
        bleKeyboard.press(KEY_DELETE);
      } else if (key == "Esc") {
        bleKeyboard.press(KEY_ESC);
      } else if (key == "lightshow") {
        if (strcmp(savedSettings.keys[18], "lighton") == 0) {
          Serial.println("lightshow");
          paletteIndex += 300;
          lightshow = true;
        } else {
          Serial.println("lightoff");
          for (int j = 0; j < 6; j++) {
            leds[j] = CRGB::Black;
          }
          FastLED.show();
          lightshow = false;
        }
      } else {
        bleKeyboard.press(key[0]);  //  single character as a key
      }
    }
  }
  delay(30);                
  bleKeyboard.releaseAll();  // Release all keys
}

void pressKeys(String keys) {  // this secrion si only for joysticks 
  Serial.println(keys);
  String key;
  int prevIndex = 0;
  for (int i = 0; i <= keys.length(); i++) {
    if (i == keys.length() || keys.charAt(i) == ' ') {
      key = keys.substring(prevIndex, i);
      prevIndex = i + 1;

      if (key == "Ctrl") {
        bleKeyboard.press(KEY_LEFT_CTRL);
      } else if (key == "Shift") {
        bleKeyboard.press(KEY_LEFT_SHIFT);
      } else if (key == "Alt") {
        bleKeyboard.press(KEY_LEFT_ALT);
      } else if (key == "Del") {
        bleKeyboard.press(KEY_DELETE);
      } else if (key == "Esc") {
        bleKeyboard.press(KEY_ESC);
      } else if (key == "left") {
        bleKeyboard.press(KEY_LEFT_ARROW);
      } else if (key == "right") {
        bleKeyboard.press(KEY_RIGHT_ARROW);
      } else if (key == "up") {
        bleKeyboard.press(KEY_UP_ARROW);
      } else if (key == "down") {
        bleKeyboard.press(KEY_DOWN_ARROW);
      } else {
        bleKeyboard.press(key[0]);  // Treat one character as a key
      }
    }
  }
}

void releaseKeys(String keys) { // this secrion si only for joysticks 
  String key;
  int prevIndex = 0;
  for (int i = 0; i <= keys.length(); i++) {
    if (i == keys.length() || keys.charAt(i) == ' ') {
      key = keys.substring(prevIndex, i);
      prevIndex = i + 1;

      if (key == "Ctrl") {
        bleKeyboard.release(KEY_LEFT_CTRL);
      } else if (key == "Shift") {
        bleKeyboard.release(KEY_LEFT_SHIFT);
      } else if (key == "Alt") {
        bleKeyboard.release(KEY_LEFT_ALT);
      } else if (key == "Del") {
        bleKeyboard.release(KEY_DELETE);
      } else if (key == "Esc") {
        bleKeyboard.release(KEY_ESC);
      } else if (key == "left") {
        bleKeyboard.release(KEY_LEFT_ARROW);
      } else if (key == "right") {
        bleKeyboard.release(KEY_RIGHT_ARROW);
      } else if (key == "up") {
        bleKeyboard.release(KEY_UP_ARROW);
      } else if (key == "down") {
        bleKeyboard.release(KEY_DOWN_ARROW);
      } else {
        bleKeyboard.release(key[0]);  // Release one keys
    }
  }
}

void serialEvent() {
  while (Serial.available()) {
    if (waitForString) {
      String input = Serial.readStringUntil('\n');
      input.trim();// Remove any leading/trailing spaces

      if (input == "reset") {
        clearEEPROM();
      } else if (input == "lightshow") {
        if (strcmp(savedSettings.keys[18], "lightoff") == 0) {
          Serial.println("lightshow");
          paletteIndex += 300;
          lightshow = true;
          strcpy(savedSettings.keys[18], "lighton");
        } else {
          Serial.println("lightoff");
          for (int j = 0; j < 6; j++) {
            leds[j] = CRGB::Black;
          }
          FastLED.show();
          lightshow = false;
          strcpy(savedSettings.keys[18], "lightoff");
        }
        saveSettings();
      } else {
        waitForString = false;  // Switch the flag, now we expect a symbol
      }
    } else {
      char input = Serial.read();
      if (input >= '0' && input <= '5') {
        int index = input - '0';
        String newKeys = "";

        while (Serial.available()) {
          char ch = Serial.read();
          if (ch == '\n') {
            break;
          }
          newKeys += ch;
        }
        newKeys = newKeys.substring(1, 20);
        char charArray[20];
        newKeys.toCharArray(charArray, 20);
        strcpy(savedSettings.keys[index], charArray);
        //savedSettings.keys[index] = charArray;
        saveSettings();
        for (int i = 0; i < 6; i++) {
          Serial.println(savedSettings.keys[i]);
        }
        Serial.print("Клавиши для сенсора ");
        Serial.print(index);
        Serial.print(" обновлены до: ");
        Serial.println(newKeys);
        waitForString = true;  // Return the flag to its original state
      }
    }
  }
}

void FillLEDsFromPaletteColors(uint8_t colorIndex) {
  uint8_t brightness = 255;

  for (int i = 0; i < NUM_LEDS; ++i) {
    leds[i] = ColorFromPalette(currentPalette, colorIndex, brightness, currentBlending);
    colorIndex += 3;
  }
}

void ChangePalettePeriodically() {
  uint8_t secondHand = (millis() / 1000) % 60;
  static uint8_t lastSecond = 99;

  if (lastSecond != secondHand) {
    lastSecond = secondHand;
    if (secondHand == 0) {
      currentPalette = RainbowColors_p;
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 10) {
      currentPalette = RainbowStripeColors_p;
      currentBlending = NOBLEND;
    }
    if (secondHand == 15) {
      currentPalette = RainbowStripeColors_p;
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 20) {
      SetupPurpleAndGreenPalette();
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 25) {
      SetupTotallyRandomPalette();
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 30) {
      SetupBlackAndWhiteStripedPalette();
      currentBlending = NOBLEND;
    }
    if (secondHand == 35) {
      SetupBlackAndWhiteStripedPalette();
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 40) {
      currentPalette = CloudColors_p;
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 45) {
      currentPalette = PartyColors_p;
      currentBlending = LINEARBLEND;
    }
    if (secondHand == 50) {
      currentPalette = myRedWhiteBluePalette_p;
      currentBlending = NOBLEND;
    }
    if (secondHand == 55) {
      currentPalette = myRedWhiteBluePalette_p;
      currentBlending = LINEARBLEND;
    }
  }
}

// This function fills the palette with totally random colors.
void SetupTotallyRandomPalette() {
  for (int i = 0; i < 16; ++i) {
    currentPalette[i] = CHSV(random8(), 255, random8());
  }
}

// This function sets up a palette of black and white stripes,
// using code.  Since the palette is effectively an array of
// sixteen CRGB colors, the various fill_* functions can be used
// to set them up.
void SetupBlackAndWhiteStripedPalette() {
  // 'black out' all 16 palette entries...
  fill_solid(currentPalette, 16, CRGB::Black);
  // and set every fourth one to white.
  currentPalette[0] = CRGB::White;
  currentPalette[4] = CRGB::White;
  currentPalette[8] = CRGB::White;
  currentPalette[12] = CRGB::White;
}

// This function sets up a palette of purple and green stripes.
void SetupPurpleAndGreenPalette() {
  CRGB purple = CHSV(HUE_PURPLE, 255, 255);
  CRGB green = CHSV(HUE_GREEN, 255, 255);
  CRGB black = CRGB::Black;

  currentPalette = CRGBPalette16(
    green, green, black, black,
    purple, purple, black, black,
    green, green, black, black,
    purple, purple, black, black);
}


// This example shows how to set up a static color palette
// which is stored in PROGMEM (flash), which is almost always more
// plentiful than RAM.  A static PROGMEM palette like this
// takes up 64 bytes of flash.
const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM = {
  CRGB::Red,
  CRGB::Gray,  // 'white' is too bright compared to red and blue
  CRGB::Blue,
  CRGB::Black,

  CRGB::Red,
  CRGB::Gray,
  CRGB::Blue,
  CRGB::Black,

  CRGB::Red,
  CRGB::Red,
  CRGB::Gray,
  CRGB::Gray,
  CRGB::Blue,
  CRGB::Blue,
  CRGB::Black,
  CRGB::Black
};

Research & Concept

This is a special device for digital artists working outdoors. This portable desk features an adjustable laptop holder, a built-in Space Mouse, and a programmable macro keyboard for a more comfortable work experience.

Before starting the research, I wanted to use an accelerometer for this purpose, but it turned out that this sensor would be awkward to use since it measures the angle of inclination relative to the ground, but I did not want that.

During my research I found several people who also made a space mouse. Space Mushroom : Full 6 DOFs Space Mouse by Shinsaku Hiura - for example, in this project 3 joysticks were used and the code used keyboard and mouse libraries. This option was also created for blender.

ORBION - Space Mouse Open Source by FaqTotum I also found this option with an encoder, LCD, joystick and LEDs and many other things. The author did a good job .

diy-spacemouse by Salim Benbouziyane. This author used a magnetometer for Spacemouse, but still used libraries using mouse and keyboard.

I liked all the options, but in my opinion it could have been done better. For example, I don’t want to use the mouse library because I want to be able to use the mouse separately at the same time when using Space Mouse.

In a global sense, my project includes several design stages.

  • 1 is the static part, which includes the overall design.

  • 2 aspect is the mechanical part, which includes the adjustable table itself and storage space. And also the mechanical part of the joysticks;

  • 3 aspect is electronics; includes system integration, PCB design required for macro keyboard and backlight, and Spacemouse.

  • 4 aspect is interface design. so that you can connect a computer to the device and program macros.

Processes and materials

In this picture you can find almost all the processes, materials and items that I will use.

Qty Description Price Link
Total All Components 41,83 $
1 FR1 PCB blank - Double Sided 4x6 in 1.5 $ https://www.sparkfun.com/products/14979
1 FR1 Copper Clad - Single Sided 4x6in 1.5 $ https://www.sparkfun.com/products/14976
4 Joystick HW-504 2.3 $ https://aliexpress.ru/item/1005005073051206.html?sku_id=12000031529566756&spm=a2g2w.productlist.search_results.0.3cef3db1zWRG2n
10 sm/6pc WS2812B Pixel LED Strip 60LEDs/m 0.4 $ https://aliexpress.ru/item/33044955667.html?sku_id=67357374372&spm=a2g2w.productlist.search_results.9.b893264blhu7dY
1 Smd capacitor 10uf 25v 0.06 $ https://aliexpress.ru/item/4000772249207.html?sku_id=10000007734222953&spm=a2g2w.productlist.search_results.6.358d38646oZN30
1 SMD Resistor 470 ohm 0,01 $ https://aliexpress.ru/item/1005006290934638.html?sku_id=12000036631775910&spm=a2g2w.productlist.search_results.0.2bcb7fa16RPsF6
4 1x5 Socket smd vertical 2.54mm 0.1 $ https://aliexpress.ru/item/1005006016749823.html?gatewayAdapt=glo2rus&sku_id=12000035338502040
1 1x5 pin header smd horizontal 2.54MM p 0.1 $ https://aliexpress.ru/item/4001235631356.html?gatewayAdapt=glo2rus&sku_id=10000015402512079
2 2x5 Vertical SMD Pin Header, 2.54mm P 0.2 $ https://aliexpress.ru/item/1005003707276223.html?gatewayAdapt=glo2rus&sku_id=12000026877839845
1 2x5 pin Ribbon Cable, 2.54mm P & Female Plugs 0.5 $ https://aliexpress.ru/item/1005001840002606.html?_randl_currency=PEN&_randl_shipto=PE&src=google&aff_fcid=81a7ff2bef2344808617110edc3135fd-1718555904388-01399-UneMJZVf&aff_fsk=UneMJZVf&aff_platform=aaf&sk=UneMJZVf&aff_trace_key=81a7ff2bef2344808617110edc3135fd-1718555904388-01399-UneMJZVf&terminal_id=0a63a43e04d94969b638ad2c9ce826db&afSmartRedirect=y&sku_id=12000017809733598
1 Esp32 Devkit V1 3 $ https://aliexpress.ru/item/1005007115327768.html?sku_id=12000039451314366&spm=a2g2w.productlist.search_results.12.1a7c3bf0UnaNpX
60x60 mm EVA Foam 10 mm 1,50 $ Local Shop
60x60 mm PVC 2mm Recycling Fab Lab
60x120 mm Fabric Gray 3,50 $ Local Shop
150x800 mm Spoong 10mm 1,50 $ Local Shop
1000x500 mm MDF 30 mm Recycling Fab Lab
1000x1000 mm Plywood 12 mm 7,75 $ Fab Lab
500x300 mm Plywood 6 mm 0,50 $ Fab Lab
1000*1000 mm Plywood 3 mm 5,00 $ Fab Lab
100 g Black ABS 1,16 $ https://www.wildberries.ru/catalog/157313163/detail.aspx
300 g Transparent PLA 3,88 $ https://www.wildberries.ru/catalog/48425888/detail.aspx
40 Screw 1.10 $ https://aliexpress.ru/item/32964226154.html?gatewayAdapt=glo2rus&sku_id=66533865305
2 Bearings 0.40 $ https://aliexpress.ru/item/4000764555530.html?gatewayAdapt=glo2rus&sku_id=10000015469174399
24 Magnets 1.30 $ https://www.wildberries.ru/catalog/34080545/detail.aspx?targetUrl=EX
100 mg. Wood Glue 2,00 $ Local Shop
500 mg. Neoprene Glue 2,58 $ Local Shop

Product evolution

  • Functionality

    Does the device work at all and how well does it work and to what extent?

  • Convenience, Aesthetics and ergonomics

    Ease of navigation. Clarity and accessibility of information.

  • Innovation

    How the project introduces new solutions or ideas. Use of modern technologies and trends.

  • Responsiveness and accessibility:

    How well the project functions in different environments.

  • Technical Execution

    How well is the work done?

  • Consistency and completeness

    How complete and thoughtful the project looks. No errors or omissions.

Is it possible to send commands directly to the 3D design program or will I still have to emulate the keyboard?

How will communication be carried out between the microcontroller and the computer?

  • Conclusion

It was a pleasure working on my latest project. And overall, my choice to take the Fab Academy course was probably the right one, because here I learned a lot of new things and used a lot of different techniques and materials in my final project. So, if you’ve read my articles or about me, you know that I’m an industrial designer with experience working with 3D printers and laser machines. But I learned everything else and applied it here for the first time.

Let’s start with how the idea for the final project came about. So the first idea I had was this: I wanted to create a large wall-mounted platform that could accommodate many different devices, lights, speakers, screens, and more. This idea came to me when I first started the course. I thought that it’s simple and I can do anything, and every week I will do something for this project, and this is what happened. Every week I did something for this. And at some point I realized that this was not a very good idea and abandoned it.

After that I decided to do something for myself as I enjoy working outdoors. And my main tool is a laptop, I decided to make a portable laptop stand. Then I thought about ease of use, since I use different hotkeys a lot and do 3D modeling, I decided to make a built-in macro keyboard and Space Mouse for 3D modeling.And for this reason I decided to use ESP32 because it has touch sensors and as we know they are silent and will not disturb when working at night. This microcontroller also supports wireless communication, which is very useful for robots with portable devices. A little about electronics In addition to the microcontroller, I used joysticks for the Space Mouse, although I was not able to achieve very good results, since to work with 3D programs I had to study programming very deeply, namely the work of 3D programs. I also needed to have the SolidWorks source code, which is not an easy task. In addition to Space Mouse, I created a macro keyboard and actually the electronic part for this was not difficult because the ESP 32 had built-in sensors for this I also used addressable LEDs, I chose them because they could be used in unlimited quantities using 1 pin. But I’ll tell you what, maybe I didn’t have many components, but I set myself the task of making a well-integrated job with a minimum number of wires and it turned out great. In general, designing and typing boards is a pleasure.

Then I remembered that I also like to work from bed. And I decided to make a device with built-in pillows. Since Wild Card Week started I’ve been thinking a lot about what I’ll be doing this week since we have some freedom and I decided to add storage space for laptop accessories to my project so they’re easy to carry around. And for this I chose vacuum forming technology. I have never done this but I liked it too, for this I used several materials like EVA foam, PVC, sponge and also I did the same using 3D printer and the result was better using vacuum molding.

In a nutshell about programming and application design, it was very, very difficult, I had a lot of errors, but I’ve managed to fix them. I also used the EEPROM library to store data in flash memory. I’ve used wireless and more. It was difficult, but I really liked the result, namely the interface design.

  • Source files

Click for downloading
SpacePad KiCad
Laser & CNC files eps
SpacePad STEP
SpacePad Ardyuino
SpacePad VSsrudio C#

SpacePad by Areg Khalatyan is licensed under CC BY-NC-SA 4.0