Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
| × | 1 | ||||
Software apps and online services | ||||||
![]() |
| |||||
![]() |
| |||||
Hand tools and fabrication machines | ||||||
![]() |
|
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
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 :
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):
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 ServerThe 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
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
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.
#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\"> <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>    <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\"> <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>    <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.
Comments
Please log in or sign up to comment.