Membangun Bluetooth Macro Keyboard Programmable ESP32 Klasik Berdasarkan Permasalahan

Pendahuluan

Macro keyboard telah menjadi alat yang sangat populer di kalangan content creator, programmer, dan gamer. Dengan menggunakan ESP32, kita dapat membuat macro keyboard Bluetooth yang dapat diprogram sesuai kebutuhan. Artikel ini akan membahas secara lengkap proses pembuatan dari nol hingga troubleshooting masalah yang mungkin muncul.

Apa itu Macro Keyboard?

Macro keyboard adalah perangkat input yang memungkinkan pengguna untuk menjalankan serangkaian perintah kompleks hanya dengan menekan satu tombol. Misalnya, dengan satu tombol, Anda dapat membuka aplikasi, mengetik teks panjang, atau menjalankan shortcut keyboard yang rumit.

Komponen yang Diperlukan

Hardware

  • ESP32 Development Board (NodeMCU ESP32 atau ESP32 DevKit).
  • Push button switches (1 buah, sesuai kebutuhan).
  • Resistor 1kΩ (untuk pull-up).
  • Breadboard.
  • Kabel jumper.

Software

Pemasalah Pertama Bagaimana Membuat Bluetooth Keyboard dengan ESP32 ?

  1. Patikan kalian sudah menginstal Liblary : BLE Keyboard.
  2. Lalu kalian buka example dari liblary esp32 keyboard bluetooth.
  3. Disana kita di beri 1 contoh untuk esp32 mengirim data ke bluetooth secara otomatis tidak kita menggunakan button di klik baru program baru jalan.
  4. Berikut programnya dengan penjelasan fungsional program.
    C++
    /**
     * This example turns the ESP32 into a Bluetooth LE keyboard that writes the words, presses Enter, presses a media key and then Ctrl+Alt+Delete
     */
    #include <BleKeyboard.h> // Include the library for Bluetooth Low Energy (BLE) keyboard functionality
    
    BleKeyboard bleKeyboard; // Create an instance of the BleKeyboard object
    
    void setup() {
      Serial.begin(115200); // Initialize serial communication at a baud rate of 115200
      Serial.println("Starting BLE work!"); // Print a message to the serial monitor
      bleKeyboard.begin(); // Initialize the BLE keyboard. This makes the ESP32 discoverable as a BLE device.
    }
    
    void loop() {
      if(bleKeyboard.isConnected()) { // Check if the ESP32 is connected to a BLE host (e.g., a computer or phone)
        Serial.println("Sending 'Hello world'..."); // Print a message to the serial monitor
        bleKeyboard.print("Hello world"); // Type and send the string "Hello world" to the connected device
    
        delay(1000); // Wait for 1 second
    
        Serial.println("Sending Enter key..."); // Print a message to the serial monitor
        bleKeyboard.write(KEY_RETURN); // Send the Enter key press
    
        delay(1000); // Wait for 1 second
    
        Serial.println("Sending Play/Pause media key..."); // Print a message to the serial monitor
        bleKeyboard.write(KEY_MEDIA_PLAY_PAUSE); // Send the Play/Pause media key press
    
        delay(1000); // Wait for 1 second
    
        //
        // Below is an example of pressing multiple keyboard modifiers 
        // which by default is commented out.
        /*
        Serial.println("Sending Ctrl+Alt+Delete..."); // Print a message to the serial monitor
        bleKeyboard.press(KEY_LEFT_CTRL); // Press and hold the Left Control key
        bleKeyboard.press(KEY_LEFT_ALT); // Press and hold the Left Alt key
        bleKeyboard.press(KEY_DELETE); // Press and hold the Delete key
        delay(100); // Wait for 100 milliseconds
        bleKeyboard.releaseAll(); // Release all currently pressed keys
        */
      }
    
      Serial.println("Waiting 5 seconds..."); // Print a message to the serial monitor
      delay(5000); // Wait for 5 seconds before the loop repeats
    }

Permasalahan selanjutnya misalkan saya ingin membuat fungsi tombol di luar contoh diatas ?

