Control servo motor through HTTP

Hi everyone!

First of all thanks for trying to help me out here, much appreciated.

Long story short, I created a webserver on my Wemos D1 mini and created 2 buttons start & stop
When pressed start the servo motor turns from 0 to 180 degrees and back from 180 to 0 degrees. Once.

Stop actually doesn’t do anything yet.
So far this works.

-Now I want to figure out a way to loop the movement of the servo motor until I press stop.
I tried many times with do/while statements but it gets stuck in the loop, so the stop commando doesn’t reach the server.

This is the code (from void loop()) to run it once. Can somebody help me trying to loop it until stop is pressed?

void loop() {

  • WiFiClient client = server.available();*

  • if (!client) {*

  • return;*

  • }*

  • Serial.println(“new client”);*

  • while(!client.available()){*

  • delay(1);*

  • }*

  • // Read the first line of the request*

  • String request = client.readStringUntil(’\r’);*

  • Serial.println(request);*

  • client.flush();*

// Match the request

if (request.indexOf("/start=1") != -1) {
client.println("Start
" );

myservo.attach(12);

for (int pos = 0; pos <= 180; pos += 180) { // goes from 0 degrees to 180 degrees
// in steps of 1 degree
myservo.write(pos); // tell servo to go to position in variable ‘pos’
delay(200);
}
for (int pos = 180; pos >= 0; pos -= 180) { // goes from 180 degrees to 0 degrees
myservo.write(pos); // tell servo to go to position in variable ‘pos’
delay(200);
}
delay(1000);
}

if (request.indexOf("/stop=1") != -1) {
myservo.detach(); // I believe this will work as stop command, let’s assume it works.
}

  • // Return the response*

  • client.println(“HTTP/1.1 200 OK”);*

  • client.println(“Content-Type: text/html”);*

  • client.println("");*

  • client.println("");*

  • client.println("");*

  • client.println("

    Test

    *
    ");

  • client.println("*
    ");

  • client.println("<a href="/start=1"">Start Motor *
    ");

  • client.println("<a href="/stop=1"">Stop Motor *
    ");

  • client.println("");*

  • delay(1);*

  • Serial.println(“Client disonnected”);*

  • Serial.println("");*

}

Again many thanks for spening time trying to help me out here.
Cheers Viskuh!

@viskuh

Your topic was Moved to it's current location / section as it is more suitable.

Could you also take a few moments to Learn How To Use The Forum.

Other general help and troubleshooting advice can be found here.
It will help you get the best out of the forum in the future.

Essentially your code needs to not block.

If you look at what loop() currently does, the arduino can only be:

  • waiting for a command from a connected client
  • updating motor position
  • sending a response to a connected client

... but it can't do more than one of these things at once because of how loop() is structured. You could say that each of those operations were "blocking", in that they're stopping the Arduino from responding to other inputs.

To get around this in order to be able to do two things at once (e.g. update motor position while waiting for a 'stop' command) you need to rework loop() to be "non-blocking". A finite state machine is a good way of achieving this. You code might have events for:

  • new client available
  • command completed
  • motor update interval elapsed

Hi Tom,

Thank you for explaining the loop() functionality. I'm new to this so very useful information.
Let me dive deeper into implementing Finite State Machine.
I'll try a few days and let everybody know what I figured out.

Viskuh

Good luck with it :slight_smile:

I forgot to reference it earlier, but this post by Nick Gammon discusses this very topic (doing more than one thing at once) and is worth a read:

http://www.gammon.com.au/forum/?id=11411

Note that Nick is talking about flashing LEDs and not dealing with motors and HTTP requests, but the approach is the important thing: i.e. letting loop run freely until it's time to do something.

Nick also has a good intro article on state machines:

http://www.gammon.com.au/statemachine

There are many different ways to code up a state machine, of course, but hopefully this gives you a starting point and helps to illustrate the technique.

Hi all,

With some help we’ve figured out a working code to create a continuous loop.
i’d like to share it here:

Cheers!


#include <ESP8266WiFi.h>
#include <Servo.h>

const char* ssid = “SSID”;
const char* password = “PASSWORD”;

int starten = 0;
WiFiServer server(80);

Servo myservo;

void setup() {
Serial.begin(115200);
delay(10);

myservo.attach(12);

// Connect to WiFi network
Serial.println();
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println(“WiFi connected”);

// Start the server
server.begin();
Serial.println(“Server started”);

// Print the IP address on serial monitor
Serial.print(“Use this URL to connect: “);
Serial.print(“http://”); //URL IP to be typed in mobile/desktop browser
Serial.print(WiFi.localIP());
Serial.println(”/”);

}

void startknop() {
Serial.println(“test”);
starten = 1;
myservo.attach(12);

for (int pos = 72; pos <= 83; pos += 6) { // goes from x degrees to x degrees
// in steps of 1 degree
myservo.write(pos); // tell servo to go to position in variable ‘pos’
delay(150); // waits 150ms for the servo to reach the position
}
for (int pos = 72; pos >= 83; pos -= 6) { // goes from x degrees to x degrees
myservo.write(pos); // tell servo to go to position in variable ‘pos’
delay(150); // waits 150ms for the servo to reach the position
}
delay(1200);
}

void stopknop() {
starten = 0;
myservo.detach();
}

void loop() {
if (starten == 1) {
startknop();
}
// Check if a client has connected
WiFiClient client = server.available();
if (!client) {
return;
}

// Wait until the client sends some data
Serial.println(“new client”);
if(!client.available()){
return;
}

// Read the first line of the request
String request = client.readStringUntil(’\r’);
Serial.println(request);
client.flush();

if (request.indexOf("/start=1") != -1) {
startknop();

}

if (request.indexOf("/stop=1") != -1) {
stopknop();

}

// Return the response

client.println(“HTTP/1.1 200 OK”);
client.println(“Content-Type: text/html”);
client.println(""); // do not forget this one
client.println("");
client.println("");
client.println("

Nodemcu Dc motor control over WiFi

“);
client.println(”

“);
client.println(”<a href="/start=1"">Start Motor
“);
client.println(”<a href="/stop=1"">Stop Motor
");

client.println("");
delay(1);
Serial.println(“Client disonnected”);
Serial.println("");
}

@viskuh

Code should be posted using code tags ( </> ) but you would have known that had you not skipped the readme you were offered when you came to the forum

Could you take a few moments to Learn How To Use The Forum.

Other general help and troubleshooting advice can be found here.
It will help you get the best out of the forum in the future.

viskuh:
Hi all,

With some help we’ve figured out a working code to create a continuous loop.
i’d like to share it here

I’m glad it’s working for you!

If you wanted to take it further, you could look at refactoring the startknop() function which is doing its timing with delay(). The startknop() function blocks for about 1.5s as written. This might not be an issue for your application.

Also I suspect the second for loop in startknop() isn’t going to do anything because of the loop conditions (it will loop while pos is >= 83, which given its initial value of 72 means the for loop won’t execute).

FWIW, here’s an example of what I mean by “non blocking”:

enum state {
  STATE_IDLE,
  STATE_SERVO_RUN_FWD,
  STATE_SERVO_RUN_BWD,
  STATE_SERVO_WAIT,
} my_state = STATE_IDLE;

enum event {
  EVENT_NULL,
  EVENT_TIMEOUT,
  EVENT_CLIENT_STOP_REQ,
  EVENT_CLIENT_START_REQ,
};

unsigned long tlast;
unsigned long tint;
uint8_t servo_pos = 0;

#define SERVO_POS_MIN             72    // degrees
#define SERVO_POS_MAX             83    // degrees
#define SERVO_POS_STEP            6     // degrees
#define SERVO_SHORT_INTERVAL      150   // ms
#define SERVO_LONG_INTERVAL       1200  // ms

void setup() {
  Serial.begin(115200);
}

enum event getClientEvent(void) {
  // This function should:
  //    1. check whether there is a client connection available, return EVENT_NULL if not
  //    2. read the client request
  //    3. send the client response, and close the client connection
  //    4. if the request ends with "/start=1", return EVENT_CLIENT_START_REQ
  //    5. if the request ends with "/stop=1", return EVENT_CLIENT_STOP_REQ
  //    6. if the request is unrecognised, return EVENT_NULL
  return EVENT_NULL;
}

void setServoPosition(uint8_t pos) {
  tlast = millis();
  // myservo.write(pos);
}

void loop() {
  enum event event = EVENT_NULL;

  // Check for a timeout event
  if (millis() - tlast > tint) {
    event = EVENT_TIMEOUT;
  } else {
    event = getClientEvent();
  }

  switch (my_state) {

    // In the idle state we're stopped and waiting for commands,
    // so we only do anything if the client requests it
    case STATE_IDLE:
      switch (event) {
        case EVENT_NULL:
        case EVENT_TIMEOUT:
        case EVENT_CLIENT_STOP_REQ:
          // Do nothing
          break;
        case EVENT_CLIENT_START_REQ:
          // Init servo state
          servo_pos = SERVO_POS_MIN;
          tint = SERVO_SHORT_INTERVAL;
          // myservo.attach(12);
          setServoPosition(servo_pos);

          // Switch to the "run fwd" state
          my_state = STATE_SERVO_RUN_FWD;
          break;
      }
      break;

    // In the run fwd/bwd states we need to update the servo position
    // each timer tick, keep track of when to reverse direction,
    // and also respond to stop requests
    case STATE_SERVO_RUN_FWD:
      switch (event) {
        case EVENT_NULL:
          break;
        case EVENT_TIMEOUT:
          // Handle direction reverse
          if (servo_pos + SERVO_POS_STEP <= SERVO_POS_MAX) {
            servo_pos += SERVO_POS_STEP;
          } else {
            servo_pos -= SERVO_POS_STEP;
            my_state = STATE_SERVO_RUN_BWD;
          }
          setServoPosition(servo_pos);
          break;
        case EVENT_CLIENT_STOP_REQ:
          // myservo.detach();
          my_state = STATE_IDLE;
          break;
        case EVENT_CLIENT_START_REQ:
          break;
      }
      break;
    case STATE_SERVO_RUN_BWD:
      switch (event) {
        case EVENT_NULL:
          break;
        case EVENT_TIMEOUT:
          if (servo_pos - SERVO_POS_STEP >= SERVO_POS_MIN) {
            servo_pos -= SERVO_POS_STEP;
          } else {
            servo_pos = SERVO_POS_MIN;
            my_state = STATE_SERVO_WAIT;
            tint = SERVO_LONG_INTERVAL;
          }
          setServoPosition(servo_pos);
          break;
        case EVENT_CLIENT_STOP_REQ:
          // myservo.detach();
          my_state = STATE_IDLE;
          break;
        case EVENT_CLIENT_START_REQ:
          break;
      }
      break;

    // In the servo wait state we're pausing for a longer delay
    // before re-initialising the servo state for another loop
    // of forward and reverse movement
    case STATE_SERVO_WAIT:
      switch (event) {
        case EVENT_NULL:
          break;
        case EVENT_TIMEOUT:
          my_state = STATE_SERVO_RUN_FWD;
          tint = SERVO_SHORT_INTERVAL;
          break;
        case EVENT_CLIENT_STOP_REQ:
          break;
        case EVENT_CLIENT_START_REQ:
          break;
      }
      break;
  }
}

I have removed the wifi/servo specifics since (a) I don’t have those libraries so I couldn’t compile the sketch to verify it, and (b) they distract from the algorithm.

This is one approach to coding a state machine. We have four variables which track the current state of the sketch:

  • tlast is the last millis() timestamp when we performed a servo position update; and tint is the interval for performing the next servo position update. Together they allow us to implement a simple timer.
  • servo_pos keeps track of the current servo position (uint8_t since that works for the values you have in your sketch, although you’d need to make it a larger type for values > 254).
  • my_state tracks what the sketch is currently doing,

The sketch can currently be doing one of four things:

  • sitting idle. The servo is stopped, and nothing will happen until a request to start is received.
  • running the servo forward
  • running the servo backward
  • sleeping at the end of a servo forward/backward loop waiting to begin again

loop() now contains no delay() calls. It simply checks to see whether it’s time to perform the next server position update, or whether there is a new client command to service.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.