/* * * */ #define WEBDUINO_FAVICON_DATA "" #include #include #include #include #include #include #include #include "EEPROM_IO.h" #include "strings.h" /*********************************************************************************/ /* Pin definitions */ #define METER_PULSE 2 #define STATUS_LED 7 #define FDOOR_STRIKE 8 #define FDOOR_CLOSED 9 #define GDOOR1_ACTIVATE 3 #define GDOOR1_OPEN 0 #define GDOOR1_CLOSED 1 #define GDOOR2_ACTIVATE 6 #define GDOOR2_OPEN 4 #define GDOOR2_CLOSED 5 /*********************************************************************************/ /* Typedefs/datastructures */ struct st_config { byte configVersion; byte mac[6]; byte def_ip[4]; byte def_netmask[4]; byte def_gateway[4]; byte ntpServer[4]; unsigned int UTC_offset; byte notifyHost[4]; unsigned int notifyPort; byte frontdoor_holdtime; byte garagedoor_nightgracetime; byte dawn; byte dusk; }; struct st_door { byte openSensePin; byte closedSensePin; byte controlPin; boolean open; boolean closed; boolean last_open; boolean last_closed; boolean controlActive; time_t controlTime; time_t openTime; }; /*********************************************************************************/ /* Variable declarations */ #define BUF1_SIZE 64 #define BUF2_SIZE 16 byte buf1[BUF1_SIZE]; byte buf2[BUF2_SIZE]; time_t time; time_t blinkTime = 0; struct st_config config = { // Config version. Set to 0 to debug (doesn't read from eeprom) 7, //0, // Ethernet MAC address 0x00, 0xA5, 0xCB, 0x28, 0xF4, 0xCC, // "Production" //0x00, 0xA5, 0xCB, 0x28, 0xF4, 0xCD, // "Testing" 10, 113, 1, 160, // Default IP if no DHCP 255, 255, 255, 0, // Default Netmask if no DHCP 10, 113, 1, 254, // Default GW if no DHCP 10, 113, 1, 1, // NTP Server 1300, // UTC offset 10, 113, 1, 255, // Host to send UDP status packets to 8888, // Port to send UDP status packets to 10, // Seconds to leave front door unlocked 1, // Minutes to leave garage doors open at night 7, // "Dawn" Hour - auto close before this. 21 // "Dusk" Hour - auto close after this. }; struct st_door frontDoor; struct st_door garageDoor1; struct st_door garageDoor2; EthernetUDP NTPSocket; time_t lastNTPtime = 0; //Server telnetServer(23); WebServer httpServer("", 80); /*********************************************************************************/ /* Utility functions */ char* mac_to_str(void *buf, const uint8_t* macAddr) { sprintf((char*)buf, "%02X:%02X:%02X:%02X:%02X:%02X", macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]); return (char*)buf; } char* ip_to_str(void *buf, const uint8_t* ipAddr) { sprintf((char*)buf, "%d.%d.%d.%d", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]); return (char*)buf; } char* time_to_str(void *buf, time_t t) { sprintf((char*)buf, "%d/%d/%d %02d:%02d:%02d", day(t), month(t), year(t), hour(t), minute(t), second(t)); return (char*)buf; } /*********************************************************************************/ /* NTP/Time functions */ // send an NTP request to the time server at the given address void sendNTPpacket() { // Packet buffer byte pb[48]; // set all bytes in the buffer to 0 memset(pb, 0, sizeof(pb)); // Initialize values needed to form NTP request pb[0] = 0b11100011; // LI, Version, Mode pb[1] = 0; // Stratum, or type of clock pb[2] = 6; // Polling Interval pb[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion pb[12] = 49; pb[13] = 0x4E; pb[14] = 49; pb[15] = 52; // all NTP fields have been given values, now // we send a packet requesting a timestamp: NTPSocket.beginPacket(config.ntpServer, 123); NTPSocket.write(pb, sizeof(pb)); NTPSocket.endPacket(); } void parseNTPresponse() { // byte pb[48]; unsigned long ntp_time = 0; float ntp_time_frac; // read the packet into the buffer NTPSocket.read(buf1, sizeof(buf1)); // NTP contains four timestamps with an integer part and a fraction part // we only use the integer part here for (int i=40; i<44; i++) ntp_time = ntp_time << 8 | buf1[i]; // part of the fractional part // could be 4 bytes but this is more precise than the 1307 RTC // which has a precision of ONE second // in fact one byte is sufficient for 1307 ntp_time_frac = ((long)buf1[44] * 256 + buf1[45]) / 65536.0; // convert NTP to UNIX time, differs seventy years = 2208988800 seconds // NTP starts Jan 1, 1900 // Unix time starts on Jan 1 1970. ntp_time -= 2208988800UL; // Adjust timezone and DST... ntp_time += (config.UTC_offset * 3600L) / 100L; // Notice the L for long calculations!! if (ntp_time_frac > 0.4) ntp_time++; // adjust fractional part, see above setTime(ntp_time); lastNTPtime = now(); } /*********************************************************************************/ /* HTTP Handler function */ void httpHandler(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete) { /* for a GET or HEAD, send the standard "it's all OK headers" */ server.httpSuccess("text/html; charset=iso-8859-1", "Content-Encoding: gzip\r\n"); /* we don't output the body for a HEAD request */ if (type == WebServer::GET) { server.writeP(index_html, sizeof(index_html)); } } void jsonHandler(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete) { /* for a GET or HEAD, send the standard "it's all OK headers" */ server.httpSuccess("application/json"); /* we don't output the body for a HEAD request */ if (type == WebServer::GET) { /* store the HTML in program memory using the P macro */ server.print("{\"fdo\":\""); server.print(((frontDoor.open )?"open" :"state")); server.print("\",\"fdc\":\""); server.print(((frontDoor.closed )?"closed":"state")); server.print("\",\"fdu\":\""); server.print(((frontDoor.controlActive)?"unlocked":"locked")); server.print("\",\"gd1o\":\""); server.print(((garageDoor1.open )?"open" :"state")); server.print("\",\"gd1c\":\""); server.print(((garageDoor1.closed)?"closed":"state")); server.print("\",\"gd2o\":\""); server.print(((garageDoor2.open )?"open" :"state")); server.print("\",\"gd2c\":\""); server.print(((garageDoor2.closed)?"closed":"state")); server.print("\"}"); } } void doHandler(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete) { if (type == WebServer::POST) { while (server.readPOSTparam((char*)buf2, BUF2_SIZE, (char*)buf1, BUF1_SIZE)) { if ((strcmp((const char*)buf2, "frontdoor") == 0)) toggleDoorControl(&frontDoor); if ((strcmp((const char*)buf2, "garagedoor1") == 0)) toggleDoorControl(&garageDoor1); if ((strcmp((const char*)buf2, "garagedoor2") == 0)) toggleDoorControl(&garageDoor2); } server.httpSuccess(); server.print("OK"); return; } server.httpSeeOther("/"); return; } void configHandler(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete) { /* for a GET or HEAD, send the standard "it's all OK headers" */ server.httpSuccess("text/html; charset=iso-8859-1", "Content-Encoding: gzip\r\n"); /* we don't output the body for a HEAD request */ if (type == WebServer::GET) { server.writeP(config_html, sizeof(config_html)); } } void configGetHandler(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete) { /* for a GET or HEAD, send the standard "it's all OK headers" */ server.httpSuccess("application/json"); /* we don't output the body for a HEAD request */ if (type == WebServer::GET) { sprintf((char*)buf1, "{\"info\":{\"version\":%d,\"time\":\"", config.configVersion); server.print((char*)buf1); server.print(time_to_str(buf2, time)); server.print("\",\"lastntp\":\""); server.print(time_to_str(buf2, lastNTPtime)); server.print("\"},\"settings\":{\"mac\":\""); server.print(mac_to_str(buf2, config.mac)); server.print("\",\"def_ip\":\""); server.print(ip_to_str(buf2, config.def_ip)); server.print("\",\"def_nm\":\""); server.print(ip_to_str(buf2, config.def_netmask)); server.print("\",\"def_gw\":\""); server.print(ip_to_str(buf2, config.def_gateway)); server.print("\",\"ntpserver\":\""); server.print(ip_to_str(buf2, config.ntpServer)); sprintf((char*)buf1, "\",\"utcoffset\":%d,\"stathost\":\"", config.UTC_offset); server.print((char*)buf1); server.print(ip_to_str(buf2, config.notifyHost)); sprintf((char*)buf1, "\",\"statport\":%u,\"fd_utime\":%u,\"gd_gtime\":%u,", config.notifyPort, config.frontdoor_holdtime, config.garagedoor_nightgracetime); server.print((char*)buf1); sprintf((char*)buf1, "\"dawn\":%u,\"dusk\":%u}}", config.dawn, config.dusk); server.print((char*)buf1); } } void configSetHandler(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete) { if (type == WebServer::POST) { server.httpSuccess(); while (server.readPOSTparam((char*)buf2, BUF2_SIZE, (char*)buf1, BUF1_SIZE)) { } return; } server.httpSeeOther("/"); return; } /*void debugHandler(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete) { server.httpSuccess("text/plain"); if (type == WebServer::GET) { sprintf((char*)buf1, "Time Now: %d/%d/%d %02d:%02d:%02d\n\0", day(time), month(time), year(time), hour(time), minute(time), second(time)); server.print((char*)buf1); sprintf((char*)buf1, "Last NTP: %d/%d/%d %02d:%02d:%02d\n\nPacket:\n\0", day(lastNTPtime), month(lastNTPtime), year(lastNTPtime), hour(lastNTPtime), minute(lastNTPtime), second(lastNTPtime)); server.print((char*)buf1); for (int i = 0; i < 48; i++) { sprintf((char*)buf1, "%02X \0", pb[i]); server.print((char*)buf1); } } } */ /*********************************************************************************/ /* Miscellaneous functions */ void setupDoor (struct st_door *door, byte cp, byte csp, byte osp) { door->controlPin = cp; door->closedSensePin = csp; door->openSensePin = osp; door->controlActive = false; pinMode(door->controlPin, OUTPUT); pinMode(door->closedSensePin, INPUT); digitalWrite(door->closedSensePin, HIGH); if (door->openSensePin != 255) { pinMode(door->openSensePin, INPUT); digitalWrite(door->openSensePin, HIGH); } } boolean readDoorState (struct st_door *door) { door->last_closed = door->closed; door->closed = !digitalRead(door->closedSensePin); door->last_open = door->open; if (door->openSensePin != 255) { door->open = !digitalRead(door->openSensePin); } else { door->open = !door->closed; } if (!door->last_open && door->open) door->openTime = time; if ((door->last_open != door->open) || (door->last_closed != door->closed)) return true; return false; } void toggleDoorControl(struct st_door *door) { if (!door->controlActive) { digitalWrite(door->controlPin, HIGH); door->controlTime = time; } else { digitalWrite(door->controlPin, LOW); } door->controlActive = !door->controlActive; } void deToggleDoorCheck(struct st_door *door, byte controlHoldTime) { if (door->controlActive && ((time - door->controlTime) >= controlHoldTime) ) toggleDoorControl(door); } void garageDoorNightCheck(struct st_door *door) { if ( lastNTPtime > 0 && !door->controlActive && door->open && ((hour(time) >= config.dusk) || (hour(time) < config.dawn)) && ((time - door->openTime) >= (config.garagedoor_nightgracetime * 60)) ) toggleDoorControl(door); } /*********************************************************************************/ /* Setup function */ void setup() { byte eepromVersion; eepromRead(0, eepromVersion); if (config.configVersion > eepromVersion) eepromWrite(0, config); if (config.configVersion > 0) eepromRead(0, config); if(Ethernet.begin(config.mac) == 0) { Ethernet.begin(config.mac, config.def_ip, config.def_gateway, config.def_netmask); } // Udp.begin(config.notifyPort); NTPSocket.begin(123); httpServer.setDefaultCommand(&httpHandler); httpServer.addCommand("json", &jsonHandler); httpServer.addCommand("do", &doHandler); httpServer.addCommand("config", &configHandler); httpServer.addCommand("config/get", &configGetHandler); httpServer.addCommand("config/set", &configSetHandler); // httpServer.addCommand("debug", &debugHandler); httpServer.begin(); setupDoor(&frontDoor, FDOOR_STRIKE, FDOOR_CLOSED, 255); setupDoor(&garageDoor1, GDOOR1_ACTIVATE, GDOOR1_CLOSED, GDOOR1_OPEN); setupDoor(&garageDoor2, GDOOR2_ACTIVATE, GDOOR2_CLOSED, GDOOR2_OPEN); pinMode(STATUS_LED, OUTPUT); sendNTPpacket(); } /*********************************************************************************/ /* MAIN PROGRAM */ void loop() { time = now(); if ((time - lastNTPtime) >= 600) sendNTPpacket(); if (NTPSocket.parsePacket() >= 46) parseNTPresponse(); readDoorState(&frontDoor); readDoorState(&garageDoor1); readDoorState(&garageDoor2); deToggleDoorCheck(&frontDoor, config.frontdoor_holdtime); deToggleDoorCheck(&garageDoor1, 1); deToggleDoorCheck(&garageDoor2, 1); garageDoorNightCheck(&garageDoor1); garageDoorNightCheck(&garageDoor2); httpServer.processConnection(); /* Toggle Status LED so we know things are OK */ if (time != blinkTime) { blinkTime = time; digitalWrite(STATUS_LED, HIGH); delay(80); digitalWrite(STATUS_LED, LOW); } }