Kalian bisa buka file liblary dari BLE Keyboard disana kalian bisa mendapatkan database dari keyboard atau kalian bisa menggunakan database yang saya miliki di bawah ini.

ASCII Characters (32-127)

Decimal Hex Character
320x20 (Space)
330x21!
340x22"
350x23#
360x24$
370x25%
380x26&
390x27'
400x28(
410x29)
420x2A*
430x2B+
440x2C,
450x2D-
460x2E.
470x2F/
480x300
490x311
500x322
510x333
520x344
530x355
540x366
550x377
560x388
570x399
580x3A:
590x3B;
600x3C<
610x3D=
620x3E>
630x3F?
640x40@
650x41A
660x42B
670x43C
680x44D
690x45E
700x46F
710x47G
720x48H
730x49I
740x4AJ
750x4BK
760x4CL
770x4DM
780x4EN
790x4FO
800x50P
810x51Q
820x52R
830x53S
840x54T
850x55U
860x56V
870x57W
880x58X
890x59Y
900x5AZ
910x5B[
920x5C\
930x5D]
940x5E^
950x5F_
960x60`
970x61a
980x62b
990x63c
1000x64d
1010x65e
1020x66f
1030x67g
1040x68h
1050x69i
1060x6Aj
1070x6Bk
1080x6Cl
1090x6Dm
1100x6En
1110x6Fo
1120x70p
1130x71q
1140x72r
1150x73s
1160x74t
1170x75u
1180x76v
1190x77w
1200x78x
1210x79y
1220x7Az
1230x7B{
1240x7C|
1250x7D}
1260x7E~
1270x7FDEL

Special Key Codes

Constant Name Hex Value Decimal Value
KEY_LEFT_CTRL0x80128
KEY_LEFT_SHIFT0x81129
KEY_LEFT_ALT0x82130
KEY_LEFT_GUI0x83131
KEY_RIGHT_CTRL0x84132
KEY_RIGHT_SHIFT0x85133
KEY_RIGHT_ALT0x86134
KEY_RIGHT_GUI0x87135
KEY_UP_ARROW0xDA218
KEY_DOWN_ARROW0xD9217
KEY_LEFT_ARROW0xD8216
KEY_RIGHT_ARROW0xD7215
KEY_BACKSPACE0xB2178
KEY_TAB0xB3179
KEY_RETURN0xB0176
KEY_ESC0xB1177
KEY_INSERT0xD1209
KEY_PRTSC0xCE206
KEY_DELETE0xD4212
KEY_PAGE_UP0xD3211
KEY_PAGE_DOWN0xD6214
KEY_HOME0xD2210
KEY_END0xD5213
KEY_CAPS_LOCK0xC1193
KEY_F10xC2194
KEY_F20xC3195
KEY_F30xC4196
KEY_F40xC5197
KEY_F50xC6198
KEY_F60xC7199
KEY_F70xC8200
KEY_F80xC9201
KEY_F90xCA202
KEY_F100xCB203
KEY_F110xCC204
KEY_F120xCD205
KEY_F130xF0240
KEY_F140xF1241
KEY_F150xF2242
KEY_F160xF3243
KEY_F170xF4244
KEY_F180xF5245
KEY_F190xF6246
KEY_F200xF7247
KEY_F210xF8248
KEY_F220xF9249
KEY_F230xFA250
KEY_F240xFB251
KEY_NUM_00xEA234
KEY_NUM_10xE1225
KEY_NUM_20xE2226
KEY_NUM_30xE3227
KEY_NUM_40xE4228
KEY_NUM_50xE5229
KEY_NUM_60xE6230
KEY_NUM_70xE7231
KEY_NUM_80xE8232
KEY_NUM_90xE9233
KEY_NUM_SLASH0xDC220
KEY_NUM_ASTERISK0xDD221
KEY_NUM_MINUS0xDE222
KEY_NUM_PLUS0xDF223
KEY_NUM_ENTER0xE0224
KEY_NUM_PERIOD0xEB235

Media Key Codes

Constant Name Hex Value Decimal Value
KEY_MEDIA_NEXT_TRACK{1, 0}1, 0
KEY_MEDIA_PREVIOUS_TRACK{2, 0}2, 0
KEY_MEDIA_STOP{4, 0}4, 0
KEY_MEDIA_PLAY_PAUSE{8, 0}8, 0
KEY_MEDIA_MUTE{16, 0}16, 0
KEY_MEDIA_VOLUME_UP{32, 0}32, 0
KEY_MEDIA_VOLUME_DOWN{64, 0}64, 0
KEY_MEDIA_WWW_HOME{128, 0}128, 0
KEY_MEDIA_LOCAL_MACHINE_BROWSER{0, 1}0, 1
KEY_MEDIA_CALCULATOR{0, 2}0, 2
KEY_MEDIA_WWW_BOOKMARKS{0, 4}0, 4
KEY_MEDIA_WWW_SEARCH{0, 8}0, 8
KEY_MEDIA_WWW_STOP{0, 16}0, 16
KEY_MEDIA_WWW_BACK{0, 32}0, 32
KEY_MEDIA_CONSUMER_CONTROL_CONFIGURATION{0, 64}0, 64
KEY_MEDIA_EMAIL_READER{0, 128}0, 128

Selanjutnya pertanyaan tambahan kalian mungkin bingung bagaimana cara pakai data base diatas?

kalian pasti melihat di data base ada angka desimal nomor 33 sampai 127 dan ada key tambahan di nomor 127++.
contoh penggunaannya seperti program di bawah ini :
 
C++
bleKeyboard.press(128); // Press and hold the Left Control key
bleKeyboard.press(97); // Press and hold the 'a'

Khusus untuk key media kalian harus membuat variable seperti kode di bawah ini
C++
MediaKeyReport keyMedia = {0, 0}; // ubah nilainya sesai key yang mau di ambil contoh "MediaKeyReport keyMedia = {1, 0};"
bleKeyboard.write(keyMedia);
  

Selanjutnya pertanyaan tambahan kalian mungkin bingung apa perbedaan antara keyboard.press vs keyboard.write ?

Fungsi Aksi
press(KEY_X) Menekan tombol (dan tetap ditekan) sampai program release di aktifkan
release(KEY_X) Melepaskan tombol tertentu
releaseAll() Melepaskan semua tombol
write(KEY_X) Menekan lalu melepaskan tombol secara otomatis

Permasalahan selanjutnya bagaimana membuat program di example bisa dikontrol dengan tombol ?

  1. Patikan kalian sudah menginstal Liblary No Delay Button
  2. Lalu kalian pilih tombol apa saya yang akan di gunakan ?
    • Contoh saja kalian ingin mengirim fungsi "CTRL + A"
    • Maka programnya akan menjadi seperti ini
      C++
      #include <BleKeyboard.h> // Include the library for Bluetooth Low Energy (BLE) keyboard functionality
      
      BleKeyboard bleKeyboard; // Create an instance of the BleKeyboard object
      
      #include <NoDelayButton.h> // Include the library for debounced button input without delay
      uint8_t pins[] = { 4 }; // Define an array of pins for the buttons. Here, pin 4 is used.
      char buttonChars[] = { 'A' }; // Define an array of characters associated with each button. 'A' is associated with the button on pin 4.
      const uint8_t expectedButtonCount = 1; // Define the number of buttons being used. Here, it's 1.
      NoDelayButton button(pins, buttonChars, 1, expectedButtonCount); // Create an instance of the NoDelayButton object, initializing it with the pin, character, and count.
      
      void setup() {
        Serial.begin(115200); // Initialize serial communication at a baud rate of 115200.
        Serial.println("Starting BLE work!"); // Print a message to the serial monitor.
        bleKeyboard.begin(); // Initialize the BLE keyboard. This makes the ESP32 discoverable as a BLE device.
      }
      
      void loop() {
        if (bleKeyboard.isConnected()) { // Check if the ESP32 is connected to a BLE host (e.g., a computer or phone).
          if (button.scanValue() == 1) { // Check if the button is pressed. 'scanValue() == 1' means the button on the first pin (index 0) has been pressed.
            Serial.println("Sending Ctrl+a"); // Print a message to the serial monitor.
            bleKeyboard.press(128); // Press and hold the Left Control key.
            bleKeyboard.press(97); // Press and hold the 'a' key.
            delay(100); // Wait for 100 milliseconds to ensure the key presses are registered.
            bleKeyboard.releaseAll(); // Release all currently pressed keys (Ctrl and 'a').
          }
        }
      }
  3.  

Permasalahan selanjutnya bagaimana tombol keyboard bisa di ubah tanpa harus flashing ?

Kita bisa menggunakan banyak metode yang penting bisa kirim data ke microkontroler, contoh saya lebih prever menggunakan web server karena tidak perlu bikin aplikasi walaupun bikin aplikasi berbasis web tapi ini mudah.

Selanjutnya ada permasalahan tambahan menggunakan wifi dan bluetooth bersamaan mengakibatkan beberapa masalah !

  1. Dari sisi daya sudah pasti menaikan penggunaan listrik, ini bermasalah jika kalian menggunakan baterai.
  2. Dari sisi program akan ada banyak program security karena bisa saja wifinya di kontrol orang tidak lain mengakibatkan isi fungsi key kita berubah. Selain itu juga ada masalah memory dan gangguan stack.
  3. Dari sisi sinyal akan menurun drastis karena bekerja gantian, tenntu mengakibatkan kinerjanya menurun dan mengakibatkan masalah baru, tidak saya bahas karena saya tidak ada pengalaman ngoprek sinyal.
Jadi saya lebih prefrer menggunakan salah satunya di kontrol saat pertama kali boot.

Selanjutnya bagaimana cara mengaktifkan wifi saat configurasi dan menyalakan bluetooth saat ingin memakai tombolnya ?

Seperti yang sudah di jelakan kita tinggal membuat program seperti ini, jika tombol yang tersambung dengan pin 4 di tekan saat boot maka wifi akan aktif  
C++
#include <Arduino.h>
#include <WiFi.h>
#include <BleKeyboard.h>

BleKeyboard bleKeyboard;

void setup()
{
    Serial.begin(115200);
    if (!digitalRead(4/*PIN CONTROL MODE*/))
    {
        WiFi.softAP(ssid);
        //Lanjutkan Program loop WiFi
        while (1)
        {

        }
    }
    else
    {
        WiFi.mode(WIFI_OFF);
        bleKeyboard.begin();
        //Lanjutkan Program Loop Bluetooth
        while (1)
        {

        }
    }
}

void loop()
{
}

Selanjutnya bagaimana membuat web server pada ESP32 ?

Dari kode di atas kalian ubah beberapa bagian sampai berubah seperti kode di bawah ini, kode di bawah ini sudah ditambahkan program untuk esp32 bisa menghasilkan web server di port 80.

C++
#include <Arduino.h>
#include <WiFi.h>
#include <BleKeyboard.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>

const char *ssid = "[ CONFIG KEYBOARD ]";  // Ganti dengan SSID WiFi Anda
AsyncWebServer server(80);

BleKeyboard bleKeyboard;

void setup() {
  Serial.begin(115200);
  if (!digitalRead(4 /*PIN CONTROL MODE*/)) {
    WiFi.softAP(ssid);
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
      request->send(200, "text/html", "WEB Server Berhasil dibuat!");
    });
    server.begin();
    while (1) {
    }
  } else {
    WiFi.mode(WIFI_OFF);
    bleKeyboard.begin();
    //Lanjutkan Program Loop Bluetooth
    while (1) {
    }
  }
}

void loop() {
}
Setelah kalian upload program diatas kalian 
  1. Klik tombol konfiguras tekan [ jangan di lepas ] 
  2. Klik reset dan menunggu 5 detik sehingga masuk boot configurasi keyboard
  3. Baru lepas tombol konfigurasi
Selanjutnya sambugkan wifi kalian ke ssid [ CONFIG KEYBOARD ]

Menyambukan wifi ke soft Akses point dari esp32 
setelah itu di browser kalian buka link http://192.168.4.1

Hasil dari Web Server esp32 yang kita buat

Jika sudah seperti diatas artinya kalian sudah bisa membuat Web Sederhana 

Pertanyaan selanjutnya bang itu cuma server sederhana saya ingin full program bluetooth macro keyboard programmable ?

Ini dia program untuk 

C++
#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <BleKeyboard.h>
#include <NoDelayButton.h>
#include <SPIFFS.h>
#include <DataParser.h>

const char index_html[] PROGMEM = R"rawliteral(
    <!DOCTYPE html>
    <html>

    <head>
        <meta charset="UTF-8">
        <title>Macro Keyboard Config</title>
        <style>
            body {
                background: linear-gradient(135deg, #0f2027, #203a43, #2c5364);
                color: white;
                font-family: 'Segoe UI', sans-serif;
                margin: 0;
                padding: 0;
            }

            .glass {
                background: rgba(0, 0, 0, 0.08);
                border-radius: 16px;
                backdrop-filter: blur(12px);
                box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5);
                padding: 24px;
                margin: 40px auto;
                width: 90%;
                max-width: 500px;
            }

            h2 {
                text-align: center;
                margin-bottom: 20px;
            }

            label {
                display: block;
                margin-top: 10px;
                font-weight: bold;
                margin-bottom: 10px;
            }

            button {
                background-color: #4CAF50;
                border: none;
                padding: 10px 16px;
                color: white;
                font-size: 16px;
                border-radius: 8px;
                cursor: pointer;
                margin-top: 50px;
                width: 100%;
            }

            button:hover {
                background-color: #3e8e41;
            }

            select,
            input[type="number"],
            input[type="text"] {
                width: 100%;
                padding: 10px;
                font-size: 16px;
                border-radius: 8px;
                border: none;
                box-sizing: border-box;
                appearance: none;
                -webkit-appearance: none;
                -moz-appearance: none;
                height: 40px;
            }

            .info-box {
                background: rgba(255, 255, 255, 0.1);
                border: 1px solid rgba(255, 255, 255, 0.2);
                border-radius: 12px;
                padding: 20px;
                margin-bottom: 25px;
                backdrop-filter: blur(8px);
            }

            .info-box h3 {
                margin-top: 0;
                color: #4CAF50;
                font-size: 18px;
            }

            .info-box p {
                margin: 10px 0;
                line-height: 1.6;
            }

            .info-box ol {
                margin: 10px 0;
                padding-left: 20px;
            }

            .info-box li {
                margin: 8px 0;
                line-height: 1.5;
            }
        </style>
    </head>

    <body>
        <div class="glass">
            <h2>ESP32 Macro Config</h2>
            <div class="info-box">
                <h3>Cara Penggunaan</h3>
                <p><strong>Prioritas Eksekusi:</strong></p>
                <ol>
                    <li><strong>Key Write</strong> - Prioritas tertinggi, menulis karakter/tombol khusus</li>
                    <li><strong>Key Media</strong> - Kontrol multimedia (jika Key Write kosong)</li>
                    <li><strong>3 Key Press</strong> - Kombinasi 3 tombol (Ctrl+Shift+C)</li>
                    <li><strong>2 Key Press</strong> - Kombinasi 2 tombol (Ctrl+C)</li>
                    <li><strong>1 Key Press</strong> - Satu tombol saja</li>
                </ol>
                <p><strong>Catatan:</strong> Sistem akan mengeksekusi hanya satu yang teratas dari daftar yang terisi.
                </p>
            </div>
            <form action="/save_macro" method="GET">
                <label for="key1">Key Press 1</label>
                <select name="key1" id="key1"></select>

                <label for="key2">Key Press 2</label>
                <select name="key2" id="key2"></select>

                <label for="key3">Key Press 3</label>
                <select name="key3" id="key3"></select>

                <label for="key4">Key Write</label>
                <select name="key4" id="key4"></select>

                <label for="delay">Delay (ms):</label>
                <input type="number" name="delay" id="delay" placeholder="Contoh: 100" />

                <label for="slot">Program Ulang Tombol Nomor :</label>
                <input type="number" name="slot" id="slot" min="1" max="1" value="1" />

                <button type="submit">Simpan Macro</button>
            </form>
        </div>

        <script>

            // Define all key options
            const keyOptions = {
                basic: [
                    { value: "", text: "--None--" },
                    { value: "128", text: "Ctrl" },
                    { value: "129", text: "Shift" },
                    { value: "130", text: "Alt" },
                    { value: "131", text: "GUI" }
                ],
                letters: [
                    { value: "65", text: "A" }, { value: "66", text: "B" }, { value: "67", text: "C" },
                    { value: "68", text: "D" }, { value: "69", text: "E" }, { value: "70", text: "F" },
                    { value: "71", text: "G" }, { value: "72", text: "H" }, { value: "73", text: "I" },
                    { value: "74", text: "J" }, { value: "75", text: "K" }, { value: "76", text: "L" },
                    { value: "77", text: "M" }, { value: "78", text: "N" }, { value: "79", text: "O" },
                    { value: "80", text: "P" }, { value: "81", text: "Q" }, { value: "82", text: "R" },
                    { value: "83", text: "S" }, { value: "84", text: "T" }, { value: "85", text: "U" },
                    { value: "86", text: "V" }, { value: "87", text: "W" }, { value: "88", text: "X" },
                    { value: "89", text: "Y" }, { value: "90", text: "Z" }
                ],
                numbers: [
                    { value: "48", text: "0" }, { value: "49", text: "1" }, { value: "50", text: "2" },
                    { value: "51", text: "3" }, { value: "52", text: "4" }, { value: "53", text: "5" },
                    { value: "54", text: "6" }, { value: "55", text: "7" }, { value: "56", text: "8" },
                    { value: "57", text: "9" }
                ],
                functionKeys: [
                    { value: "194", text: "F1" }, { value: "195", text: "F2" }, { value: "196", text: "F3" },
                    { value: "197", text: "F4" }, { value: "198", text: "F5" }, { value: "199", text: "F6" },
                    { value: "200", text: "F7" }, { value: "201", text: "F8" }, { value: "202", text: "F9" },
                    { value: "203", text: "F10" }, { value: "204", text: "F11" }, { value: "205", text: "F12" }
                ],
                arrows: [
                    { value: "218", text: "Up Arrow" }, { value: "217", text: "Down Arrow" },
                    { value: "216", text: "Left Arrow" }, { value: "215", text: "Right Arrow" }
                ],
                special: [
                    { value: "178", text: "Backspace" }, { value: "179", text: "Tab" },
                    { value: "176", text: "Enter" }, { value: "177", text: "Escape" },
                    { value: "32", text: "Space" }, { value: "212", text: "Delete" },
                    { value: "209", text: "Insert" }, { value: "210", text: "Home" },
                    { value: "213", text: "End" }, { value: "211", text: "Page Up" },
                    { value: "214", text: "Page Down" }, { value: "193", text: "Caps Lock" }
                ],
                multimedia: [
                    { value: "1'0", text: "Next Track" }, { value: "4'0", text: "Previous Track" },
                    { value: "8'0", text: "Play/Pause" }, { value: "16'0", text: "Mute" },
                    { value: "32'0", text: "Volume Up" }, { value: "64'0", text: "Volume Down" },
                    { value: "128'0", text: "Home" }, { value: "0'1", text: "My Computer" },
                    { value: "0'2", text: "Calculator" }, { value: "0'4", text: "Bookmarks" },
                    { value: "0'8", text: "Search" }, { value: "0'32", text: "Back" },
                    { value: "0'128", text: "Email Reader" }
                ]
            };

            // Function to populate select elements
            function populateSelect(selectId, includeAll = false) {
                const select = document.getElementById(selectId);
                select.innerHTML = ''; // Clear existing options

                // Add default option
                const defaultOption = document.createElement('option');
                defaultOption.value = '';
                defaultOption.textContent = '--None--';
                select.appendChild(defaultOption);

                // For key write select, add all options organized by groups
                const groups = [
                    { name: 'Basic Keys', options: keyOptions.basic.slice(1) },
                    { name: 'Letters', options: keyOptions.letters },
                    { name: 'Numbers', options: keyOptions.numbers },
                    { name: 'Function Keys', options: keyOptions.functionKeys },
                    { name: 'Arrow Keys', options: keyOptions.arrows },
                    { name: 'Special Keys', options: keyOptions.special },
                    { name: 'Multimedia', options: keyOptions.multimedia }
                ];

                groups.forEach(group => {
                    const optgroup = document.createElement('optgroup');
                    optgroup.label = group.name;

                    group.options.forEach(option => {
                        const optionElement = document.createElement('option');
                        optionElement.value = option.value;
                        optionElement.textContent = option.text;
                        optgroup.appendChild(optionElement);
                    });

                    select.appendChild(optgroup);
                });

            }

            // Initialize all select elements when page loads
            document.addEventListener('DOMContentLoaded', function () {
                populateSelect('key1');
                populateSelect('key2');
                populateSelect('key3');
                populateSelect('key4');
            });
        </script>
    </body>

    </html>
    )rawliteral";

// Definisikan pin dan karakter untuk tombol
uint8_t pins[] = { 4 };
char buttonChars[] = { 'A' };
const uint8_t expectedButtonCount = 1;
NoDelayButton button(pins, buttonChars, 1, expectedButtonCount);

BleKeyboard bleKeyboard;

uint8_t keyPress1Button1 = 0;
uint8_t keyPress2Button1 = 0;
uint8_t keyPress3Button1 = 0;
uint8_t keyWriteButton1 = 0;
uint8_t delayButton1 = 100;
MediaKeyReport keyWriteMediaButton1 = { 0, 0 };

void loadMacroSlot1();

const char *ssid = "[ CONFIG KEYBOARD ]";  // Ganti dengan SSID WiFi Anda
AsyncWebServer server(80);
void handleSaveMacro(AsyncWebServerRequest *request);

void setup() {
  Serial.begin(115200);
  if (!SPIFFS.begin(true)) {
    Serial.println("SPIFFS Gagal");
    while (1) {

      delay(1000);
    }
  }
  if (button.scanValue() == 1) {
    WiFi.softAP(ssid);
    Serial.println("WiFi AP Created.");
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
      request->send(200, "text/html", index_html);
    });

    server.on("/save_macro", HTTP_GET, handleSaveMacro);
    server.begin();
    while (1) {

      delay(1000);
    }
  } else {
    WiFi.mode(WIFI_OFF);
    Serial.println("WiFi is OFF.");
    bleKeyboard.begin();
    loadMacroSlot1();
    while (1) {
      if (bleKeyboard.isConnected()) {
        if (button.scanValue() == 1) {

          if (delayButton1 > 0) {
            if (keyWriteButton1 != 0) {
              bleKeyboard.write(keyWriteButton1);
              Serial.println("Key Write: " + String(keyWriteButton1));
            } else if (keyWriteMediaButton1[0] != 0 || keyWriteMediaButton1[1] != 0) {
              bleKeyboard.write(keyWriteMediaButton1);
              Serial.println("Media Key: " + String(keyWriteMediaButton1[0]) + "," + String(keyWriteMediaButton1[1]));
            } else if (keyPress1Button1 != 0 && keyPress2Button1 != 0 && keyPress3Button1 != 0) {
              bleKeyboard.press(keyPress1Button1);
              delay(1);
              bleKeyboard.press(keyPress2Button1);
              delay(1);
              bleKeyboard.press(keyPress3Button1);
              bleKeyboard.releaseAll();
            } else if (keyPress1Button1 != 0 && keyPress2Button1 != 0) {
              bleKeyboard.press(keyPress1Button1);
              delay(1);
              bleKeyboard.press(keyPress2Button1);
              bleKeyboard.releaseAll();
            } else if (keyPress1Button1 != 0) {
              bleKeyboard.press(keyPress1Button1);
              bleKeyboard.releaseAll();
            }
            delay(delayButton1);
          }
        }
      }
    }
  }
}

void loop() {
}

void handleSaveMacro(AsyncWebServerRequest *request) {
  String keyPress1 = request->hasParam("key1") ? request->getParam("key1")->value() : "0";
  String keyPress2 = request->hasParam("key2") ? request->getParam("key2")->value() : "0";
  String keyPress3 = request->hasParam("key3") ? request->getParam("key3")->value() : "0";
  String keyWrite = request->hasParam("key4") ? request->getParam("key4")->value() : "0";
  String delay = request->hasParam("delay") ? request->getParam("delay")->value() : "0";
  String slot = request->hasParam("slot") ? request->getParam("slot")->value() : "1";

  String filename = "/macro" + slot + ".txt";
  String content = keyPress1 + "," + keyPress2 + "," + keyPress3 + "," + keyWrite + "," + delay;

  File file = SPIFFS.open(filename, FILE_WRITE);
  if (file) {
    file.print(content);
    file.close();
    request->send(200, "text/html",
                  "Macro slot " + slot + " saved! <br>" + content + "<script>"
                                                                    "setTimeout(function(){ window.location.href = '/'; }, 2000);"
                                                                    "</script>");
  } else {
    request->send(500, "text/plain", "Gagal menyimpan ke SPIFFS");
  }
}
void loadMacroSlot1() {
  String filename = "/macro1.txt";
  if (!SPIFFS.exists(filename)) {
    Serial.println("File tidak ada.");
    return;
  }

  File file = SPIFFS.open(filename, FILE_READ);
  if (!file) {
    Serial.println("Gagal membuka file.");
    return;
  }

  String line = file.readStringUntil('\n');
  file.close();

  String output[10];
  int jumlahString = 0;
  Serial.println("=== Loading Macro Slot 1 ===");
  Serial.println("Line: " + line);
  DataParser::parse(line, output, ',', jumlahString);
  // Substring setiap field
  keyPress1Button1 = output[0].toInt();
  keyPress2Button1 = output[1].toInt();
  keyPress3Button1 = output[2].toInt();
  // Cek apakah keyWriteButton1 adalah angka atau string
  // Jika tidak ada karakter pemisah, berarti itu adalah angka
  if (output[3].indexOf("'") == -1) {
    keyWriteButton1 = output[3].toInt();
  } else if (output[3].indexOf("'") != -1) {
    String output3[2];
    DataParser::parse(output[3], output3, '\'', jumlahString);
    keyWriteMediaButton1[0] = output3[0].toInt();  // Inisialisasi dengan nilai default
    keyWriteMediaButton1[1] = output3[1].toInt();  // Inisialisasi dengan nilai default
  }

  delayButton1 = output[4].toInt() > 100 ? output[4].toInt() : 100;  // Set minimum delay to 100ms

  // Debug print
  Serial.println("=== Macro Slot 1 Loaded ===");
  Serial.println("Key1: " + (String)keyPress1Button1);
  Serial.println("Key2: " + (String)keyPress2Button1);
  Serial.println("Key3: " + (String)keyPress3Button1);
  Serial.println("Key4: " + (String)keyWriteButton1);
  Serial.println("Key4: " + String(keyWriteMediaButton1[0]) + "," + String(keyWriteMediaButton1[1]));
  Serial.println("Delay: " + (String)delayButton1);
}

Komentar

Postingan populer dari blog ini

Proyek IoT: Membangun Sistem Pencatatan Data Sederhana Menggunakan ESP32 dan Google Spreadsheet

Ekspos Server Lokal ke Internet dengan Cloudflare Tunneling