#define WLED_DEFINE_GLOBAL_VARS //only in one source file, wled.cpp! #include "wled.h" #include "wled_ethernet.h" #include #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) #include "soc/soc.h" #include "soc/rtc_cntl_reg.h" #endif #ifdef WLED_USE_ETHERNET // The following six pins are neither configurable nor // can they be re-assigned through IOMUX / GPIO matrix. // See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-ethernet-kit-v1.1.html#ip101gri-phy-interface const managed_pin_type esp32_nonconfigurable_ethernet_pins[WLED_ETH_RSVD_PINS_COUNT] = { { 21, true }, // RMII EMAC TX EN == When high, clocks the data on TXD0 and TXD1 to transmitter { 19, true }, // RMII EMAC TXD0 == First bit of transmitted data { 22, true }, // RMII EMAC TXD1 == Second bit of transmitted data { 25, false }, // RMII EMAC RXD0 == First bit of received data { 26, false }, // RMII EMAC RXD1 == Second bit of received data { 27, true }, // RMII EMAC CRS_DV == Carrier Sense and RX Data Valid }; const ethernet_settings ethernetBoards[] = { // None { }, // WT32-EHT01 // Please note, from my testing only these pins work for LED outputs: // IO2, IO4, IO12, IO14, IO15 // These pins do not appear to work from my testing: // IO35, IO36, IO39 { 1, // eth_address, 16, // eth_power, 23, // eth_mdc, 18, // eth_mdio, ETH_PHY_LAN8720, // eth_type, ETH_CLOCK_GPIO0_IN // eth_clk_mode }, // ESP32-POE { 0, // eth_address, 12, // eth_power, 23, // eth_mdc, 18, // eth_mdio, ETH_PHY_LAN8720, // eth_type, ETH_CLOCK_GPIO17_OUT // eth_clk_mode }, // WESP32 { 0, // eth_address, -1, // eth_power, 16, // eth_mdc, 17, // eth_mdio, ETH_PHY_LAN8720, // eth_type, ETH_CLOCK_GPIO0_IN // eth_clk_mode }, // QuinLed-ESP32-Ethernet { 0, // eth_address, 5, // eth_power, 23, // eth_mdc, 18, // eth_mdio, ETH_PHY_LAN8720, // eth_type, ETH_CLOCK_GPIO17_OUT // eth_clk_mode }, // TwilightLord-ESP32 Ethernet Shield { 0, // eth_address, 5, // eth_power, 23, // eth_mdc, 18, // eth_mdio, ETH_PHY_LAN8720, // eth_type, ETH_CLOCK_GPIO17_OUT // eth_clk_mode }, // ESP3DEUXQuattro { 1, // eth_address, -1, // eth_power, 23, // eth_mdc, 18, // eth_mdio, ETH_PHY_LAN8720, // eth_type, ETH_CLOCK_GPIO17_OUT // eth_clk_mode } }; #endif /* * Main WLED class implementation. Mostly initialization and connection logic */ WLED::WLED() { } // turns all LEDs off and restarts ESP void WLED::reset() { briT = 0; #ifdef WLED_ENABLE_WEBSOCKETS ws.closeAll(1012); #endif long dly = millis(); while (millis() - dly < 450) { yield(); // enough time to send response to client } setAllLeds(); DEBUG_PRINTLN(F("MODULE RESET")); ESP.restart(); } bool oappendi(int i) { char s[11]; sprintf(s, "%d", i); return oappend(s); } bool oappend(const char* txt) { uint16_t len = strlen(txt); if (olen + len >= OMAX) return false; // buffer full strcpy(obuf + olen, txt); olen += len; return true; } void prepareHostname(char* hostname) { const char *pC = serverDescription; uint8_t pos = 5; while (*pC && pos < 24) { // while !null and not over length if (isalnum(*pC)) { // if the current char is alpha-numeric append it to the hostname hostname[pos] = *pC; pos++; } else if (*pC == ' ' || *pC == '_' || *pC == '-' || *pC == '+' || *pC == '!' || *pC == '?' || *pC == '*') { hostname[pos] = '-'; pos++; } // else do nothing - no leading hyphens and do not include hyphens for all other characters. pC++; } // if the hostname is left blank, use the mac address/default mdns name if (pos < 6) { sprintf(hostname + 5, "%*s", 6, escapedMac.c_str() + 6); } else { //last character must not be hyphen while (pos > 0 && hostname[pos -1] == '-') { hostname[pos -1] = 0; pos--; } } } //handle Ethernet connection event void WiFiEvent(WiFiEvent_t event) { #ifdef WLED_USE_ETHERNET char hostname[25] = "wled-"; #endif switch (event) { #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) case SYSTEM_EVENT_ETH_START: DEBUG_PRINT(F("ETH Started")); break; case SYSTEM_EVENT_ETH_CONNECTED: DEBUG_PRINT(F("ETH Connected")); if (!apActive) { WiFi.disconnect(true); } if (staticIP != (uint32_t)0x00000000 && staticGateway != (uint32_t)0x00000000) { ETH.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8)); } else { ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); } // convert the "serverDescription" into a valid DNS hostname (alphanumeric) prepareHostname(hostname); ETH.setHostname(hostname); showWelcomePage = false; break; case SYSTEM_EVENT_ETH_DISCONNECTED: DEBUG_PRINT(F("ETH Disconnected")); // This doesn't really affect ethernet per se, // as it's only configured once. Rather, it // may be necessary to reconnect the WiFi when // ethernet disconnects, as a way to provide // alternative access to the device. forceReconnect = true; break; #endif default: break; } } void WLED::loop() { handleTime(); handleIR(); // 2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too handleConnection(); handleSerial(); handleNotifications(); handleTransitions(); #ifdef WLED_ENABLE_DMX handleDMX(); #endif userLoop(); usermods.loop(); yield(); handleIO(); handleIR(); handleAlexa(); yield(); if (doReboot) reset(); if (doCloseFile) { closeFile(); yield(); } if (!realtimeMode || realtimeOverride) // block stuff if WARLS/Adalight is enabled { if (apActive) dnsServer.processNextRequest(); #ifndef WLED_DISABLE_OTA if (WLED_CONNECTED && aOtaEnabled) ArduinoOTA.handle(); #endif handleNightlight(); handlePlaylist(); yield(); handleHue(); #ifndef WLED_DISABLE_BLYNK handleBlynk(); #endif yield(); if (!offMode || strip.isOffRefreshRequred) strip.service(); #ifdef ESP8266 else if (!noWifiSleep) delay(1); //required to make sure ESP enters modem sleep (see #1184) #endif } yield(); #ifdef ESP8266 MDNS.update(); #endif //millis() rolls over every 50 days if (lastMqttReconnectAttempt > millis()) { rolloverMillis++; lastMqttReconnectAttempt = 0; } if (millis() - lastMqttReconnectAttempt > 30000) { lastMqttReconnectAttempt = millis(); initMqtt(); yield(); // refresh WLED nodes list refreshNodeList(); if (nodeBroadcastEnabled) sendSysInfoUDP(); yield(); } //LED settings have been saved, re-init busses //This code block causes severe FPS drop on ESP32 with the original "if (busConfigs[0] != nullptr)" conditional. Investigate! if (doInitBusses) { doInitBusses = false; DEBUG_PRINTLN(F("Re-init busses.")); busses.removeAll(); uint32_t mem = 0; for (uint8_t i = 0; i < WLED_MAX_BUSSES; i++) { if (busConfigs[i] == nullptr) break; mem += BusManager::memUsage(*busConfigs[i]); if (mem <= MAX_LED_MEMORY) busses.add(*busConfigs[i]); /* // this is done in strip.finalizeInit() if (busConfigs[i]->adjustBounds(ledCount)) { mem += busses.memUsage(*busConfigs[i]); if (mem <= MAX_LED_MEMORY) { busses.add(*busConfigs[i]); //RGBW mode is enabled if at least one of the strips is RGBW strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(busConfigs[i]->type)); //refresh is required to remain off if at least one of the strips requires the refresh. strip.isOffRefreshRequred |= BusManager::isOffRefreshRequred(busConfigs[i]->type); } } */ delete busConfigs[i]; busConfigs[i] = nullptr; } strip.finalizeInit(); yield(); serializeConfig(); } yield(); handleWs(); // DEBUG serial logging #ifdef WLED_DEBUG if (millis() - debugTime > 59999) { DEBUG_PRINTLN(F("---DEBUG INFO---")); DEBUG_PRINT(F("Runtime: ")); DEBUG_PRINTLN(millis()); DEBUG_PRINT(F("Unix time: ")); toki.printTime(toki.getTime()); DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap()); #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) if (psramFound()) { DEBUG_PRINT(F("Total PSRAM: ")); DEBUG_PRINT(ESP.getPsramSize()/1024); DEBUG_PRINTLN("kB"); DEBUG_PRINT(F("Free PSRAM: ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB"); } else DEBUG_PRINTLN(F("No PSRAM")); #endif DEBUG_PRINT(F("Wifi state: ")); DEBUG_PRINTLN(WiFi.status()); if (WiFi.status() != lastWifiState) { wifiStateChangedTime = millis(); } lastWifiState = WiFi.status(); DEBUG_PRINT(F("State time: ")); DEBUG_PRINTLN(wifiStateChangedTime); DEBUG_PRINT(F("NTP last sync: ")); DEBUG_PRINTLN(ntpLastSyncTime); DEBUG_PRINT(F("Client IP: ")); DEBUG_PRINTLN(Network.localIP()); DEBUG_PRINT(F("Loops/sec: ")); DEBUG_PRINTLN(loops / 10); loops = 0; debugTime = millis(); } loops++; #endif // WLED_DEBUG toki.resetTick(); } void WLED::setup() { #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detection #endif Serial.begin(115200); Serial.setTimeout(50); DEBUG_PRINTLN(); DEBUG_PRINT(F("---WLED ")); DEBUG_PRINT(versionString); DEBUG_PRINT(" "); DEBUG_PRINT(VERSION); DEBUG_PRINTLN(F(" INIT---")); #ifdef ARDUINO_ARCH_ESP32 DEBUG_PRINT(F("esp32 ")); DEBUG_PRINTLN(ESP.getSdkVersion()); #else DEBUG_PRINT(F("esp8266 ")); DEBUG_PRINTLN(ESP.getCoreVersion()); #endif DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); registerUsermods(); #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) if (psramFound()) { // GPIO16/GPIO17 reserved for SPI RAM managed_pin_type pins[2] = { {16, true}, {17, true} }; pinManager.allocateMultiplePins(pins, 2, PinOwner::SPI_RAM); } #endif //DEBUG_PRINT(F("LEDs inited. heap usage ~")); //DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); #ifdef WLED_DEBUG pinManager.allocatePin(1, true, PinOwner::DebugOut); // GPIO1 reserved for debug output #endif #ifdef WLED_USE_DMX //reserve GPIO2 as hardcoded DMX pin pinManager.allocatePin(2, true, PinOwner::DMX); #endif for (uint8_t i=1; i 0) ArduinoOTA.setHostname(cmDNS); } #endif #ifdef WLED_ENABLE_DMX initDMX(); #endif // HTTP server page init initServer(); #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); //enable brownout detector #endif } void WLED::beginStrip() { // Initialize NeoPixel Strip and button strip.finalizeInit(); // busses created during deserializeConfig() strip.populateDefaultSegments(); strip.setBrightness(0); strip.setShowCallback(handleOverlayDraw); if (turnOnAtBoot) { if (briS > 0) bri = briS; else if (bri == 0) bri = 128; } else { briLast = briS; bri = 0; } if (bootPreset > 0) { applyPreset(bootPreset, CALL_MODE_INIT); } colorUpdated(CALL_MODE_INIT); // init relay pin if (rlyPin>=0) digitalWrite(rlyPin, (rlyMde ? bri : !bri)); } void WLED::initAP(bool resetAP) { if (apBehavior == AP_BEHAVIOR_BUTTON_ONLY && !resetAP) return; if (!apSSID[0] || resetAP) strcpy_P(apSSID, PSTR("WLED-AP")); if (resetAP) strcpy_P(apPass, PSTR(DEFAULT_AP_PASS)); DEBUG_PRINT(F("Opening access point ")); DEBUG_PRINTLN(apSSID); WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); WiFi.softAP(apSSID, apPass, apChannel, apHide); if (!apActive) // start captive portal if AP active { DEBUG_PRINTLN(F("Init AP interfaces")); server.begin(); if (udpPort > 0 && udpPort != ntpLocalPort) { udpConnected = notifierUdp.begin(udpPort); } if (udpRgbPort > 0 && udpRgbPort != ntpLocalPort && udpRgbPort != udpPort) { udpRgbConnected = rgbUdp.begin(udpRgbPort); } if (udpPort2 > 0 && udpPort2 != ntpLocalPort && udpPort2 != udpPort && udpPort2 != udpRgbPort) { udp2Connected = notifier2Udp.begin(udpPort2); } e131.begin(false, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT); dnsServer.setErrorReplyCode(DNSReplyCode::NoError); dnsServer.start(53, "*", WiFi.softAPIP()); } apActive = true; } bool WLED::initEthernet() { #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) static bool successfullyConfiguredEthernet = false; if (successfullyConfiguredEthernet) { // DEBUG_PRINTLN(F("initE: ETH already successfully configured, ignoring")); return false; } if (ethernetType == WLED_ETH_NONE) { return false; } if (ethernetType >= WLED_NUM_ETH_TYPES) { DEBUG_PRINT(F("initE: Ignoring attempt for invalid ethernetType ")); DEBUG_PRINTLN(ethernetType); return false; } DEBUG_PRINT(F("initE: Attempting ETH config: ")); DEBUG_PRINTLN(ethernetType); // Ethernet initialization should only succeed once -- else reboot required ethernet_settings es = ethernetBoards[ethernetType]; managed_pin_type pinsToAllocate[10] = { // first six pins are non-configurable esp32_nonconfigurable_ethernet_pins[0], esp32_nonconfigurable_ethernet_pins[1], esp32_nonconfigurable_ethernet_pins[2], esp32_nonconfigurable_ethernet_pins[3], esp32_nonconfigurable_ethernet_pins[4], esp32_nonconfigurable_ethernet_pins[5], { (int8_t)es.eth_mdc, true }, // [6] = MDC is output and mandatory { (int8_t)es.eth_mdio, true }, // [7] = MDIO is bidirectional and mandatory { (int8_t)es.eth_power, true }, // [8] = optional pin, not all boards use { ((int8_t)0xFE), false }, // [9] = replaced with eth_clk_mode, mandatory }; // update the clock pin.... if (es.eth_clk_mode == ETH_CLOCK_GPIO0_IN) { pinsToAllocate[9].pin = 0; pinsToAllocate[9].isOutput = false; } else if (es.eth_clk_mode == ETH_CLOCK_GPIO0_OUT) { pinsToAllocate[9].pin = 0; pinsToAllocate[9].isOutput = true; } else if (es.eth_clk_mode == ETH_CLOCK_GPIO16_OUT) { pinsToAllocate[9].pin = 16; pinsToAllocate[9].isOutput = true; } else if (es.eth_clk_mode == ETH_CLOCK_GPIO17_OUT) { pinsToAllocate[9].pin = 17; pinsToAllocate[9].isOutput = true; } else { DEBUG_PRINT(F("initE: Failing due to invalid eth_clk_mode (")); DEBUG_PRINT(es.eth_clk_mode); DEBUG_PRINTLN(F(")")); return false; } if (!pinManager.allocateMultiplePins(pinsToAllocate, 10, PinOwner::Ethernet)) { DEBUG_PRINTLN(F("initE: Failed to allocate ethernet pins")); return false; } if (!ETH.begin( (uint8_t) es.eth_address, (int) es.eth_power, (int) es.eth_mdc, (int) es.eth_mdio, (eth_phy_type_t) es.eth_type, (eth_clock_mode_t) es.eth_clk_mode )) { DEBUG_PRINTLN(F("initC: ETH.begin() failed")); // de-allocate the allocated pins for (managed_pin_type mpt : pinsToAllocate) { pinManager.deallocatePin(mpt.pin, PinOwner::Ethernet); } return false; } successfullyConfiguredEthernet = true; DEBUG_PRINTLN(F("initC: *** Ethernet successfully configured! ***")); return true; #else return false; // Ethernet not enabled for build #endif } void WLED::initConnection() { #ifdef WLED_ENABLE_WEBSOCKETS ws.onEvent(wsEvent); #endif WiFi.disconnect(true); // close old connections #ifdef ESP8266 WiFi.setPhyMode(WIFI_PHY_MODE_11N); #endif if (staticIP[0] != 0 && staticGateway[0] != 0) { WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(1, 1, 1, 1)); } else { WiFi.config(0U, 0U, 0U); } lastReconnectAttempt = millis(); if (!WLED_WIFI_CONFIGURED) { DEBUG_PRINT(F("No connection configured. ")); if (!apActive) initAP(); // instantly go to ap mode return; } else if (!apActive) { if (apBehavior == AP_BEHAVIOR_ALWAYS) { initAP(); } else { DEBUG_PRINTLN(F("Access point disabled.")); WiFi.softAPdisconnect(true); WiFi.mode(WIFI_STA); } } showWelcomePage = false; DEBUG_PRINT(F("Connecting to ")); DEBUG_PRINT(clientSSID); DEBUG_PRINTLN("..."); // convert the "serverDescription" into a valid DNS hostname (alphanumeric) char hostname[25] = "wled-"; prepareHostname(hostname); #ifdef ESP8266 WiFi.hostname(hostname); #endif WiFi.begin(clientSSID, clientPass); #ifdef ARDUINO_ARCH_ESP32 WiFi.setSleep(!noWifiSleep); WiFi.setHostname(hostname); #else wifi_set_sleep_type((noWifiSleep) ? NONE_SLEEP_T : MODEM_SLEEP_T); #endif } void WLED::initInterfaces() { DEBUG_PRINTLN(F("Init STA interfaces")); #ifndef WLED_DISABLE_HUESYNC if (hueIP[0] == 0) { hueIP[0] = Network.localIP()[0]; hueIP[1] = Network.localIP()[1]; hueIP[2] = Network.localIP()[2]; } #endif // init Alexa hue emulation if (alexaEnabled) alexaInit(); #ifndef WLED_DISABLE_OTA if (aOtaEnabled) ArduinoOTA.begin(); #endif strip.service(); // Set up mDNS responder: if (strlen(cmDNS) > 0) { #ifndef WLED_DISABLE_OTA if (!aOtaEnabled) //ArduinoOTA begins mDNS for us if enabled MDNS.begin(cmDNS); #else MDNS.begin(cmDNS); #endif DEBUG_PRINTLN(F("mDNS started")); MDNS.addService("http", "tcp", 80); MDNS.addService("wled", "tcp", 80); MDNS.addServiceTxt("wled", "tcp", "mac", escapedMac.c_str()); } server.begin(); if (udpPort > 0 && udpPort != ntpLocalPort) { udpConnected = notifierUdp.begin(udpPort); if (udpConnected && udpRgbPort != udpPort) udpRgbConnected = rgbUdp.begin(udpRgbPort); if (udpConnected && udpPort2 != udpPort && udpPort2 != udpRgbPort) udp2Connected = notifier2Udp.begin(udpPort2); } if (ntpEnabled) ntpConnected = ntpUdp.begin(ntpLocalPort); #ifndef WLED_DISABLE_BLYNK initBlynk(blynkApiKey, blynkHost, blynkPort); #endif e131.begin(e131Multicast, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT); reconnectHue(); initMqtt(); interfacesInited = true; wasConnected = true; } byte stacO = 0; uint32_t lastHeap; unsigned long heapTime = 0; void WLED::handleConnection() { if (millis() < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == AP_BEHAVIOR_ALWAYS)) return; if (lastReconnectAttempt == 0) initConnection(); // reconnect WiFi to clear stale allocations if heap gets too low if (millis() - heapTime > 5000) { uint32_t heap = ESP.getFreeHeap(); if (heap < JSON_BUFFER_SIZE+512 && lastHeap < JSON_BUFFER_SIZE+512) { DEBUG_PRINT(F("Heap too low! ")); DEBUG_PRINTLN(heap); forceReconnect = true; } lastHeap = heap; heapTime = millis(); } byte stac = 0; if (apActive) { #ifdef ESP8266 stac = wifi_softap_get_station_num(); #else wifi_sta_list_t stationList; esp_wifi_ap_get_sta_list(&stationList); stac = stationList.num; #endif if (stac != stacO) { stacO = stac; DEBUG_PRINT(F("Connected AP clients: ")); DEBUG_PRINTLN(stac); if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) { // trying to connect, but not connected if (stac) WiFi.disconnect(); // disable search so that AP can work else initConnection(); // restart search } } } if (forceReconnect) { DEBUG_PRINTLN(F("Forcing reconnect.")); initConnection(); interfacesInited = false; forceReconnect = false; wasConnected = false; return; } if (!Network.isConnected()) { if (interfacesInited) { DEBUG_PRINTLN(F("Disconnected!")); interfacesInited = false; initConnection(); } if (millis() - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED) initConnection(); if (!apActive && millis() - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) initAP(); } else if (!interfacesInited) { // newly connected DEBUG_PRINTLN(""); DEBUG_PRINT(F("Connected! IP address: ")); DEBUG_PRINTLN(Network.localIP()); initInterfaces(); userConnected(); usermods.connected(); // shut down AP if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) { dnsServer.stop(); WiFi.softAPdisconnect(true); apActive = false; DEBUG_PRINTLN(F("Access point disabled.")); } } }