/// /// /// Grow Controller /// /// /// The beginnings of a WiFi accessible switch. /// #include #include #include #include #include #include #include #include #include #include #include #include //#include // Application includes #include "./DNSServer.h" // Patched lib #include "WiFiConfiguration.h" #include "Web_RootPage.h" #include "Web_RSSIGraph.h" #include "Web_FavIcon.h" #include "Web_APConfig.h" #include "ProjGlobals.h" #include "Web_Resources.h" #include "GrowController.h" // APIs available in this (.ino) file #include "fauxmoESP.h" // If there is no hardware, such as with an ESP-01 module only, // set this. Then it won't loop through reset constantly, and // other behaviors that will be determined. #define HW_SIMULATE // SW Update Deployment instructions: // // 1) If any resources changed, rebuild Web_Resources.h // 2) Update this version number // 3) Build the binary // 4) Rename the .bin to match the version (e.g. GrowController v1.02.12.bin) // 5) Move the product (.bin) to the server location // 6) Update the PHP script on the server to match this version (if it does not auto-adapt) // 7) Visit the node's web page and initiate an update, or let it check on its own cycle // const String MyVer = "GrowController v2.00.11"; ///< Compared to the server version for SW Updates #define UPDATE_INTERVAL (12 * 60 * 60 * 1000) ///< Check once every 12 hours bool swUpdateInProcess = false; ///< Status of an update to keep the relays from running // ESP12-E pinout Version 2 // // +------------------+ // | __ __ _____ | // | | |_| |_| | // | |__________ | // |__________________| // Reset | !Reset Tx | Tx // Current | ADC Rx | Rx // Prog En | CHPD-EN GPIO5 | FAULT // SW_RESET | GPIO16 GPIO4 | SW_TOGGLE // OLED SCL | GPIO14 GPIO0 | // RLY Set2 | GPIO12 GPIO2 | OLED SDA // RLY Set1 | GPIO13 GPIO15 | Pull-down // | Vcc Gnd | // +------------------+ // ESP12-E pinout Version 1 // // +------------------+ // | __ __ _____ | // | | |_| |_| | // | |__________ | // |__________________| // Reset | !Reset Tx | Tx // Current | ADC Rx | Rx // Prog En | CHPD-EN GPIO5 | // SW_RESET | GPIO16 GPIO4 | SW_TOGGLE // RLY Set2 | GPIO14 GPIO0 | // LED On | GPIO12 GPIO2 | // RLY Set1 | GPIO13 GPIO15 | LED WiFi // | Vcc Gnd | // +------------------+ // +-------------+ // SW_RESET ---+----|(----------->| WiFiConfig |-----> LED_WIFI // | | | // | | |-----> isStationMode // | |-------------| // +----|>o---------->| Timer | // | |-----> Factory Reset // RESET_MS --------------------->| | // +-------------+ // #define SW_RESET 16 ///< The local reset switch #define RESET_MS 8000 ///< Hold Reset for this long to factory reset #define LED_WIFI 15 ///< The Pin controlling the WiFi on LED bool isStationMode = false; ///< Tracks if the WiFi is in station mode // WiFI LED Duty Cycle // // |******* | AP Mode // |************************* | Joining AP // |************************************************ | Joined // | | | | | | // 0 100 200 300 400 500ms // #define LED_APMODE__MS 80 #define LED_JOINING_MS 250 #define LED_JOINED__MS 490 #define LED_PERIOD__MS 500 // __ // // | \ // // onRef ------------------------------|- \ +-----------+ // // | +-----| S Q |--+--> CurrStatus // // +--------|+ / | | | // // +---------+ | |__/ +--| R !Q | +--> LED_ON // // ANA_IN --+--->| average |----+-->iRawSum/X | +-----------+ // // | +---------+ | __ | // // | | | \ | // // +--> iRaw +--------|- \ | // // | +--+ // // offRef ------------------------------|+ / // // |__/ // // #define ANA_IN A0 ///< The Analog input signal #define SENSE_SAMPLE_MS 20 ///< The Sample rate for the analog input #define AVG_RATIO 64 ///< Average Ratio (new is 1/AVG_RATIO of old) #define LED_ON 12 ///< The load-on indicating LED int32_t iRaw; ///< The raw sampled value int32_t iRawSum; ///< The Averaged Sum of Raw Samples bool CurrStatus = false; ///< Track the current sense indication of power on/off // _ +------------------+ // Turn On -------------| \____ | +-----------+ | // +--|>o--|_/ | _ +-->| D !Q |--+ // CurrStatus --| and +-------->) \ | | // | _ +-------->) +--->| ck Q |--------> RelayState // +-------| \____| +---->)_/ +-----------+ // Turn Off-------------|_/ | or // and | // SW_TOGGLE ----|>o---------------+ // #define SW_TOGGLE 4 ///< The local toggle switch bool RelayState = false; ///< The current tracked relay drive state // +-----------+ // | _ _ | // | _/ _| |_ |--------> Relay Set // RelayState ---------------------------------->| _ _ | // | \_ _| |_ |--------> Relay Reset // | | // +-----------+ // #define RELAY_PULSE_MS 15 ///< Duration of a pulse #define RLY_SET1 13 ///< The set-pin for the relay #define RLY_SET2 14 ///< The reset-pin for the relay // Timing: // // | | | | | // _________________________ // 3WaySwitch ____________________/ \__________________ // | | |b->| | |b->| // ______________________ // RelayState __________/ \______________________ // | |a->| | |a->| | // | | | | | // +++++++ ++++++++++ // +++ +++ +++ +++ // AnalogIn ++++++++++++ ++++++++++ +++++++++++++++ // | | | | | // CurrStatus |Off |On |Off |On |Off // // a) Locally applied change in load to filtered AnalogIn delay // b) Externally applied change in load to filtered AnalogIn delay // bool DebugValue; ///< Makes it chatty while running const byte DNS_PORT = 53; ///< Capture DNS requests on port 53 IPAddress apIP(192,168,4,1); ///< Private network for server DNSServer dnsServer; ///< Create the DNS object ESP8266WebServer server(80); ///< For the user experience fauxmoESP fauxmo; ///< For the WEMO emulator ConfigManager wifiConfig; ///< Track various configuration items ESP8266HTTPUpdate Updater; bool updateCheck = false; ///< Set when a SW update check is pending uint16_t autoOffTimer; ///< When the output is turned on, this is set to timeout in seconds // local functions void WiFiStateHandler(); ///< WiFi interface manager void StartWebServer(); ///< Activate the web server void HandleNotFound(); ///< Webpage for not-found URL void ProcessAutoOff(bool init = false); ///< Process the auto-off timer String AP_SSID = "GrowController"; ///< When in Access Point, this is the prefix void GetMac(); String macToStr(const uint8_t* mac); ///< Create C++ String with the mac String clientMac = ""; ///< Hosts the MAC to easily identify this node // =========================================================================== typedef enum { WFC_ConnectToAP, WFC_JoiningAP, WFC_JoinedAP, WFC_CreateAP, WFC_HostingAP, } WiFiConnectState; WiFiConnectState wifiConState; ///< Track the current WiFi state void SetLED(int pin, int state) { digitalWrite(pin, state); if (pin == LED_WIFI) ; // Serial.printf("SET LED_WIFI to %d\n", state); else if (pin == LED_ON) Serial.printf("SET LED_ON to %d\n", state); else Serial.printf("***** ERROR setting LED pin %d to %d\n", pin, state); } void ResetMonitor(bool init = false) { static unsigned long timeAtReset; static bool monitorActive; if (init) { timeAtReset = millis(); monitorActive = true; } else if (monitorActive) { int sw_state = digitalRead(SW_RESET); #ifdef HW_SIMULATE sw_state = 1; #endif if (sw_state == 1) { // not pressed monitorActive = false; } else if ((millis() - timeAtReset) > RESET_MS) { // reset has been held long enough FactoryReset(true); } } } // // Average the new sample by summation with 7/8 of the running Sum. // void ProcessCurrentSense() { static unsigned long last = millis(); unsigned long now = millis(); if (now - last > SENSE_SAMPLE_MS) { last = now; iRaw = analogRead(ANA_IN); // 1023 = 1.0v iRawSum = iRawSum - (iRawSum / AVG_RATIO) + iRaw; // Now have running avg. if (DebugValue) Serial.printf("Sample: %d, rawSum: %d, relay: %d (%d%d), \n", iRaw, iRawSum, RelayState, digitalRead(RLY_SET1), digitalRead(RLY_SET2)); if (iRawSum / AVG_RATIO > wifiConfig.getOnRef() && !CurrStatus) { CurrStatus = true; SetLED(LED_ON, 1); ProcessAutoOff(true); autoOffTimer = wifiConfig.getAutoOff(); // if zero, we do nothing... Serial.printf("Auto Off Timer set to %d\n", autoOffTimer); } else if (iRawSum / AVG_RATIO < wifiConfig.getOffRef() && CurrStatus) { CurrStatus = false; SetLED(LED_ON, 0); } } } // _______ ___ __ _______ // Press \_________________________/ \___/ \___/ // | | // v v // _ _ // Toggle _______/ \____________.............../ \______________.... // // Sample | | | | | ... // Block |---- 80 ms ---| |---- 80 ms -----| #define Block_ms 800 #define SAMPL_ms 100 void ProcessToggleSw() { static unsigned long last = millis(); static bool lastSWState = false; static uint8_t blockTics; unsigned long now = millis(); if (now - last > SAMPL_ms) { last = now; if (blockTics) { blockTics--; return; } bool swState = !digitalRead(SW_TOGGLE); // active low is pressed. if (DebugValue) Serial.printf("Sample: %d, rawSum: %d, relay: %d (%d%d), sw: %d\n", iRaw, iRawSum, RelayState, digitalRead(RLY_SET1), digitalRead(RLY_SET2), swState); if (swState != lastSWState && swState == true) { Serial.printf("Toggle pushed\n"); blockTics = Block_ms / SAMPL_ms; SetCircuit(CMD_Toggle); } lastSWState = swState; } } // Drive Relay controls pulsing either the set or the reset pin // // If the control signal changes, then it will generate a pulse on the // appropriate relay driver pin, ensuring that the alternate pin is // first off. // // This API is also called periodically, which permits it to then // time and auto-off any active control pin. // // @param i can be -1 for timing, 0 to drive one relay pin, 1 to drive the other. // void DriveRelay(int i = -1) { static int currentSignal = -1; static bool isActive = false; static unsigned long timeSet = 0; if (swUpdateInProcess) i = 2; switch (i) { case -1: // Any active timing to process if (isActive && (millis() - timeSet > RELAY_PULSE_MS)) { digitalWrite(RLY_SET1, LOW); digitalWrite(RLY_SET2, LOW); isActive = false; } break; case 0: if (i != currentSignal) { currentSignal = i; digitalWrite(RLY_SET2, LOW); // Turn off the alternate digitalWrite(RLY_SET1, HIGH); timeSet = millis(); isActive = true; } break; case 1: if (i != currentSignal) { currentSignal = i; digitalWrite(RLY_SET1, LOW); // Turn off the alternate digitalWrite(RLY_SET2, HIGH); timeSet = millis(); isActive = true; } break; case 2: default: // Error, or the force-off state digitalWrite(RLY_SET1, LOW); // Turn off the elements digitalWrite(RLY_SET2, LOW); isActive = false; break; } } void ProcessNameChange() { char xName[CFG_NAMESIZE]; fauxmo.getDeviceName(0, xName, CFG_NAMESIZE); if (0 != strcmp(wifiConfig.getName().c_str(), xName)) { fauxmo.renameDevice(0, wifiConfig.getName().c_str()); } } void ProcessSWUpdate() { static unsigned long lastCheck; unsigned long nowCheck = millis(); if ((nowCheck - lastCheck) > (UPDATE_INTERVAL)) { lastCheck = nowCheck; updateCheck = true; } if (updateCheck) { WiFiClient wifiClient; swUpdateInProcess = true; Serial.printf("SW Update start '%s'...\n", wifiConfig.getURL().c_str()); t_httpUpdate_return swRet = Updater.update(wifiClient, wifiConfig.getURL(), MyVer); switch (swRet) { default: case HTTP_UPDATE_FAILED: Serial.printf("SW Update - failed.\n"); Serial.printf("Error: %s\n", Updater.getLastErrorString().c_str()); delay(100); ESP.restart(); break; case HTTP_UPDATE_NO_UPDATES: Serial.printf("SW Update - no updates.\n"); break; case HTTP_UPDATE_OK: Serial.printf("SW Update - update ok.\n"); break; } swUpdateInProcess = false; updateCheck = false; Serial.printf("SW Update - process end.\n"); } } void ProcessConsole() { if (Serial.available()) { int ch = Serial.read(); Serial.printf("Serial.available() => %d\n", ch); switch (ch) { case '0': iRawSum = 10; break; case '1': iRawSum = 1000; break; case 'd': DebugValue = !DebugValue; break; case 'u': updateCheck = true; break; case '\x0D': // Ignore break; case '?': default: DebugValue = false; Serial.printf("Commands:\n"); Serial.printf(" 0|1 Off or On (by forcing rawSum\n"); Serial.printf(" d Debug toggle\n"); Serial.printf(" u update check\n"); break; } } static unsigned long last = millis(); if (millis() - last > 5000) { static uint32_t refFreeHeap; uint32_t freeHeap = ESP.getFreeHeap(); last = millis(); if (freeHeap != refFreeHeap) Serial.printf("[MAIN] Free heap: %d bytes\n", ESP.getFreeHeap()); refFreeHeap = freeHeap; } } void ProcessAutoOff(bool init) { static unsigned long lastCheck; unsigned long nowCheck = millis(); if (init) lastCheck = nowCheck; if (autoOffTimer && ((nowCheck - lastCheck) >= 1000)) { lastCheck += 1000; --autoOffTimer; SetLED(LED_ON, autoOffTimer & 1); // During auto-Off, blink slowly... if (autoOffTimer == 0) { Serial.printf("AutoOff time remaining 0 sec. Off Now.\n"); SetCircuit(CMD_Off); } else { Serial.printf("AutoOff time remaining %d sec\n", autoOffTimer); } } } void HWIOInit() { // User push-buttons pinMode(SW_TOGGLE, INPUT); pinMode(SW_RESET, INPUT); // Turn off relay drivers pinMode(RLY_SET1, OUTPUT); digitalWrite(RLY_SET1, 0); pinMode(RLY_SET2, OUTPUT); digitalWrite(RLY_SET2, 0); // Turn off user LEDs pinMode(LED_ON, OUTPUT); digitalWrite(LED_ON, 0); pinMode(LED_WIFI, OUTPUT); digitalWrite(LED_WIFI, 0); } void setup(void) { HWIOInit(); ResetMonitor(true); /// Initialize the reset switch monitor for long press Serial.begin(115200); Serial.printf("\n******************************************************************\n"); Serial.printf(" GrowController Web Server - Build " __DATE__ " " __TIME__ "\n"); Serial.printf(" Version %s\n", MyVer.c_str()); Serial.printf(" Copyright (c) 2018 by Smartware Computing, all rights reserved.\n"); Serial.printf("******************************************************************\n"); GetMac(); AP_SSID += "-"; AP_SSID += clientMac; wifiConfig.load(); String name = wifiConfig.getName(); String ssid = wifiConfig.getSSID(); String pass = wifiConfig.getPassword(); if (ssid == "" || pass == "") wifiConState = WFC_CreateAP; else wifiConState = WFC_ConnectToAP; #if 0 if (MDNS.begin("esp")) { // clientMac.c_str() //MDNS.addService("switch", "tcp", 80); //MDNS.addServiceTxt("switch", "tcp", "switchkey", "SWITCHVALUE"); Serial.printf("MDNS responder started.\n"); } #endif StartWebServer(); #if 1 fauxmo.addDevice(name.c_str()); fauxmo.enable(true); fauxmo.onSetState([](unsigned char device_id, const char * device_name, bool state) { Serial.printf("[MAIN] Device #%d (%s) state command: %s\n", device_id, device_name, state ? "ON" : "OFF"); if ((state && !CurrStatus) || (!state && CurrStatus)) { SetCircuit(RelayState ? CMD_Off : CMD_On); } }); fauxmo.onGetState([](unsigned char device_id, const char * device_name) { (void) device_id; (void) device_name; return CurrStatus; // GetCircuitStatus(); // whatever the state of the device is }); #endif } void loop(void) { ResetMonitor(); // Monitor for long press to factory reset WiFiStateHandler(); // start/connect as AP or Station ProcessSWUpdate(); // query the server for fresh SW fauxmo.handle(); // WEMO interface ProcessNameChange(); // Update fauxmo if the name changes ProcessCurrentSense(); // Get status by measuring current ProcessToggleSw(); // Monitor for user pressing toggle DriveRelay(); // Do what the relays need, if anything ProcessConsole(); ProcessAutoOff(); // If auto-off is enabled... } // ########################################################################### void WiFiStateHandler() { static unsigned long timeStateChange = 0; static unsigned long timeLEDControl; unsigned long timeLEDCycle; String ssid = wifiConfig.getSSID(); String pass = wifiConfig.getPassword(); IPAddress myIP; timeLEDCycle = millis() - timeLEDControl; if (timeLEDCycle >= LED_PERIOD__MS) { timeLEDControl = millis(); timeLEDCycle = 0; } switch (wifiConState) { case WFC_ConnectToAP: isStationMode = true; WiFi.mode(WIFI_STA); ssid = wifiConfig.getSSID(); pass = wifiConfig.getPassword(); WiFi.begin(ssid.c_str(), pass.c_str()); timeStateChange = millis(); timeLEDControl = timeStateChange; wifiConState = WFC_JoiningAP; break; case WFC_JoiningAP: if (timeLEDCycle <= LED_JOINING_MS) SetLED(LED_WIFI, 1); else SetLED(LED_WIFI, 0); if (WiFi.status() == WL_CONNECTED) { myIP = WiFi.localIP(); Serial.printf("IP Address: %s\n", myIP.toString().c_str()); timeStateChange = millis(); StartWebServer(); wifiConState = WFC_JoinedAP; } else if (millis() - timeStateChange > 30000) { timeStateChange = millis(); wifiConState = WFC_CreateAP; // failed for 30 sec, now what. retry or CreateAP? } break; case WFC_CreateAP: if (timeLEDCycle <= LED_APMODE__MS) SetLED(LED_WIFI, 1); else SetLED(LED_WIFI, 0); isStationMode = false; Serial.printf("Starting Access Point %s\n", AP_SSID.c_str()); WiFi.softAP(AP_SSID.c_str()); myIP = WiFi.softAPIP(); Serial.printf("IP Address: %s\n", myIP.toString().c_str()); dnsServer.start(DNS_PORT, "*", apIP); timeStateChange = millis(); StartWebServer(); wifiConState = WFC_HostingAP; break; case WFC_JoinedAP: if (timeLEDCycle <= LED_JOINED__MS) SetLED(LED_WIFI, 1); else SetLED(LED_WIFI, 0); server.handleClient(); break; case WFC_HostingAP: if (timeLEDCycle <= LED_APMODE__MS) SetLED(LED_WIFI, 1); else SetLED(LED_WIFI, 0); server.handleClient(); dnsServer.processNextRequest(); break; } } bool GetCircuitStatus() { return CurrStatus; } void SetCircuit(CircuitCmd_T newState) { if (swUpdateInProcess) return; switch (newState) { case CMD_Off: if (GetCircuitStatus()) { RelayState = !RelayState; DriveRelay(RelayState); } break; case CMD_On: if (!GetCircuitStatus()) { RelayState = !RelayState; DriveRelay(RelayState); } break; case CMD_Toggle: RelayState = !RelayState; DriveRelay(RelayState); break; default: break; } } void HandleCircuitOn() { SetCircuit(CMD_On); HandleGetState(); // Reply with current state } void HandleCircuitOff() { SetCircuit(CMD_Off); HandleGetState(); // Reply with current state } void HandleCircuitToggle() { SetCircuit(CMD_Toggle); HandleGetState(); // Reply with current state } // { // "id": "5c:cf:7f:c0:52:82", // "name": "3-Way Switch", // "state": 0, // "sense": 56, // "ip": "192.168.1.23", // "rssi": -63, // "countdown": "3:45", // "uptime": "0:11:45", // "wifimode": "station"|"ap", // } // void HandleGetState() { int day, hr, min, sec; char _upTime[15]; // 0000.00:00:00\0 + 1 spare char _timeout[10]; // 00:00:00\0 + 1 spare sec = millis() / 1000; min = sec / 60; hr = min / 60; day = hr / 24; if (day) snprintf(_upTime, sizeof(_upTime), "%dd %d:%02d:%02d", day, hr % 24, min % 60, sec % 60); else if (hr) snprintf(_upTime, sizeof(_upTime), "%d:%02d:%02d", hr, min % 60, sec % 60); else snprintf(_upTime, sizeof(_upTime), "%02d:%02d", min % 60, sec % 60); sec = autoOffTimer; min = sec / 60; hr = min / 60; if (hr) snprintf(_timeout, sizeof(_timeout), "%d:%02d:%02d", hr, min % 60, sec % 60); else snprintf(_timeout, sizeof(_timeout), "%d:%02d", min, sec % 60); GetMac(); String modeText = (isStationMode) ? "Station" : "Access Point"; String message = "{ \"id\": \"" + String(clientMac) + "\", "; message += "\"name\": \"" + wifiConfig.getName() + "\", "; message += "\"version\": \"" + MyVer + "\", "; message += "\"state\": " + String(CurrStatus) + ", "; message += "\"raw\": " + String(iRaw) + ", "; message += "\"sense\": " + String(iRawSum / AVG_RATIO) + ", "; message += "\"ip\": \"" + String(WiFi.localIP()[0]) + "." + String(WiFi.localIP()[1]) + "." + String(WiFi.localIP()[2]) + "." + String(WiFi.localIP()[3]) + "\", "; message += "\"rssi\": " + String(WiFi.RSSI()) + ", "; message += "\"countdown\": \"" + String(_timeout) + "\", "; message += "\"uptime\": \"" + String(_upTime) + "\", "; message += "\"wifimode\": \"" + modeText + "\", "; message += "\"toggle\": " + String(!digitalRead(SW_TOGGLE)) + ", "; message += "\"reset\": " + String(!digitalRead(SW_RESET)); message += " }"; if (DebugValue) Serial.println(message + "\0"); server.sendHeader("Access-Control-Allow-Origin", String("*"), true); server.send(200, "text/plain", message); } // Prototypes: // const char Button_css[] // const char favicon_ico[] // const char Green1x1_png[] // const char index_htm[] // const char index_js[] // const char rssi_htm[] // const char rssi_js[] void HandleSWUpdateCheck() { updateCheck = true; HandleAPConfigPage(); } void HandleButton_css() { server.sendHeader("Access-Control-Allow-Origin", String("*"), true); server.send_P(200, "text/plain", Button_css); } void HandleIndex_js() { server.sendHeader("Access-Control-Allow-Origin", String("*"), true); server.send_P(200, "text/plain", index_js); } void HandleAbout_htm() { server.sendHeader("Access-Control-Allow-Origin", String("*"), true); server.send_P(200, "text/html", about_htm); } void HandleAbout_js() { server.sendHeader("Access-Control-Allow-Origin", String("*"), true); server.send_P(200, "text/plain", about_js); } void HandleMyIP_js() { server.sendHeader("Access-Control-Allow-Origin", String("*"), true); String message = "var mySite = 'http://"; message += String(WiFi.localIP()[0]); message += "." + String(WiFi.localIP()[1]); message += "." + String(WiFi.localIP()[2]); message += "." + String(WiFi.localIP()[3]); message += "';\n"; server.send(200, "text/plain", message); } void StartWebServer() { // path /firmware //httpUpdater.setup(&server, update_path, update_username, update_password); server.on("/", HandleRootPage); server.on("/myip.js", HandleMyIP_js); server.on("/button.css", HandleButton_css); server.on("/index.js", HandleIndex_js); server.on("/about", HandleAbout_htm); server.on("/about.js", HandleAbout_js); server.on("/favicon.ico", HandleFavIcon); server.on("/PlantModel.png", HandlePlantModel); server.on("/Heater.png", HandleHeater); server.on("/Water.png", HandleWater); server.on("/config", HandleAPConfigPage); server.on("/swupdatecheck", HandleSWUpdateCheck); server.on("/scan", HandleAPScan); server.on("/green1x1.png", HandleGreen1x1); server.on("/rssi", HandleRSSIPage); server.on("/rssi.js", HandleRSSI_JS); server.on("/curr", HandleCurrPage); server.on("/curr.js", HandleCurr_JS); server.on("/on", HandleCircuitOn); server.on("/off", HandleCircuitOff); server.on("/toggle", HandleCircuitToggle); server.on("/state", HandleGetState); server.on("/icon.png", HandleIcon); server.on("/Open.png", HandleOpenDoor); server.onNotFound(HandleNotFound); server.begin(); Serial.println("HTTP server started"); } void HandleNotFound() { Serial.printf("not found: %s\n", server.uri().c_str()); const DirEntry *de = Directory; boolean found = false; while (*de->Filename) { if (0 == strcmp(de->Filename, server.uri().c_str())) { Serial.printf("send %s\n", de->Filename); server.send_P(200, de->Filetype, de->Filedata, de->Filesize); found = true; break; } else { de++; } } if (!found) { Serial.printf("!found - redirect to home\n"); server.sendHeader("Location", String("/"), true); server.send(302, "text/plain", "Not supported. Heading for home."); } } void GetMac() { unsigned char mac[6]; WiFi.macAddress(mac); clientMac = macToStr(mac); } String macToStr(const uint8_t * mac) { String result; for (int i = 0; i < 6; ++i) { result += String(mac[i], 16); if (i < 5) result += ':'; } return result; }