/* * Stairs lighting * Copyright 2017 (c) Mark van Renswoude * * https://git.x2software.net/pub/Stairs */ #include #include #include #include #include #include #include extern "C" { #include } #include "config.h" #include "debug.h" #include "global.h" #include "components/PCA9685.h" #include "settings/connection.h" #include "server/static.h" #include "server/settings.h" #include "server/firmware.h" #include "server/api.h" #include "server/geocode.h" ADC_MODE(ADC_VCC); // Forward declarations void initWiFi(); #ifdef SerialDebug void wifiEvent(WiFiEvent_t event); void updateDebugStatus(); #endif void updateLED(); void updateNTPClient(); void startServer(); void stopServer(); void handleNotFound(AsyncWebServerRequest* request); AsyncWebServer server(80); PCA9685* pwmDriver; WiFiUDP ntpUDP; NTPClient* ntpClient = nullptr; bool accessPoint = false; bool stationMode = false; bool forceAccessPoint = false; uint32_t stationModeStart = 0; uint32_t blinkOnTime = 0; enum LEDState { Off, BlinkLow, BlinkHigh, On }; bool ledAP = false; LEDState ledWiFi = Off; #ifdef SerialDebug uint32_t debugStatusTime = 0; #endif void setup() { _dinit(); currentTime = millis(); blinkOnTime = currentTime; if (!SPIFFS.begin()) _dln("Setup :: failed to mount file system"); connectionSettings->read(); stepsSettings->read(); pinMode(PinAPButton, INPUT_PULLUP); pinMode(PinLEDAP, OUTPUT); pinMode(PinLEDSTA, OUTPUT); _dln("Setup :: initializing PCA9685"); pwmDriver = new PCA9685(); pwmDriver->setAddress(PWMDriverAddress, PinSDA, PinSCL); pwmDriver->setPWMFrequency(PWMDriverPWMFrequency); pwmDriver->setAll(0); _dln("Setup :: initializing Stairs"); stairs = new Stairs(); stairs->init(pwmDriver); _dln("Setup :: starting initialization sequence"); stairs->set(0, 255); delay(300); uint8_t stepCount = stepsSettings->count(); for (int step = 1; step < stepCount; step++) { stairs->set(step - 1, 0); stairs->set(step, 255); delay(300); } stairs->set(stepCount - 1, 0); _dln("Setup :: initializing WiFi"); WiFi.persistent(false); WiFi.mode(WIFI_OFF); #ifdef SerialDebug // onEvent is already deprecated, but since I'm only using it // for debug purposes we'll see how long it lasts... #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" WiFi.onEvent(wifiEvent); _d("WiFi :: MAC address: "); _dln(WiFi.macAddress()); #pragma GCC diagnostic pop #endif initWiFi(); _dln("Setup :: registering routes"); registerStaticRoutes(&server); registerSettingsRoutes(&server); registerFirmwareRoutes(&server); registerAPIRoutes(&server); registerGeocodeRoutes(&server); _dln("Setup :: starting HTTP server"); server.onNotFound(handleNotFound); server.begin(); } void loop() { if (shouldReboot) { _dln("Loop :: reboot requested, so long and thanks for all the fish!"); delay(100); ESP.restart(); } currentTime = millis(); #ifdef SerialDebug updateDebugStatus(); #endif if (connectionSettingsChanged) { _dln("Loop :: connection settings changed"); initWiFi(); connectionSettingsChanged = false; } if (stationModeStart > 0) { bool isConnected = WiFi.status() == WL_CONNECTED; if (isConnected) { _d("WiFi :: connected, IP address: "); _dln(WiFi.localIP()); stationModeStart = 0; } else if (stationMode && accessPoint && currentTime - stationModeStart >= StationModeTimeout) { _dln("WiFi :: unable to connect, switching off station mode, status:"); _dln(WiFi.status()); #ifdef SerialDebug WiFi.printDiag(Serial); #endif // Connecting to access point is taking too long and is blocking // the access point mode, stop trying stationMode = false; WiFi.disconnect(); WiFi.mode(WIFI_AP); } } // TODO check AP button updateLED(); updateNTPClient(); // TODO check for triggers every 10 seconds or so stairs->tick(); } void initWiFi() { WiFi.disconnect(); WiFi.softAPdisconnect(); accessPoint = connectionSettings->flag(AccessPoint) || forceAccessPoint; stationMode = connectionSettings->flag(StationMode) && connectionSettings->ssid() != nullptr; WiFi.mode(accessPoint && stationMode ? WIFI_AP_STA : accessPoint ? WIFI_AP : stationMode ? WIFI_STA : WIFI_OFF); if (accessPoint) { _dln("WiFi :: starting access point"); String ssidString = DefaultAPSSIDPrefix + String(ESP.getChipId(), HEX); if (WiFi.softAP((const char *)ssidString.c_str())) { _d("WiFi :: IP address: "); _dln(WiFi.softAPIP()); } else _d("WiFi :: failed to start soft access point"); } if (stationMode) { _d("WiFi :: starting station mode to: "); _dln(connectionSettings->ssid()); stationModeStart = currentTime; if (WiFi.begin(connectionSettings->ssid(), connectionSettings->password())) { if (connectionSettings->flag(DHCP)) // I've had the same issue as described here with config(0, 0, 0): // https://stackoverflow.com/questions/40069654/how-to-clear-static-ip-configuration-and-start-dhcp wifi_station_dhcpc_start(); else WiFi.config(connectionSettings->ip(), connectionSettings->gateway(), connectionSettings->subnetMask()); } else _d("WiFi :: failed to start station mode"); } } #ifdef SerialDebug void wifiEvent(WiFiEvent_t event) { switch (event) { case WIFI_EVENT_STAMODE_CONNECTED: _dln("WiFi:: station mode: connected"); break; case WIFI_EVENT_STAMODE_DISCONNECTED: _dln("WiFi:: station mode: disconnected"); break; case WIFI_EVENT_STAMODE_AUTHMODE_CHANGE: _dln("WiFi:: station mode: authmode change"); break; case WIFI_EVENT_STAMODE_GOT_IP: _dln("WiFi:: station mode: got IP"); _dln(WiFi.localIP()); break; case WIFI_EVENT_STAMODE_DHCP_TIMEOUT: _dln("WiFi:: station mode: DHCP timeout"); break; case WIFI_EVENT_SOFTAPMODE_STACONNECTED: _dln("WiFi:: soft AP mode: station connected"); break; case WIFI_EVENT_SOFTAPMODE_STADISCONNECTED: _dln("WiFi:: soft AP mode: station disconnected"); break; } } void updateDebugStatus() { if (currentTime - debugStatusTime < 5000) return; debugStatusTime = currentTime; _d("Status :: available heap: "); _dln(ESP.getFreeHeap()); if (ntpClient != nullptr) { _d("Status :: time: "); uint32_t time = ntpClient->getEpochTime(); _d(day(time)); _d("-"); _d(month(time)); _d("-"); _d(year(time)); _d(" "); _d(hour(time)); _d(":"); _d(minute(time)); _d(":"); _dln(second(time)); } } #endif void updateLED() { uint8_t value = (currentTime - blinkOnTime >= 1000) ? LOW : HIGH; WiFiMode_t mode = WiFi.getMode(); if (mode == WIFI_AP_STA || mode == WIFI_AP) { if (!ledAP) { digitalWrite(PinLEDAP, HIGH); ledAP = true; } } else { if (ledAP) { digitalWrite(PinLEDAP, LOW); ledAP = false; } } if (mode == WIFI_AP_STA || mode == WIFI_STA) { wl_status_t status = WiFi.status(); if (status == WL_CONNECTED) { if (ledWiFi != On) { digitalWrite(PinLEDSTA, HIGH); ledWiFi = On; } } else { LEDState expectedState = value == HIGH ? BlinkHigh : BlinkLow; if (ledWiFi != expectedState) { digitalWrite(PinLEDSTA, value); ledWiFi = expectedState; } } } else { if (ledWiFi != Off) { digitalWrite(PinLEDSTA, LOW); ledWiFi = Off; } } if (currentTime - blinkOnTime >= 2000) blinkOnTime = currentTime; } void updateNTPClient() { if (ntpClient == nullptr && WiFi.status() == WL_CONNECTED) { _dln("NTP :: initializing NTP client"); // TODO make NTP address and refresh interval configurable ntpClient = new NTPClient(ntpUDP, "nl.pool.ntp.org", 0, 5 * 60 * 1000); ntpClient->begin(); } if (ntpClient != nullptr) ntpClient->update(); } void handleNotFound(AsyncWebServerRequest *request) { _d("HTTP :: not found: "); _dln(request->url()); request->send(404); }