/* * ProvisionSoftAP.cpp * * Created on: Apr 17, 2019 * Modified: 2024 * Author: miro */ #include #include "WiFi.h" #include "Arduino.h" #include #include #include "Settings.h" #include #include "freertos/event_groups.h" #include static const char *TAG = "soft_ap"; using namespace std::placeholders; extern const uint8_t provision_html_start[] asm("_binary_provision_html_start"); extern const uint8_t provision_html_end[] asm("_binary_provision_html_end"); extern const uint8_t logo_png_start[] asm("_binary_logo_png_start"); extern const uint8_t logo_png_end[] asm("_binary_logo_png_end"); const uint8_t sta_ip[] = {172, 68, 4, 1}; const uint8_t net_mask[] = {255, 255, 255, 0}; const uint8_t first_ip[] = {172, 68, 4, 10}; const uint8_t last_ip[] = {172, 68, 4, 100}; ProvisionSoftAP::ProvisionSoftAP(int port) { m_webServer = new AsyncWebServer(port); m_webSocket = new AsyncWebSocket("/provision"); } void ProvisionSoftAP::wifiEvent(arduino_event_id_t event, arduino_event_info_t info) { switch (event) { case ARDUINO_EVENT_WIFI_AP_STACONNECTED: ESP_LOGI(TAG, "station " MACSTR " joined, AID=%d", MAC2STR(info.wifi_ap_staconnected.mac), info.wifi_ap_staconnected.aid); break; case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: ESP_LOGI(TAG, "station " MACSTR " left", MAC2STR(info.wifi_ap_stadisconnected.mac)); m_webSocket->closeAll(); break; default: break; } } void ProvisionSoftAP::websocketEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) { ESP_LOGI(TAG, "type:%d", type); if (type == WS_EVT_CONNECT) { ESP_LOGI(TAG, "Websocket client id:%d connection received", client->id()); m_clients.push_back(client->id()); } else if (type == WS_EVT_DISCONNECT) { ESP_LOGI(TAG, "Client id:%d disconnected", client->id()); m_clients.remove(client->id()); } else if (type == WS_EVT_DATA) { ESP_LOGI(TAG, "Data client %d received: ", client->id()); parseWebsocketCommand((char*) data, len); } } void ProvisionSoftAP::handlePortal(AsyncWebServerRequest* request) { ESP_LOGI(TAG, "Serving html, requested url: %s", request->url().c_str()); AsyncWebServerResponse * response = request->beginResponse_P(200, "text/html", (PGM_P)provision_html_start); request->send(response); } void ProvisionSoftAP::handleLogo(AsyncWebServerRequest* request) { AsyncWebServerResponse * response = request->beginResponse_P(200, "image/png", logo_png_start, logo_png_end-logo_png_start); response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate"); response->addHeader("Pragma", "no-cache"); response->addHeader("Expires", "-1"); request->send(response); } /** * Initializes WiFi as AP, configures web, dhcp and dns servers and sets up captive portal. * When provisioning is done, credentials are saved and device is reset. * * ssid[in] ssid of the AP * password[in] password for the AP */ void ProvisionSoftAP::init(const char * ssid, const char * password) { // start AP /* Soft AP network parameters */ IPAddress apIP(sta_ip); IPAddress netMsk(net_mask); IPAddress firstIp(first_ip); wifiEventId = WiFi.onEvent(std::bind(&ProvisionSoftAP::wifiEvent, this, _1, _2)); WiFi.mode(WIFI_MODE_AP); WiFi.softAPConfig(apIP, apIP, netMsk, firstIp); WiFi.softAP(ssid, password); delay(200); // setup the dns m_dnsServer.setErrorReplyCode(DNSReplyCode::NoError); m_dnsServer.setTTL(30); m_dnsServer.start(53, "*", apIP); // setup the http server m_webSocket->onEvent(std::bind(&ProvisionSoftAP::websocketEvent, this, _1, _2, _3, _4, _5, _6)); m_webServer->addHandler(m_webSocket); m_webServer->on("/", std::bind(&ProvisionSoftAP::handlePortal, this, _1)); m_webServer->on("/provision.html", std::bind(&ProvisionSoftAP::handlePortal, this, _1)); m_webServer->on("/generate_204", std::bind(&ProvisionSoftAP::handlePortal, this, _1)); //Android captive portal. Maybe not needed. Might be handled by notFound handler. m_webServer->on("/fwlink", std::bind(&ProvisionSoftAP::handlePortal, this, _1)); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler. m_webServer->on("/logo.png", std::bind(&ProvisionSoftAP::handleLogo, this, _1)); m_webServer->onNotFound(std::bind(&ProvisionSoftAP::handlePortal, this, _1)); m_webServer->begin(); while (true) { vTaskDelay(1); m_dnsServer.processNextRequest(); // if (m_startWifiScan) // { // m_startWifiScan = false; // ESP_LOGI(TAG, "Starting scan"); // int num_networks = WiFi.scanNetworks(); // ESP_LOGI(TAG, "Found %d networks", num_networks); // // m_jsonBuffer.clear(); // // JsonObject& root = m_jsonBuffer.createObject(); // // root[TYPE] = WIFI_SCAN; // // JsonArray& data = root.createNestedArray(DATA); // // for (int n = 0; n < num_networks; n++) // // { // // JsonArray& data_inner = data.createNestedArray(); // // data_inner.add(WiFi.SSID(n)); // // data_inner.add(WiFi.encryptionType(n) == WIFI_AUTH_OPEN ? 0 : 1); // // data_inner.add(WiFi.RSSI(n)); // // } // // size_t len = root.printTo(m_buff, sizeof(m_buff)); // // m_webSocket->textAll(m_buff, len); // // waitBufferEmpty(); // } // if(m_tryConnect) // { // m_tryConnect = false; // tryConnect(m_ssid, m_password); // } // if(m_tailgate) // { // m_dnsServer.stop(); // m_webSocket->closeAll(0, "bye"); // waitBufferEmpty(); // WiFi.removeEvent(wifiEventId); // return; // } } for (int i = 200; i >= 0; i--) { ESP_LOGI(TAG, "sleeping %d seconds...", i); vTaskDelay(1000 / portTICK_PERIOD_MS); } } void ProvisionSoftAP::parseWebsocketCommand(char* data, size_t len) { data[len] = 0; ESP_LOGI(TAG, "'%s'", data); // m_jsonBuffer.clear(); // if(len<1024) // data[len] = '\0'; // JsonObject& root = m_jsonBuffer.parseObject(data); // const char * command = root[COMMAND]; // ESP_LOGI(TAG, "command: %s", command); // if (command && strcmp(command, WIFI_SCAN) == 0) // { // m_startWifiScan = true; // ESP_LOGI(TAG, "Start scan command received"); // } // else if (command && strcmp(command, WIFI_CONFIG) == 0) // { // const char * ssid = root[SSID]; // const char * pwd = root[PWD]; // strcpy(m_ssid, ssid); // strcpy(m_password, pwd); // ESP_LOGI(TAG, "ssid/credentials received: '%s'/'%s'", m_ssid, m_password); // m_tryConnect = true; // } // else if (command && strcmp(command, TAILGATE) == 0) // { // m_tailgate = true; // ESP_LOGI(TAG, "tailgate command received"); // } } void ProvisionSoftAP::waitBufferEmpty() { // ESP_LOGI(TAG, "waitBufferEmpty start"); // bool empty = false; // while (!empty) // { // empty = true; // for (const auto& c : m_clients) // { // if (m_webSocket->client(c) && !m_webSocket->client(c)->isQueueEmpty()) // { // empty = false; // break; // } // } // vTaskDelay(10); // } // ESP_LOGI(TAG, "waitBufferEmpty end"); } void ProvisionSoftAP::tryConnect(const char * ssid, const char * pwd) { ESP_LOGI(TAG, "trying to connect to %s", ssid); WiFi.begin(ssid, pwd); int connRes = WiFi.waitForConnectResult(); if(connRes == WL_CONNECTED) { // all is gud! ESP_LOGI(TAG, "we're connected, sending confirmation"); // m_settings.storeSsidPwd(ssid, pwd); // m_jsonBuffer.clear(); // JsonObject& root = m_jsonBuffer.createObject(); // root[TYPE] = ERROR; // root[CODE] = 0; // size_t len = root.printTo(m_buff, sizeof(m_buff)); // m_webSocket->textAll(m_buff, len); // waitBufferEmpty(); // vTaskDelay(5000 / portTICK_PERIOD_MS); ESP_LOGI(TAG, "restarting..."); esp_restart(); } // ESP_LOGE(TAG, "nuh-uh peppernip, error %d", connRes); // m_jsonBuffer.clear(); // JsonObject& root = m_jsonBuffer.createObject(); // root[TYPE] = ERROR; // root[CODE] = 1; // size_t len = root.printTo(m_buff, sizeof(m_buff)); // m_webSocket->textAll(m_buff, len); // waitBufferEmpty(); m_webSocket->closeAll(); } ProvisionSoftAP::~ProvisionSoftAP() { delete m_webServer; }