#include <SoftSerial_INT0.h> #include <DigiKeyboard.h> SoftSerial mySerial(2, 1); // RX P2, TX P1 void setup() { mySerial.begin(9600); DigiKeyboard.sendKeyStroke(0); // Init HID pinMode(1, OUTPUT); digitalWrite(1, LOW); } void loop() { DigiKeyboard.update(); if (mySerial.available()) { char c = mySerial.read(); digitalWrite(1, HIGH); DigiKeyboard.print(c); digitalWrite(1, LOW); DigiKeyboard.update(); DigiKeyboard.sendKeyStroke(0, 0); // Final release DigiKeyboard.delay(5); // Small delay for serial stability } }
and for the esp32 (it's big):
/********* Rui Santos & Sara Santos - Random Nerd Tutorials Complete project details at https://Randomnerdtutorials.com/getting-started-esp32-c3-super-mini/ Final version with client-side image pre-processing. - Swapped jsQR for the more powerful ZXing-JS library. - Can now scan both QR Codes and common 1D Barcodes. - Increased max image size to 1200px for better detail. - Added "hints" to the scanner to focus on common formats, improving speed and accuracy. *********/ #include <WiFi.h> #include <WiFiClient.h> #include <WebServer.h> #define TX_PIN 4 // GPIO4 as TX (connect to Digispark P2) #define RX_PIN 5 // GPIO5 as RX (optional, connect to Digispark P1 if needed) HardwareSerial mySerial(1); // Replace with your network credentials const char* ssid = "Vladimir Routin"; const char* password = "inhousetm"; // Create a web server object WebServer server(80); // Helper function to decode URL-encoded strings String urlDecode(String str) { String decoded = ""; char temp[] = "0x00"; for (unsigned int i = 0; i < str.length(); i++) { if (str[i] == '%') { if (i + 2 < str.length()) { temp[2] = str[i + 1]; temp[3] = str[i + 2]; char decodedChar = (char)strtol(temp, NULL, 16); decoded += decodedChar; i += 2; } } else if (str[i] == '+') { decoded += ' '; } else { decoded += str[i]; } } return decoded; } // Helper function to remove all spaces from a string String removeSpaces(String str) { String noSpaces = ""; for (unsigned int i = 0; i < str.length(); i++) { if (str[i] != ' ') { noSpaces += str[i]; } } return noSpaces; } // Handlers for writing data void handleWriteSerialNumber() { if (server.hasArg("serial_number")) { mySerial.println(urlDecode(server.arg("serial_number"))); server.send(200, "text/plain", "OK"); } else { server.send(400, "text/plain", "Missing serial_number"); } } void handleWriteBuildId() { if (server.hasArg("build_id")) { mySerial.println(urlDecode(server.arg("build_id"))); server.send(200, "text/plain", "OK"); } else { server.send(400, "text/plain", "Missing build_id"); } } void handleWriteFeatureByte() { if (server.hasArg("feature_byte")) { mySerial.println(removeSpaces(urlDecode(server.arg("feature_byte")))); server.send(200, "text/plain", "OK"); } else { server.send(400, "text/plain", "Missing feature_byte"); } } // Handlers for navigation void handleArrowLeft() { digitalWrite(8, HIGH); mySerial.println("123456789012345678901234567890"); delay(500); digitalWrite(8, LOW); server.send(200, "text/plain", "OK"); } void handleArrowRight() { digitalWrite(8, HIGH); mySerial.println("ARROW_RIGHT"); delay(500); digitalWrite(8, LOW); server.send(200, "text/plain", "OK"); } void handleArrowUp() { digitalWrite(8, HIGH); mySerial.println("ARROW_UP"); delay(500); digitalWrite(8, LOW); server.send(200, "text/plain", "OK"); } void handleArrowDown() { digitalWrite(8, HIGH); mySerial.println("ARROW_DOWN"); delay(500); digitalWrite(8, LOW); server.send(200, "text/plain", "OK"); } void handleArrowEnter() { digitalWrite(8, HIGH); mySerial.println("ARROW_ENTER"); delay(500); digitalWrite(8, LOW); server.send(200, "text/plain", "OK"); } // Function to handle the root URL void handleRoot() { String html = "<!DOCTYPE html><html>"; html += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"; html += "<link href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap\" rel=\"stylesheet\">"; html += "<script src=\"https://unpkg.com/@zxing/library@latest/umd/index.min.js\"></script>"; html += "<style>"; html += "body { font-family: 'Roboto', Arial, sans-serif; background: linear-gradient(to bottom, #eceff1, #cfd8dc); color: #263238; margin: 0; padding: 20px; }"; html += "h1 { text-align: center; color: #1565c0; font-size: 28px; margin-bottom: 30px; }"; html += ".container { max-width: 700px; margin: 0 auto; background: #ffffff; padding: 30px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); }"; html += "form { margin: 15px 0; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 10px; }"; html += "label { font-size: 16px; font-weight: 500; color: #37474f; width: 120px; text-align: right; }"; html += "input[type=\"text\"] { padding: 10px; font-size: 16px; border: 1px solid #b0bec5; border-radius: 6px; background: #f5f7fa; transition: border-color 0.3s; flex-grow: 1; }"; html += ".qr-button { background: #4caf50; }"; html += "</style>"; html += "</head><body><div class=\"container\">"; html += "<h1>ESP32 Control Panel</h1>"; html += "<form id=\"serial_form\"><label for=\"serial_number\">Serial Number:</label><input type=\"text\" id=\"serial_number\" name=\"serial_number\" maxlength=\"50\"><label for='qr_serial' class='qr-button' style='padding: 10px 20px; border-radius: 6px; color: white; cursor: pointer;'>Upload Code</label><input type='file' id='qr_serial' accept='image/*' style='display:none;' onchange='scanUploadedCode(\"serial_number\", this)'><input type=\"button\" value=\"WRITE\" onclick=\"sendData('serial_form', '/write_serial', 'serial_feedback')\"></form><div id=\"serial_feedback\" class=\"feedback\"></div>"; html += "<form id=\"build_form\"><label for=\"build_id\">Build ID:</label><input type=\"text\" id=\"build_id\" name=\"build_id\" maxlength=\"50\"><label for='qr_build' class='qr-button' style='padding: 10px 20px; border-radius: 6px; color: white; cursor: pointer;'>Upload Code</label><input type='file' id='qr_build' accept='image/*' style='display:none;' onchange='scanUploadedCode(\"build_id\", this)'><input type=\"button\" value=\"WRITE\" onclick=\"sendData('build_form', '/write_build', 'build_feedback')\"></form><div id=\"build_feedback\" class=\"feedback\"></div>"; html += "<form id=\"feature_form\"><label for=\"feature_byte\">Feature Byte:</label><input type=\"text\" id=\"feature_byte\" name=\"feature_byte\" maxlength=\"70\" style='flex-basis: 300px;'><label for='qr_feature' class='qr-button' style='padding: 10px 20px; border-radius: 6px; color: white; cursor: pointer;'>Upload Code</label><input type='file' id='qr_feature' accept='image/*' style='display:none;' onchange='scanUploadedCode(\"feature_byte\", this)'><input type=\"button\" value=\"WRITE\" onclick=\"sendData('feature_form', '/write_feature', 'feature_feedback')\"></form><div id=\"feature_feedback\" class=\"feedback\"></div>"; html += "<div class=\"nav-buttons\" style='display: flex; justify-content: center; gap: 10px; margin-top: 20px;'><input type=\"button\" value=\"Left\" onclick=\"sendNav('/arrow_left', 'nav_feedback')\"><input type=\"button\" value=\"Right\" onclick=\"sendNav('/arrow_right', 'nav_feedback')\"><input type=\"button\" value=\"Up\" onclick=\"sendNav('/arrow_up', 'nav_feedback')\"><input type=\"button\" value=\"Down\" onclick=\"sendNav('/arrow_down', 'nav_feedback')\"><input type=\"button\" value=\"Enter\" onclick=\"sendNav('/arrow_enter', 'nav_feedback')\"></div><div id=\"nav_feedback\" class=\"feedback\"></div>"; html += "<canvas id=\"processingCanvas\" style=\"display:none;\"></canvas>"; html += "<script>"; // --- CHANGE 1: Increased max dimension for more detail --- html += "const MAX_DIMENSION = 1200;"; html += "let canvas = document.getElementById('processingCanvas');"; html += "let ctx = canvas.getContext('2d');"; html += "function scanUploadedCode(fieldId, input) {"; html += " if (!input.files || !input.files[0]) { return; }"; html += " let reader = new FileReader();"; html += " reader.onload = function(e) {"; html += " let img = new Image();"; html += " img.onload = function() {"; html += " let width = img.width, height = img.height;"; html += " if (width > MAX_DIMENSION || height > MAX_DIMENSION) {"; html += " if (width > height) { height *= MAX_DIMENSION / width; width = MAX_DIMENSION; }"; html += " else { width *= MAX_DIMENSION / height; height = MAX_DIMENSION; }"; html += " }"; html += " canvas.width = width; canvas.height = height;"; html += " ctx.drawImage(img, 0, 0, width, height);"; html += " const resizedImageUrl = canvas.toDataURL();"; // --- CHANGE 2: Give the scanner hints to improve accuracy --- html += " const hints = new Map();"; html += " const formats = [ZXing.BarcodeFormat.QR_CODE, ZXing.BarcodeFormat.EAN_13, ZXing.BarcodeFormat.CODE_128, ZXing.BarcodeFormat.DATA_MATRIX, ZXing.BarcodeFormat.ITF, ZXing.BarcodeFormat.PDF_417, ZXing.BarcodeFormat.UPC_A];"; html += " hints.set(ZXing.DecodeHintType.POSSIBLE_FORMATS, formats);"; html += " const codeReader = new ZXing.BrowserMultiFormatReader(hints);"; html += " codeReader.decodeFromImageUrl(resizedImageUrl)"; html += " .then(result => {"; html += " if (result && result.getText()) {"; html += " console.log('Code found:', result.getText());"; html += " document.getElementById(fieldId).value = result.getText();"; html += " }"; html += " })"; html += " .catch(err => {"; html += " console.error('Scan failed:', err);"; html += " alert('Could not read any code. Please use a clear, well-lit image taken straight-on.');"; html += " });"; html += " };"; html += " img.src = e.target.result;"; html += " };"; html += " reader.readAsDataURL(input.files[0]);"; html += "}"; html += "function sendData(formId, url, feedbackId) { var form=document.getElementById(formId); var data=new FormData(form); var f=document.getElementById(feedbackId); f.textContent='Sending...'; f.classList.add('show'); fetch(url,{method:'POST',body:new URLSearchParams(data)}).then(r=>{if(r.ok){f.textContent='Sent!';}else{f.textContent='Error!';} setTimeout(()=>{f.classList.remove('show');f.textContent='';},2000);}).catch(e=>alert('Network error: '+e)); }"; html += "function sendNav(url, feedbackId) { var f=document.getElementById(feedbackId); f.textContent='Sent!'; f.classList.add('show'); fetch(url,{method:'POST'}).then(r=>{if(!r.ok){alert('Error sending nav command');} setTimeout(()=>{f.classList.remove('show');f.textContent='';},1000);}).catch(e=>alert('Network error: '+e)); }"; html += "</script>"; html += "</div></body></html>"; server.send(200, "text/html", html); } void setup() { Serial.begin(9600); mySerial.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN); WiFi.begin(ssid, password); Serial.print("Connecting to WiFi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWiFi connected."); Serial.print("IP address: "); Serial.println(WiFi.localIP()); pinMode(8, OUTPUT); server.on("/", handleRoot); server.on("/write_serial", HTTP_POST, handleWriteSerialNumber); server.on("/write_build", HTTP_POST, handleWriteBuildId); server.on("/write_feature", HTTP_POST, handleWriteFeatureByte); server.on("/arrow_left", HTTP_POST, handleArrowLeft); server.on("/arrow_right", HTTP_POST, handleArrowRight); server.on("/arrow_up", HTTP_POST, handleArrowUp); server.on("/arrow_down", HTTP_POST, handleArrowDown); server.on("/arrow_enter", HTTP_POST, handleArrowEnter); server.begin(); Serial.println("HTTP server started"); } void loop() { server.handleClient(); }