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:

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:

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

Please take a few moments to Learn and Use The Forum

How to get the best out of this forum Installation & Troubleshooting

Hello, Welcome to the Arduino Forum. This guide explains how to get the best out of this forum. Please read and follow the instructions below. Being new here you might think this is having rules for the sake of rules, but that is not the case. If you don’t follow the guidelines all that happens is there is a long exchange of posts while we try to get you to tell us what we need in order to help you, which is frustrating for you and frustrating for us. The people who try to help with your pro…

It will help you get the very best out of the forum in the future.

  • Your OS and version can be valuable information, please include it along with extra security you are using. Antivirus etc.
  • Always list the version of the IDE you are using and the board version if applicable.
  • Use quote or add error messages as an attachment NOT a picture.
  • How to insert an image into your post. ( Thanks sterretje )
  • Add your sketch where applicable but please use CODE TAGS
  • Add a SCHEMATIC were needed even if it is hand drawn
  • Add working links to any specific hardware as needed (NOT links to similar items)
  • Remember that the people trying to help cannot see your problem so give as much information as you can

COMMON ISSUES

  • Ensure you have FULLY inserted the USB cables.
  • Check you have a COMMON GROUND where required. ( Thanks Perry)
  • Where possible use USB 2.0 ports or a USB 2.0 POWERED HUB to rule out USB 3.0 issues.
  • Try other computers where possible.
  • Try other USB leads where possible.
  • You may not have the correct driver installed. CH340/341 or CP2102 or FT232 VCP Drivers - FTDI
  • There may be a problem with the board check or remove your wiring first.
  • Remove any items connected to pins 0 and 1.

COMPUTER RELATED

  • Close any other serial programs before opening the IDE.
  • Ensure you turn off any additional security / antivirus just to test.
  • There may be a problem with the PC try RESTARTING it.
  • You may be selecting the wrong COM port.
  • Avoid cloud/network based installations where possible OR ensure your Network/Cloud software is RUNNING.
  • Clear your browsers CACHE.
  • Close the IDE before using any other serial programs.
  • Preferably install IDE’s as ADMINISTRATOR or your OS equivalent

ARDUINO SPECIFIC BOARDS

  • CH340/341 based clones do not report useful information to the “get board info” button.
  • NANO (Old Types) some require you to use the OLD BOOTLOADER option.
  • NANO (ALL Types) See the specific sections lower in the forum.
  • NANO (NEW Types) Install your board CORE’s.
  • Unless using EXTERNAL PROGRAMMERS please leave the IDE selection at default “AVRISP mkII”.
  • Boards using a MICRO usb connector need a cable that is both DATA and CHARGE. Many are CHARGE ONLY.

CREATE editor install locations.

  • On macOs ~/Applications/ArduinoCreateAgent-1.1/ArduinoCreateAgent.app/Contents/MacOS/config.ini
  • On Linux ~/ArduinoCreateAgent-1.1/config.ini
  • On Windows C:\Users[your user]\AppData\Roaming\ArduinoCreateAgent-1.1

Performing the above actions may help resolve your problem without further help.

Language problem ?

Try a language closer to your native language:

Thanks to all those who helped and added to this list.

Rev 2.1.A

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.