Moving a servo for curtain purposes. Code done, but improvement is possible

Hi everybody,

I recently dug out the Arduino i had laying around in my house, and the next thing i knew, i got a motorized curtain project finished for 80%.

I am using a sketch with a http server, containing three URL’s that open, close and stop a MG995 servo. I am using the delay function to determine the time the servo needs to spin in order to open/close the curtains. A value of 1000 makes the servo turn, 2000 makes it turn the other way, and 1400 makes it halt.

My domoticz system has a virtual switch that sends the arduino a URL post command to open and close the curtain.

The problem with this sketch is that i cannot interrupt one of these controls. This is actually needed to do a emergency stop for example.

is there any way to make the servo turn for x time with the possibility to interrupt the delay?

Since this is the first sketch i kinda built up myself, is there anything if forgot? or is there maybe too much stuff in there?

Cheers,
Sjoerd

/*
Door Sjoerd de Jong
Openen en sluiten gordijnen
*/

#include <SPI.h>
#include <Ethernet.h>
#include <Servo.h>

Servo microservo;
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };//Macadres
byte ip[] = { 192, 168, 240, 7 }; //LAN IP-adres
byte gateway[] = { 192, 168, 240, 254 }; //Gateway
byte subnet[] = { 255, 255, 255, 0 };//subnet mask
EthernetServer server(80);//server port

