Created
November 25, 2019 16:17
-
-
Save Xfennec/e1215febb15b40c21bf029b38a31640b to your computer and use it in GitHub Desktop.
ESP8266 RC car with Websocket and Gamepad API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //#include <Arduino.h> | |
| #include <ESP8266WiFi.h> | |
| #include <ESP8266WiFiMulti.h> | |
| #include <WebSocketsServer.h> | |
| #include <ESP8266WebServer.h> | |
| #include <ESP8266mDNS.h> | |
| #include <Hash.h> | |
| #define USE_SERIAL Serial | |
| ESP8266WiFiMulti WiFiMulti; | |
| ESP8266WebServer server(80); | |
| WebSocketsServer webSocket = WebSocketsServer(81); | |
| // uses the L293D chip for the motor | |
| #define MOTOR1_ENABLE D5 | |
| #define MOTOR1_DIRA D3 | |
| #define MOTOR1_DIRB D4 | |
| #define MOTOR2_ENABLE D6 | |
| #define MOTOR2_DIRA D7 | |
| #define MOTOR2_DIRB D8 | |
| #define MAX_DAC_VAL 1023 | |
| float currentSteering = 0; | |
| float currentThrottle = 0; | |
| String html = R"=====( | |
| <html> | |
| <head> | |
| <script> | |
| var ws = new WebSocket('ws://'+location.hostname+':81/', ['arduino']); | |
| ws.onopen = function () { | |
| document.getElementById('status').innerHTML = 'connected'; | |
| //ws.send('Connect ' + new Date()); | |
| }; | |
| ws.onclose = function () { | |
| document.getElementById('status').innerHTML = 'closed'; | |
| }; | |
| ws.onerror = function (error) { | |
| document.getElementById('status').innerHTML = 'error'; | |
| console.log('WebSocket Error ', error); | |
| }; | |
| ws.onmessage = function (e) { | |
| console.log('Server: ', e.data); | |
| }; | |
| function sendData(data) { | |
| console.log('data', data); | |
| ws.send(data.throttle + '/' + data.steering); | |
| } | |
| var haveEvents = 'GamepadEvent' in window; | |
| var haveWebkitEvents = 'WebKitGamepadEvent' in window; | |
| var controllers = {}; | |
| var controls = { | |
| steering: 0, | |
| throttle: 0, | |
| } | |
| var rAF = window.mozRequestAnimationFrame || | |
| window.webkitRequestAnimationFrame || | |
| window.requestAnimationFrame; | |
| function connecthandler(e) { | |
| addgamepad(e.gamepad); | |
| } | |
| function addgamepad(gamepad) { | |
| controllers[gamepad.index] = gamepad; | |
| rAF(updateStatus); | |
| } | |
| function disconnecthandler(e) { | |
| removegamepad(e.gamepad); | |
| } | |
| function removegamepad(gamepad) { | |
| delete controllers[gamepad.index]; | |
| } | |
| function updateStatus() { | |
| scangamepads(); | |
| var throttle = 0; | |
| var needUpdate = false; | |
| for (j in controllers) { | |
| var controller = controllers[j]; | |
| for (var i=0; i<controller.buttons.length; i++) { | |
| var val = controller.buttons[i]; | |
| var pressed = val == 1.0; | |
| if (typeof(val) == "object") { | |
| pressed = val.pressed; | |
| val = val.value; | |
| } | |
| //if (pressed) console.log(i, val, pressed); | |
| if (i == 6) { | |
| throttle -= val; | |
| } | |
| if (i == 7) { | |
| throttle += val; | |
| } | |
| } | |
| throttle = throttle.toFixed(4); | |
| if (throttle != controls.throttle) { | |
| needUpdate = true; | |
| controls.throttle = throttle; | |
| } | |
| for (var i=0; i<controller.axes.length; i++) { | |
| //console.log(i + ": " + controller.axes[i].toFixed(4)); | |
| if (i == 0) { | |
| d = controller.axes[i].toFixed(4); | |
| if (d != controls.steering) { | |
| needUpdate = true; | |
| controls.steering = d; | |
| } | |
| } | |
| } | |
| } | |
| if (needUpdate) { | |
| sendData(controls); | |
| console.log(controls); | |
| } | |
| rAF(updateStatus); | |
| } | |
| function scangamepads() { | |
| var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []); | |
| for (var i = 0; i < gamepads.length; i++) { | |
| if (gamepads[i]) { | |
| if (!(gamepads[i].index in controllers)) { | |
| addgamepad(gamepads[i]); | |
| } else { | |
| controllers[gamepads[i].index] = gamepads[i]; | |
| } | |
| } | |
| } | |
| } | |
| if (haveEvents) { | |
| window.addEventListener("gamepadconnected", connecthandler); | |
| window.addEventListener("gamepaddisconnected", disconnecthandler); | |
| } else if (haveWebkitEvents) { | |
| window.addEventListener("webkitgamepadconnected", connecthandler); | |
| window.addEventListener("webkitgamepaddisconnected", disconnecthandler); | |
| } else { | |
| setInterval(scangamepads, 500); | |
| } | |
| </script> | |
| </head> | |
| <body> | |
| RC Car status: <span id="status">unknown</span> | |
| </body> | |
| </html> | |
| )====="; | |
| #define BUFF_LEN 128 | |
| void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { | |
| char data[BUFF_LEN]; | |
| char *sep; | |
| switch (type) { | |
| case WStype_DISCONNECTED: | |
| USE_SERIAL.printf("[%u] Disconnected!\n", num); | |
| break; | |
| case WStype_CONNECTED: { | |
| IPAddress ip = webSocket.remoteIP(num); | |
| USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); | |
| // send message to client | |
| webSocket.sendTXT(num, "Connected"); | |
| } | |
| break; | |
| case WStype_TEXT: | |
| //USE_SERIAL.printf("[%u] get Text: %s\n", num, payload); | |
| strncpy(data, (char *)payload, BUFF_LEN); | |
| data[BUFF_LEN-1] = 0; | |
| sep = strchr(data, '/'); | |
| *sep = 0; | |
| currentThrottle = atof(data); | |
| currentSteering = atof(sep + 1); | |
| //USE_SERIAL.printf("%f - %f\n", currentThrottle, currentSteering); | |
| break; | |
| } | |
| } | |
| void setup() { | |
| //USE_SERIAL.begin(921600); | |
| USE_SERIAL.begin(115200); | |
| //USE_SERIAL.begin(9600); | |
| //USE_SERIAL.setDebugOutput(true); | |
| USE_SERIAL.println(); | |
| USE_SERIAL.println(); | |
| USE_SERIAL.println(); | |
| //---set pin directions | |
| pinMode(MOTOR1_ENABLE,OUTPUT); | |
| pinMode(MOTOR1_DIRA,OUTPUT); | |
| pinMode(MOTOR1_DIRB,OUTPUT); | |
| pinMode(MOTOR2_ENABLE,OUTPUT); | |
| pinMode(MOTOR2_DIRA,OUTPUT); | |
| pinMode(MOTOR2_DIRB,OUTPUT); | |
| WiFiMulti.addAP("SSID", "passphrase"); | |
| while (WiFiMulti.run() != WL_CONNECTED) { | |
| delay(1000); | |
| } | |
| Serial.print("IP address: "); | |
| Serial.println(WiFi.localIP()); | |
| // start webSocket server | |
| webSocket.begin(); | |
| webSocket.onEvent(webSocketEvent); | |
| if (MDNS.begin("esp")) { | |
| USE_SERIAL.println("MDNS responder started"); | |
| } | |
| // handle index | |
| server.on("/", []() { | |
| // send index.html | |
| server.send(200, "text/html", html.c_str()); | |
| }); | |
| server.begin(); | |
| } | |
| void loop() { | |
| static float steering; | |
| static float throttle; | |
| if (currentSteering != steering) { | |
| steering = currentSteering; | |
| analogWrite(MOTOR2_ENABLE, abs(steering * MAX_DAC_VAL)); | |
| if (steering > 0) { | |
| digitalWrite(MOTOR2_DIRA,HIGH); | |
| digitalWrite(MOTOR2_DIRB,LOW); | |
| } else { | |
| digitalWrite(MOTOR2_DIRB,HIGH); | |
| digitalWrite(MOTOR2_DIRA,LOW); | |
| } | |
| } | |
| if (currentThrottle != throttle) { | |
| throttle = currentThrottle; | |
| analogWrite(MOTOR1_ENABLE, abs(throttle * MAX_DAC_VAL)); | |
| if (throttle > 0) { | |
| digitalWrite(MOTOR1_DIRA,HIGH); | |
| digitalWrite(MOTOR1_DIRB,LOW); | |
| } else { | |
| digitalWrite(MOTOR1_DIRB,HIGH); | |
| digitalWrite(MOTOR1_DIRA,LOW); | |
| } | |
| } | |
| MDNS.update(); | |
| webSocket.loop(); | |
| server.handleClient(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment