![](/media/cache/profiles/7c39a5f991bef4a1e34187677a65871d.jpg.50x50_q85.jpg)
A remote timer, equipped with a scheduling Web interface, controls a "DigitalOut".
Dependencies: Timezone NTPClient
WebTimer
The WebTimer is an Mbed device enabling to remotely control a DigitalOut. It has a "calendar-like" user interface to edit the schedule - a table you can click on (or tap on a touch-screen device).
The program was ported to Mbed from the Tuxgraphics site. Thank you Guido for sharing!
- When starting, it will print it's IP address over the USB serial link to the terminal screen. However, you can set a different static IP address if you modify the code in the
main.cpp
. - To connect to the WebTimer, type the IP address into the web browser edit box and hit
ENTER
. - When a cell is green then the timer keeps it's output
ON
during that period of time. If a cell is not green it means the output isOFF
.
- To select or deselect multiple cells you can click (keep down the button) and move the mouse over more cells.
- The smallest time interval on this 24h timer is 15minutes.
- To save the changes type in the password and hit
ENTER
or click on theSave
button. - The default password is "secret". But you can change it by editing the source code in
main.cpp
.
![]() | ![]() |
main.cpp
- Committer:
- hudakz
- Date:
- 2020-11-12
- Revision:
- 2:6d02d3d314a2
- Parent:
- 1:2d2517f82319
- Child:
- 3:2862fe67dce0
File content as of revision 2:6d02d3d314a2:
/********************************************* * Author: Guido Socher * Copyright: GPL V2 * See http://www.gnu.org/licenses/gpl.html * * 24h timer with web interface. * * See http://tuxgraphics.org/electronics/ * * Ported to mbed by Zoltan Hudak *********************************************/ #include "mbed.h" #include "EthernetInterface.h" #include "TCPSocket.h" #include <stdio.h> #include <string> #include "websrv_help_functions.h" #include "mbed_mktime.h" #include "NTPClient.h" #include "Timezone.h" #define IP "192.168.1.181" #define GATEWAY "192.168.1.1" #define NETMASK "255.255.255.0" #define PORT 80 #define STR_BUFFER_SIZE 40 #define NTP_SERVER "europe.pool.ntp.org" /**/ EthernetInterface* net; TCPSocket server; TCPSocket* client; char httpBuf[1500]; char httpHeader[256]; const int OFF = 0; const int ON = 1; const char PASSWORD[] = "secret"; // Change as you like DigitalOut output(LED1); char strBuf[STR_BUFFER_SIZE + 1]; uint16_t tablebitmap[6] = { 0 }; // each of the 96 bits represents one field in the time table Thread thread; EventQueue eventQueue; tm tmNow; DigitalOut relay(LED1, 0); NTPClient* ntp; TimeChangeRule cest = { "CEST", Last, Sun, Mar, 2, 1 * 60 }; // Central European Summer Time = NTP + 1 hour TimeChangeRule cet = { "CET", Last, Sun, Oct, 2, 0 }; // Central European Time = NTP Timezone cetZone(cest, cet); // Central European Time Zone /** * @brief * @note * @param * @retval */ uint8_t verifyPassword(char* str) { if (strncmp(PASSWORD, str, strlen(PASSWORD)) == 0) return(1); else return(0); } /** * @brief * @note * @param * @retval */ void setHeaderToHttp200ok(void) { strcpy(httpHeader, "HTTP/1.0 200 OK"); } /** * @brief * @note * @param * @retval */ void addHttpDataUint8(uint8_t i) { sprintf(strBuf, "%i", i); // convert integer to string if (i < 10) strcat(httpBuf, "0"); strcat(httpBuf, strBuf); } /** * @brief * @note * @param * @retval */ void addHttpDataUint16(uint16_t ui) { uint8_t i = 6; if (ui == 0) { strcat(httpBuf, "0"); return; } // convert a 16bit unsigned integer into a printable string strBuf[i] = '\0'; while (ui && i) { i--; strBuf[i] = (char)((ui % 10 & 0xf)) + 0x30; ui = ui / 10; } strcat(httpBuf, &strBuf[i]); } /** * @brief Check the tablebitmap * @note * @param * @retval 0 there is no change compared to previous minute and current state is off * 1 the on/off state changed from on to off * 2 the on/off state changed from off to on * 3 there is no change compared to previous minute and current state is on */ uint8_t checkTableBitmap(uint8_t h, uint8_t m) { uint8_t tablepage, bitpos; uint8_t curr = 0; uint8_t prev = 0; tablepage = h / 4; // which table page is the current if (tablepage > 5) tablepage = 5; // bounds check in case of garbage time bitpos = ((h % 4) * 60 + m) / 15; if (bitpos > 15) bitpos = 15; // bounds check in case of garbage time if (tablebitmap[tablepage] & (1 << bitpos)) { curr = 1; } // now check what the previous value was if (m > 0) { m--; } else { m = 59; if (h > 0) { h--; } else { h = 23; } } tablepage = h / 4; // which table page is the current if (tablepage > 5) tablepage = 5; // bounds check in case of garbage time bitpos = ((h % 4) * 60 + m) / 15; if (bitpos > 15) bitpos = 15; // bounds check in case of garbage time if (tablebitmap[tablepage] & (1 << bitpos)) { prev = 1; } if (prev == curr && curr == 0) return(0); if (prev == curr && curr == 1) return(3); if (curr == 1 && prev == 0) return(2); return(1); } /** * @brief * @note * @param * @retval */ void checkRelayStatus(void) { uint8_t i; i = checkTableBitmap(tmNow.tm_hour, tmNow.tm_min); if (i == 2 || i == 3) { relay = ON; } else { relay = OFF; } } /** * @brief * @note * @param * @retval */ void printPageSetClock(void) { strcpy(httpHeader, "HTTP/1.0 200 OK"); memset(httpBuf, 0, sizeof(httpBuf)); strcpy(httpBuf, "<meta name=viewport content=\"width=device-width\">\n"); /*$off*/ strcat ( httpBuf, "<a href=/>[←Back]</a>\n" "<h2>Set clock</h2>\n<pre>" "<form action=cc method=get>\n" "<blockquote>" "<input type=radio name=mt value=0 checked>Automatically by NTP server<br>" "<input type=radio name=mt value=1>Manually to" "</blockquote>" "time :<input type=text name=tv id=tf size=10>\n\n" "passw:<input type=password name=pw size=10><input type=submit value=\"set time\">\n" "</form>\n" "<script>\n" "d=new Date();\n" "e=document.getElementById(\"tf\");\n" "mo=d.getMonth()+1;\n" "e.value=d.getFullYear()+\"-\"+mo+\"-\"+d.getDate()+\"-\"+d.getHours()+\":\"+d.getMinutes();\n" "</script>\n" "</pre><hr>\n" ); /*$on*/ } /** * @brief * @note * @param * @retval */ void printPageRelay(void) { setHeaderToHttp200ok(); memset(httpBuf, 0, sizeof(httpBuf)); strcpy(httpBuf, "<meta name=viewport content=\"width=device-width\">\n"); strcat(httpBuf, "<a href=/>[←Back]</a> <a href=/sw>[↻Refresh]</a>\n""<h2>Switch</h2>\n""<pre>state: "); if (relay == ON) { strcat(httpBuf, "<font color=#00AA00>ON</font>"); } else { strcat(httpBuf, "OFF"); } strcat(httpBuf, "\n<form action=/cr method=get>"); strcat(httpBuf, "<input type=hidden name=sw value="); if (relay == ON) { strcat(httpBuf, "0>\npassw: <input type=password size=10 name=pw>"); strcat(httpBuf, "<input type=submit value=\"switch off\"></form>\n"); } else { strcat(httpBuf, "1>\npassw: <input type=password size=10 name=pw>"); strcat(httpBuf, "<input type=submit value=\"switch on\"></form>\n"); } strcat(httpBuf, "</pre><hr>\n"); } /** * @brief * @note In the function fill we encode all the 96 fiels into 6 numbers * with 16 bit. Every bit represents a table box. If the box was * green then the bit is 1 otherwise zero. * @param * @retval */ void printJavaScriptFunctions(void) { setHeaderToHttp200ok(); // /*$off*/ memset(httpBuf, 0, sizeof(httpBuf)); strcpy ( httpBuf, "var col=\"\";\n" "var cc=0;\n" "var e,i,j;\n" "function dw(s){document.writeln(s)}\n" "function mc(e){if (cc) e.style.backgroundColor=col}\n" "function wr(t,i){dw(\"<tr><td>\"+t+\"</td><td id=t\"+i+\" onmousedown=cbg(this) onmouseover=mc(this)> </td></tr>\")}\n" "function dwh(s){dw(\"<table border=1><tr><td>Time</td><td>green=on<br>white=off</td></tr>\")}\n" "function cbg(e)\n" "{\n" "cc=1;\n" "col=e.style.backgroundColor ? \"\" : \"#7AFA7A\";\n" "e.style.backgroundColor=col;\n" "}\n" "function sc(){cc=0}\n" "function fill(ff){\n" "var b=new Array();\n" "for(i=0;i<6;i++){\n" "b[i]=0;\n" "for(j=0;j<16;j++){\n" "e=document.getElementById(\"t\"+(i*16+j));\n" "if (e.style.backgroundColor)\n" "b[i]|=1<<j;\n" "}\n" "}\n" "ff.elements[0].value=b[0] +\"-\"+b[1]+\"-\"+b[2]+\"-\"+b[3]+\"-\"+b[4]+\"-\"+b[5]+\"-\";\n" "}\n" "function it(a)\n" "{\n" "for(i=0;i<6;i++){\n" "for(j=0;j<16;j++){\n" "e=document.getElementById(\"t\"+(i*16+j));\n" "if (a[i]&(1<<j))\n" "e.style.backgroundColor=\"#7AFA7A\";\n" "}\n" "}\n" "}" ); /*$on*/ } /** * @brief This is the top level main web page * @note * @param * @retval */ void printPageMain(void) { setHeaderToHttp200ok(); memset(httpBuf, 0, sizeof(httpBuf)); strcpy ( httpBuf, "<meta name=viewport content=\"width=device-width\">\n" "<a href=/sw>[Switch]</a> <a href=/sc>[Set clock]</a> <a href=/>[↻Refresh]</a>\n" ); /*$on*/ strcat(httpBuf, "<h2><pre>Watering Timer<br>"); strcat(httpBuf, "Output: "); if (relay == ON) { strcat(httpBuf, "<font color=#00AA00>ON</font>"); } else { strcat(httpBuf, "OFF"); } //strcat(httpBuf, "\n"); strcat(httpBuf, " Time: "); addHttpDataUint8(tmNow.tm_hour); strcat(httpBuf, ":"); addHttpDataUint8(tmNow.tm_min); strcat(httpBuf, "</pre></h2>\n"); /*$off*/ strcat ( httpBuf, //"<br>Click table fields to change.\n" "<script src=p1.js></script>\n" "<script>\n" "dw(\"<table onmouseup=sc()>\");\n" "dw(\"<tr><td>\");\n" "dwh();\n" "var i,t;\n" "for(i=0;i<48;i++){\n" "t=\"\";\n" "if (i%4==0) t=i/4+\":00\";\n" "wr(t,i);\n" "}\n" "dw(\"</table></td><td>\");\n" "dwh();\n" "for(i=48;i<96;i++){\n" "t=\"\";\n" "if (i%4==0) t=i/4+\":00\";\n" "wr(t,i);\n" "}\n" "dw(\"</table></td></tr></table>\");\n" "dw(\"1 cell=15min\");\n" ); /*$on*/ // initialize the table with the current settings strcat(httpBuf, "it(new Array("); addHttpDataUint16(tablebitmap[0]); int i = 1; while (i < 6) { strcat(httpBuf, ","); addHttpDataUint16(tablebitmap[i]); i++; } strcat(httpBuf, "));\n"); /*$off*/ strcat( httpBuf, "</script>\n" "<br><br>\n" "<form onsubmit=fill(this) action=st method=get>\n" "<input type=hidden name=tt>\n" "passw: <input type=password size=10 name=pw>\n" "<input type=submit value=save>\n" "</form>\n" ); /*$on*/ } // takes a string of the form command/Number and analyse it (e.g "cc?tv=8%3A15&pw=secret HTTP/1.1") // The first char of the url ('/') is already removed. int8_t analyseUrl(char* str) { int8_t i; int8_t j; uint8_t n; if (str[0] == ' ') { printPageMain(); return(1); } if (strncmp("sc", str, 2) == 0) { printPageSetClock(); return(1); } if (strncmp("sw", str, 2) == 0) { printPageRelay(); return(1); } if (strncmp("p1.js", str, 5) == 0) { printJavaScriptFunctions(); return(2); } // save the tablebitmap if (strncmp("st", str, 2) == 0) { // save the tablebitmap if (find_key_val(str, strBuf, STR_BUFFER_SIZE, (char*)"pw")) { urldecode(strBuf); if (verifyPassword(strBuf)) { if (find_key_val(str, strBuf, STR_BUFFER_SIZE, (char*)"tt")) { urldecode(strBuf); strBuf[37] = '\0'; // max length // gStrbuf is a string like this: 11-58-0-0-0-0- // no space allowed if (!isdigit(strBuf[0])) { return(0); } n = 0; i = 0; j = 0; while (strBuf[i] && n < 6) { if (strBuf[i] == '-') { strBuf[i] = '\0'; tablebitmap[n] = atol(&strBuf[j]); n++; j = i + 1; } i++; } checkRelayStatus(); printPageMain(); return(1); // main page } } } return(-1); } // change clock if (strncmp("cc", str, 2) == 0) { if (find_key_val(str, strBuf, STR_BUFFER_SIZE, (char*)"pw")) { urldecode(strBuf); if (verifyPassword(strBuf)) { if (find_key_val(str, strBuf, STR_BUFFER_SIZE, (char*)"mt")) { urldecode(strBuf); if (strBuf[0] == '0') { ntp->setTime(NTP_SERVER, 123, 3000); // set clock set_time(cetZone.toLocal(rtc_read())); // daylight saving time } else if (find_key_val(str, strBuf, STR_BUFFER_SIZE, (char*)"tv")) { urldecode(strBuf); strBuf[16] = '\0'; // strBuf is a string like: 2020-10-21-11:58 // no space allowed if (!isdigit(strBuf[0])) { return(0); } char* token = strBuf; char* delim; delim = strchr(token, '-'); *delim = '\0'; tmNow.tm_year = atoi(token) - 1900; token = delim + 1; delim = strchr(token, '-'); *delim = '\0'; tmNow.tm_mon = atoi(token) - 1; token = delim + 1; delim = strchr(token, '-'); *delim = '\0'; tmNow.tm_mday = atoi(token); token = delim + 1; delim = strchr(token, ':'); *delim = '\0'; tmNow.tm_hour = atoi(token); token = delim + 1; tmNow.tm_min = atoi(token); tmNow.tm_isdst = 1; // apply daylight saving time_t seconds; _rtc_maketime(&tmNow, &seconds, RTC_FULL_LEAP_YEAR_SUPPORT); set_time(seconds); } time_t seconds = rtc_read(); _rtc_localtime(seconds, &tmNow, RTC_FULL_LEAP_YEAR_SUPPORT); checkRelayStatus(); printPageMain(); return(1); // main page } return(0); } } return(-1); } i = 0; // switch on or off // change relay state FORM if (strncmp("cr", str, 2) == 0) { if (find_key_val(str, strBuf, STR_BUFFER_SIZE, (char*)"sw")) { if (strBuf[0] == '1') { i = 1; } if (find_key_val(str, strBuf, STR_BUFFER_SIZE, (char*)"pw")) { urldecode(strBuf); if (verifyPassword(strBuf)) { if (i) relay = ON; else relay = OFF; printPageMain(); return(1); // main page } } return(-1); } return(0); } return(0); } /** * @brief * @note * @param * @retval */ void sendHttp(TCPSocket* client, char* header, char* content, bool js = false) { char content_length[10] = { }; sprintf(content_length, "%u\r\n", strlen(content)); if (js) { strcat(header, "Content-Type: application/x-javascript\r\n\r\n"); } else { strcat(header, "\r\nContent-Type: text/html\r\n"); strcat(header, "Content-Length: "); strcat(header, content_length); strcat(header, "Pragma: no-cache\r\n"); strcat(header, "Connection: About to close\r\n\r\n"); } char c = content[0]; memmove(httpBuf + strlen(header), httpBuf, strlen(content)); // make room for the header strcpy(httpBuf, header); // copy the header on front of the content httpBuf[strlen(header)] = c; client->send((uint8_t*)httpBuf, strlen(httpBuf)); } /** * @brief * @note * @param * @retval */ void onTick() { time_t seconds = rtc_read(); #ifdef DEBUG printf("Seconds since January 1, 1970 = %llu s\r\n", seconds); #endif _rtc_localtime(seconds, &tmNow, RTC_FULL_LEAP_YEAR_SUPPORT); checkRelayStatus(); } /** * @brief * @note * @param * @retval */ int main(void) { printf("\r\nStarting..\r\n"); eventQueue.call_every(1s, onTick); thread.start(callback(&eventQueue, &EventQueue::dispatch_forever)); //net = NetworkInterface::get_default_instance(); net = new EthernetInterface(); //net->set_network("192.168.1.181","255.255.255.0","192.168.1.1"); // use static IP address, netmask, gateway if (!net) { printf("Error! No network inteface found.\n"); return 0; } //net->set_network (IP, NETMASK, GATEWAY); // include this to use static IP address nsapi_size_or_error_t r = net->connect(); if (r != NSAPI_ERROR_OK) { printf("Error! net->connect() returned: %d\n", r); return r; } // Show the network address SocketAddress addr; net->get_ip_address(&addr); printf("IP address: %s\n", addr.get_ip_address() ? addr.get_ip_address() : "None"); net->get_netmask(&addr); printf("Netmask: %s\n", addr.get_ip_address() ? addr.get_ip_address() : "None"); net->get_gateway(&addr); printf("Gateway: %s\n", addr.get_ip_address() ? addr.get_ip_address() : "None"); // Create NTP connection ntp = new NTPClient(*net); ntp->setTime(NTP_SERVER, 123, 3000); // set clock set_time(cetZone.toLocal(rtc_read())); // daylight saving time /* Open the server on ethernet stack */ server.open(net); /* Bind the HTTP port (TCP 80) to the server */ server.bind(PORT); /* Listen for clients */ server.listen(); //listening for http GET request while (true) { client = server.accept(); if (client) { #ifdef DEBUG client->getpeername(&addr); printf("Connection succeeded!\n\rIP: %s\n\r", addr.get_ip_address()); #endif client->recv(httpBuf, 1500); if (strncmp(httpBuf, "GET", 3) != 0) { setHeaderToHttp200ok(); strcpy(httpBuf, "<h1>200 OK</h1>"); sendHttp(client, httpHeader, httpBuf); } else { int cmd = analyseUrl((char*) &httpBuf[5]); // "GET [this is the tex to be analysed]" switch (cmd) { case -1: strcpy(httpHeader, "HTTP/1.0 401 Unauthorized"); strcpy(httpBuf, "<h1>401 Unauthorized</h1>"); sendHttp(client, httpHeader, httpBuf); break; case 0: strcpy(httpHeader, "HTTP/1.0 404 Page Not Found"); strcpy(httpBuf, "<h1>404 Page Not Found</h1>"); sendHttp(client, httpHeader, httpBuf); break; case 1: sendHttp(client, httpHeader, httpBuf); break; case 2: sendHttp(client, httpHeader, httpBuf, true); break; } } client->close(); } } }