String readString;
void setup() {
// Open serial communications and wait for port to open:
 Serial.begin(9600);
 while (!Serial) {
 ; // wait for serial port to connect. Needed for Leonardo only
 }
 
 microservo.attach(7);
 microservo.writeMicroseconds(1400); //niet bewegen bij start
 // start the Ethernet connection and the server:
 Ethernet.begin(mac, ip, gateway, subnet);
 server.begin();
 Serial.print("server is at ");
 Serial.println(Ethernet.localIP());
}
void loop() {

 // Create a client connection
 EthernetClient client = server.available();
 if (client) {
 while (client.connected()) {
 if (client.available()) {
 char c = client.read();

 //read char by char HTTP request
 if (readString.length() < 100) {
 //store characters to string
 readString += c;
 //Serial.print(c);
 }
 //if HTTP request has ended
 if (c == '\n') {
 Serial.println(readString); //print to serial monitor for debuging

 client.println("HTTP/1.1 200 OK"); //send new page
 client.println("Content-Type: text/html");
 client.println();
 client.println("<HTML>");
 client.println("<HEAD>");
 client.println("<meta name='apple-mobile-webapp-capable' content='yes' />");
 client.println("<meta name='apple-mobile-webapp-status-bar-style' content='black-translucent' />");
 client.println("<TITLE>Gordijnen Peerlenburg open/dicht</TITLE>");
 client.println("</HEAD>");
 client.println("<BODY>");
 client.println("<H1>Gordijnen open of dicht</H1>");
 client.println("<hr />");
 client.println("
");
 client.println("<H2>Servo op pin 7, </H2>");
 client.println("
"); 
 client.println("
");
 client.println("<a href=\"/?open\"\">Open</a>

");
 client.println("<a href=\"/?stop\"\">stop</a>

");
 client.println("<a href=\"/?dicht\"\">dicht</a>
");
 client.println("
");
 client.println("</BODY>");
 client.println("</HTML>");

 delay(1);
 //stopping client
 client.stop();
 //controls the Arduino if you press the buttons

 if (readString.indexOf("?open") >0)
 { // in steps of 1 degree
 microservo.writeMicroseconds(2000); //Open (linksom)
 delay (10000); //tien seconden laten lopen
 microservo.writeMicroseconds(1400); //stop
 }
 
 if (readString.indexOf("?dicht") >0)
 {
 microservo.writeMicroseconds(1000); //Dicht (rechtsom)
 delay (10000); //tien seconden laten lopen
 microservo.writeMicroseconds(1400); //stop
 }

  if (readString.indexOf("?stop") >0)
 {
 microservo.writeMicroseconds(1400); //stop
 }
 
 //clearing string for next read
 readString="";

 }
 }
 }
}
}

is there any way to make the servo turn for x time with the possibility to interrupt the delay?

Yes. Don't use the delay() function, instead use millis() to do the timing.

Save the start time of the movement then each time through loop() check whether the required interval has passed. If not, go round loop() again checking the emergency stop input each time and act on it.

This method of timing is often referred to as Blink Without Delay derived from the BlinkWithoutDelay example in the IDE

The trick would be to record the current time using millis() when you start the curtains moving and then every time through the loop check to see if the current time is greater than or equal to the time you recorded plus your delay (10000).

I made a few changes to your code so you can see what I mean…

/*
Door Sjoerd de Jong
Openen en sluiten gordijnen
*/

#include <SPI.h>
#include <Ethernet.h>
#include <Servo.h>

Servo microservo;
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };//Macadres
byte ip[] = { 192, 168, 240, 7 }; //LAN IP-adres
byte gateway[] = { 192, 168, 240, 254 }; //Gateway
byte subnet[] = { 255, 255, 255, 0 };//subnet mask
EthernetServer server(80);//server port

String readString;

unsigned long time;
boolean moving;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

  microservo.attach(7);
  microservo.writeMicroseconds(1400); //niet bewegen bij start
  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip, gateway, subnet);
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());
}
void loop() {

  // Create a client connection
  EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        //read char by char HTTP request
        if (readString.length() < 100) {
          //store characters to string
          readString += c;
          //Serial.print(c);
        }
        //if HTTP request has ended
        if (c == '\n') {
          Serial.println(readString); //print to serial monitor for debuging

          client.println("HTTP/1.1 200 OK"); //send new page
          client.println("Content-Type: text/html");
          client.println();
          client.println("<HTML>");
          client.println("<HEAD>");
          client.println("<meta name='apple-mobile-webapp-capable' content='yes' />");
          client.println("<meta name='apple-mobile-webapp-status-bar-style' content='black-translucent' />");
          client.println("<TITLE>Gordijnen Peerlenburg open/dicht</TITLE>");
          client.println("</HEAD>");
          client.println("<BODY>");
          client.println("<H1>Gordijnen open of dicht</H1>");
          client.println("<hr />");
          client.println("
");
          client.println("<H2>Servo op pin 7, </H2>");
          client.println("
");
          client.println("
");
          client.println("<a href=\"/?open\"\">Open</a>

");
          client.println("<a href=\"/?stop\"\">stop</a>

");
          client.println("<a href=\"/?dicht\"\">dicht</a>
");
          client.println("
");
          client.println("</BODY>");
          client.println("</HTML>");

          delay(1);
          //stopping client
          client.stop();
          //controls the Arduino if you press the buttons

          if (readString.indexOf("?open") > 0)
          { // in steps of 1 degree
            microservo.writeMicroseconds(2000); //Open (linksom)
            time = millis();
            moving = true;
          }

          if (readString.indexOf("?dicht") > 0)
          {
            microservo.writeMicroseconds(1000); //Dicht (rechtsom)
            time = millis();
            moving = true;
          }

          if (readString.indexOf("?stop") > 0)
          {
            microservo.writeMicroseconds(1400); //stop
            moving = false;
          }

          //clearing string for next read
          readString = "";

        }
      }
    }
  }
  if (moving)
  {
    if (millis() >= time + 10000)
    {
      microservo.writeMicroseconds(1400);
      moving = false;
    }
  }
}

What UKHeliBob said while I was typing…

The demo several things at a time illustrates the use of millis() to manage timing.

…R

BH72:
The trick would be to record the current time using millis() when you start the curtains moving and then every time through the loop check to see if the current time is greater than or equal to the time you recorded plus your delay (10000).

I made a few changes to your code so you can see what I mean…

/*

Door Sjoerd de Jong
Openen en sluiten gordijnen
*/

#include <SPI.h>
#include <Ethernet.h>
#include <Servo.h>

Servo microservo;
byte mac = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };//Macadres
byte ip = { 192, 168, 240, 7 }; //LAN IP-adres
byte gateway = { 192, 168, 240, 254 }; //Gateway
byte subnet = { 255, 255, 255, 0 };//subnet mask
EthernetServer server(80);//server port

String readString;

unsigned long time;
boolean moving;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

microservo.attach(7);
  microservo.writeMicroseconds(1400); //niet bewegen bij start
  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip, gateway, subnet);
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());
}
void loop() {

// Create a client connection
  EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

//read char by char HTTP request
        if (readString.length() < 100) {
          //store characters to string
          readString += c;
          //Serial.print(c);
        }
        //if HTTP request has ended
        if (c == ‘\n’) {
          Serial.println(readString); //print to serial monitor for debuging

client.println(“HTTP/1.1 200 OK”); //send new page
          client.println(“Content-Type: text/html”);
          client.println();
          client.println("");
          client.println("");
          client.println("");
          client.println("");
          client.println(“Gordijnen Peerlenburg open/dicht”);
          client.println("");
          client.println("");
          client.println(“

Gordijnen open of dicht

”);
          client.println("
");
          client.println("
“);
          client.println(“

Servo op pin 7,

“);
          client.println(”
“);
          client.println(”
“);
          client.println(”<a href=”/?open”">Open

“);
          client.println(”<a href="/?stop"">stop

“);
          client.println(”<a href="/?dicht"">dicht
“);
          client.println(”
“);
          client.println(”");
          client.println("");

delay(1);
          //stopping client
          client.stop();
          //controls the Arduino if you press the buttons

if (readString.indexOf("?open") > 0)
          { // in steps of 1 degree
            microservo.writeMicroseconds(2000); //Open (linksom)
            time = millis();
            moving = true;
          }

if (readString.indexOf("?dicht") > 0)
          {
            microservo.writeMicroseconds(1000); //Dicht (rechtsom)
            time = millis();
            moving = true;
          }

if (readString.indexOf("?stop") > 0)
          {
            microservo.writeMicroseconds(1400); //stop
            moving = false;
          }

//clearing string for next read
          readString = “”;

}
      }
    }
  }
  if (moving)
  {
    if (millis() >= time + 10000)
    {
      microservo.writeMicroseconds(1400);
      moving = false;
    }
  }
}




What UKHeliBob said while I was typing....

Wow, great stuff! didn’t know about millis, but it works like a charm!

I have been trying to create a fourth option. Besides ‘Open’, ‘Closed’ and ‘Stop’, i would like to add an option ‘half open’. I know how to do this with delay (just create a link that runs the servo for half the time), but i don’t know how to re-use the millis function (because it has already been used to let the curtain move for 10000ms).

That would be the second step in my project. The third step would be to let the Arduino know what status of the curtain is in, so the ‘close’ function can be variable. For example: If the curtain is half open, the close url should only turn the servo for 5000ms. If the curtain is fully open, the servo should move 10000ms.

I think that if the arduino knows what status the curtain is in, it’s (for example) possible to ignore the close task if the is already closed.

Anyone knows how this can be done?

By the way: I’m planning on sharing a step-by-step tutorial on the Domoticz website as soon as my project is finished. I think many people on there will find it usefull :slight_smile:

but i don't know how to re-use the millis function (because it has already been used to let the curtain move for 10000ms

If you know how to move the curtain for 10000ms then you also know how to move it for 5000ms. Just make the period for the move 5000 instead of 10000.

But isn't the millis function already declared by the following rule in the code?

if (millis() >= time + 10000)

This value is therefor being re-used in every task. Doesn't this make it impossible to re-use it with another value?

sjdejong:
But isn't the millis function already declared by the following rule in the code?

if (millis() >= time + 10000)

This value is therefor being re-used in every task. Doesn't this make it impossible to re-use it with another value?

That is not the correct way to use millis(). It should ALWAYS be used with subtraction. It is the difference that matters - not the absolute value.

if (millis() - startTime >= 1000) {

Think of millis() as providing the same function as a clock. You can time as many different things as you like during the day using your clock - how long to stay in bed, time to get to the bus stop, time to cook a chicken.

Have another look at the demo several things at a time

In your case the "half-open" could be dealt with very easily. Just use two "half-opens" to make a "full-open".

...R

But isn't the millis function already declared by the following rule in the code?

No. As Robin has explained you can use the value of millis() as many times as you like in your program.

sjdejong:
I think that if the arduino knows what status the curtain is in, it’s (for example) possible to ignore the close task if the is already closed.

Anyone knows how this can be done?

You can use an additional few booleans to keep track of whether the curtain is opened, closed, or half opened so that it will not try to close when the curtain is already closed (or try to open all the way when half open, etc). Something like this (note: I didn’t type this on the IDE so it may not compile without error as I was unable to verify it) would be what I mean, pay attention to the boolean halfMoving, halfOpen, and closed. This example assumes that the initial position upon applying power is curtains closed (also “stop” will lose track of position).

/*
Door Sjoerd de Jong
Openen en sluiten gordijnen
*/

#include <SPI.h>
#include <Ethernet.h>
#include <Servo.h>

Servo microservo;
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };//Macadres
byte ip[] = { 192, 168, 240, 7 }; //LAN IP-adres
byte gateway[] = { 192, 168, 240, 254 }; //Gateway
byte subnet[] = { 255, 255, 255, 0 };//subnet mask
EthernetServer server(80);//server port

String readString;

unsigned long time;
boolean moving = false;
boolean halfMoving = false;
boolean halfOpen = false;
boolean closed = true;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

  microservo.attach(7);
  microservo.writeMicroseconds(1400); //niet bewegen bij start
  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip, gateway, subnet);
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());
}
void loop() {

  // Create a client connection
  EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        //read char by char HTTP request
        if (readString.length() < 100) {
          //store characters to string
          readString += c;
          //Serial.print(c);
        }
        //if HTTP request has ended
        if (c == '\n') {
          Serial.println(readString); //print to serial monitor for debuging

          client.println("HTTP/1.1 200 OK"); //send new page
          client.println("Content-Type: text/html");
          client.println();
          client.println("<HTML>");
          client.println("<HEAD>");
          client.println("<meta name='apple-mobile-webapp-capable' content='yes' />");
          client.println("<meta name='apple-mobile-webapp-status-bar-style' content='black-translucent' />");
          client.println("<TITLE>Gordijnen Peerlenburg open/dicht</TITLE>");
          client.println("</HEAD>");
          client.println("<BODY>");
          client.println("<H1>Gordijnen open of dicht</H1>");
          client.println("<hr />");
          client.println("
");
          client.println("<H2>Servo op pin 7, </H2>");
          client.println("
");
          client.println("
");
          client.println("<a href=\"/?open\"\">Open</a>

");
          client.println("<a href=\"/?stop\"\">stop</a>

");
          client.println("<a href=\"/?dicht\"\">dicht</a>
");
          client.println("
");
          client.println("</BODY>");
          client.println("</HTML>");

          delay(1);
          //stopping client
          client.stop();
          //controls the Arduino if you press the buttons

          if (readString.indexOf("?open") > 0)
          { // in steps of 1 degree
            if (closed)
            {
              microservo.writeMicroseconds(2000); //Open (linksom)
              time = millis();
              moving = true;
              closed = false;
            }
            if (halfOpen)
            {
              halfMoving = true;
              halfOpen = false;
            }
          }

          if (readString.indexOf("?halfopen") > 0)
          { // in steps of 1 degree
            if (!(halfOpen)
          {
            if (closed)
              {
                microservo.writeMicroseconds(2000); //Open (linksom)
                time = millis();
                halfMoving = true;
                halfOpen = true;
                closed = false;
              }
              else
              {
                microservo.writeMicroseconds(1000); //Dicht (rechtsom)
                time = millis();
                halfMoving = true;
                halfOpen = true;
              }
            }
          }

          if (readString.indexOf("?dicht") > 0)
          {

            if (!(closed)
          {
            microservo.writeMicroseconds(1000); //Dicht (rechtsom)
              time = millis();
              if (halfOpen)
              {
                halfMoving = true;
                halfOpen = false;
                closed = true;
              }
              else
              {
                moving = true;
                closed = true;
              }
            }
          }

          if (readString.indexOf("?stop") > 0)
          {
            microservo.writeMicroseconds(1400); //stop
            moving = false;
            halfMoving = false;
          }

          //clearing string for next read
          readString = "";

        }
      }
    }
  }
  if (moving)
  {
    if (millis() - time >= 10000)
    {
      microservo.writeMicroseconds(1400);
      moving = false;
    }
  }
  if (halfMoving)
  {
    if (millis() - time >= 5000)
    {
      microservo.writeMicroseconds(1400);
      halfMoving = false;
    }
  }
}

Robin2:
That is not the correct way to use millis(). It should ALWAYS be used with subtraction. It is the difference that matters - not the absolute value.

if (20000 - 10000 >= 10000) {
if (20000 >= 10000 + 10000) {

This is a matter of personal preference, as the math works out exactly the same either way...

This is a matter of personal preference, as the math works out exactly the same either way...

What happens when millis() rolls over to zero if you don't use subtraction ?

BH72:
This is a matter of personal preference, as the math works out exactly the same either way…

What @UKHeliBob has said is the principal reason for using subtraction.

I also think the use of subtraction makes it more obvious that it is the difference which is important and not the absolute value of millis().

…R

UKHeliBob:
What happens when millis() rolls over to zero if you don't use subtraction ?

Good point! Subtraction it is.