/* * * */ #include #include #include #include #include //#include #include #include "EEPROM_IO.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 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; }; /*********************************************************************************/ /* Variable declarations */ time_t time; time_t blinkTime = 0; struct st_config config = { // Config version. Set to 0 to debug (doesn't read from eeprom) //5, 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 7, // "Dawn" 21 // "Dusk" - used to shut garage doors at night. }; 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 */ /* const char* ip_to_str(const uint8_t* ipAddr) { static char buf[16]; sprintf(buf, "%d.%d.%d.%d\0", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]); return 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(pb, sizeof(pb)); // 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 | pb[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)pb[44] * 256 + pb[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) { /* store the HTML in program memory using the P macro - See index.html for content, encode_html.sh for getting this data */ // /* P(message) = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc5, 0x56, 0x5b, 0x8f, 0x9b, 0x38, 0x14, 0x7e, 0xcf, 0xaf, 0xb0, 0xd4, 0x59, 0x99, 0xa8, 0x01, 0x02, 0x53, 0xfa, 0x40, 0x08, 0x2f, 0x53, 0xed, 0x4a, 0xab, 0x55, 0xbb, 0x52, 0x2f, 0xef, 0x06, 0x1b, 0xf0, 0x04, 0x6c, 0x6a, 0x9b, 0xcc, 0x44, 0xd1, 0xfc, 0xf7, 0x3d, 0x36, 0x24, 0x43, 0x67, 0x32, 0xea, 0x54, 0xfb, 0x50, 0x25, 0x0a, 0xe6, 0x3b, 0xdf, 0xb9, 0xfa, 0x9c, 0xa3, 0x64, 0x8d, 0xe9, 0xda, 0x3c, 0x6b, 0x18, 0xa1, 0x79, 0x66, 0xb8, 0x69, 0x59, 0xfe, 0x41, 0x4a, 0x85, 0x6e, 0xa4, 0x30, 0x4a, 0xb6, 0x59, 0x38, 0x62, 0x8b, 0xac, 0x63, 0x86, 0x20, 0x4e, 0xb7, 0x78, 0xcf, 0xd9, 0x5d, 0x2f, 0x95, 0xc1, 0x48, 0x90, 0x8e, 0xcd, 0xdf, 0x4b, 0xd0, 0x61, 0xc2, 0x6c, 0xf1, 0x1d, 0xa7, 0xa6, 0xd9, 0x46, 0xef, 0xd7, 0x1b, 0xc4, 0x05, 0x37, 0x9c, 0xb4, 0xbe, 0x2e, 0x49, 0xcb, 0xb6, 0x51, 0x00, 0x50, 0x47, 0xee, 0x79, 0x37, 0x74, 0x73, 0x68, 0xd0, 0x4c, 0xb9, 0x77, 0x52, 0x00, 0xb4, 0xde, 0x60, 0x70, 0xa8, 0xcd, 0xc1, 0x3a, 0x2e, 0x24, 0x3d, 0xa0, 0x63, 0x41, 0xca, 0x5d, 0xad, 0xe4, 0x20, 0xa8, 0x5f, 0xca, 0x56, 0xaa, 0xf4, 0x4d, 0x51, 0x14, 0x9b, 0x0a, 0x1c, 0xfa, 0x15, 0xe9, 0x78, 0x7b, 0x48, 0xf1, 0x37, 0xa6, 0x28, 0x11, 0x04, 0xaf, 0x34, 0x11, 0xda, 0x07, 0x83, 0xbc, 0x7a, 0x58, 0x34, 0x31, 0x3a, 0xb6, 0x5c, 0x30, 0xbf, 0x61, 0xbc, 0x6e, 0x4c, 0x9a, 0xac, 0xff, 0xd8, 0x00, 0x7a, 0xfd, 0x04, 0xb5, 0x60, 0xd0, 0x93, 0x9a, 0xa1, 0xa3, 0x61, 0xf7, 0xc6, 0x27, 0x2d, 0xaf, 0x45, 0x5a, 0x42, 0x32, 0x4c, 0x59, 0x91, 0x3d, 0x29, 0x46, 0x7d, 0x63, 0xe3, 0x5b, 0x05, 0x54, 0x16, 0x83, 0x31, 0x52, 0x5c, 0x62, 0x77, 0x44, 0xd5, 0x5c, 0xf8, 0x2d, 0xab, 0x4c, 0x4a, 0x06, 0x23, 0x4f, 0x80, 0x72, 0x9e, 0x1c, 0x02, 0x06, 0x4f, 0xfa, 0x3d, 0xa1, 0x94, 0x8b, 0xda, 0x2f, 0x24, 0xbc, 0x77, 0x69, 0xd2, 0xdf, 0x5b, 0xe9, 0xa3, 0xfd, 0x73, 0xd8, 0x20, 0x70, 0x55, 0x4d, 0xe3, 0xf5, 0x7a, 0x24, 0x0d, 0xa2, 0x95, 0xe5, 0x8e, 0x51, 0xa8, 0x19, 0x6b, 0x57, 0x81, 0xec, 0x99, 0x98, 0x8e, 0x65, 0x2b, 0xf5, 0x19, 0xd7, 0x86, 0x18, 0xe6, 0xce, 0x97, 0xaa, 0x48, 0x12, 0xfb, 0xd9, 0x4c, 0x61, 0xa4, 0xd7, 0x60, 0xba, 0x90, 0x8a, 0xc2, 0x6d, 0x28, 0x42, 0xf9, 0xa0, 0xd3, 0x77, 0xa3, 0xb3, 0x99, 0x2b, 0x74, 0xdc, 0x73, 0xcd, 0x0b, 0xde, 0x72, 0x73, 0x48, 0x1b, 0x4e, 0x29, 0x13, 0xcf, 0xc2, 0xb9, 0xe4, 0x2a, 0x49, 0x2a, 0xcb, 0x3b, 0xc7, 0x79, 0x89, 0x53, 0x25, 0x89, 0x2b, 0xf7, 0x63, 0x02, 0x17, 0x2d, 0x55, 0x96, 0x95, 0x85, 0x53, 0x87, 0x64, 0xba, 0x54, 0xbc, 0x37, 0xc8, 0x1c, 0x7a, 0x68, 0x47, 0x7b, 0x23, 0xe1, 0x2d, 0xd9, 0x93, 0x11, 0xc5, 0x48, 0xab, 0x72, 0x8b, 0x1b, 0x63, 0xfa, 0x34, 0x0c, 0x4b, 0x49, 0x59, 0x70, 0xfb, 0x7d, 0x60, 0xea, 0x10, 0x94, 0xb2, 0x0b, 0xc7, 0xa3, 0x1f, 0x05, 0x49, 0x10, 0x07, 0x1d, 0x17, 0xc1, 0xad, 0xc6, 0x39, 0x18, 0x76, 0xaa, 0x3f, 0xb3, 0x9c, 0x2f, 0xae, 0x3c, 0x2a, 0xcb, 0xa1, 0x83, 0x8b, 0x5f, 0x06, 0x0a, 0x26, 0xe8, 0xe0, 0x55, 0x83, 0x28, 0x0d, 0x97, 0xc2, 0x5b, 0xa2, 0xe3, 0x02, 0xa1, 0x2b, 0x0f, 0xbf, 0xa9, 0x28, 0xc1, 0x4b, 0x48, 0x89, 0x97, 0xbb, 0x99, 0xf8, 0x78, 0x15, 0xf4, 0x52, 0x1b, 0x0f, 0x53, 0x89, 0x57, 0xe8, 0x58, 0x29, 0x68, 0x65, 0x0a, 0x83, 0x97, 0xa2, 0xe8, 0x61, 0xb9, 0x81, 0xef, 0xa4, 0x5d, 0xd3, 0xe8, 0x15, 0xea, 0x35, 0x51, 0xd0, 0xbc, 0x56, 0x3f, 0x7a, 0x6e, 0x20, 0xfe, 0x25, 0x03, 0xf1, 0xdc, 0xc0, 0xd0, 0x53, 0xe8, 0x9e, 0xcf, 0xd0, 0x42, 0x83, 0xf6, 0x00, 0xb1, 0xe8, 0xc9, 0xc6, 0x13, 0xe1, 0x98, 0x6f, 0x50, 0x33, 0xf3, 0xf7, 0xe7, 0x4f, 0x1f, 0x3d, 0x7c, 0xab, 0xa5, 0x00, 0xcb, 0x67, 0x8f, 0xc0, 0x25, 0x23, 0xc9, 0xd2, 0x18, 0x29, 0x1b, 0x07, 0xcd, 0x18, 0x3b, 0x76, 0x58, 0xa1, 0x3d, 0x69, 0x4f, 0xac, 0x31, 0x7e, 0x8c, 0xde, 0x22, 0x90, 0x2c, 0x03, 0x62, 0x8c, 0xf2, 0x70, 0xd9, 0x12, 0xad, 0xb1, 0xe3, 0x81, 0x00, 0xbb, 0x1e, 0xc1, 0x2e, 0x56, 0x84, 0x1e, 0xa6, 0xa7, 0x66, 0xe6, 0x0b, 0xef, 0x98, 0x1c, 0x8c, 0x37, 0x8f, 0x71, 0x85, 0xa2, 0xf5, 0x7a, 0xed, 0x38, 0x96, 0xe9, 0x3a, 0xe8, 0x74, 0xd1, 0xa1, 0x5b, 0x80, 0x8b, 0xcc, 0x6e, 0x1b, 0x78, 0x50, 0xbe, 0x47, 0xce, 0xd3, 0xd6, 0x2e, 0x05, 0x00, 0x9a, 0x38, 0x8f, 0x12, 0xf4, 0x2f, 0x51, 0x3b, 0xf4, 0x0f, 0x11, 0x0c, 0xf8, 0xb1, 0x45, 0xaf, 0x9f, 0x2c, 0x4b, 0x00, 0x16, 0x99, 0x5b, 0x12, 0x93, 0xfa, 0x0f, 0x8b, 0xc3, 0xca, 0x94, 0xfd, 0x81, 0x55, 0x6b, 0x3d, 0xc0, 0x36, 0xad, 0xa8, 0x9c, 0x98, 0x8f, 0x73, 0x9a, 0x7f, 0x82, 0x11, 0xc9, 0x42, 0x60, 0x40, 0x2b, 0x1a, 0xfa, 0x54, 0x61, 0x98, 0x14, 0x66, 0xe3, 0x96, 0x7f, 0x9d, 0x86, 0xef, 0x65, 0xad, 0xf2, 0xb9, 0x9b, 0x1b, 0x37, 0x65, 0x3f, 0xa8, 0x84, 0x63, 0x7c, 0x63, 0x90, 0xb0, 0xcf, 0x5b, 0xdd, 0x13, 0xb1, 0xbd, 0xce, 0x67, 0x05, 0x19, 0x37, 0x53, 0x9e, 0x71, 0xd1, 0x0f, 0xd3, 0x64, 0x4c, 0xcb, 0x6a, 0x94, 0x9f, 0x77, 0x97, 0x73, 0x4b, 0xec, 0x45, 0x0d, 0x30, 0x3c, 0x7f, 0xda, 0x06, 0x47, 0xb6, 0x5a, 0x18, 0x85, 0xf9, 0xcb, 0x4e, 0xcf, 0x21, 0x43, 0xe3, 0xbf, 0xb6, 0x34, 0xcf, 0xd2, 0x05, 0xdd, 0xdf, 0x92, 0xaf, 0x1d, 0xd6, 0x53, 0xc2, 0x7f, 0xb9, 0x89, 0x72, 0x19, 0xa3, 0xe8, 0xd5, 0x39, 0xc7, 0xff, 0x23, 0xe7, 0xf8, 0x37, 0xe5, 0x1c, 0x5f, 0xcc, 0x39, 0x7e, 0x21, 0xe7, 0xf0, 0x34, 0x0a, 0x4e, 0x04, 0x8f, 0x69, 0xe4, 0x42, 0xf7, 0x47, 0x64, 0xf1, 0x1f, 0x55, 0xc9, 0x65, 0xbc, 0x90, 0x08, 0x00, 0x00, }; server.writeP(message, sizeof(message)); // */ /* P(message) = "Door Control" "" "" "" "" "" "" "
" "

