Project tutorial
Arduino & Android Curtain Control

Arduino & Android Curtain Control

Control your Curtains with Android using Arduino MKR and a Motor - Incl. Android App, HTTPserver, RTC alarms and 3D print

  • 2,141 views
  • 0 comments
  • 10 respects

Components and supplies

Necessary tools and machines

3drag
3D Printer (generic)

Apps and online services

About this project

General Description

This is a cool Arduino project to automatically open and close your curtains in the morning and in the evening, plus a manual override. It's assuming the mechanics are there (my old curtains had a simple looping-rope ), only the web and motor control are handled in this project.

This Project contains the software functions for:

  • WiFi Setup
  • Webserver interface, based on simple HTTP interface.
  • Real-time clock alarm handling.
  • Time-sync update via NTP server,
  • Daily Sunrise and Sunset calculation for RTC alarm.
  • Motor-controller with Lockup detect.
  • Over-ride for open-close via hard-buttons

Project Setup

This motor-controller applications opens and closed curtains. In my case I have an old (60's!) manual curtain opener that runs with simple rope up and down on the side of the curtains. This project drives the 'ropes' by replacing a part with a stepper-belt and a drive ir with a Stepper-motor with a pulley :

Why a StepperMotor?

As my old curtain-rig needed quite some torque to run up and down, I could choose between a Servo motor (or DC-Motor with gearbox) and a Stepper. Basically the Servo would give me more Torque, but I would not be able to position the curtain exactly, and also I can not sense the end of the curtain-rail by measuring the current of the motor when it stalls (Servo-motors hardly show current fluctuation as its compensated by the mechanical; gear-box).

So choice is for a stepper motor, and no detect sensors (switches) on the curtain ... lean and mean :). This means also I have to run the Stepper slow (Stepper-Torque curves are slow-speed -> high torque, high speed-> slow torque. Please take a look at my other projects for FullStepper and HalfStepper.

Implemented motor functiuons:

  • Motor full step driver - function void MotorLoop(long int lps,int dir)
  • Lock out detect - function int CalcArray() / #define MOTORLOCK
  • Slow start (higher torque) setting #define MOTORSTART
  • Variable torque (not used) setting #define MOTORVARI

As we use the Arduino MotorShield with the L298N full-bridge, we are doing software controlled timing. This runs quite ok for a slow stepper motor, but measuring the current at the correct time-point is a challenge (you will see drops in your current-measurement sometimes, and it needs to be filtered-out.)

Other challenge is that my old curtain rail had some ditches, so at some points it has more resistance, meaning current fluctuations in the Stepper. At the end, it was hard to differentiate an end-point of the curtain vs a rail-variation. So the software filters out the bad-rail area , (#def BADRAIL1 / BADRAIL2 in usteps )and keeps on tracking to the end :)

There is a Debugging function (http://<IP>/D) in the software build-in, that gives you the possibility to dump the measured buffers (Current, Looped AvgCurrent, Lock%). Copy-past this to an excel sheet and make a graph from it (its semicolon separated data):

Time calculations

As stated this project opens and closed your curtains ar sun-rise and sun-set. This means 2 things:

  • You have to get the real time of today
  • You have to calculate sunrise and sunset

The real-clock time is done by syncing to an NTP server. This is done every day (at 1:30 at night). If this fails, you can manually override by addressing the WebServer with a time set and date set function.

Calculation of the sunset and sunrise is quite complicated. Algorithms need the sun-inclination and a function with (Co)-Sine/Tan function. As I don't care if my curtains open or closed a minute later or so, I use a VERY rough linear calculation algorithm,. It would be a To-Do to set this in a look-up table, or make a web functions to read the sunrise or sunset from a website - any idea's ????

Web Server

The web server is build with the Arduino WiFi client functions defined in the <WiFi101.h> setup. This is creating a basic HTML web server, responding to requests. (Its wise to give your Arduino MKR a fixed IP at your home-router)

Addressing the http://<IP> of your Server in a browser gives a general menu with the options to open/close the curtain, or look at advanced info with status and counters. This are the secret options:

  • http://<IP>/I : info page
  • http://<IP>/X : force to sync to NTP Time server
  • http://<IP>/A<x> : Set Alarm to alarm nr <x> 1(sunrise), 2(sunset) or 3(time-sync)
  • http://<IP>/R<x> : Reset curtain position to : zero closed ,<0> or Max open<1>
  • http://<IP>/D :Dump Data buffers
  • http://<IP>/settimehh:mm:ss : set RTC time
  • http://<IP>/setdatedd/mm/yy : set RTC date - European notation - yeah !
  • http://<IP>/setweekendsahh:mm:ss : set weekend Saturday override rise-time
  • http://<IP>/setweekendsuhh:mm:ss : set weekend Sunday override rise-time
  • http://<IP>/setweekmofrhh:mm:ss : set week Mo-Fr override rise-time
  • http://<IP>/setsummer<x> : Set Automatic Summertime correction 0:off, 1:on
  • http://<IP>/setutp<x> : set UTP-zone hour-correction + or - x-hours
  • http://<IP>/O : close curtains
  • http://<IP>/C: open curtains

Code Info

Straightforward Arduino sketch coded. All variables are kept global where possible (except some simple counters). WiFi login is in separate Arduino_secrets.h file. Setup and loop call functions in the sketch file. HTTP Server scans requests only, cuts of remaining data.

Extra's

  • European Summer time implementation.
  • Early morning rise time correction, ie for weekends a little bit later open curtains :)
  • Android App using HTTP-calls

Android App

Additional the MIT App Inventor 2, you can build your own Android app to control your curtains. App inventor works with blocks and is pretty simple once you understand the details. lots of demo video's and examples available.

The app handles the basic functions and gives you a screen shot of the web-interface. Looks more neat :)

You can download the APK or download the AI2-app to re-use it- see downloads.

Code

Curtain_Server.inoC/C++
Arduino Sketch
#include <WiFi101.h>
#include <RTCZero.h>
#include <WiFiUdp.h>
#include "arduino_secrets.h"

/*  Curtain Controller V1.2
 * 
 *  Curtain Position  \~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~/
 *                     |     |     |    |       |        |         |       |    |
 *                     |     |     |    |       |        |         |       |    |
 *                     |     |     |    |       |        |         |       |    |
 *   Position        <-Opened                                              Closed->
 *   Absolute uStep: MAXSTEPS                                                  ZERO
 *   Status:         100                           50/51                          0
 *                     |     |     |    |       |        |         |       |    |
 *                     |     |     |    |       |        |         |       |    |
 *                     |     |     |    |       |        |         |       |    |
 *     
 * 
 */

/************ Global Variables and Definitions ************/
#define DBG 1                         // Debug mode for serial momitor, leave it and no Seriall is spammed

// Motor Settings and Variables
//#define  MOTORLOCK  25    // 10* % current amplitude volume  variantion to signal a lock-up. set level higher at higher motorspeed (lower delay)
#define  MOTORSTART 1000    // ammount of usteps the slow start is working at start : 2 turns
#define  MOTORVARI  10000   // amount of usteps when the variable power kicks in : xx turns - never - disable with 10000
#define  MOTORCURRENT 990   // expected Motor current at start
#define  MAXSTEPS 5450      // MAX steps required to open/close curtain = 13.5 rotations 
#define  MINPEAK -30        // filter out peaks lower then -XX
#define  BADRAIL1 4450      // step-coubnter whith Bad rail
#define  BADRAIL2 4690      // step-coubnter whith Bad rail
int MOTORLOCK=25;

// Motor Driver IO Settings
const int DIRA=12;    // IN1 = DIRA, switches one leg Half-BridgeA  
const int BRAKEA=9;   // IN2 = DIRA xNor BRAKEA, switched one leg of BridgeA  => 1= DIRA 0= #DIRA
const int ENA=3;      // ENA = PWMA, enable signal for half Bridge A
const int DIRB=0;     // IN3 = DIRB, switches one leg of Half-BridgeB (!! ex pin 13 on Arduino UNO)
const int BRAKEB=8;   // IN3 = DIRB xNor BRAKEB, switches one leg of Half-BridgeB => 
const int ENB=11;     // ENB = PWMB, enable singnal for Half Bridge B
const int ARRAY=16;           // over current Monitoring Array Size
const int SARRAY=256;         // long term array Size - for Debugging purposes : data dump
const int Current0 = A0;      // Port for analog current measurement A0 = SenseA
const int Current1 = A1;      // Port for analog current measurement A1 = SenseB

int CurrentArray0[ARRAY];   // Current sample monitor Array
int ShadowArray[SARRAY];    // 3 Debugging Array's - Dumps all current measurements
int AverageArray0[SARRAY];
int AmplitudeArray0[SARRAY];
int CounterSh=0;            // ARRAY / SARRAY Sounters
int CounterAv=0;;
int CounterAm=0;
int CounterCu=0;
long int CounterSt=MAXSTEPS/2;      // ustep counter - to remember curtain status
int AvgCurrent=0;
int Lock;
int Sequencer[7][4] ={     // Sequencer[MotorPin][Value] 4 states
       0,    1,    1,    0,    // DIRA = IN1
       0,    0,    0,    0,    // BRAKEA = IN2 ,  high : follows DIRA, Low = IN2 = #IN1
     255,  255,  255 , 255,    // ENA PWM, 
       1,    1,    0,    0,    // DIRB = IN3
       0,    0,    0,    0,    // BRAKEB = N4, high : follows DIRB, Low :IN4 = #IN3
     255,  255,  255,  255,    // ENB PWM, 
    4800, 4800, 4800, 4800     // uSec Delay per 1/4 Phase
};

//Define ServerSettings
#define SERVER_PORT 80                // Server Port
#define VERSION "2.1.0"                     // Software verson nr
#define MAX_MISSED_DATA 2000          // MAX data missed from Client before time-out (accept short messages only)
char ssid[] = SECRET_SSID;            // your network SSID (name) - arduino_secrets.h
char pass[] = SECRET_PASS;            // your network password - arduino_secrets.h
int keyIndex = 0;                     // your network key Index number (needed only for WEP)
int status = WL_IDLE_STATUS;
WiFiServer server(SERVER_PORT);
IPAddress Myip;
long Myrssi;

// Define NTP settings
#define NTP_PACKET_SIZE 48           // NTP time stamp is in the first 48 bytes of the message
#define NTP_TIMEOUT 3                // NTP time out for parsing UDP packet 
unsigned int localPort = 2390;        // local port to listen for UDP packets
IPAddress timeServer(129,6,15,29);    //  USA NTP server server
byte packetBuffer[ NTP_PACKET_SIZE];  // buffer to hold incoming and outgoing packets
WiFiUDP Udp;                          // A UDP instance to let us send and receive packets over UDP

// Define RTC Settings
RTCZero rtc;                         // RTC Clock
unsigned long epoch= 1541062800UL;   // epoch time global variable, adapted by NTP routines
byte epoch_valid = 0;                // Flag for valid Epoch time received via NTP server
byte manual_valid = 0;               // Flag for valid manual time set
String TimeString = "00:00:00";
String DateString = "00-00-00";
byte summerTime = 1;                 // correction for summertime - see correction functions check_eusummertime()
byte utcTime = 1;                   // time is UTC time + 1 for netherlands
byte weekendCorrect =1;              // weekend rise time correct : overides calculatd rise time on Sundays
byte weekCorrect =1;
byte weekendsuHour = 9;               // Weekend Su rise time override alarm Hours
byte weekendsuMinutes = 30;           // weekend Su tise time override alarm Minutes
byte weekendsaHour = 8;               // Weekend Sa rise time override alarm Hours
byte weekendsaMinutes = 0;           // weekend Sa tise time override alarm Minutes
byte weekmofrHour = 7;               // Week rise time override alarm Hours
byte weekmofrMinutes = 00;           // week tise time override alarm Minutes

String mydays[7] = {"Monday", "Tuesday" ,"Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };

// Define button variables
const int LocalLedPin = 6;                 // Local Led Pin
const int Button1Pin = A4;                 // Local Led Pin
const int Button2Pin = A5;                 // Local Led Pin
int button1 =0;
int button2 = 0;
int bb1 = 0;      // bounce memory
int bb2 =0;       // bounce memory

byte CurtainStatus=50;                    // Flag status of the Curtains: 0=closed-direction 50=unknown0 51=unknown1 100=opened-direction
int CurtainPosition=MAXSTEPS/2;           // EXACT CurtainPosition in usteps - updated every run, initialised at zero
unsigned int OpenSuccess=0;               // Counts succesfull full openings
unsigned int CloseSuccess=0;              // Counts succesfull full closings
unsigned int NoSuccess=0;                 // Counts unsuccesfull full open/close
unsigned int BlockSuccess=0;            // Counts blocks, if unsuccessfull

byte GeneralAlarm = 0;                 // General alarm action 0=void  1= morning 2= evening 3=synC 4=closebutton 5=openbutton,
byte AlarmCounter = 1;                 // Alarm counter, loops through the MyAlarm Array
byte MyAlarm [3][4] = {     // Alarm definition [AlarmNr][Value index] HH,MM,SS,TYPE
 01,30,00, 3,               // Alarm 3 : sync , in initial at 01.30 am
 07,30,00, 1,               // Alarm 1 : morning open curtains, initialy 7.30am
 17,30,00, 2                // Alarm 2 :evening close curtains, initialy 7.30 pm (17.30)
};

int Dusk2Dawn[12][2]= {
515,1012,
485,1062,
430,1114,
359,1169,
293,1221,
248,1267,
247,1280,
286,1247,
336,1183,
386,1113,
441,1046,
493,1006  
};


void setup() {   //Initialize serial and wait for port to open:
#if DBG
  Serial.begin(9600);delay(1000);
  Serial.println("Welcome to my Arduino MKR1000");
#endif
  pinMode(Button1Pin,INPUT);         // set A4 io to digital input
  pinMode(Button2Pin,INPUT);         // set A5 io to digital input
  //establish L298N motor driver pins
  pinMode(DIRA, OUTPUT); //CH A 
  pinMode(BRAKEA, OUTPUT); //brake (disable) CH A
  pinMode(DIRB, OUTPUT); //CH B 
  pinMode(BRAKEB, OUTPUT); //brake (disable) CH B
  // pin 3 PWMA out
  // pin 11 PWMB out  
  analogReference(AR_INTERNAL); // 2.23V internal reference 
  analogReadResolution(12); // 12 bit resolution is ok, gives 0-4096 value on A0 @ 2.23V reference. SenseA senses 1,65V/Amp => 2.23*(A0/4096)/1.65 =>  A0/3 mA = Motor current
  analogWrite(ENA,255);    // always high - not used
  analogWrite(ENB,255);    // always high - not used
  MotorPowerDown();
  
/*********** WIFI SETUP  **********/
  if (WiFi.status() == WL_NO_SHIELD) {   // check for the presence of the shield:
#if DBG
    Serial.println("WiFi shield not present");
#endif
    while (true);     // don't continue if no shield
  }
delay(1000);
StartMyWifi();      // Start Wifi login 

/*********** RTC SETUP  **********/
rtc.begin();
rtc.setEpoch(epoch);                             // Set Time RTC to base time
/*********** NTP Setup **********/
delay(1000);
epoch = readUnixEpochUsingNetworkTimeProtocol();// Get NTP unix time (secs since 1-1-70) UTC + 1 hour
rtc.setEpoch(epoch);                             // set time to real time - if received
/*********** Alarm Setup ************/
UpdateRTCStrings();                              // fill in time/date string  -just for info
CalculateAlarmTimes();                           // Calculate Alarm times
ResetAlarm();                                    // Re-Arm RTC Alarm to next alarm
/*********** Server SETUP  **********/
server.begin();                                  // Start HTTP Server
//InitCurtains();                                // init Curtains
}



void loop() {

 /******* Check Server and Clients ******/
CheckServerClients();

/******* Check button controller inputs ******/
button2= digitalRead(Button2Pin); // read button 2
bb2 = bb2 | button2;              // remember button2 pressed
if (GeneralAlarm == 0 ) GeneralAlarm= (bb2 & !button2)*5;     // Set Alarm5 (Open curtains) when released - if no other alarm was active
button1= digitalRead(Button1Pin); // read button 1
bb1 = bb1 | button1;              // remember button1 pressed
if (GeneralAlarm == 0 ) GeneralAlarm= (bb1 & !button1)*4;    // Set Alarm4 (Close curtains) when released - if no other alarm was active

/******* Check alarm status ******/
switch(GeneralAlarm){                                // Alarm Actions, Flags are set by RTC interrupt timer alarmMatch()
  case 1: // Morning Alarm / open curtains or button 2
    OpenCurtains();
    GeneralAlarm=0;
    ResetAlarm();                                    // Set RTC on next alarm
    break;
  case 2: // Evening Alarm / close curtains or button 1
    CloseCurtains();
    GeneralAlarm=0;
    ResetAlarm();                                    // Set RTC on next alarm
    break;
  case 3: // Sync Alarm
    StartMyWifi();                                   // Start/Check Wifi login 
    epoch = readUnixEpochUsingNetworkTimeProtocol(); // NTP unix time (= secs since 1-1-1970) UTC + corrected UTC-zone
    rtc.setEpoch(epoch);                             // Set new Time on RTC
    server.begin();                                  // Start HTTP Server
    CalculateAlarmTimes();                           // calculate new alarm times
    ResetAlarm();                                    // Set RTC on next alarm
    GeneralAlarm=0;
    break; 
  case 4:                      // Close curtains by button 1
    CloseCurtains();
    GeneralAlarm=0;
    bb1=0;
    break;
  case 5:                     // Open curtains by button 2
    OpenCurtains();
    GeneralAlarm=0;
    bb2=0;
    break;
     
  }

}


/*********** String Routine for RTC **************/

// update and Serial print the Strings that show time and date in right format for print
void UpdateRTCStrings(){
// move RTC time to TimeString global variable
TimeString = String(rtc.getHours(),DEC )+":";
if (rtc.getMinutes()<10) { TimeString= TimeString + "0"; }
TimeString = TimeString + String(rtc.getMinutes(),DEC) + ":";
if (rtc.getSeconds()<10) { TimeString= TimeString + "0"; }
TimeString = TimeString + String(rtc.getSeconds(),DEC);
// move RTC Date to DateString global variable
DateString = String(rtc.getDay(),DEC )+"-";
DateString = DateString + String(rtc.getMonth(),DEC) + "-";
DateString = DateString + "20" + String(rtc.getYear(),DEC);
#if DBG
Serial.print("Time: ");
Serial.print(DateString);Serial.print("/");Serial.println(TimeString);
#endif
}


// RTC interupt routine, set General Alarm (main loop check) and setup next alarm in the Array
void alarmMatch()
{
rtc.disableAlarm();                      // disable, no interrupts while handling Alarm status in main
GeneralAlarm = MyAlarm[AlarmCounter][3]; // set General alarm
  AlarmCounter = (AlarmCounter+1)%3;     // loop the alarm counter to next alarm
#if DBG
   UpdateRTCStrings();
   Serial.print("RTC-Alarm active, #: ");Serial.println(GeneralAlarm);
#endif
// NO OTHER ACTION IN ISR (Int Service Routine) !, DO NOT CALL RTC.xxx commands
}


// Re-Arm RTC alarm routine after event has happened
void ResetAlarm(){
  GeneralAlarm=0;
  rtc.setAlarmHours( MyAlarm[AlarmCounter][0] );   // Set hoursalarm
  rtc.setAlarmMinutes( MyAlarm[AlarmCounter][1] ); // Set minutes alarm
  rtc.setAlarmSeconds(0);                          // Set seconds alarm = 0
  rtc.enableAlarm(rtc.MATCH_HHMMSS);
  rtc.attachInterrupt(alarmMatch);                // Set Alarm Interrupt
#if DBG
   Serial.print("RTC-Alarm re-armed, next alarm #");Serial.print(MyAlarm[AlarmCounter][3]);Serial.print(" at ");
   Serial.print(rtc.getAlarmHours());Serial.print(":"); Serial.println(rtc.getAlarmMinutes());
#endif
}

// Calculation of the alarm times for sunset and sunrise - check xls file for used algorithms - very tricky :)
void CalculateAlarmTimes(){

int rise = calc_sunrise();
int set = calc_sunset();

#if DBG
UpdateRTCStrings();
Serial.print("\nDate: "); Serial.print( mydays[weekday( rtc.getEpoch() ) -1 ] );Serial.print(" ,"); Serial.print(DateString);
if(summerTime) {
   if(check_eu_summertime()&0x01) Serial.print("\nInto Summertime. "); else Serial.print("\nOut of Summertime. ");
   if((check_eu_summertime()>>4)&0x01) Serial.print("Clock changed today."); 
}
Serial.print("\nDawn-set :");Serial.print(rise);Serial.print("min. / ");Serial.print(rise/60);Serial.print(":");Serial.print(rise%60);
Serial.print("\nDusk-rise:");Serial.print(set);Serial.print("min. / ");Serial.print(set/60);Serial.print(":");Serial.print(set%60);Serial.print("\n");
#endif

    MyAlarm[1][0] = (byte) ( (rise+(check_eu_summertime()&summerTime)*60 )/60);                            // Set next Sync Alarm Hour sunrise
    MyAlarm[1][1] = (byte) ( (rise+(check_eu_summertime()&summerTime)*60 )%60);                            // Set next Sync Alarm Minute sunris
    MyAlarm[2][0] = (byte) ( (set+(check_eu_summertime()&summerTime)*60 )/60);                             // Set next Sync Alarm Hour sunset
    MyAlarm[2][1]= (byte)  ( (set+(check_eu_summertime()&summerTime)*60 )%60);                             // Set next Sync Alarm Minute sunset
    MyAlarm[0][0] = (byte) 1;                      // Set next Sync Alarm, is fixed to Hour : 1:50 midnight
    MyAlarm[0][1] = (byte) 50;                     // Set next Sync Alarm Minute

if (weekendCorrect) {   // Correct weekend Rise time - al
   if (weekday( rtc.getEpoch()) == 6 ){
      MyAlarm[1][0] = (byte) weekendsaHour;                // Set weekend alarm
      MyAlarm[1][1] = (byte) weekendsaMinutes;              // Set weekend alarm
      }
   if (weekday( rtc.getEpoch()) == 7 ){
      MyAlarm[1][0] = (byte) weekendsuHour;                // Set weekend alarm
      MyAlarm[1][1] = (byte) weekendsuMinutes;              // Set weekend alarm
      } 
   }
if( weekCorrect) { // correct week rise time, only if later!
     if ( (weekday( rtc.getEpoch()) < 6)  && (MyAlarm[1][0]<weekmofrHour) ){
     MyAlarm[1][0] = (byte) weekmofrHour;                // Set weekend alarm
     MyAlarm[1][1] = (byte) weekmofrMinutes;              // Set weekend alarm
     } 
  }
  
}



// Calculate Sun rise from Dusk2Dawn Table - minutes into Current RTC Day
int calc_sunrise(){
  return (Dusk2Dawn[rtc.getMonth()-1][0] + ((rtc.getDay()-1)*(Dusk2Dawn[rtc.getMonth()%12][0]-Dusk2Dawn[rtc.getMonth()-1][0]))/31 ); // rise time in total minutes of the day
}
// Calculate Sun Set from Dusk2Dawn Table - minutes into Current RTC Day
int calc_sunset(){
  return(Dusk2Dawn[rtc.getMonth()-1][1] + ((rtc.getDay()-1)*(Dusk2Dawn[rtc.getMonth()%12][1]-Dusk2Dawn[rtc.getMonth()-1][1]))/31);
}
// Calculates if current day is in European Summertime period : last sunday in March till last sunday in october
// Function returns : 0x00=no summertime 0x01=yes its summer timee 0x10=no summertime, it switched-exact-today! 0x11=yes its summertime-exact-today!
byte check_eu_summertime() {
int wkdy=weekday( rtc.getEpoch() );
int mn = rtc.getMonth();
int dy = rtc.getDay();

if ( (mn>=11) || (mn<=2) || ( (mn==3)&&(dy<=24) ) ) return(0x00);  // outside summertime period
if ( (mn>=4)  || ( (mn>=10)&&(dy<=24) ) ) return(0x01);           // inside summer time period
if ( mn==10 )                                                     // check last sunday of october : 25th-31st turn is om last sunday of october
  {
  if  (wkdy== 7) return(0x10);  // day7 = sunday = lastday in October, today is the turn !!
   else {
        if( (dy-24-wkdy) <=0 )return(0x01);
        else return(0x00);
        }
  } // check month 10

if ( mn==3 )                                                     // check last sunday of March : 25th-31st turn is om last sunday of october
  {
  if  (wkdy== 7) return(0x11);  // day7 = sunday = lastday in October, today is the turn !!
   else {
        if ( (dy-24-wkdy) <=0 )return(0x00);
        else return(0x01);
        }
  } // check month 3

}


/**** WIFI ROUTINES  *****/

// Login to local network  //
void StartMyWifi(){
int t=0;
if ( status != WL_CONNECTED ) { // only try if not yet connected
#if DBG
  Serial.print("\nScanning available networks... ");
  //listNetworks();
#endif
  while ( status != WL_CONNECTED && t<5) {   // attempt to connect to WiFi network:
#if DBG
       Serial.print("\nAttempting to connect to Network named: ");
       Serial.print(ssid);                // print the network name (SSID);
#endif
       status = WiFi.begin(ssid, pass);     // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
       delay(5000);                         // wait 5 seconds for connection:
       t=t=+1;                              // try-counter
  }
  if ( status == WL_CONNECTED ) {
       //server.begin();                           // start the web server outside this loop!
#if DBG
       printWiFiStatus();                        // you're connected now, so print out the status
#endif
  }
  else{                                          // no connection possible : exit without server started
#if DBG
    Serial.print("\nConnection not possible after several retries.");                   
#endif
  }
}
else{
#if DBG
    Serial.print("\nAlready connected.");                     // you're already connected
    printWiFiStatus(); 
#endif
    }
}

// LIST NETWORKS VIA SERIAL PRINT - only for DEBUG
#if DBG
void listNetworks() {
  if (Serial) {
    // Print Mac Adress
    printMacAddress();
    // scan for nearby networks:
    Serial.println("\n** Scan Networks **");
    int numSsid = WiFi.scanNetworks();
    if (numSsid == -1)
    {
      Serial.println("Couldn't get a wifi connection");
      while (true);
    }

    // print the list of networks seen:
    Serial.print("\nnumber of available networks:");
    Serial.println(numSsid);
    // print the network number and name for each network found:
    for (int thisNet = 0; thisNet < numSsid; thisNet++) {
      Serial.print(thisNet);
      Serial.print(") ");
      Serial.print(WiFi.SSID(thisNet));
      Serial.print("\tSignal: ");
      Serial.print(WiFi.RSSI(thisNet));
      Serial.print(" dBm");
      Serial.print("\tEncryption: ");
      printEncryptionType(WiFi.encryptionType(thisNet));
      Serial.flush();
    }
  }
}
#endif


// SERIALPRINT your MAc Adress - only for debug
#if DBG
void printMacAddress() {
  // the MAC address of your Wifi shield
  byte mac[6];
  WiFi.macAddress(mac);
  Serial.print("MAC: ");
  Serial.print(mac[5], HEX);
  Serial.print(":");
  Serial.print(mac[4], HEX);
  Serial.print(":");
  Serial.print(mac[3], HEX);
  Serial.print(":");
  Serial.print(mac[2], HEX);
  Serial.print(":");
  Serial.print(mac[1], HEX);
  Serial.print(":");
  Serial.println(mac[0], HEX);
}
#endif

// SERIALPRINT encryption type - only for debug
#if DBG
void printEncryptionType(int thisType) {
  // read the encryption type and print out the name:
  switch (thisType) {
    case ENC_TYPE_WEP:
      Serial.println("WEP");
      break;
    case ENC_TYPE_TKIP:
      Serial.println("WPA");
      break;
    case ENC_TYPE_CCMP:
      Serial.println("WPA2");
      break;
    case ENC_TYPE_NONE:
      Serial.println("None");
      break;
    case ENC_TYPE_AUTO:
      Serial.println("Auto");
      break;
  }
}
#endif


// SERIALPRINT Wifi Status - only for debug
#if DBG
void printWiFiStatus() {
  if (Serial) {
    // print the SSID of the network you're attached to:
    Serial.print("\nSSID: ");
    Serial.print(WiFi.SSID());

    // print your WiFi shield's IP address:
    IPAddress ip = WiFi.localIP();
    Serial.print("\nIP Address: ");
    Serial.print(ip);

    // print the received signal strength:
    long rssi = WiFi.RSSI();
    Serial.print("\nsignal strength (RSSI):");
    Serial.print(rssi);
    Serial.print(" dBm");
    // print where to go in a browser:
    Serial.print("\nTo see this page in action, open a browser to http://");
    Serial.println(ip);
  }
}
#endif


// send an NTP request to the time server at the given address, return epoch (unix time = seconds since 1-1-1970)
// Epoch time is corrected for UTC-zone[int utctime] and for summer time [ byte summerTime]
// Syncs RTC to Epoch time (old or new)
unsigned long readUnixEpochUsingNetworkTimeProtocol()
{
  unsigned long epch=0;
  int t=0;
  epoch=rtc.getEpoch();         // save current Epoch time
#if DBG
    Serial.print("Epoch rtc:");Serial.println(epoch);
#endif
  Udp.begin(localPort);
  sendNTPpacket(timeServer);    // send an NTP packet to a time server
  while (!Udp.parsePacket() ){   // wait to see if a reply is available
    delay(500);
    t++;
#if DBG
    Serial.print(".");
#endif
    if (t>NTP_TIMEOUT) break;             // max <NTP_TIMEOUT> times to parse packed, otherwise skip
  }
  if ( t<NTP_TIMEOUT ) {
    // We've received a packet, read the data from it
    Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
#if DBG
    Serial.println("UDP packet received from NTP Server.");
#endif

    //the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. First, esxtract the two words:

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    // combine the four bytes (two words) into a long integer
    // this is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;
     // now convert NTP time into everyday time for RTCZero usage
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;
    epch = secsSince1900 - seventyYears;                                                        // UTC Greenwich time 
    if (epch>1541062800UL){                                                                     // sanity check if the receive UDP packet had sensible info
       epoch_valid = 1;                                                                         // Set global varialbel for NTP time received - one time set, will never be reset
       if ((check_eu_summertime() && summerTime )) 
            {rtc.setEpoch(epch + 3600 + 3600*utcTime);}                                          // if its summertime, and summertimeSet and alarm3 (only correct midnight) : correct summertime
       else {rtc.setEpoch(epch + 3600*utcTime);}                                                 // else set RTC to Epoch received
       epoch=rtc.getEpoch();
       } 
  else {   
       //no right NTP, but check to correct summertime
       if ( (check_eu_summertime()==0x11) && summerTime && (GeneralAlarm==3) ) epoch=epoch+3600;  // summert time starts today: add one our - only now during Alarm3 check (midnight)
       if ( (check_eu_summertime()==0x10) && summerTime && (GeneralAlarm==3) ) epoch=epoch-3600;  // summert time stops today: substract one our - only now during Alarm3 check (midnight)  
       rtc.setEpoch(epoch);
#if DBG
    Serial.println("NTP time not received");
#endif
       }
    } // end loop NTP packet received
    else {
#if DBG
       Serial.println("NTP time not received");
#endif
    }
    
#if DBG
    Serial.print("Epoch exit:");Serial.println(epoch);
#endif
  Udp.stop(); 
  return(epoch);
}


// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress & address)
{
    // set all bytes in the buffer to 0
     memset(packetBuffer, 0, NTP_PACKET_SIZE);
     // Initialize values needed to form NTP request
     // (see URL above for details on the packets
     packetBuffer[0] = 0b11100011;   // LI, Version, Mode
     packetBuffer[1] = 0;     // Stratum, or type of clock
     packetBuffer[2] = 6;     // Polling Interval
     packetBuffer[3] = 0xEC;  // Peer Clock Precision
     // 8 bytes of zero for Root Delay & Root Dispersion
     packetBuffer[12]  = 49;
     packetBuffer[13]  = 0x4E;
     packetBuffer[14]  = 49;
     packetBuffer[15]  = 52;
     // all NTP fields have been given values, now you can send a packet requesting a timestamp:
     Udp.beginPacket(address, 123); //NTP requests are to port 123
     Udp.write(packetBuffer, NTP_PACKET_SIZE);
     Udp.endPacket();
#if DBG
    Serial.println("UDP packet send to NTP Server.");
#endif
}


/*************** Curtain Functions***********/
void InitCurtains(){
#if DBG
    Serial.println("Initialising Curtains -close and open-");
#endif



}

int OpenCurtains(){
#if DBG
    Serial.print("Opening Curtains :");Serial.print(CurtainStatus); Serial.print(":");Serial.print(CounterSt);
#endif
if ( CurtainStatus!=100 || (CurtainStatus==100 && (100*CurtainPosition/MAXSTEPS)<95 ) ){  // only open when its not opened, or when it was opened less than 95%
  if (CurtainStatus==50) MotorLoop(MAXSTEPS,0);                // Unknown status: run full loop
  else MotorLoop(MAXSTEPS-CurtainPosition,0);                  // else turns MAXSTEPS-CurtPosition usteps  CCW :  only the needed steps are counted to run assuming Curtainstatus was known
 CurtainPosition = CurtainPosition + CounterSt;                // Update Absolute position
 DumpData();
   if ( (100*CounterSt/MAXSTEPS)>95) {                              // Opened with a full run, so its at position MAX
     ++OpenSuccess;                                                 // so sucesfull count
     CurtainStatus = 100;                                           // status is opened
     CurtainPosition=MAXSTEPS;                                      // set position to MAX as well, correct possible slips
     }               
   else  {                                                         // Not opened with a full run
    if (Lock>MOTORLOCK ) {
       ++BlockSuccess;                                              // if not opened fully , and it had a lock: count Blocked
       if (CurtainStatus==50){CurtainStatus=100;CurtainPosition=MAXSTEPS;} // if it had a lock and it was first run : curtain status direction open and fully at position MAX
       }
    else {
       ++NoSuccess;                                                  // if not opened fully, but no lock, count no full run success
      CurtainStatus = 100;                                            // Curtain status = unknown
      }                              
   }

}
}


// close curtains, with conditions for first runs to intialise the full open or full close position
// At start, position is unknown, so first run with a Lock means (probably) its at its end
void CloseCurtains () {
#if DBG
    Serial.print("Closing Curtains :");Serial.print(CurtainStatus); Serial.print(":");Serial.print(CounterSt);
#endif
if ( CurtainStatus!=0 || (CurtainStatus==0 && (100*CurtainPosition/MAXSTEPS)>5 ) ){  // only close when its not closed, or when it was closed less than 90%
  if (CurtainStatus==50) MotorLoop(MAXSTEPS,1);                     // Unknown status: run full loop
  else MotorLoop(CurtainPosition,1);                               // if known : turns CurtPosition usteps  CW  : only needed steps are counted to run assuming Curtainstatus was known
  CurtainPosition = CurtainPosition - CounterSt;                   // Update Absolute position
  DumpData();
   if ( (100*CounterSt/MAXSTEPS)>95) {                            // Closed with a full run, so fully closed at position = 0
     ++CloseSuccess;                                              // so sucesfull count
     CurtainStatus = 0;                                           // status is set to direction closed
     CurtainPosition=0;                                           // set position to 0 as well, correct possible slips
     }               
   else  {                                                         // Not closed with a full run
    if (Lock>MOTORLOCK ) {
       ++BlockSuccess;                                            // if not closed fully , and it had a lock: count Blocked
       if (CurtainStatus==50){CurtainStatus=0;CurtainPosition=0;} // if it had a lock and it was first run : curtain status direction closed and fully at position 0
       }
    else {
       ++NoSuccess;                                                // if not closed fully, but no lock, count no full run success
      CurtainStatus = 0;                                          // Curtain status = unknown, probably first run to middle
      }                              
   }                             
}
}


/********* SERVER ROUTINES *************/

// Check server if client is there, and sercve requests //
void CheckServerClients() {

  // Local Varaibles
  String currentLine = "";  // date line input client
  int d = 0; // client data counter
  int v = 0; // forloop counter
  byte hr,mn,sc; // hour and minute read characters
  
  Myip = WiFi.localIP();
  Myrssi = WiFi.RSSI(); // RSSI data
  WiFiClient client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
#if DBG
    Serial.println("new client");           // print a message out the serial port
#endif
    currentLine = "";                       // make a String to hold incoming data from the client
    d = 0;
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
#if DBG
        Serial.write(c);                     // print it out the serial monitor - debug only
#endif
        if (c == '\n') {                    // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();
            client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"); // metaview
            // the content of the HTTP response follows the header:
            client.println("<body style=\"background-color:#cccccc\">"); // set color CCS HTML5 style 
            client.print( "<p style=\"font-family:verdana; color:GhostWhite\">&nbsp<font size=1>o</font><font size=2>o</font><font size=3>O</font><font size=2>O</font><font size=1>o</font><font size=2>O</font><font size=3>O</font><font size=2>o</font><font size=1>o</font> <br>");
            client.print( "<font size=5>FoxCurt </font>&nbsp&nbsp&nbsp&nbsp<font size=3>Version ");client.print(VERSION);client.println("</font><br>");
            client.print( "<font size=2>");client.print(WiFi.SSID());client.println(" / ");client.print(Myip);client.println("</font></p>");
            client.print("<p style=\"font-family:verdana; size:2; color:#888888\">Curtain Status: ");
               if(CurtainStatus==0) client.print("Closed ("); else {if(CurtainStatus==100) client.print("Opened (");else client.print("Unknown (");}
               client.print(100*CurtainPosition/MAXSTEPS);client.print("%).<br><br>");
;
            client.print("Click <a href=\"/O\">open</a> open the curtains.<br>");
            client.print("Click <a href=\"/C\">close</a> close the curtains.<br><br>");
            client.print("Click <a href=\"/I\">info</a> to see status info.<br>");
            client.print("Click <a href=\"/H\">help</a> for Help.<br><br>");
            // The HTTP response ends with another blank line:
            client.println();
            // break out of the while loop:
#if DBG
            Serial.println("*Html Home-page send, break out*");
#endif
            break;
          }
          else {      // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        }
        else if (c != '\r') {    // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }

        // Check to see if the client request was "GET /C" or "GET /O or GET/I":
        if (currentLine.endsWith("GET /C")) {
          client.println("HTTP/1.1 200 OK");
          client.println("Content-type:text/html");
          client.println();
          client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"); // metaview
          client.println("<body style=\"background-color:#cccccc\">");
          client.print("Curtains closing.<br>");
          client.print("Click <a href=\"/\">here</a> to return to menu.<br>");
          client.print("<meta http-equiv=\"refresh\" content=\"3;url=/\" />");
          client.println();
          CloseCurtains();
#if DBG
          Serial.println("*Curtains Closed*");
#endif
          break;

        }
        if (currentLine.endsWith("GET /O")) {
          client.println("HTTP/1.1 200 OK");
          client.println("Content-type:text/html");
          client.println();
          client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"); // metaview
          client.println("<body style=\"background-color:#cccccc\">");
          client.print("Curtains opening.<br>");
          client.print("Click <a href=\"/\">here</a> to return to menu.<br>");
          client.print("<meta http-equiv=\"refresh\" content=\"3;url=/\" />");
          client.println();
          OpenCurtains();
#if DBG
          Serial.println("*Curtains Opened*");
#endif
          break;
        }

        if (currentLine.endsWith("GET /I")) {
          client.println("HTTP/1.1 200 OK");
          client.println("Content-type:text/html");
          client.println();
          client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"); // metaview
            client.println("<body style=\"background-color:#cccccc\">"); // set color CCS HTML5 style 
            client.print( "<p style=\"font-family:verdana; color:GhostWhite\">&nbsp<font size=1>o</font><font size=2>o</font><font size=3>O</font><font size=2>O</font><font size=1>o</font><font size=2>O</font><font size=3>O</font><font size=2>o</font><font size=1>o</font> <br>");
            client.print( "<font size=5>FoxCurt </font>&nbsp&nbsp&nbsp&nbsp<font size=4>Info Menu</font><br>");
            client.print("<p style=\"font-family:verdana; size:2; color:#888888\">-------------------------------------------<br>");
          client.print("IP Adress : "); client.print(Myip); client.print("<br>");
          client.print("Ssid : "); client.print(WiFi.SSID()); 
          client.print(", Rssi : "); client.print(Myrssi); client.print("dB<br>");
          UpdateRTCStrings();
          client.print("Date/Time : "); client.print( mydays[weekday( rtc.getEpoch() ) -1 ] );client.print(", "); client.print(DateString); client.print(" ");   
          client.print(TimeString); client.print("<br>");   
          if(summerTime) {
               if(check_eu_summertime()&0x01) client.print("\nInto Summertime. "); else client.print("\nOut of Summertime. ");
               if((check_eu_summertime()>>4)&0x01) client.print("Clock changed today.<br><br>"); else client.print("<br><br>");
               }
          client.print("NTP time received : "); client.print(epoch_valid); client.print(".<br>");
          client.print("Manual time set : "); client.print(manual_valid); client.print(".<br>");
          client.print("UTC-time zone : "); if (utcTime>=0 ) client.print("+"); else client.print("-");
          client.print(abs(utcTime)); client.print(" hr.<br>"); 
          client.print("Summer-time : "); if (summerTime ) client.print("used<br>"); else client.print("not used<br>");
          client.print("Weekend rise-time correct: "); client.print(weekendCorrect); client.print("<br>");
          if(weekendCorrect) {
            client.print("Saturday set at : ");
            client.print(weekendsaHour); client.print(":");client.print(weekendsaMinutes);client.print(":00<br>");
            client.print("Sunday set at : ");
            client.print(weekendsuHour); client.print(":");client.print(weekendsuMinutes);client.print(":00<br>");
          }
          client.print("Workweek rise-time correct: "); client.print(weekCorrect);  client.print("<br>");
          if(weekCorrect) {
            client.print("Monday-Friday set at : ");
            client.print(weekmofrHour); client.print(":");client.print(weekmofrMinutes);client.print(":00<br>");
          }
          client.print("<br>Curtain Status : "); 
           if(CurtainStatus==0) client.print("Closed (Position "); else {if(CurtainStatus==100) client.print("Opened (Position ");else client.print("Unknown (Position");}
           client.print(100*CurtainPosition/MAXSTEPS);client.print("%).<br>");
          client.print("Morning Alarm1 : "); client.print(MyAlarm[1][0]); client.print(":");client.print(MyAlarm[1][1]);client.print(":00 <br>");
          client.print("Evening Alarm2 : "); client.print(MyAlarm[2][0]); client.print(":");client.print(MyAlarm[2][1]);client.print(":00 <br>");
          client.print("Synchro Alarm3 : "); client.print(MyAlarm[0][0]); client.print(":");client.print(MyAlarm[0][1]);client.print(":00<br>");          
          client.print("Next Alarm : #"); client.print(MyAlarm[AlarmCounter][3]);client.print("<br><br>");
          client.print("Succes full Open : "); client.print(OpenSuccess);client.print("<br>");
          client.print("Succes full Close : "); client.print(CloseSuccess);client.print("<br>");
          client.print("No full open/close : "); client.print(BlockSuccess);client.print("<br>");
          client.print("Lockup Detected : "); client.print(NoSuccess);client.print("<br>");
          client.print("MotorLock set at: "); client.print(MOTORLOCK);client.print("<br>");
          client.print("-------------------------------------------<br>");
          client.print("<font size=1>Click <a href=\"/\">here</a> to return to menu.<br></p>");
          client.println();
#if DBG
          Serial.println("*Info Send, break out*");
#endif
          break;
        }

        if (currentLine.endsWith("GET /H")) {
          client.println("HTTP/1.1 200 OK");
          client.println("Content-type:text/html");
          client.println();
          client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"); // metaview
          client.println("<body style=\"background-color:#cccccc\">");
          client.print( "<p style=\"font-family:verdana; size:5; color:GhostWhite\">HELP menu.<br>");
          client.print("<p style=\"font-family:verdana; size:2; color:#888888\">Use HTML-command   http:\\\\"); 
          client.print(Myip); client.print("\\[command]<br><br>");
          client.print("[command] = L[x] - SetLock, R[x] - (Re)setPosition, A[x] - SetAlarmNr<br>");
          client.print("[command] = D - DumpData, X - Synctime<br><br>");
          client.print("[command] = settime[HH:MM:SS] for time set.<br>");
          client.print("[command] = setdate[DD/MM/YY] for date set.<br>");
          client.print("[command] = setweekend[HH:MM:SS] for weekend rise time override.(0=reset)<br>");          
          client.print("[command] = setutc[x] , for utc-time correct (-11..+11)<br>");
          client.print("[command] = setsummer[x] , for summertime correct On/Off (0..1)<br>");
          client.print("<br>");
          client.print("Click <a href=\"/\">here</a> to return to menu.<br>");
          client.println();
#if DBG
          Serial.println("*Help info Send, break out*");
#endif
          break;
        }
        
        if (currentLine.endsWith("GET /D")) {  // Dump last Data of the Motor Current-array to WebPage
          client.println("HTTP/1.1 200 OK");
          client.println("Content-type:text/html");
          client.println();
          client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"); // metaview
          client.println("<body style=\"background-color:#cccccc\">");
          client.print("Current;Avg;%Vol;L:"); client.print(Lock); client.print(" S:");client.print(CounterSt); client.print("<br>");
          for(v=0;v<SARRAY;++v) {
            client.print(ShadowArray[v]); client.print(";");client.print(AverageArray0[v]);client.print(";");client.print(AmplitudeArray0[v]);client.print(";<br>");
            }
          client.print(".<br>");
          client.print("Click <a href=\"/\">here</a> to return to menu.<br>");
          client.println();
#if DBG
          Serial.println("*Dump Info Send, break out*");
#endif
          break;
        }

        
        if (currentLine.endsWith("GET /R")) {
          //client.read(); client.read();// read the space %20    
          hr = ( ((byte) client.read()-48) )%2; // reAD ONE OR ZERO
          client.println("HTTP/1.1 200 OK"); 
          client.println("Content-type:text/html");
          client.println();    
          client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"); // metaview
          client.println("<body style=\"background-color:#cccccc\">");           
          if(hr==0) client.print("Reset Curtain-position to Closed(=0)<br>");  else client.print("Reset Curtain-position to Opened(=100)<br>");
          client.print("<br>Click <a href=\"/\">here</a> to return to menu.<br>");
          client.print("<meta http-equiv=\"refresh\" content=\"3;url=/\" />");
          client.println();
          CurtainPosition=hr*MAXSTEPS;
          CurtainStatus = hr*100;
#if DBG
          Serial.println("*Position ReSet, break out*");
#endif
          break;
        }

        
        if (currentLine.endsWith("GET /L")) {
          //client.read(); client.read();// read the space %20         
          hr = ( ((byte) client.read()-48)*10 + (byte) client.read()-48 )%100; // read hour AScii, and make it simple rounded up for 0-23 hr
          client.println("HTTP/1.1 200 OK"); 
          client.println("Content-type:text/html");
          client.println();  
          client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"); // metaview
          client.println("<body style=\"background-color:#cccccc\">");
          client.print("New Lock : "); client.print(hr); client.print(".<br>");
          client.print("<br>Click <a href=\"/\">here</a> to return to menu.<br>");
          client.print("<meta http-equiv=\"refresh\" content=\"3;url=/\" />");
          client.println();
          MOTORLOCK=hr;
#if DBG
          Serial.println("*Lock Set, break out*");
#endif
          break;
        }


        
        if (currentLine.endsWith("GET /A")) {
          //client.read(); client.read();// read the space %20         
          hr = ( ((byte) client.read()-48) )%4; // read alarm, value 1,2,or 3
          client.println("HTTP/1.1 200 OK"); 
          client.println("Content-type:text/html");
          client.println();  
          client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"); // metaview
          client.println("<body style=\"background-color:#cccccc\">");
          client.print("New Alarm: "); client.print(hr); client.print(".<br>");
          client.print("<br>Click <a href=\"/\">here</a> to return to menu.<br>");
          client.print("<meta http-equiv=\"refresh\" content=\"3;url=/\" />");
          client.println();
          AlarmCounter=hr%3; // index is one more looped
#if DBG
          Serial.println("*Alarm-type Set, break out*");
#endif
          break;
        }
        
        if (currentLine.endsWith("GET /settime")) {
          //client.read(); client.read();// read the space %20         
          hr = ( ((byte) client.read()-48)*10 + (byte) client.read()-48 )%24; // read hour AScii, and make it simple rounded up for 0-23 hr
          client.read(); // read the ":"
          mn = ( ((byte) client.read()-48)*10 + (byte) client.read()-48 )%60; // read minutes Ascii and make it simple rounder up for 0-59 min
          client.read(); // read the ":"
          sc = ( ((byte) client.read()-48)*10 + (byte) client.read()-48 )%60; // read Seconds Ascii and make it simple rounder up for 0-59 sec
          client.println("HTTP/1.1 200 OK"); 
          client.println("Content-type:text/html");
          client.println();    
          client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"); // metaview
          client.println("<body style=\"background-color:#cccccc\">");
          client.print("New Time Set to : "); client.print(hr); client.print("hr,");
          client.print(mn); client.print("min,"); 
          client.print(sc); client.print("sec<br>"); 
          client.print("<br>Click <a href=\"/\">here</a> to return to menu.<br>");
          client.print("<meta http-equiv=\"refresh\" content=\"5;url=/\" />");
          client.println();
          rtc.setHours(hr);rtc.setMinutes(mn);rtc.setSeconds(sc); // set new date/time and reset alarm, set global epoch and calcualte alarm times
          epoch = rtc.getEpoch();
          CalculateAlarmTimes();
          ResetAlarm();
          manual_valid=1;
#if DBG
          Serial.println("*Time Set, break out*");
#endif
          break;
        }

        if (currentLine.endsWith("GET /setdate")) {
          //client.read(); client.read();// read the space %20         
          hr = ( ((byte) client.read()-48)*10 + (byte) client.read()-48 )%32; // read day AScii, and make it simple rounded up for 31 days
          client.read(); // read the "."
          mn = ( ((byte) client.read()-48)*10 + (byte) client.read()-48 )%13; // read month Ascii and make it simple rounder up for 1-12
          client.read(); // read the "."
          sc = ( ((byte) client.read()-48)*10 + (byte) client.read()-48 )%100; // read year Ascii and make it simple rounder up 0-99
          client.println("HTTP/1.1 200 OK"); 
          client.println("Content-type:text/html");
          client.println();       
          client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"); // metaview
          client.println("<body style=\"background-color:#cccccc\">");
          client.print("New Date Set to : "); client.print(hr); client.print("-");
          client.print(mn); client.print("-"); 
          client.print(sc); client.print("<br>"); 
          client.print("<br>Click <a href=\"/\">here</a> to return to menu.<br>");
...

This file has been truncated, please download it to see its full contents.
Curtain.aiaCSS
MIT App Inventor Code
No preview (download only).
Curtain.apkC/C++
APK file for Android install
No preview (download only).

Custom parts and enclosures

Nema42 Wallmount Casing
STL file - 3D printer

Schematics

Schematic
Schematic for MKR1000 + MKR2UNO + MOTORSHIELD R3 + BUTTONS
Curtain server a2dctzvb4a

Comments

Similar projects you might like

Servo Control with TV Remote Control

Project showcase by eldo85

  • 7,013 views
  • 5 comments
  • 22 respects

Control an LED with the Remote Control

Project showcase by Nicholas_N

  • 4,467 views
  • 2 comments
  • 12 respects

Universal Remote Control using Arduino, 1Sheeld and Android

Project tutorial by Ashraf Nabil

  • 8,346 views
  • 5 comments
  • 12 respects

Tutorial Arduino Mini Laser Turret Control

Project in progress by Josh From BreakoutBros.com

  • 10,145 views
  • 5 comments
  • 34 respects

PHPoC - Arduino Self Balancing Robot with BT+Web Control

Project in progress by Suyog Gunjal

  • 3,993 views
  • 2 comments
  • 24 respects
Add projectSign up / Login