|
| 1 | +// SPDX-FileCopyrightText: 2024 John Park for Adafruit Industries |
| 2 | +// |
| 3 | +// SPDX-License-Identifier: MIT |
| 4 | +/* |
| 5 | + * Feather ESP32 Bluetooth LE gamepad https://github.com/lemmingDev/ESP32-BLE-Gamepad |
| 6 | + * Deep sleep with wake on START button press |
| 7 | + * https://randomnerdtutorials.com/esp32-deep-sleep-arduino-ide-wake-up-sources/ |
| 8 | +
|
| 9 | + * OTA WiFi uploads |
| 10 | + * https://docs.espressif.com/projects/arduino-esp32/en/latest/ota_web_update.html |
| 11 | + * Sketch > Compile binary, then http://esp32.local/?userid=admin&pwd=admin |
| 12 | + * pick compiled .bin, upload. |
| 13 | + */ |
| 14 | + |
| 15 | +#include <Arduino.h> |
| 16 | +#include <BleGamepad.h> |
| 17 | +#include <Adafruit_NeoPixel.h> |
| 18 | + |
| 19 | +#include <WiFi.h> |
| 20 | +#include <WiFiClient.h> |
| 21 | +#include <WebServer.h> |
| 22 | +#include <ESPmDNS.h> |
| 23 | +#include <Update.h> |
| 24 | + |
| 25 | +const char* host = "esp32"; |
| 26 | +const char* ssid = "xxxxxx"; // your WiFi SSID here |
| 27 | +const char* password = "xxxxxx"; // your WiFi password here |
| 28 | +WebServer server(80); |
| 29 | + |
| 30 | +/* |
| 31 | + * Login page |
| 32 | + */ |
| 33 | + |
| 34 | +const char* loginIndex = |
| 35 | + "<form name='loginForm'>" |
| 36 | + "<table width='20%' bgcolor='A09F9F' align='center'>" |
| 37 | + "<tr>" |
| 38 | + "<td colspan=2>" |
| 39 | + "<center><font size=4><b>ESP32 Login Page</b></font></center>" |
| 40 | + "<br>" |
| 41 | + "</td>" |
| 42 | + "<br>" |
| 43 | + "<br>" |
| 44 | + "</tr>" |
| 45 | + "<tr>" |
| 46 | + "<td>Username:</td>" |
| 47 | + "<td><input type='text' size=25 name='userid'><br></td>" |
| 48 | + "</tr>" |
| 49 | + "<br>" |
| 50 | + "<br>" |
| 51 | + "<tr>" |
| 52 | + "<td>Password:</td>" |
| 53 | + "<td><input type='Password' size=25 name='pwd'><br></td>" |
| 54 | + "<br>" |
| 55 | + "<br>" |
| 56 | + "</tr>" |
| 57 | + "<tr>" |
| 58 | + "<td><input type='submit' onclick='check(this.form)' value='Login'></td>" |
| 59 | + "</tr>" |
| 60 | + "</table>" |
| 61 | +"</form>" |
| 62 | +"<script>" |
| 63 | + "function check(form)" |
| 64 | + "{" |
| 65 | + "if(form.userid.value=='admin' && form.pwd.value=='admin')" |
| 66 | + "{" |
| 67 | + "window.open('/serverIndex')" |
| 68 | + "}" |
| 69 | + "else" |
| 70 | + "{" |
| 71 | + " alert('Error Password or Username')/*displays error message*/" |
| 72 | + "}" |
| 73 | + "}" |
| 74 | +"</script>"; |
| 75 | + |
| 76 | +/* |
| 77 | + * Server Index Page |
| 78 | + */ |
| 79 | + |
| 80 | +const char* serverIndex = |
| 81 | +"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>" |
| 82 | +"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>" |
| 83 | + "<input type='file' name='update'>" |
| 84 | + "<input type='submit' value='Update'>" |
| 85 | + "</form>" |
| 86 | + "<div id='prg'>progress: 0%</div>" |
| 87 | + "<script>" |
| 88 | + "$('form').submit(function(e){" |
| 89 | + "e.preventDefault();" |
| 90 | + "var form = $('#upload_form')[0];" |
| 91 | + "var data = new FormData(form);" |
| 92 | + " $.ajax({" |
| 93 | + "url: '/update'," |
| 94 | + "type: 'POST'," |
| 95 | + "data: data," |
| 96 | + "contentType: false," |
| 97 | + "processData:false," |
| 98 | + "xhr: function() {" |
| 99 | + "var xhr = new window.XMLHttpRequest();" |
| 100 | + "xhr.upload.addEventListener('progress', function(evt) {" |
| 101 | + "if (evt.lengthComputable) {" |
| 102 | + "var per = evt.loaded / evt.total;" |
| 103 | + "$('#prg').html('progress: ' + Math.round(per*100) + '%');" |
| 104 | + "}" |
| 105 | + "}, false);" |
| 106 | + "return xhr;" |
| 107 | + "}," |
| 108 | + "success:function(d, s) {" |
| 109 | + "console.log('success!')" |
| 110 | + "}," |
| 111 | + "error: function (a, b, c) {" |
| 112 | + "}" |
| 113 | + "});" |
| 114 | + "});" |
| 115 | + "</script>"; |
| 116 | + |
| 117 | +////////////////////////////////////// GAMEPAD |
| 118 | +#define numOfButtons 12 |
| 119 | +// sleep wake button definition (also update line in setup(): 'esp_sleep_enable_ext0_wakeup(GPIO_NUM_4,0);') |
| 120 | +#define BUTTON_PIN_BITMASK 0x10 // start button on RTC GPIO pin 4 which is 0x10 (2^4 in hex) |
| 121 | +RTC_DATA_ATTR int bootCount = 0; |
| 122 | + |
| 123 | +BleGamepad bleGamepad("ItsyController", "Adafruit", 100); // name, manufacturer, batt level to start |
| 124 | +byte previousButtonStates[numOfButtons]; |
| 125 | +byte currentButtonStates[numOfButtons]; |
| 126 | + |
| 127 | +// ItsyBitsy EPS32: 13, 12, 14, 33, 32, 7, 5, 27, 15, 20, 8, 22, 21, 19, 36, 37, 38, 4, 26, 25 |
| 128 | +// RTC IO: 13, 12, 14, 33, 32, 27, 15, 36, 37, 38, 4, 26, 25 |
| 129 | +// pins that act funny: 5, 37, 22 |
| 130 | +byte buttonPins[numOfButtons] = { 13, 12, 14, 33, 32, 7, 27, 15, 21, 19, 4, 26 }; // ItsyBitsy |
| 131 | +byte physicalButtons[numOfButtons] = { 1, 2, 4, 5, 7, 8, 15, 16, 13, 14, 12, 11 }; // controller assignments |
| 132 | +// b0, b1, b3, b4, b6, b7, b14, b15, b12, b13, b10, b11 |
| 133 | +// gampad: O/b0, X/b1, ^/b3, []]/b4, l_trig/b6, r_trig/b7, up/b14 , down/b15 , left/b12 , right/b13, select/b11, start/b10 |
| 134 | + |
| 135 | +int last_button_press = millis(); |
| 136 | +int sleepTime = 30000; // how long is it inactive before going to sleep |
| 137 | + |
| 138 | +// void print_wakeup_reason(){ |
| 139 | +// esp_sleep_wakeup_cause_t wakeup_reason; |
| 140 | +// wakeup_reason = esp_sleep_get_wakeup_cause(); |
| 141 | +// switch(wakeup_reason) |
| 142 | +// { |
| 143 | +// case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break; |
| 144 | +// case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break; |
| 145 | +// case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break; |
| 146 | +// case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break; |
| 147 | +// case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break; |
| 148 | +// default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break; |
| 149 | +// } |
| 150 | +// } |
| 151 | + |
| 152 | +Adafruit_NeoPixel pixel(1, 0, NEO_GRB + NEO_KHZ800); // Itsy on-board NeoPixel |
| 153 | + |
| 154 | +void setup() |
| 155 | +{ |
| 156 | + Serial.begin(115200); |
| 157 | + delay(500); |
| 158 | + |
| 159 | + //Print the wakeup reason for ESP32 |
| 160 | + // print_wakeup_reason(); |
| 161 | + esp_sleep_enable_ext0_wakeup(GPIO_NUM_4,0); //1 = High, 0 = Low |
| 162 | + |
| 163 | + for (byte currentPinIndex = 0; currentPinIndex < numOfButtons; currentPinIndex++) |
| 164 | + { |
| 165 | + pinMode(buttonPins[currentPinIndex], INPUT_PULLUP); |
| 166 | + previousButtonStates[currentPinIndex] = HIGH; |
| 167 | + currentButtonStates[currentPinIndex] = HIGH; |
| 168 | + } |
| 169 | + |
| 170 | + bleGamepad.begin(); |
| 171 | + delay(100); |
| 172 | + pixel.begin(); |
| 173 | + pixel.clear(); |
| 174 | + |
| 175 | + // Connect to WiFi network |
| 176 | + WiFi.begin(ssid, password); |
| 177 | + Serial.println(""); |
| 178 | + |
| 179 | + // Wait for connection |
| 180 | + while (WiFi.status() != WL_CONNECTED) { |
| 181 | + delay(500); |
| 182 | + Serial.print("."); |
| 183 | + } |
| 184 | + Serial.println(""); |
| 185 | + Serial.print("Connected to "); |
| 186 | + Serial.println(ssid); |
| 187 | + Serial.print("IP address: "); |
| 188 | + Serial.println(WiFi.localIP()); |
| 189 | + |
| 190 | + /*use mdns for host name resolution*/ |
| 191 | + if (!MDNS.begin(host)) { //http://esp32.local |
| 192 | + Serial.println("Error setting up MDNS responder!"); |
| 193 | + while (1) { |
| 194 | + delay(1000); |
| 195 | + } |
| 196 | + } |
| 197 | + Serial.println("mDNS responder started"); |
| 198 | + /*return index page which is stored in serverIndex */ |
| 199 | + server.on("/", HTTP_GET, []() { |
| 200 | + server.sendHeader("Connection", "close"); |
| 201 | + server.send(200, "text/html", loginIndex); |
| 202 | + }); |
| 203 | + server.on("/serverIndex", HTTP_GET, []() { |
| 204 | + server.sendHeader("Connection", "close"); |
| 205 | + server.send(200, "text/html", serverIndex); |
| 206 | + }); |
| 207 | + /*handling uploading firmware file */ |
| 208 | + server.on("/update", HTTP_POST, []() { |
| 209 | + server.sendHeader("Connection", "close"); |
| 210 | + server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); |
| 211 | + ESP.restart(); |
| 212 | + }, []() { |
| 213 | + HTTPUpload& upload = server.upload(); |
| 214 | + if (upload.status == UPLOAD_FILE_START) { |
| 215 | + Serial.printf("Update: %s\n", upload.filename.c_str()); |
| 216 | + if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size |
| 217 | + Update.printError(Serial); |
| 218 | + } |
| 219 | + } else if (upload.status == UPLOAD_FILE_WRITE) { |
| 220 | + /* flashing firmware to ESP*/ |
| 221 | + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { |
| 222 | + Update.printError(Serial); |
| 223 | + } |
| 224 | + } else if (upload.status == UPLOAD_FILE_END) { |
| 225 | + if (Update.end(true)) { //true to set the size to the current progress |
| 226 | + Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); |
| 227 | + } else { |
| 228 | + Update.printError(Serial); |
| 229 | + } |
| 230 | + } |
| 231 | + }); |
| 232 | + server.begin(); |
| 233 | + |
| 234 | +} |
| 235 | + |
| 236 | +void loop() |
| 237 | +{ |
| 238 | + server.handleClient(); |
| 239 | + delay(1); |
| 240 | + |
| 241 | + if (bleGamepad.isConnected()) |
| 242 | + { |
| 243 | + pixel.setPixelColor(0, 0x000033); |
| 244 | + pixel.show(); |
| 245 | + |
| 246 | + for (byte currentIndex = 0; currentIndex < numOfButtons; currentIndex++) |
| 247 | + { |
| 248 | + currentButtonStates[currentIndex] = digitalRead(buttonPins[currentIndex]); |
| 249 | + |
| 250 | + if (currentButtonStates[currentIndex] != previousButtonStates[currentIndex]) |
| 251 | + { |
| 252 | + last_button_press = millis(); // update last_button_press for sleep timing |
| 253 | + |
| 254 | + if (currentButtonStates[currentIndex] == LOW) |
| 255 | + { |
| 256 | + bleGamepad.press(physicalButtons[currentIndex]); |
| 257 | + } |
| 258 | + else |
| 259 | + { |
| 260 | + bleGamepad.release(physicalButtons[currentIndex]); |
| 261 | + } |
| 262 | + } |
| 263 | + } |
| 264 | + |
| 265 | + if (currentButtonStates != previousButtonStates) |
| 266 | + { |
| 267 | + for (byte currentIndex = 0; currentIndex < numOfButtons; currentIndex++) |
| 268 | + { |
| 269 | + previousButtonStates[currentIndex] = currentButtonStates[currentIndex]; |
| 270 | + } |
| 271 | + |
| 272 | + bleGamepad.sendReport(); |
| 273 | + } |
| 274 | + if (millis() - last_button_press > sleepTime) { |
| 275 | + esp_deep_sleep_start(); |
| 276 | + } |
| 277 | + } |
| 278 | +} |
0 commit comments