15 Park Lane

" "

Door Control

" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "
Open
Unlocked
Closed
Open
Closed
Open
Closed
" "
" "" ""; server.printP(message); */ } } void ajaxHandler(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.write("{\"fdo\":\""); server.write(((frontDoor.open)?"open":"state")); server.write("\", \"fdc\":\""); server.write(((frontDoor.closed)?"closed":"state")); server.write("\", \"fdu\":\""); server.write(((frontDoor.controlActive)?"unlocked":"locked")); server.write("\", \"gd1o\":\""); server.write(((garageDoor1.open)?"open":"state")); server.write("\", \"gd1c\":\""); server.write(((garageDoor1.closed)?"closed":"state")); server.write("\", \"gd2o\":\""); server.write(((garageDoor2.open)?"open":"state")); server.write("\", \"gd2c\":\""); server.write(((garageDoor2.closed)?"closed":"state")); server.write("\"}"); } } void doHandler(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete) { if (type == WebServer::POST) { bool repeat; char name[16], value[16]; do { repeat = server.readPOSTparam(name, 16, value, 16); if ((strcmp(name, "frontdoor") == 0)) toggleDoorControl(&frontDoor); if ((strcmp(name, "garagedoor1") == 0)) toggleDoorControl(&garageDoor1); if ((strcmp(name, "garagedoor2") == 0)) toggleDoorControl(&garageDoor2); } while (repeat); server.httpSuccess(); server.write("OK"); } server.httpSeeOther("/"); return; } /*********************************************************************************/ /* 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->last_closed != door->closed)) return true; return false; } void toggleDoorControl(struct st_door *door) { if (!door->controlActive) { digitalWrite(door->controlPin, HIGH); door->controlTime = now(); } else { digitalWrite(door->controlPin, LOW); } door->controlActive = !door->controlActive; } /*********************************************************************************/ /* Setup function */ void setup() { byte eepromVersion; /* config.configVersion = 3; config.mac[0] = 0x00; config.mac[1] = 0xA5; config.mac[2] = 0xCB; config.mac[3] = 0x28; config.mac[4] = 0xF4; config.mac[5] = 0xCD; config.def_ip[0] = config.def_gateway[0] = 10; config.def_ip[1] = config.def_gateway[1] = 113; config.def_ip[2] = config.def_gateway[2] = 1; config.def_ip[3] = 160; config.def_gateway[3] = 254; config.def_netmask[0] = config.def_netmask[1] = config.def_netmask[2] = 255; config.def_netmask[3] = 0; config.notifyHost[0] = 10; config.notifyHost[1] = 113; config.notifyHost[2] = 1; config.notifyHost[3] = 255; config.notifyPort = 8888; config.ntpServer[0] = 10; config.ntpServer[1] = 113; config.ntpServer[2] = 1; config.ntpServer[3] = 1; config.UTC_offset = 1300; config.dawn = 7; config.dusk = 21; */ 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", &ajaxHandler); httpServer.addCommand("do", &doHandler); 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(); readDoorState(&frontDoor); readDoorState(&garageDoor1); readDoorState(&garageDoor2); if ((time - lastNTPtime) >= 600) sendNTPpacket(); if (NTPSocket.available() >= 46) parseNTPresponse(); if (frontDoor.controlActive && ((time - frontDoor.controlTime) >= 10) ) { toggleDoorControl(&frontDoor); } if ( (garageDoor1.controlActive && ((time - garageDoor1.controlTime) >= 1) ) || ( lastNTPtime > 0 && ((hour() > config.dusk) || (hour() < config.dawn)) && !garageDoor1.controlActive && garageDoor1.open && ((time - garageDoor1.controlTime) >= (2 * 60)) ) ) { toggleDoorControl(&garageDoor1); } // if (garageDoor2.controlActive && ((time - garageDoor2.controlTime) >= 1) ) { // toggleDoorControl(&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); } }