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