14. Interface and application programming¶
Group Assignment¶
This week, at the initiative of Shushan and Rudolf, we conducted a lesson on C# programming using Windows Forms and Processing Shushan offered to share her knowledge during a group assignment. It was a really good experience because I knew about visual studio and that you can create an application in the C# programming language. But it was really interesting. Shushan explained how I could create a simple desktop application, how to add a GUI, and how to connect a device to the application.
After that Rudolf explained us Processing.ide and G4P GUI Builder. I used it once before . This is a great app for writing apps because if you have used Ardino.Ide they are very similar and you can say that Processing.ide is the progenitor of Arduino and this program has many similar ways of writing programs and it is very easy to use .
This lesson was very productive for me because as I said I had very little experience with working in the intro Processing.ide - but this program was problematic to install on my computer and I was afraid of other programs. With the help of this lessons the Visual Studio became a little clearer and I I decided to use it in the future.
Individual Assignment¶
As I said earlier this week I’m going to develop an application for my final project. For my final project, I’ve chosen the topic of developing a laptop stand with a built-in macro keyboard and in my project I used the EEPROM library, with which you can write information to permanent memory, in my case these will be the keyboard combinations. The main problem is that I can only record this information using a serial monitor of Arduino.ino. And so I decided to develop applications with which I will further change the settings of my device and thus make it more convenient to use, and also people who want to use my device will be able to change macros using this application without knowing programming. .
Graphic Design¶
The project started with graphic design, for which I used Adobe Illustrator and made the first renderings for my application.
First I developed icons for my project and it so happened that I needed to use six touch buttons for my project and I decided to use the most common ones such as UNDO , REDO , COPY , PASTE, I also left two free sensors for programming.
Well, I plan to make all the buttons programmable; I will use these icons only for the initial settings. And for this I also want to add a reset button so that you can reset all the settings to their original settings.
This is what I came up with, since my project will be made from plywood. I added the plywood texture and used my final project design to keep everything harmonious, and also added a gradient and am going to use that as a highlight when choosing colors.
App Development UI Design¶
Application development is new to me and at first I had no idea what software I could work with, but in recent weeks, namely with input devices, I became familiar with the processor and began to use it, but it so happened that I had a problem with the installation applications. And that’s why I decided to use Visual Studio (You can download here.).
First Attempt¶
After installation since I will need to create a desktop application, I decided to use Windows Forms with C#.
After creating a document, a preview window is automatically created, which you can then work on.
First I filled the background and set the transparency color to the same color. Then I try to create my own border shape. As I already designed it in Adobe Illustrator, I simply export the background image to png, set it as a Windows image and set the border style to “None”. So it worked, but after I tried to change something, it turns out that Visual Studio has multiple programming and building environments, and in order to change something, I need to change it everywhere at the same time. I didn’t know about it at that moment errors started to appear a picture boxe in the software and I just closed it.
Second Attempt¶
The second attempt was successful.
I started experimenting to understand how everything worked and I started adding buttons and combo boxes , since these two elements will be the most important for me .
And since I already knew how to make window with custom border , I started to make custom buttons.
As it turns out, the same method works for buttons, but with one difference, you don’t need to set a transparent key for the color; here you can simply add transparency to the background.
Once I figured out the buttons, I decided to add a background window and I also decided to add shadows, but it turned out that this method does not work for translucent photos In the end I just changed the photo and put the option without shadow.
Since I removed the frame from Windows, the standard minimize and close buttons were also removed and I added a close button, also using PNG but for this I needed to write code.
An important point, in order to do everything correctly, you first need to double-click on the button and after that the code for the button is initialized in the code editor, where you can add the code.
private void button2_Click(object sender, EventArgs e)
{
this.Close();
}
Another important point, I thought for a long time about how to implement opening tabs to configure macros.
How I did it for the first added table layout panel, set transparency and added a pre-prepared PNG for the background.
After that I added an picture boxe for the icon and a label for the name and also png and transparency.
Here I have also used a combo box and added code to auto-fill all the fields.
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", "Control", "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);
}
So, in order to make the tabs appear when the buttons are clicked, I wrote the following code. Here is the code for one button i repeat this 6 times.
private void button1_Click(object sender, EventArgs e)
{
panel2.Visible = false;
panel3.Visible = false;
panel4.Visible = false;
panel5.Visible = false;
panel6.Visible = false;
// Show or hide Panel1 every time the button is clicked
panel1.Visible = !panel1.Visible;
//If necessary, perform other actions when opening/closing Panel1
if (panel1.Visible)
{
comboBox1.Visible = true;
comboBox2.Visible = true;
comboBox3.Visible = true;
}
else
{
}
}
I also added the code for the palette, but I didn’t like it and deleted it because the code was not saved in the future and plan to use this button as an colorplate switch for LED backlight.
At the end I also added reset and save buttons.
That’s all there is to designing a user interface. In the future I will connect the code to the ESP 32 and use Bluetooth to send these commands.
Communication¶
Since I used a serial monitor in my final project to install macros on the ESP32 and the Arduino code was almost ready, I decided not to change the communication type and use the serial port.
On the right tab you can see the initial Arduino code.
#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;
//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;
// 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] = {
// 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
};
// Deadzone to filter out unintended movements. Increase if the mouse has small movements when it should be idle or the mouse is too senstive to subtle movements.
int DEADZONE = 200; // Recommended to have this as small as possible for V2 to allow smaller knob range of motion.
struct MacroSettings {
char keys[18][20]; // Клавиши для макросов
};
bool keysPressed[12] = {false,false,false,false,false,false,false,false,false,false,false,false};
// 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
// Centerpoint variable to be populated during setup routine.
int centerPoints[8];
// 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); // Запись настроек в EEPROM
EEPROM.commit(); // Фиксация записи
Serial.println("Настройки сохранены.");
}
void loadSettings() {
EEPROM.get(0, savedSettings); // Загрузка настроек из 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], "lightshow");
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");
saveSettings();
}
void setup() {
EEPROM.begin(sizeof(savedSettings)); // Initialize EEPROM with the size of the structure
delay(2000); // Проверка задержки - позволяет перепрограммирование при случайном превышении мощности с LED
Serial.begin(115200);
Serial.println("Запуск BLE работы!");
bleKeyboard.begin();
FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS); // Типичный порядок GRB
currentPalette = RainbowColors_p;
currentBlending = LINEARBLEND;
// Read idle/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("Клавиатура подключена.");
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();
}
}
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);
}
//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;
}
}
// 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);
}
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]); // Обработка одного символа как клавиши
}
}
}
delay(30); // Задержка для эмуляции удержания клавиши
bleKeyboard.releaseAll(); // Отпуск всех клавиш
}
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]); // Обработка одного символа как клавиши
}
}
}
}
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]); // Обработка одного символа как клавиши
}
}
}
}
void serialEvent() {
while (Serial.available()) {
sendMacro("lightshow");
if (waitForString) {
String input = Serial.readStringUntil('\n');
input.trim(); // Удаление любых ведущих/завершающих пробелов
if (input == "reset") {
clearEEPROM();
} else {
waitForString = false; // Переключение флага, теперь ожидаем символ
}
} 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; // Возвращаем флаг в исходное состояние
}
}
}
}
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
};
So the first thing I needed to do was add a port to connect to my microcontroller.
So I found and added a serial port from the Toolbox . And by clicking on it in , the properties window opened on lower right corner, in which I wrote bitrate and the port that I will use.
Then I added the code to open the port.
public Form1()
{
InitializeComponent();
InitializeComboBoxes();
serialPort1.Open();
}
It seemed to me that there would be no problems with this, but it turned out to be a lot of problems.
The first problem I encountered was that when I clicked the Start button to render my application, errors appeared.
As it turned out, the problem was that the port was busy in Arduino.ide , I just closed the serial monitor and everything started.
From this error it was clear that the connection was normal and my application recognized and connected to the specified port , but the second error appeared when I started sending commands.
Unfortunately, I didn’t take any videos or screenshots, but to solve the problem I decided to write a little code for debugging. The code is simple: first I checked if everything is ok with the ESP 32 and wrote the code to make the internal LED blink 1 time at startup and the code to make the LED blink when I send a command.
void setup() {
Serial.begin(115200);
pinMode(2,OUTPUT);
digitalWrite(2,1);
delay(500);
digitalWrite(2,0);
delay(500);
}
void loop() {
if(Serial.available()){
String data=Serial.readString();
char d1=data.charAt(0);
digitalWrite(2,1);
delay(500);
digitalWrite(2,0);
delay(500);
}
}
private void Send_Click(object sender, EventArgs e)
{
ESP2.Write("Hello World");
Console.WriteLine("trying upload data");
}
When I pressed the button, a message appeared in the console that I wrote, but the LED did not blink.
In general, this means that the microcontroller works, the button also works, but there was no communication. And for this I decided to use Arduino and used the same code for debugging.
Unfortunatly i didnt take video , but with arduino eweryting was fine an led was blinking.
To be honest I have no idea why this was happening I spent hours trying to fix this error and the last thing I did was I wrote code to initialize the serial port directly in the code and without using toolbox and it worked.
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();
ESP2 = new SerialPort("COM16", 115200);
ESP2.Open();
}
private void Send_Click(object sender, EventArgs e)
{
ESP2.Write("2 Ctrl Shift");
Console.WriteLine("trying upload data");
}
}
}
After that, I began to assign commands to each button.
Reset button¶
And I started with the Reset command. Using a double click on the desired button I opened the code, replaced the name with Reset and wrote a command to send data.
In Arduino code I used the eeprom library to save the settings given to me in Flash memory, and for the reset function I wrote a function that deletes everything from memory and rewrites the data.
Here are just code snippets to explain the process.
private void Reset_Click(object sender, EventArgs e)
{
ESP2.Write("reset");
}
void serialEvent() {
while (Serial.available()) {
if (waitForString) {
if (input == "reset") {
clearEEPROM();
}
}
}
}
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();
}
Backlight Button¶
In the same way, I opened the code for setting the backlight and assigned the lightshow command. I also added code for debugging.
When receiving the lightshow command, the program first checks if the stored data matches light off (if lightoff=lightoff ), it turns on the backlight and save the lighton data. If not, it turns it off and rewrites the data. In this way, if you send the lightshow command a second time, when compared with the recorded data, the program will understand that they do not match and will not turn on the LEDs and will save lightoff at the end.
private void Collor(object sender, EventArgs e)
{
ESP2.Write("lightshow");
Console.WriteLine("toggle light");
}
void serialEvent() {
while (Serial.available()) {
if (waitForString) {
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();
}
}
}
}
Macros¶
I did the same for macros, but here I first created a string that reads if there is information in the comboboxes or not and then adding information from the next combobox and so on And at the end - for this button I set the number which I used in the code in the Arduino code in this example it is 5 + the data that I want to record I also added code for debugging.
In this part of the code, if data appears in the serial port, the program first reads the index, then parses the information character by character and writes it to the character set then replaces the received data in place of the previous data and saves the new data. And prints to the serial monitor for debugging.
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("5 " + textToSend + "\n");
Console.WriteLine("Sent: " + "0 " + textToSend);
}
else
{
Console.WriteLine("Serial port is not open.");
}
}
void serialEvent() {
while (Serial.available()) {
if (waitForString) {
String input = Serial.readStringUntil('\n');
input.trim(); // Удаление любых ведущих/завершающих пробелов
else {
waitForString = false; // Переключение флага, теперь ожидаем символ
}
} 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; // Возвращаем флаг в исходное состояние
}
}
}
}
After that, I added the code for all macros in the same way.
I also added code to recognize the com port because different computers may have different com ports.
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);
Complatet version¶
That’s all the code for Arduino and Windows forms
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 при каждом нажатии на кнопку
panel1.Visible = !panel1.Visible;
// Если нужно, выполните другие действия при открытии/закрытии Panel1
if (panel1.Visible)
{
comboBox1.Visible = true;
comboBox2.Visible = true;
comboBox3.Visible = true;// Дополнительные действия при открытии Panel1
}
else
{
// Дополнительные действия при закрытии Panel1
}
}
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;
// Показываем или скрываем Panel1 при каждом нажатии на кнопку
panel2.Visible = !panel2.Visible;
// Если нужно, выполните другие действия при открытии/закрытии Panel1
if (panel2.Visible)
{
comboBox4.Visible = true;
comboBox5.Visible = true;
comboBox6.Visible = true;// Дополнительные действия при открытии Panel1
}
else
{
// Дополнительные действия при закрытии Panel1
}
}
}
private void button4_Click(object sender, EventArgs e)
{
{
panel1.Visible = false;
panel2.Visible = false;
panel4.Visible = false;
panel5.Visible = false;
panel6.Visible = false;
// Показываем или скрываем Panel1 при каждом нажатии на кнопку
panel3.Visible = !panel3.Visible;
// Если нужно, выполните другие действия при открытии/закрытии Panel1
if (panel3.Visible)
{
comboBox7.Visible = true;
comboBox8.Visible = true;
comboBox9.Visible = true;// Дополнительные действия при открытии Panel1
}
else
{
// Дополнительные действия при закрытии Panel1
}
}
}
private void button6_Click(object sender, EventArgs e)
{
{
panel1.Visible = false;
panel3.Visible = false;
panel4.Visible = false;
panel5.Visible = false;
panel2.Visible = false;
// Показываем или скрываем Panel1 при каждом нажатии на кнопку
panel6.Visible = !panel6.Visible;
// Если нужно, выполните другие действия при открытии/закрытии Panel1
if (panel6.Visible)
{
comboBox16.Visible = true;
comboBox17.Visible = true;
comboBox18.Visible = true;// Дополнительные действия при открытии Panel1
}
else
{
// Дополнительные действия при закрытии Panel1
}
}
}
private void button7_Click(object sender, EventArgs e)
{
{
panel1.Visible = false;
panel3.Visible = false;
panel4.Visible = false;
panel2.Visible = false;
panel6.Visible = false;
// Показываем или скрываем Panel1 при каждом нажатии на кнопку
panel5.Visible = !panel5.Visible;
// Если нужно, выполните другие действия при открытии/закрытии Panel1
if (panel5.Visible)
{
comboBox13.Visible = true;
comboBox14.Visible = true;
comboBox15.Visible = true;// Дополнительные действия при открытии Panel1
}
else
{
// Дополнительные действия при закрытии Panel1
}
}
}
private void button5_Click_1(object sender, EventArgs e)
{
{
panel1.Visible = false;
panel3.Visible = false;
panel2.Visible = false;
panel5.Visible = false;
panel6.Visible = false;
// Показываем или скрываем Panel1 при каждом нажатии на кнопку
panel4.Visible = !panel4.Visible;
// Если нужно, выполните другие действия при открытии/закрытии Panel1
if (panel4.Visible)
{
comboBox10.Visible = true;
comboBox11.Visible = true;
comboBox12.Visible = true;// Дополнительные действия при открытии Panel1
}
else
{
// Дополнительные действия при закрытии Panel1
}
}
}
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.");
}
}
}
}
#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;
//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;
// 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] = {
// 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
};
// Deadzone to filter out unintended movements. Increase if the mouse has small movements when it should be idle or the mouse is too senstive to subtle movements.
int DEADZONE = 200; // Recommended to have this as small as possible for V2 to allow smaller knob range of motion.
struct MacroSettings {
char keys[19][20]; // Клавиши для макросов
};
bool keysPressed[12] = { false, false, false, false, false, false, false, false, false, false, false, false };
// 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
// Centerpoint variable to be populated during setup routine.
int centerPoints[8];
// 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); // Запись настроек в EEPROM
EEPROM.commit(); // Фиксация записи
Serial.println("Настройки сохранены.");
}
void loadSettings() {
EEPROM.get(0, savedSettings); // Загрузка настроек из 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); // Проверка задержки - позволяет перепрограммирование при случайном превышении мощности с LED
Serial.begin(115200);
Serial.println("Запуск BLE работы!");
bleKeyboard.begin();
FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS); // Типичный порядок 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("Клавиатура подключена.");
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();
}
}
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);
}
//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;
}
}
// 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);
}
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]); // Обработка одного символа как клавиши
}
}
}
delay(30); // Задержка для эмуляции удержания клавиши
bleKeyboard.releaseAll(); // Отпуск всех клавиш
}
void pressKeys(String keys) { //for joysticks only
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]); // Обработка одного символа как клавиши
}
}
}
}
void releaseKeys(String keys) { //for joysticks only
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]); // Обработка одного символа как клавиши
}
}
}
}
void serialEvent() {
while (Serial.available()) {
if (waitForString) {
String input = Serial.readStringUntil('\n');
input.trim(); // Удаление любых ведущих/завершающих пробелов
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; // Переключение флага, теперь ожидаем символ
}
} 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; // Возвращаем флаг в исходное состояние
}
}
}
}
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
};
Setup file creating¶
After that I wanted to create this file for the application so that it can be installed on every device.
For this I installed the Microsoft visual studio installer projects 2022 extension.
Conclusion¶
I really enjoyed developing the application. this is something between design and programming, as in programming with a microcontroller, here too you need to first write a script to understand what the application should do, each and what should open. The user part is very similar to the hardware part, only virtual. As for my specific application, at first glance it may seem very simple, but here I worked a lot on both the application itself and at the same time on the device itself so that everything was harmonious. And the trick of this application is that it can change the behavior of the device, thereby making it usable and interactive. And the main thing is that the device remembers all the latest settings when turned on. In general, I can say that this week I not only tried my hand at developing applications, but also did something really useful.
-
Source files¶
Click for downloading
SpacePad VSstudio
SpacePad Ardyuino