Servo control through key press on webpage
Posted: Thu Dec 12, 2019 4:25 am
Hello, I am also another noob trying to tackle the WiFi RC car project. I believe I am in the home stretch with the project but having trouble getting the servo control to work smoothly. I will do my best to explain it simply as that is the only way I understand it (explained like i am five).
So firstly, most of my code is based off of the sparkfun tutorial (https://learn.sparkfun.com/tutorials/wi ... t-software). I have tweaked it here and there, but the idea is the same. I wish to control the car through a webpage with arrow keys. I am only using 1 DC motor (up/forward and down/backward) and 1 servo (left turn and right turn).
The DC motor code is simple. The webpage only needs to loop through twice (when key down, run motor, there is no change in speed or anything else even if the key is continuously held, when key up, stop). Props to the sparkfun tutorial for using the "sendCommand" variable.
The servo motor is different. If the left or right arrow is just quickly tapped. It will operate just like the DC motor, read key down, move towards 10 deg(R) or 170 deg(L) by 1 degree and stop at key up. The issue arises when the key is held down. I have taken out the "sendCommand" variable for L/R because when pressed, the webpage needs to let the servo know (arrow key(webpage)->8266->servo) to increase/decrease the angle incrementally. So the webpage needs to constantly read the key state (key up or key down) for the left and right function to update the servo. When I press the L or R key, I can see the "readings" (F12) on the webpage. I also have serial.prints on the serial monitor to monitor the response to the servo. The response time is negligible for DC motor control (Up and down key). Tapping the L or R key also provides an instant response. However, holding the L or R key for a while queues a list of "readings" on the webpage. This long queue takes a while to process and thus delays the servo to move on the other end. For example, if I hold the L or R key for 5 seconds, the queue list would show and slowly process all the while moving the servo incrementally. When I let go of the key, the queue is still processing and thus the servo would still continue to move long after I have lifted off the key in an attempt to finish up its given commands. This is the main issue I am facing. A secondary problem is that when key is held down, a constant stream of "readings" stating L-pressed or R-pressed is shown on the webpage. The serial monitor would show an increase/decrease in degree input but would have the order wrong. I have posted screen shots of this below.
Readings such as: L-Presed, L-Pressed, L-Pressed, L-Pressed, L-Pressed, L-Pressed, L-Pressed, L-Stop.
Serial monitor output: L-Pressed, L-Pressed, L-Pressed, L-Stop, L-Pressed, L-Pressed, L-Pressed, L-Pressed.
My goal is to have the servo continue turning as long as the key is pressed but stop immediately upon key release (just like the DC motor). Any advice or work around with what I have? Any opinion is greatly appreciated!
Screenshots:
https://ibb.co/vdq11w2
https://ibb.co/YfsN96G
https://ibb.co/qkvJnRy
So firstly, most of my code is based off of the sparkfun tutorial (https://learn.sparkfun.com/tutorials/wi ... t-software). I have tweaked it here and there, but the idea is the same. I wish to control the car through a webpage with arrow keys. I am only using 1 DC motor (up/forward and down/backward) and 1 servo (left turn and right turn).
The DC motor code is simple. The webpage only needs to loop through twice (when key down, run motor, there is no change in speed or anything else even if the key is continuously held, when key up, stop). Props to the sparkfun tutorial for using the "sendCommand" variable.
The servo motor is different. If the left or right arrow is just quickly tapped. It will operate just like the DC motor, read key down, move towards 10 deg(R) or 170 deg(L) by 1 degree and stop at key up. The issue arises when the key is held down. I have taken out the "sendCommand" variable for L/R because when pressed, the webpage needs to let the servo know (arrow key(webpage)->8266->servo) to increase/decrease the angle incrementally. So the webpage needs to constantly read the key state (key up or key down) for the left and right function to update the servo. When I press the L or R key, I can see the "readings" (F12) on the webpage. I also have serial.prints on the serial monitor to monitor the response to the servo. The response time is negligible for DC motor control (Up and down key). Tapping the L or R key also provides an instant response. However, holding the L or R key for a while queues a list of "readings" on the webpage. This long queue takes a while to process and thus delays the servo to move on the other end. For example, if I hold the L or R key for 5 seconds, the queue list would show and slowly process all the while moving the servo incrementally. When I let go of the key, the queue is still processing and thus the servo would still continue to move long after I have lifted off the key in an attempt to finish up its given commands. This is the main issue I am facing. A secondary problem is that when key is held down, a constant stream of "readings" stating L-pressed or R-pressed is shown on the webpage. The serial monitor would show an increase/decrease in degree input but would have the order wrong. I have posted screen shots of this below.
Readings such as: L-Presed, L-Pressed, L-Pressed, L-Pressed, L-Pressed, L-Pressed, L-Pressed, L-Stop.
Serial monitor output: L-Pressed, L-Pressed, L-Pressed, L-Stop, L-Pressed, L-Pressed, L-Pressed, L-Pressed.
My goal is to have the servo continue turning as long as the key is pressed but stop immediately upon key release (just like the DC motor). Any advice or work around with what I have? Any opinion is greatly appreciated!
Screenshots:
https://ibb.co/vdq11w2
https://ibb.co/YfsN96G
https://ibb.co/qkvJnRy
Code: Select all
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>
#include <Servo.h>
ESP8266WiFiMulti wifiMulti; // Create an instance of the ESP8266WiFiMulti class, called 'wifiMulti'
ESP8266WebServer server(80); // Create a webserver object that listens for HTTP request on port 80
const char index_html[] PROGMEM={"<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n\n <script type=\"text/javascript\">\n var sendCommand = 1;\n\n//Key Pressed\n var keys = {\"38\": false, \"40\": false, \"37\": false, \"39\": false};\n\n document.addEventListener(\"keydown\", keyDownHandler, false);\n document.addEventListener(\"keyup\", keyUpHandler, false);\n\n function keyDownHandler(evt) {\n if(evt.keyCode == 38 && sendCommand) {\n sendData(\"U-Pressed\"); \n sendCommand = 0;\n keys[evt.keyCode] = true;\n }\n else if(evt.keyCode == 40 && sendCommand) {\n sendData(\"D-Pressed\");\n sendCommand = 0;\n keys[evt.keyCode] = true;\n }\n if(evt.keyCode == 37) { //sendcommands taken out for LR because it needs \n sendData(\"L-Pressed\"); //constant signal to webpage for new angle input while DC motors need 1 ON/OFF\n keys[evt.keyCode] = true;\n }\n else if(evt.keyCode == 39) {\n sendData(\"R-Pressed\");\n keys[evt.keyCode] = true;\n }\n }\n\n function keyUpHandler(evt) {\n if(evt.keyCode == 38) {\n sendData(\"U/D-Stop\"); \n keys[evt.keyCode] = false;\n sendCommand = 1;\n }\n else if(evt.keyCode == 40) {\n sendData(\"U/D-Stop\");\n keys[evt.keyCode] = false;\n sendCommand = 1;\n }\n if(evt.keyCode == 37) {\n sendData(\"L/R-Stop\");\n keys[evt.keyCode] = false;\n sendCommand2 = 1;\n }\n else if(evt.keyCode == 39) {\n sendData(\"L/R-Stop\");\n keys[evt.keyCode] = false;\n }\n }\n \n function sendData(motorData) {\n var xhttp = new XMLHttpRequest();\n xhttp.onreadystatechange = function() {\n if (this.readyState == 4 && this.status == 200) {\n }\n };\n xhttp.open(\"GET\", \"setMotors?motorState=\"+motorData, true);\n xhttp.send();\n }\n\n </script>\n </head>\n <body>\n <h2>Node MCU v0.9 DC Motor and Servo Control page</h2>\n <br/>\n <ul> \n <li>Use the arrow keys send commands to the motors.</li>\n <li>When a arrow key is pressed, this page sends a XML request for the ESP32 to respond to.</li>\n <li>The ESP32 responds by sending a motor command to the Serial Controlled Motor Driver. </li>\n <li>By sending a XML request, we're able to control the motors without having to reload the page!</li>\n <li>Currently, the DC motor code works well but the Servo cannot operate smoothly.</li>\n </ul>\n </body>\n</html>"};
#define ledr D1
#define ledl D2
#define ENA D5
#define IN1 D6
#define IN2 D7
int speed = 500; // Analog DC PWM speed
Servo myservo; // Creating servo object
int pos = 90; // Starting position of Servo
static int count;
void setup(){
Serial.begin(9600); // Start the Serial communication to send messages to the computer
delay(10);
Serial.println('\n');
pinMode(ledr, OUTPUT); //turns an led on depending on whether L or R is pressed
pinMode(ledl, OUTPUT);
pinMode(ENA, OUTPUT);
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
myservo.attach(D3);
myservo.write(pos);
//Establish and Enable WiFi connection-----------------------------------------------------------
wifiMulti.addAP("username1", "password1"); // add Wi-Fi networks you want to connect to
wifiMulti.addAP("username2", "password2");
wifiMulti.addAP("username3", "password3");
Serial.println("Connecting ...");
int i = 0;
while (wifiMulti.run() != WL_CONNECTED) { // Wait for the Wi-Fi to connect: scan for Wi-Fi networks, and connect to the strongest of the networks above
delay(250);
Serial.print('.');
}
Serial.println('\n');
Serial.print("Connected to ");
Serial.println(WiFi.SSID()); // Tell us what network we're connected to
Serial.print("IP address:\t");
Serial.println(WiFi.localIP()); // Send the IP address of the ESP8266 to the computer
if (MDNS.begin("esp8266")) { // Start the mDNS responder for esp8266.local
Serial.println("mDNS responder started");
}
else {
Serial.println("Error setting up MDNS responder!");
}
//-----------------------------------------------------------------------------------------------
server.on("/", handleRoot); // Call the 'handleRoot' function when a client requests URI "/"
server.on("/setMotors", handleMotors);
server.onNotFound(handleNotFound); // When a client requests an unknown URI (i.e. something other than "/"), call function "handleNotFound"
server.begin(); // Actually start the server
Serial.println("HTTP server started");
}
void loop(){
server.handleClient(); // Listen for HTTP requests from clients
}
void handleRoot() {
server.send(200, "text/html", index_html);
}
void handleMotors() {
String motorState = "OFF";
String t_state = server.arg("motorState"); //Refer xhttp.open("GET", "setMotors?motorState="+motorData, true);
Serial.println("----------------------------------");
Serial.println(t_state);
if(t_state.startsWith("U-Pressed")) { //Drive Forward (UP Arrow)
Serial.println("Moving Forward");
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
analogWrite(ENA, speed);
}
else if(t_state.startsWith("D-Pressed")) { //Reverse (DOWN Arrow)
Serial.println("Moving Backward");
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
analogWrite(ENA, speed);
}
if(t_state.startsWith("R-Pressed")) { //Turn Right (Right Arrow)
myservo.attach(D3);
count ++;
Serial.print("Count: ");
Serial.println(count);
if(pos>0) {
for(int i=count-1; i<count; i++){
Serial.println("Servo counterclockwise R");
digitalWrite(ledr, HIGH);
pos--;
Serial.print("R turn: ");
Serial.println(pos);
myservo.write(pos);
myservo.detach();
}
}
else {
myservo.detach();
t_state = "L/R Stop";
count=0;
}
}
else if(t_state.startsWith("L-Pressed")) { //Turn Left (LEFT Arrow)
myservo.attach(D3);
count ++;
Serial.print("Count: ");
Serial.println(count);
if(pos<170) {
for(int i=count-1; i<count; i++){
Serial.println("Servo counterclockwise L");
digitalWrite(ledl, HIGH);
pos++;
Serial.print("L turn: ");
Serial.println(pos);
myservo.write(pos);
myservo.detach();
}
}
else {
myservo.detach();
t_state = "L/R-Stop";
count=0;
}
}
if(t_state.startsWith("U/D-Stop")) { //Stop if sendData(Stop) for Up and Down
Serial.println("DC Motor disabled");
digitalWrite(ENA, LOW);
delay(50);
}
if(t_state.startsWith("L/R-Stop")) { //Stop if sendData(Stop) for Left and Right
Serial.println("Servo disabled");
digitalWrite(ledl, LOW);
digitalWrite(ledr, LOW);
myservo.detach();
count = 0;
Serial.print("Ending count: ");
Serial.println(count);
}
Serial.println("----------------------------------");
server.send(200, "text/plain", motorState); //Send web page
}
void handleNotFound(){
server.send(404, "text/plain", "404: Not found"); // Send HTTP status 404 (Not Found) when there's no handler for the URI in the request
}
//------------------------------------------HTML CODE---------------------------------------------------//
/* Text -> C/C++ converted above in index_html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript">
var sendCommand = 1;
//Key Pressed
var keys = {"38": false, "40": false, "37": false, "39": false};
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
function keyDownHandler(evt) {
if(evt.keyCode == 38 && sendCommand) {
sendData("U-Pressed");
sendCommand = 0;
keys[evt.keyCode] = true;
}
else if(evt.keyCode == 40 && sendCommand) {
sendData("D-Pressed");
sendCommand = 0;
keys[evt.keyCode] = true;
}
if(evt.keyCode == 37) { //sendcommands taken out for LR because it needs
sendData("L-Pressed"); //constant signal to webpage for new angle input while DC motors need 1 for ON/OFF
keys[evt.keyCode] = true;
}
else if(evt.keyCode == 39) {
sendData("R-Pressed");
keys[evt.keyCode] = true;
}
}
function keyUpHandler(evt) {
if(evt.keyCode == 38) {
sendData("U/D-Stop");
keys[evt.keyCode] = false;
sendCommand = 1;
}
else if(evt.keyCode == 40) {
sendData("U/D-Stop");
keys[evt.keyCode] = false;
sendCommand = 1;
}
if(evt.keyCode == 37) {
sendData("L/R-Stop");
keys[evt.keyCode] = false;
sendCommand2 = 1;
}
else if(evt.keyCode == 39) {
sendData("L/R-Stop");
keys[evt.keyCode] = false;
}
}
function sendData(motorData) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
}
};
xhttp.open("GET", "setMotors?motorState="+motorData, true);
xhttp.send();
}
</script>
</head>
<body>
<h2>Node MCU v0.9 DC Motor and Servo Control page</h2>
<br/>
<ul>
<li>Use the arrow keys send commands to the motors.</li>
<li>When a arrow key is pressed, this page sends a XML request for the ESP32 to respond to.</li>
<li>The ESP32 responds by sending a motor command to the Serial Controlled Motor Driver. </li>
<li>By sending a XML request, we're able to control the motors without having to reload the page!</li>
<li>Currently, the DC motor code works well but the Servo cannot operate smoothly.</li>
</ul>
</body>
</html>
*/