diff --git a/bin/build-all.sh b/bin/build-all.sh index 2f52a47f..63dfc01c 100755 --- a/bin/build-all.sh +++ b/bin/build-all.sh @@ -4,7 +4,7 @@ set -e source bin/version.sh -COUNTRIES="US EU433 EU865 CN JP ANZ" +COUNTRIES="US EU433 EU865 CN JP ANZ KR" #COUNTRIES=US #COUNTRIES=CN diff --git a/docs/software/sw-design.md b/docs/software/sw-design.md index d6413dda..90ebb4d1 100644 --- a/docs/software/sw-design.md +++ b/docs/software/sw-design.md @@ -5,5 +5,5 @@ This is a mini design doc for developing the meshtastic software. * Our [project board](https://github.com/orgs/meshtastic/projects/1) - shows what things we are currently working on and remaining work items for the current release. * [Power Management](power.md) * [Mesh algorithm](mesh-alg.md) -* [Bluetooth API](bluetooth-api.md) and porting guide for new clients (iOS, python, etc...) +* [Device API](device-api.md) and porting guide for new clients (iOS, python, etc...) * TODO: how to port the device code to a new device. diff --git a/platformio.ini b/platformio.ini index e60b93b8..fb6508e7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -172,7 +172,7 @@ build_flags = -Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.3 ;-DCFG_DEBUG=3 src_filter = - ${arduino_base.src_filter} - - + ${arduino_base.src_filter} - - - lib_ignore = BluetoothOTA monitor_port = /dev/ttyACM1 @@ -253,7 +253,7 @@ lib_deps = ; The Portduino based sim environment on top of linux [env:linux] platform = https://github.com/geeksville/platform-portduino.git -src_filter = ${env.src_filter} - - - +src_filter = ${env.src_filter} - - - - build_flags = ${arduino_base.build_flags} -O0 framework = arduino board = linux_x86_64 diff --git a/src/configuration.h b/src/configuration.h index 55464d6c..9087ca8c 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -95,6 +95,8 @@ along with this program. If not, see . // Standard definitions for ESP32 targets // +#define HAS_WIFI + #define GPS_SERIAL_NUM 1 #define GPS_RX_PIN 34 #ifdef USE_JTAG diff --git a/src/esp32/WiFiServerAPI.cpp b/src/esp32/WiFiServerAPI.cpp index b5d55560..974b0f3c 100644 --- a/src/esp32/WiFiServerAPI.cpp +++ b/src/esp32/WiFiServerAPI.cpp @@ -44,7 +44,7 @@ WiFiServerPort::WiFiServerPort() : WiFiServer(MESHTASTIC_PORTNUM) {} void WiFiServerPort::init() { - DEBUG_MSG("Listening on TCP port %d\n", MESHTASTIC_PORTNUM); + DEBUG_MSG("API server sistening on TCP port %d\n", MESHTASTIC_PORTNUM); begin(); } diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index b460775a..0b57bfb3 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -32,6 +32,7 @@ along with this program. If not, see . #include "main.h" #include "mesh-pb-constants.h" #include "target_specific.h" +#include "meshwifi/meshwifi.h" #include "utils.h" using namespace meshtastic; /** @todo remove */ @@ -709,6 +710,11 @@ void Screen::drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUi screen->debugInfo.drawFrameSettings(display, state, x, y); } +void Screen::drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + Screen *screen = reinterpret_cast(state->userData); + screen->debugInfo.drawFrameWiFi(display, state, x, y); +} // restore our regular frame list void Screen::setFrames() @@ -740,6 +746,11 @@ void Screen::setFrames() // call a method on debugInfoScreen object (for more details) normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline; + if (isWifiAvailable()) { + // call a method on debugInfoScreen object (for more details) + normalFrames[numframes++] = &Screen::drawDebugInfoWiFiTrampoline; + } + ui.setFrames(normalFrames, numframes); ui.enableAllIndicators(); @@ -828,6 +839,135 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 } // Jm +void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ +#ifdef HAS_WIFI + const char *wifiName = radioConfig.preferences.wifi_ssid; + const char *wifiPsw = radioConfig.preferences.wifi_password; + + displayedNodeNum = 0; // Not currently showing a node pane + + display->setFont(ArialMT_Plain_10); + + // The coordinates define the left starting point of the text + display->setTextAlignment(TEXT_ALIGN_LEFT); + + if (radioConfig.preferences.wifi_ap_mode) { + display->drawString(x, y, String("WiFi: Software AP")); + } else if (WiFi.status() != WL_CONNECTED) { + display->drawString(x, y, String("WiFi: Not Connected")); + } else { + display->drawString(x, y, String("WiFi: Connected")); + + display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())), y, + "RSSI " + String(WiFi.RSSI())); + } + + /* + - WL_CONNECTED: assigned when connected to a WiFi network; + - WL_NO_SSID_AVAIL: assigned when no SSID are available; + - WL_CONNECT_FAILED: assigned when the connection fails for all the attempts; + - WL_CONNECTION_LOST: assigned when the connection is lost; + - WL_DISCONNECTED: assigned when disconnected from a network; + - WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and remains active until the number of + attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED); + - WL_SCAN_COMPLETED: assigned when the scan networks is completed; + - WL_NO_SHIELD: assigned when no WiFi shield is present; + + */ + + if (WiFi.status() == WL_CONNECTED) { + if (radioConfig.preferences.wifi_ap_mode) { + display->drawString(x, y + FONT_HEIGHT * 1, "IP: " + String(WiFi.softAPIP().toString().c_str())); + } else { + display->drawString(x, y + FONT_HEIGHT * 1, "IP: " + String(WiFi.localIP().toString().c_str())); + } + } else if (WiFi.status() == WL_NO_SSID_AVAIL) { + display->drawString(x, y + FONT_HEIGHT * 1, "SSID Not Found"); + } else if (WiFi.status() == WL_CONNECTION_LOST) { + display->drawString(x, y + FONT_HEIGHT * 1, "Connection Lost"); + } else if (WiFi.status() == WL_CONNECT_FAILED) { + display->drawString(x, y + FONT_HEIGHT * 1, "Connection Failed"); + } else if (WiFi.status() == WL_DISCONNECTED) { + display->drawString(x, y + FONT_HEIGHT * 1, "Disconnected"); + } else if (WiFi.status() == WL_IDLE_STATUS) { + display->drawString(x, y + FONT_HEIGHT * 1, "Idle ... Reconnecting"); + } else { + // Codes: + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code + if (getWifiDisconnectReason() == 2) { + display->drawString(x, y + FONT_HEIGHT * 1, "Authentication Invalid"); + } else if (getWifiDisconnectReason() == 3) { + display->drawString(x, y + FONT_HEIGHT * 1, "De-authenticated"); + } else if (getWifiDisconnectReason() == 4) { + display->drawString(x, y + FONT_HEIGHT * 1, "Disassociated Expired"); + } else if (getWifiDisconnectReason() == 5) { + display->drawString(x, y + FONT_HEIGHT * 1, "AP - Too Many Clients"); + } else if (getWifiDisconnectReason() == 6) { + display->drawString(x, y + FONT_HEIGHT * 1, "NOT_AUTHED"); + } else if (getWifiDisconnectReason() == 7) { + display->drawString(x, y + FONT_HEIGHT * 1, "NOT_ASSOCED"); + } else if (getWifiDisconnectReason() == 8) { + display->drawString(x, y + FONT_HEIGHT * 1, "Disassociated"); + } else if (getWifiDisconnectReason() == 9) { + display->drawString(x, y + FONT_HEIGHT * 1, "ASSOC_NOT_AUTHED"); + } else if (getWifiDisconnectReason() == 10) { + display->drawString(x, y + FONT_HEIGHT * 1, "DISASSOC_PWRCAP_BAD"); + } else if (getWifiDisconnectReason() == 11) { + display->drawString(x, y + FONT_HEIGHT * 1, "DISASSOC_SUPCHAN_BAD"); + } else if (getWifiDisconnectReason() == 13) { + display->drawString(x, y + FONT_HEIGHT * 1, "IE_INVALID"); + } else if (getWifiDisconnectReason() == 14) { + display->drawString(x, y + FONT_HEIGHT * 1, "MIC_FAILURE"); + } else if (getWifiDisconnectReason() == 15) { + display->drawString(x, y + FONT_HEIGHT * 1, "4WAY_HANDSHAKE_TIMEOUT"); + } else if (getWifiDisconnectReason() == 16) { + display->drawString(x, y + FONT_HEIGHT * 1, "GROUP_KEY_UPDATE_TIMEOUT"); + } else if (getWifiDisconnectReason() == 17) { + display->drawString(x, y + FONT_HEIGHT * 1, "IE_IN_4WAY_DIFFERS"); + } else if (getWifiDisconnectReason() == 18) { + display->drawString(x, y + FONT_HEIGHT * 1, "Invalid Group Cipher"); + } else if (getWifiDisconnectReason() == 19) { + display->drawString(x, y + FONT_HEIGHT * 1, "Invalid Pairwise Cipher"); + } else if (getWifiDisconnectReason() == 20) { + display->drawString(x, y + FONT_HEIGHT * 1, "AKMP_INVALID"); + } else if (getWifiDisconnectReason() == 21) { + display->drawString(x, y + FONT_HEIGHT * 1, "UNSUPP_RSN_IE_VERSION"); + } else if (getWifiDisconnectReason() == 22) { + display->drawString(x, y + FONT_HEIGHT * 1, "INVALID_RSN_IE_CAP"); + } else if (getWifiDisconnectReason() == 23) { + display->drawString(x, y + FONT_HEIGHT * 1, "802_1X_AUTH_FAILED"); + } else if (getWifiDisconnectReason() == 24) { + display->drawString(x, y + FONT_HEIGHT * 1, "CIPHER_SUITE_REJECTED"); + } else if (getWifiDisconnectReason() == 200) { + display->drawString(x, y + FONT_HEIGHT * 1, "BEACON_TIMEOUT"); + } else if (getWifiDisconnectReason() == 201) { + display->drawString(x, y + FONT_HEIGHT * 1, "NO_AP_FOUND"); + } else if (getWifiDisconnectReason() == 202) { + display->drawString(x, y + FONT_HEIGHT * 1, "AUTH_FAIL"); + } else if (getWifiDisconnectReason() == 203) { + display->drawString(x, y + FONT_HEIGHT * 1, "ASSOC_FAIL"); + } else if (getWifiDisconnectReason() == 204) { + display->drawString(x, y + FONT_HEIGHT * 1, "HANDSHAKE_TIMEOUT"); + } else if (getWifiDisconnectReason() == 205) { + display->drawString(x, y + FONT_HEIGHT * 1, "CONNECTION_FAIL"); + } else { + display->drawString(x, y + FONT_HEIGHT * 1, "Unknown Status"); + } + } + + display->drawString(x, y + FONT_HEIGHT * 2, "SSID: " + String(wifiName)); + display->drawString(x, y + FONT_HEIGHT * 3, "PWD: " + String(wifiPsw)); + + /* Display a heartbeat pixel that blinks every time the frame is redrawn */ +#ifdef SHOW_REDRAWS + if (heartbeat) + display->setPixel(0, 0); + heartbeat = !heartbeat; +#endif +#endif +} + void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { displayedNodeNum = 0; // Not currently showing a node pane @@ -838,29 +978,21 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat display->setTextAlignment(TEXT_ALIGN_LEFT); char batStr[20]; - if (powerStatus->getHasBattery()) - { + if (powerStatus->getHasBattery()) { int batV = powerStatus->getBatteryVoltageMv() / 1000; int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10; - snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c", - batV, - batCv, - powerStatus->getBatteryChargePercent(), - powerStatus->getIsCharging() ? '+' : ' ', - powerStatus->getHasUSB() ? 'U' : ' '); + snprintf(batStr, sizeof(batStr), "B %01d.%02dV %3d%% %c%c", batV, batCv, powerStatus->getBatteryChargePercent(), + powerStatus->getIsCharging() ? '+' : ' ', powerStatus->getHasUSB() ? 'U' : ' '); // Line 1 display->drawString(x, y, batStr); - } - else - { + } else { // Line 1 display->drawString(x, y, String("USB")); - } + } - - //TODO: Display status of the BT radio + // TODO: Display status of the BT radio // display->drawString(x + SCREEN_WIDTH - display->getStringWidth("BT On"), y, "BT On"); // Line 2 @@ -874,20 +1006,15 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat minutes %= 60; hours %= 24; - display->drawString(x, y + FONT_HEIGHT * 1, String(days) + "d " - + (hours < 10 ? "0" : "") + String(hours) + ":" - + (minutes < 10 ? "0" : "") + String(minutes) + ":" - + (seconds < 10 ? "0" : "") + String(seconds)); - display->drawString(x + SCREEN_WIDTH - display->getStringWidth("Mode " + String(channelSettings.modem_config)), y + FONT_HEIGHT * 1, "Mode " + String(channelSettings.modem_config)); - - // Line 3 - // TODO: Use this line for WiFi information. - // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth("WiFi: 192.168.0.100"))) / 2, y + FONT_HEIGHT * 2, "WiFi: 192.168.0.100"); + display->drawString(x, y + FONT_HEIGHT * 1, + String(days) + "d " + (hours < 10 ? "0" : "") + String(hours) + ":" + (minutes < 10 ? "0" : "") + + String(minutes) + ":" + (seconds < 10 ? "0" : "") + String(seconds)); + display->drawString(x + SCREEN_WIDTH - display->getStringWidth("Mode " + String(channelSettings.modem_config)), + y + FONT_HEIGHT * 1, "Mode " + String(channelSettings.modem_config)); // Line 4 drawGPScoordinates(display, x, y + FONT_HEIGHT * 3, gpsStatus); - /* Display a heartbeat pixel that blinks every time the frame is redrawn */ #ifdef SHOW_REDRAWS if (heartbeat) diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index c9ab8544..0fc6e588 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -46,6 +46,7 @@ class DebugInfo /// Renders the debug screen. void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); std::string channelName; @@ -220,6 +221,8 @@ class Screen : public concurrency::PeriodicTask static void drawDebugInfoSettingsTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + static void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + /// Queue of commands to execute in doTask. TypedQueue cmdQueue; /// Whether we are using a display diff --git a/src/main.cpp b/src/main.cpp index dc188809..f5626e45 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -37,6 +37,8 @@ #include "SPILock.h" #include "graphics/Screen.h" #include "main.h" +#include "meshwifi/meshhttp.h" +#include "meshwifi/meshwifi.h" #include "sleep.h" #include "target_specific.h" #include @@ -328,6 +330,9 @@ void setup() } #endif + // Initialize Wifi + initWifi(); + if (!rIf) recordCriticalError(ErrNoRadio); else @@ -394,6 +399,8 @@ void loop() userButtonAlt.tick(); #endif + loopWifi(); + // Show boot screen for first 3 seconds, then switch to normal operation. static bool showingBootScreen = true; if (showingBootScreen && (millis() > 3000)) { @@ -420,5 +427,8 @@ void loop() // feel slow msecstosleep = 10; + // TODO: This should go into a thread handled by FreeRTOS. + handleWebResponse(); + delay(msecstosleep); } diff --git a/src/main.h b/src/main.h index 74ad0b44..366ef861 100644 --- a/src/main.h +++ b/src/main.h @@ -10,6 +10,8 @@ extern bool ssd1306_found; extern bool isCharging; extern bool isUSBPowered; + + // Global Screen singleton. extern graphics::Screen screen; //extern Observable newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class @@ -23,4 +25,4 @@ const char *getDeviceName(); -void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(); \ No newline at end of file +void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(); diff --git a/src/mesh/MeshRadio.h b/src/mesh/MeshRadio.h index 14b649da..070c7c9a 100644 --- a/src/mesh/MeshRadio.h +++ b/src/mesh/MeshRadio.h @@ -41,6 +41,12 @@ #define CH_SPACING_ANZ 0.5f #define NUM_CHANNELS_ANZ 20 +// KR channel settings (KR920-923) +// Start from TTN download channel freq. (921.9f is for download, others are for uplink) +#define CH0_KR 921.9f // MHz +#define CH_SPACING_KR 0.2f +#define NUM_CHANNELS_KR 8 + // FIXME add defs for other regions and use them here #ifdef HW_VERSION_US #define CH0 CH0_US @@ -73,9 +79,14 @@ #define CH0 CH0_ANZ #define CH_SPACING CH_SPACING_ANZ #define NUM_CHANNELS NUM_CHANNELS_ANZ +#elif defined(HW_VERSION_KR) +// Republic of Korea +#define CH0 CH0_KR +#define CH_SPACING CH_SPACING_KR +#define NUM_CHANNELS NUM_CHANNELS_KR #else // HW version not set - assume US #define CH0 CH0_US #define CH_SPACING CH_SPACING_US #define NUM_CHANNELS NUM_CHANNELS_US -#endif +#endif \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0bed2400..9f545a3f 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -15,6 +15,7 @@ #include "mesh-pb-constants.h" #include #include +#include "meshwifi/meshwifi.h" NodeDB nodeDB; @@ -419,6 +420,12 @@ void NodeDB::updateFrom(const MeshPacket &mp) updateTextMessage = true; powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); notifyObservers(true); // Force an update whether or not our node counts have changed + +// This is going into the wifidev feature branch +// Only update the WebUI if WiFi is enabled +//#if WiFi_MODE != 0 +// notifyWebUI(); +//#endif } } break; diff --git a/src/meshwifi/meshhttp.cpp b/src/meshwifi/meshhttp.cpp new file mode 100644 index 00000000..9106ab08 --- /dev/null +++ b/src/meshwifi/meshhttp.cpp @@ -0,0 +1,793 @@ +#include "meshwifi/meshhttp.h" +#include "NodeDB.h" +#include "configuration.h" +#include "main.h" +#include "meshwifi/meshwifi.h" +#include +#include + +WebServer webserver(80); + +// Maximum number of messages for chat history. Don't make this too big -- it'll use a +// lot of memory! +const uint16_t maxMessages = 50; + +struct message_t { + char sender[10]; + char message[250]; + int32_t gpsLat; + int32_t gpsLong; + uint32_t time; + bool fromMe; +}; + +struct messages_t { + message_t history[maxMessages]; +}; + +messages_t messages_history; + +String something = ""; +String sender = ""; + +void handleWebResponse() +{ + if (isWifiAvailable() == 0) { + return; + } + + // We're going to handle the DNS responder here so it + // will be ignored by the NRF boards. + handleDNSResponse(); + + webserver.handleClient(); +} + +void initWebServer() +{ + webserver.onNotFound(handleNotFound); + webserver.on("/json/chat/send/channel", handleJSONChatHistory); + webserver.on("/json/chat/send/user", handleJSONChatHistory); + webserver.on("/json/chat/history/channel", handleJSONChatHistory); + webserver.on("/json/chat/history/dummy", handleJSONChatHistoryDummy); + webserver.on("/json/chat/history/user", handleJSONChatHistory); + webserver.on("/json/stats", handleJSONChatHistory); + webserver.on("/hotspot-detect.html", handleHotspot); + webserver.on("/css/style.css", handleStyleCSS); + webserver.on("/scripts/script.js", handleScriptsScriptJS); + webserver.on("/", handleRoot); + webserver.begin(); +} + +void handleJSONChatHistory() +{ + int i; + + String out = ""; + out += "{\n"; + out += " \"data\" : {\n"; + out += " \"chat\" : "; + out += "["; + out += "\"" + sender + "\""; + out += ","; + out += "\"" + something + "\""; + out += "]\n"; + + for (i = 0; i < maxMessages; i++) { + out += "["; + out += "\"" + String(messages_history.history[i].sender) + "\""; + out += ","; + out += "\"" + String(messages_history.history[i].message) + "\""; + out += "]\n"; + } + + out += "\n"; + out += " }\n"; + out += "}\n"; + + webserver.send(200, "application/json", out); + return; +} + +void handleNotFound() +{ + String message = ""; + message += "File Not Found\n\n"; + message += "URI: "; + message += webserver.uri(); + message += "\nMethod: "; + message += (webserver.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += webserver.args(); + message += "\n"; + + for (uint8_t i = 0; i < webserver.args(); i++) { + message += " " + webserver.argName(i) + ": " + webserver.arg(i) + "\n"; + } + Serial.println(message); + webserver.send(404, "text/plain", message); +} + +/* + This supports the Apple Captive Network Assistant (CNA) Portal +*/ +void handleHotspot() +{ + DEBUG_MSG("Hotspot Request\n"); + + /* + If we don't do a redirect, be sure to return a "Success" message + otherwise iOS will have trouble detecting that the connection to the SoftAP worked. + */ + + String out = ""; + // out += "Success\n"; + out += "\n"; + webserver.send(200, "text/html", out); + return; +} + +void notifyWebUI() +{ + DEBUG_MSG("************ Got a message! ************\n"); + MeshPacket &mp = devicestate.rx_text_message; + NodeInfo *node = nodeDB.getNode(mp.from); + sender = (node && node->has_user) ? node->user.long_name : "???"; + + static char tempBuf[256]; // mesh.options says this is MeshPacket.encrypted max_size + assert(mp.decoded.which_payload == SubPacket_data_tag); + snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.data.payload.bytes); + + something = tempBuf; +} + +/* + To convert text to c strings: + + https://tomeko.net/online_tools/cpp_text_escape.php?lang=en +*/ +void handleRoot() +{ + + String out = ""; + out += + "\n" + "\n" + "\n" + "\n" + "\n" + " \n" + " Meshtastic - Chat\n" + " \n" + "\n" + "\n" + "\n" + "

This area is under development. Please don't file bugs.

\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "
\n" + "\t
\n" + "\t\t
Meshtastic - Chat
\n" + "\t
\n" + "\n" + "\t
\n" + "
\n" + "\t
\n" + "\t\t
Users
\n" + "\t
\n" + "
    \n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "\n" + "
\n" + "\t\t All Users\n" + "\t\t
\n" + "\n" + "
\n" + " \n" + "
\n" + "
    \n" + "\t\t
\n" + " \n" + "
\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "
\n" + " \n" + "
\n" + " \n" + "
\n" + "\n" + "\n" + "\n" + "\n" + "\n" + ""; + webserver.send(200, "text/html", out); + return; +} + +void handleStyleCSS() +{ + + String out = ""; + out += + "/* latin-ext */\n" + "@font-face {\n" + " font-family: 'Lato';\n" + " font-style: normal;\n" + " font-weight: 400;\n" + " src: local('Lato Regular'), local('Lato-Regular'), url(./Google.woff2) format('woff2');\n" + " unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;\n" + "}\n" + "\n" + "\n" + "*, *:before, *:after {\n" + " box-sizing: border-box;\n" + "}\n" + "\n" + "body {\n" + " background: #C5DDEB;\n" + " font: 14px/20px \"Lato\", Arial, sans-serif;\n" + " padding: 40px 0;\n" + " color: white;\n" + "}\n" + "\n" + "\n" + " \n" + ".grid {\n" + " display: grid;\n" + " grid-template-columns:\n" + "\t1fr 4fr;\n" + " grid-template-areas:\n" + "\t\"header header\"\n" + "\t\"sidebar content\";\n" + " margin: 0 auto;\n" + " width: 750px;\n" + " background: #444753;\n" + " border-radius: 5px;\n" + "}\n" + "\n" + ".top {grid-area: header;}\n" + ".side {grid-area: sidebar;}\n" + ".main {grid-area: content;}\n" + "\n" + ".top {\n" + " border-bottom: 2px solid white;\n" + "}\n" + ".top-text {\n" + " font-weight: bold;\n" + " font-size: 24px;\n" + " text-align: center;\n" + " padding: 20px;\n" + "}\n" + "\n" + ".side {\n" + " width: 260px;\n" + " float: left;\n" + "}\n" + ".side .side-header {\n" + " padding: 20px;\n" + " border-bottom: 2px solid white;\n" + "}\n" + "\n" + ".side .side-header .side-text {\n" + " padding-left: 10px;\n" + " margin-top: 6px;\n" + " font-size: 16px;\n" + " text-align: left;\n" + " font-weight: bold;\n" + " \n" + "}\n" + "\n" + ".channel-list ul {\n" + " padding: 20px;\n" + " height: 570px;\n" + " list-style-type: none;\n" + "}\n" + ".channel-list ul li {\n" + " padding-bottom: 20px;\n" + "}\n" + "\n" + ".channel-list .channel-name {\n" + " font-size: 20px;\n" + " margin-top: 8px;\n" + " padding-left: 8px;\n" + "}\n" + "\n" + ".channel-list .message-count {\n" + " padding-left: 16px;\n" + " color: #92959E;\n" + "}\n" + "\n" + ".icon {\n" + " display: inline-block;\n" + " width: 1em;\n" + " height: 1em;\n" + " stroke-width: 0;\n" + " stroke: currentColor;\n" + " fill: currentColor;\n" + "}\n" + "\n" + ".icon-map-marker {\n" + " width: 0.5714285714285714em;\n" + "}\n" + "\n" + ".icon-circle {\n" + " width: 0.8571428571428571em;\n" + "}\n" + "\n" + ".content {\n" + " display: flex;\n" + " flex-direction: column;\n" + " flex-wrap: nowrap;\n" + "/* width: 490px; */\n" + " float: left;\n" + " background: #F2F5F8;\n" + "/* border-top-right-radius: 5px;\n" + " border-bottom-right-radius: 5px; */\n" + " color: #434651;\n" + "}\n" + ".content .content-header {\n" + " flex-grow: 0;\n" + " padding: 20px;\n" + " border-bottom: 2px solid white;\n" + "}\n" + "\n" + ".content .content-header .content-from {\n" + " padding-left: 10px;\n" + " margin-top: 6px;\n" + " font-size: 20px;\n" + " text-align: center;\n" + " font-size: 16px;\n" + "}\n" + ".content .content-header .content-from .content-from-highlight {\n" + " font-weight: bold;\n" + "}\n" + ".content .content-header .content-num-messages {\n" + " color: #92959E;\n" + "}\n" + "\n" + ".content .content-history {\n" + " flex-grow: 1;\n" + " padding: 20px 20px 20px;\n" + " border-bottom: 2px solid white;\n" + " overflow-y: scroll;\n" + " height: 375px;\n" + "}\n" + ".content .content-history ul {\n" + " list-style-type: none;\n" + " padding-inline-start: 10px;\n" + "}\n" + ".content .content-history .message-data {\n" + " margin-bottom: 10px;\n" + "}\n" + ".content .content-history .message-data-time {\n" + " color: #a8aab1;\n" + " padding-left: 6px;\n" + "}\n" + ".content .content-history .message {\n" + " color: white;\n" + " padding: 8px 10px;\n" + " line-height: 20px;\n" + " font-size: 14px;\n" + " border-radius: 7px;\n" + " margin-bottom: 30px;\n" + " width: 90%;\n" + " position: relative;\n" + "}\n" + ".content .content-history .message:after {\n" + " bottom: 100%;\n" + " left: 7%;\n" + " border: solid transparent;\n" + " content: \" \";\n" + " height: 0;\n" + " width: 0;\n" + " position: absolute;\n" + " pointer-events: none;\n" + " border-bottom-color: #86BB71;\n" + " border-width: 10px;\n" + " margin-left: -10px;\n" + "}\n" + ".content .content-history .my-message {\n" + " background: #86BB71;\n" + "}\n" + ".content .content-history .other-message {\n" + " background: #94C2ED;\n" + "}\n" + ".content .content-history .other-message:after {\n" + " border-bottom-color: #94C2ED;\n" + " left: 93%;\n" + "}\n" + ".content .content-message {\n" + " flex-grow: 0;\n" + " padding: 10px;\n" + "}\n" + ".content .content-message textarea {\n" + " width: 100%;\n" + " border: none;\n" + " padding: 10px 10px;\n" + " font: 14px/22px \"Lato\", Arial, sans-serif;\n" + " margin-bottom: 10px;\n" + " border-radius: 5px;\n" + " resize: none;\n" + "}\n" + "\n" + ".content .content-message button {\n" + " float: right;\n" + " color: #94C2ED;\n" + " font-size: 16px;\n" + " text-transform: uppercase;\n" + " border: none;\n" + " cursor: pointer;\n" + " font-weight: bold;\n" + " background: #F2F5F8;\n" + "}\n" + ".content .content-message button:hover {\n" + " color: #75b1e8;\n" + "}\n" + "/* Tooltip container */\n" + ".tooltip {\n" + " color: #86BB71;\n" + " position: relative;\n" + " display: inline-block;\n" + " border-bottom: 1px dotted black; /* If you want dots under the hoverable text */\n" + "}\n" + "/* Tooltip text */\n" + ".tooltip .tooltiptext {\n" + " visibility: hidden;\n" + " width: 120px;\n" + " background-color: #444753;\n" + " color: #fff;\n" + " text-align: center;\n" + " padding: 5px 0;\n" + " border-radius: 6px;\n" + " /* Position the tooltip text - see examples below! */\n" + " position: absolute;\n" + " z-index: 1;\n" + "}\n" + "\n" + "/* Show the tooltip text when you mouse over the tooltip container */\n" + ".tooltip:hover .tooltiptext {\n" + " visibility: visible;\n" + "}\n" + "\n" + ".online, .offline, .me {\n" + " margin-right: 3px;\n" + " font-size: 10px;\n" + "}\n" + "\n" + ".online {\n" + " color: #86BB71;\n" + "}\n" + "\n" + ".offline {\n" + " color: #E38968;\n" + "}\n" + "\n" + ".me {\n" + " color: #94C2ED;\n" + "}\n" + "\n" + ".align-left {\n" + " text-align: left;\n" + "}\n" + "\n" + ".align-right {\n" + " text-align: right;\n" + "}\n" + "\n" + ".float-right {\n" + " float: right;\n" + "}\n" + "\n" + ".clearfix:after {\n" + " visibility: hidden;\n" + " display: block;\n" + " font-size: 0;\n" + " content: \" \";\n" + " clear: both;\n" + " height: 0;\n" + "}"; + + webserver.send(200, "text/css", out); + return; +} + +void handleScriptsScriptJS() +{ + String out = ""; + out += "String.prototype.toHHMMSS = function () {\n" + " var sec_num = parseInt(this, 10); // don't forget the second param\n" + " var hours = Math.floor(sec_num / 3600);\n" + " var minutes = Math.floor((sec_num - (hours * 3600)) / 60);\n" + " var seconds = sec_num - (hours * 3600) - (minutes * 60);\n" + "\n" + " if (hours < 10) {hours = \"0\"+hours;}\n" + " if (minutes < 10) {minutes = \"0\"+minutes;}\n" + " if (seconds < 10) {seconds = \"0\"+seconds;}\n" + "// return hours+':'+minutes+':'+seconds;\n" + "\treturn hours+'h'+minutes+'m';\n" + "}\n" + "String.prototype.padLeft = function (length, character) { \n" + " return new Array(length - this.length + 1).join(character || ' ') + this; \n" + "};\n" + "\n" + "Date.prototype.toFormattedString = function () {\n" + " return [String(this.getFullYear()).substr(2, 2),\n" + "\t\t\tString(this.getMonth()+1).padLeft(2, '0'),\n" + " String(this.getDate()).padLeft(2, '0')].join(\"/\") + \" \" +\n" + " [String(this.getHours()).padLeft(2, '0'),\n" + " String(this.getMinutes()).padLeft(2, '0')].join(\":\");\n" + "};\n" + "\n" + "function getData(file) {\n" + "\tfetch(file)\n" + "\t.then(function (response) {\n" + "\t\treturn response.json();\n" + "\t})\n" + "\t.then(function (datafile) {\n" + "\t\tupdateData(datafile);\n" + "\t})\n" + "\t.catch(function (err) {\n" + "\t\tconsole.log('error: ' + err);\n" + "\t});\n" + "}\n" + "\t\n" + "function updateData(datafile) {\n" + "// Update System Details\n" + "\tupdateSystem(datafile);\n" + "//\tUpdate Userlist and message count\n" + "\tupdateUsers(datafile);\n" + "// Update Chat\n" + "\tupdateChat(datafile);\n" + "}\n" + "\n" + "function updateSystem(datafile) {\n" + "// Update System Info \n" + "\tvar sysContainer = document.getElementById(\"content-from-id\");\n" + "\tvar newHTML = datafile.data.system.channel;\n" + "\tvar myDate = new Date( datafile.data.system.timeGPS *1000);\n" + "\tnewHTML += ' @' + myDate.toFormattedString();\n" + "\tvar newSec = datafile.data.system.timeSinceStart;\n" + "\tvar strsecondUp = newSec.toString();\n" + "\tnewHTML += ' Up:' + strsecondUp.toHHMMSS();\n" + "\tsysContainer.innerHTML = newHTML;\n" + "}\n" + "\n" + "function updateUsers(datafile) {\n" + "\tvar mainContainer = document.getElementById(\"userlist-id\");\n" + "\tvar htmlUsers = '';\n" + "\tvar timeBase = datafile.data.system.timeSinceStart;\n" + "//\tvar lookup = {};\n" + " for (var i = 0; i < datafile.data.users.length; i++) {\n" + " htmlUsers += formatUsers(datafile.data.users[i],timeBase);\n" + "\t}\n" + "\tmainContainer.innerHTML = htmlUsers;\n" + "}\n" + "\n" + "function formatUsers(user,timeBase) {\n" + "\tnewHTML = '
  • ';\n" + " newHTML += '
    ' + user.NameLong + '(' + user.NameShort + ')
    ';\n" + " newHTML += '
    ';\n" + "\tvar secondsLS = timeBase - user.lastSeen;\n" + "\tvar strsecondsLS = secondsLS.toString();\n" + "\tnewHTML += 'Seen: '+strsecondsLS.toHHMMSS()+' ago ';\n" + "\tif (user.lat == 0 || user.lon == 0) {\n" + "\t\tnewHTML += '';\n" + "\t} else {\n" + "\t\tnewHTML += '
    lat:' + user.lat + ' lon:'+ user.lon+ " + "'';\n" + "\t}\n" + " newHTML += '
    ';\n" + " newHTML += '
  • ';\n" + "\treturn(newHTML);\n" + "}\n" + "\n" + "function onlineStatus(time) {\n" + "\tif (time < 3600) {\n" + "\t\treturn \"online\"\n" + "\t} else {\n" + "\t\treturn \"offline\"\n" + "\t}\n" + "}\n" + "\n" + "function updateChat(datafile) {\n" + "// Update Chat\n" + "\tvar chatContainer = document.getElementById(\"chat-history-id\");\n" + "\tvar htmlChat = '';\n" + "\tvar timeBase = datafile.data.system.timeSinceStart;\n" + "\tfor (var i = 0; i < datafile.data.chat.length; i++) {\n" + "\t\thtmlChat += formatChat(datafile.data.chat[i],timeBase);\n" + "\t}\n" + "\tchatContainer.innerHTML = htmlChat;\n" + "\tscrollHistory();\n" + "}\n" + "\n" + "function formatChat(data,timeBase) {\n" + "\tvar secondsTS = timeBase - data.timestamp;\n" + "\tvar strsecondsTS = secondsTS.toString();\n" + "\tnewHTML = '
  • ';\n" + "\tif (data.local == 1) {\n" + "\t\tnewHTML += '
    ';\n" + "\t\tnewHTML += '' + data.NameLong + '(' + data.NameShort + ')';\n" + "\t\tnewHTML += '' + strsecondsTS.toHHMMSS() + ' ago';\n" + "\t\tnewHTML += '
    ';\n" + "\t\tnewHTML += '
    ' + data.chatLine + '
    ';\n" + "\t} else {\n" + "\t\tnewHTML += '
    ';\n" + "\t\tnewHTML += '' + strsecondsTS.toHHMMSS() + ' ago    ';\n" + "\t\tnewHTML += '' + data.NameLong + '(' + data.NameShort + ')';\n" + "//\t\tnewHTML += '';\n" + "\t\tnewHTML += '
    ';\n" + "\t\tnewHTML += '
    ' + data.chatLine + '
    ';\n" + "\t}\n" + "\n" + " newHTML += '
  • ';\n" + "\treturn(newHTML);\t\n" + "}\n" + "\n" + "function scrollHistory() {\n" + "\tvar chatContainer = document.getElementById(\"chat-div-id\");\n" + "\tchatContainer.scrollTop = chatContainer.scrollHeight;\n" + "}\n" + "\n" + "\n" + "getData('/json/chat/history/dummy');\n" + "\n" + "\n" + "//window.onload=function(){\n" + "//\talert('onload');\n" + "// Async - Run scroll 0.5sec after onload event\n" + "//\tsetTimeout(scrollHistory(),500);\n" + "// }"; + + webserver.send(200, "text/javascript", out); + return; +} + +void handleJSONChatHistoryDummy() +{ + String out = ""; + out += "{\n" + "\t\"data\": {\n" + "\t\t\"system\": {\n" + "\t\t\t\"timeSinceStart\": 3213544,\n" + "\t\t\t\"timeGPS\": 1600830985,\n" + "\t\t\t\"channel\": \"ourSecretPlace\"\n" + "\t\t},\n" + "\t\t\"users\": [{\n" + "\t\t\t\t\"NameShort\": \"J\",\n" + "\t\t\t\t\"NameLong\": \"John\",\n" + "\t\t\t\t\"lastSeen\": 3207544,\n" + "\t\t\t\t\"lat\" : -2.882243,\n" + "\t\t\t\t\"lon\" : -111.038580\n" + "\t\t\t},\n" + "\t\t\t{\n" + "\t\t\t\t\"NameShort\": \"D\",\n" + "\t\t\t\t\"NameLong\": \"David\",\n" + "\t\t\t\t\"lastSeen\": 3212544,\n" + "\t\t\t\t\"lat\" : -12.24452,\n" + "\t\t\t\t\"lon\" : -61.87351\n" + "\t\t\t},\n" + "\t\t\t{\n" + "\t\t\t\t\"NameShort\": \"P\",\n" + "\t\t\t\t\"NameLong\": \"Peter\",\n" + "\t\t\t\t\"lastSeen\": 3213444,\n" + "\t\t\t\t\"lat\" : 0,\n" + "\t\t\t\t\"lon\" : 0\n" + "\t\t\t},\n" + "\t\t\t{\n" + "\t\t\t\t\"NameShort\": \"M\",\n" + "\t\t\t\t\"NameLong\": \"Mary\",\n" + "\t\t\t\t\"lastSeen\": 3211544,\n" + "\t\t\t\t\"lat\" : 16.45478,\n" + "\t\t\t\t\"lon\" : 11.40166\n" + "\t\t\t}\n" + "\t\t],\n" + "\t\t\"chat\": [{\n" + "\t\t\t\t\"local\": 0,\n" + "\t\t\t\t\"NameShort\": \"J\",\n" + "\t\t\t\t\"NameLong\": \"John\",\n" + "\t\t\t\t\"chatLine\": \"Hello\",\n" + "\t\t\t\t\"timestamp\" : 3203544\n" + "\t\t\t},\n" + "\t\t\t{\n" + "\t\t\t\t\"local\": 0,\n" + "\t\t\t\t\"NameShort\": \"D\",\n" + "\t\t\t\t\"NameLong\": \"David\",\n" + "\t\t\t\t\"chatLine\": \"Hello There\",\n" + "\t\t\t\t\"timestamp\" : 3204544\n" + "\t\t\t},\n" + "\t\t\t{\n" + "\t\t\t\t\"local\": 0,\n" + "\t\t\t\t\"NameShort\": \"J\",\n" + "\t\t\t\t\"NameLong\": \"John\",\n" + "\t\t\t\t\"chatLine\": \"Where you been?\",\n" + "\t\t\t\t\"timestamp\" : 3205544\n" + "\t\t\t},\n" + "\t\t\t{\n" + "\t\t\t\t\"local\": 0,\n" + "\t\t\t\t\"NameShort\": \"D\",\n" + "\t\t\t\t\"NameLong\": \"David\",\n" + "\t\t\t\t\"chatLine\": \"I was on Channel 2\",\n" + "\t\t\t\t\"timestamp\" : 3206544\n" + "\t\t\t},\n" + "\t\t\t{\n" + "\t\t\t\t\"local\": 0,\n" + "\t\t\t\t\"NameShort\": \"J\",\n" + "\t\t\t\t\"NameLong\": \"John\",\n" + "\t\t\t\t\"chatLine\": \"With Mary again?\",\n" + "\t\t\t\t\"timestamp\" : 3207544\n" + "\t\t\t},\n" + "\t\t\t{\n" + "\t\t\t\t\"local\": 0,\n" + "\t\t\t\t\"NameShort\": \"D\",\n" + "\t\t\t\t\"NameLong\": \"David\",\n" + "\t\t\t\t\"chatLine\": \"She's better looking than you\",\n" + "\t\t\t\t\"timestamp\" : 3208544\n" + "\t\t\t},\n" + "\t\t\t{\n" + "\t\t\t\t\"local\": 0,\n" + "\t\t\t\t\"NameShort\": \"M\",\n" + "\t\t\t\t\"NameLong\": \"Mary\",\n" + "\t\t\t\t\"chatLine\": \"Well, Hi\",\n" + "\t\t\t\t\"timestamp\" : 3209544\n" + "\t\t\t},\n" + "\t\t\t{\n" + "\t\t\t\t\"local\": 0,\n" + "\t\t\t\t\"NameShort\": \"D\",\n" + "\t\t\t\t\"NameLong\": \"David\",\n" + "\t\t\t\t\"chatLine\": \"You're Here\",\n" + "\t\t\t\t\"timestamp\" : 3210544\n" + "\t\t\t},\n" + "\t\t\t{\n" + "\t\t\t\t\"local\": 0,\n" + "\t\t\t\t\"NameShort\": \"M\",\n" + "\t\t\t\t\"NameLong\": \"Mary\",\n" + "\t\t\t\t\"chatLine\": \"Wanted to say Howdy.\",\n" + "\t\t\t\t\"timestamp\" : 3211544\n" + "\t\t\t},\n" + "\t\t\t{\n" + "\t\t\t\t\"local\": 0,\n" + "\t\t\t\t\"NameShort\": \"D\",\n" + "\t\t\t\t\"NameLong\": \"David\",\n" + "\t\t\t\t\"chatLine\": \"Better come down and visit sometime\",\n" + "\t\t\t\t\"timestamp\" : 3212544\n" + "\t\t\t},\n" + "\t\t\t{\n" + "\t\t\t\t\"local\": 1,\n" + "\t\t\t\t\"NameShort\": \"P\",\n" + "\t\t\t\t\"NameLong\": \"Peter\",\n" + "\t\t\t\t\"chatLine\": \"Where is everybody?\",\n" + "\t\t\t\t\"timestamp\" : 3213444\n" + "\t\t\t}\n" + "\t\t]\n" + "\t}\n" + "}"; + + webserver.send(200, "application/json", out); + return; +} \ No newline at end of file diff --git a/src/meshwifi/meshhttp.h b/src/meshwifi/meshhttp.h new file mode 100644 index 00000000..42c6644b --- /dev/null +++ b/src/meshwifi/meshhttp.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +void initWebServer(); + +void handleNotFound(); + +void handleWebResponse(); + +void handleJSONChatHistory(); + +void notifyWebUI(); + +void handleHotspot(); + + +void handleStyleCSS(); +void handleRoot(); +void handleScriptsScriptJS(); +void handleJSONChatHistoryDummy(); \ No newline at end of file diff --git a/src/meshwifi/meshwifi.cpp b/src/meshwifi/meshwifi.cpp new file mode 100644 index 00000000..84a9a5a5 --- /dev/null +++ b/src/meshwifi/meshwifi.cpp @@ -0,0 +1,255 @@ +#include "meshwifi.h" +#include "NodeDB.h" +#include "WiFiServerAPI.h" +#include "configuration.h" +#include "main.h" +#include "meshwifi/meshhttp.h" +#include +#include + +static void WiFiEvent(WiFiEvent_t event); +// static void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); + +DNSServer dnsServer; +static WiFiServerPort *apiPort; + +uint8_t wifiDisconnectReason = 0; + +bool isWifiAvailable() +{ + const char *wifiName = radioConfig.preferences.wifi_ssid; + const char *wifiPsw = radioConfig.preferences.wifi_password; + + if (*wifiName && *wifiPsw) { + + // Once every 10 seconds, try to reconnect. + + return 1; + } else { + return 0; + } +} + +// Disable WiFi +void deinitWifi() +{ + /* + Note from Jm (jm@casler.org - Sept 16, 2020): + + A bug in the ESP32 SDK was introduced in Oct 2019 that keeps the WiFi radio from + turning back on after it's shut off. See: + https://github.com/espressif/arduino-esp32/issues/3522 + + Until then, WiFi should only be allowed when there's no power + saving on the 2.4g transceiver. + */ + + WiFi.mode(WIFI_MODE_NULL); + DEBUG_MSG("WiFi Turned Off\n"); + // WiFi.printDiag(Serial); +} + +// Startup WiFi +void initWifi() +{ + if (isWifiAvailable() == 0) { + return; + } + + if (radioConfig.has_preferences) { + const char *wifiName = radioConfig.preferences.wifi_ssid; + const char *wifiPsw = radioConfig.preferences.wifi_password; + + /* + if (0) { + radioConfig.preferences.wifi_ap_mode = 1; + strcpy(radioConfig.preferences.wifi_ssid, "MeshTest2"); + strcpy(radioConfig.preferences.wifi_password, "12345678"); + } else { + radioConfig.preferences.wifi_ap_mode = 0; + strcpy(radioConfig.preferences.wifi_ssid, "meshtastic"); + strcpy(radioConfig.preferences.wifi_password, "meshtastic!"); + } + */ + + if (*wifiName && *wifiPsw) { + if (radioConfig.preferences.wifi_ap_mode) { + + IPAddress apIP(192, 168, 42, 1); + WiFi.onEvent(WiFiEvent); + + WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); + DEBUG_MSG("STARTING WIFI AP: ssid=%s, ok=%d\n", wifiName, WiFi.softAP(wifiName, wifiPsw)); + DEBUG_MSG("MY IP ADDRESS: %s\n", WiFi.softAPIP().toString().c_str()); + + dnsServer.start(53, "*", apIP); + + } else { + WiFi.mode(WIFI_MODE_STA); + WiFi.onEvent(WiFiEvent); + // esp_wifi_set_ps(WIFI_PS_NONE); // Disable power saving + + WiFiEventId_t eventID = WiFi.onEvent( + [](WiFiEvent_t event, WiFiEventInfo_t info) { + Serial.print("\nWiFi lost connection. Reason: "); + Serial.println(info.disconnected.reason); + // wifiDisconnectReason = info.disconnected.reason; + }, + WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); + + DEBUG_MSG("JOINING WIFI: ssid=%s\n", wifiName); + if (WiFi.begin(wifiName, wifiPsw) == WL_CONNECTED) { + DEBUG_MSG("MY IP ADDRESS: %s\n", WiFi.localIP().toString().c_str()); + } else { + DEBUG_MSG("Started Joining WIFI\n"); + } + } + } + } else + DEBUG_MSG("Not using WIFI\n"); +} + +/// Perform idle loop processing required by the wifi layer +void loopWifi() +{ + // FIXME, once we have coroutines - just use a coroutine instead of this nasty loopWifi() + if (apiPort) + apiPort->loop(); +} + +static void initApiServer() +{ + // Start API server on port 4403 + if (!apiPort) { + apiPort = new WiFiServerPort(); + apiPort->init(); + } +} +static void WiFiEvent(WiFiEvent_t event) +{ + DEBUG_MSG("************ [WiFi-event] event: %d ************\n", event); + + switch (event) { + case SYSTEM_EVENT_WIFI_READY: + DEBUG_MSG("WiFi interface ready\n"); + break; + case SYSTEM_EVENT_SCAN_DONE: + DEBUG_MSG("Completed scan for access points\n"); + break; + case SYSTEM_EVENT_STA_START: + DEBUG_MSG("WiFi client started\n"); + break; + case SYSTEM_EVENT_STA_STOP: + DEBUG_MSG("WiFi clients stopped\n"); + break; + case SYSTEM_EVENT_STA_CONNECTED: + DEBUG_MSG("Connected to access point\n"); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + DEBUG_MSG("Disconnected from WiFi access point\n"); + // Event 5 + + reconnectWiFi(); + break; + case SYSTEM_EVENT_STA_AUTHMODE_CHANGE: + DEBUG_MSG("Authentication mode of access point has changed\n"); + break; + case SYSTEM_EVENT_STA_GOT_IP: + DEBUG_MSG("Obtained IP address: \n"); + Serial.println(WiFi.localIP()); + + // Start web server + initWebServer(); + initApiServer(); + + break; + case SYSTEM_EVENT_STA_LOST_IP: + DEBUG_MSG("Lost IP address and IP address is reset to 0\n"); + break; + case SYSTEM_EVENT_STA_WPS_ER_SUCCESS: + DEBUG_MSG("WiFi Protected Setup (WPS): succeeded in enrollee mode\n"); + break; + case SYSTEM_EVENT_STA_WPS_ER_FAILED: + DEBUG_MSG("WiFi Protected Setup (WPS): failed in enrollee mode\n"); + break; + case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT: + DEBUG_MSG("WiFi Protected Setup (WPS): timeout in enrollee mode\n"); + break; + case SYSTEM_EVENT_STA_WPS_ER_PIN: + DEBUG_MSG("WiFi Protected Setup (WPS): pin code in enrollee mode\n"); + break; + case SYSTEM_EVENT_AP_START: + DEBUG_MSG("WiFi access point started\n"); + Serial.println(WiFi.softAPIP()); + + // Start web server + initWebServer(); + initApiServer(); + + break; + case SYSTEM_EVENT_AP_STOP: + DEBUG_MSG("WiFi access point stopped\n"); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + DEBUG_MSG("Client connected\n"); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + DEBUG_MSG("Client disconnected\n"); + break; + case SYSTEM_EVENT_AP_STAIPASSIGNED: + DEBUG_MSG("Assigned IP address to client\n"); + break; + case SYSTEM_EVENT_AP_PROBEREQRECVED: + DEBUG_MSG("Received probe request\n"); + break; + case SYSTEM_EVENT_GOT_IP6: + DEBUG_MSG("IPv6 is preferred\n"); + break; + case SYSTEM_EVENT_ETH_START: + DEBUG_MSG("Ethernet started\n"); + break; + case SYSTEM_EVENT_ETH_STOP: + DEBUG_MSG("Ethernet stopped\n"); + break; + case SYSTEM_EVENT_ETH_CONNECTED: + DEBUG_MSG("Ethernet connected\n"); + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + DEBUG_MSG("Ethernet disconnected\n"); + break; + case SYSTEM_EVENT_ETH_GOT_IP: + DEBUG_MSG("Obtained IP address\n"); + break; + default: + break; + } +} + +void handleDNSResponse() +{ + if (radioConfig.preferences.wifi_ap_mode) { + dnsServer.processNextRequest(); + } +} + +void reconnectWiFi() +{ + const char *wifiName = radioConfig.preferences.wifi_ssid; + const char *wifiPsw = radioConfig.preferences.wifi_password; + + if (radioConfig.has_preferences) { + + if (*wifiName && *wifiPsw) { + + DEBUG_MSG("... Reconnecting to WiFi access point"); + + WiFi.mode(WIFI_MODE_STA); + WiFi.begin(wifiName, wifiPsw); + } + } +} + +uint8_t getWifiDisconnectReason() +{ + return wifiDisconnectReason; +} \ No newline at end of file diff --git a/src/meshwifi/meshwifi.h b/src/meshwifi/meshwifi.h new file mode 100644 index 00000000..d38b2a41 --- /dev/null +++ b/src/meshwifi/meshwifi.h @@ -0,0 +1,24 @@ +#pragma once + +#include "configuration.h" +#include +#include + +#ifdef HAS_WIFI +#include +#include +#endif + +void initWifi(); +void deinitWifi(); + +/// Perform idle loop processing required by the wifi layer +void loopWifi(); + +bool isWifiAvailable(); + +void handleDNSResponse(); + +void reconnectWiFi(); + +uint8_t getWifiDisconnectReason(); \ No newline at end of file diff --git a/src/nimble/BluetoothUtil.cpp b/src/nimble/BluetoothUtil.cpp index 490bc13d..f294802c 100644 --- a/src/nimble/BluetoothUtil.cpp +++ b/src/nimble/BluetoothUtil.cpp @@ -1,10 +1,9 @@ #include "BluetoothUtil.h" #include "BluetoothSoftwareUpdate.h" #include "NimbleBluetoothAPI.h" -#include "NodeDB.h" // FIXME - we shouldn't really douch this here - we are using it only because we currently do wifi setup when ble gets turned on #include "PhoneAPI.h" #include "PowerFSM.h" -#include "WiFi.h" +#include #include "configuration.h" #include "esp_bt.h" #include "host/util/util.h" @@ -13,6 +12,7 @@ #include "services/gap/ble_svc_gap.h" #include "services/gatt/ble_svc_gatt.h" #include +#include "meshwifi/meshwifi.h" static bool pinShowing; @@ -503,33 +503,8 @@ void reinitBluetooth() nimble_port_freertos_init(ble_host_task); } -void initWifi() -{ - // Note: Wifi is not yet supported ;-) - strcpy(radioConfig.preferences.wifi_ssid, ""); - strcpy(radioConfig.preferences.wifi_password, ""); - if (radioConfig.has_preferences) { - const char *wifiName = radioConfig.preferences.wifi_ssid; - - if (*wifiName) { - const char *wifiPsw = radioConfig.preferences.wifi_password; - if (radioConfig.preferences.wifi_ap_mode) { - DEBUG_MSG("STARTING WIFI AP: ssid=%s, ok=%d\n", wifiName, WiFi.softAP(wifiName, wifiPsw)); - } else { - WiFi.mode(WIFI_MODE_STA); - DEBUG_MSG("JOINING WIFI: ssid=%s\n", wifiName); - if (WiFi.begin(wifiName, wifiPsw) == WL_CONNECTED) { - DEBUG_MSG("MY IP ADDRESS: %s\n", WiFi.localIP().toString().c_str()); - } else { - DEBUG_MSG("Started Joining WIFI\n"); - } - } - } - } else - DEBUG_MSG("Not using WIFI\n"); -} - bool bluetoothOn; +bool firstTime = 1; // Enable/disable bluetooth. void setBluetoothEnable(bool on) @@ -542,11 +517,29 @@ void setBluetoothEnable(bool on) Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap()); // ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) ); reinitBluetooth(); - initWifi(); + + // Don't try to reconnect wifi before bluetooth is configured. + // WiFi is initialized from main.cpp in setup() . + if (firstTime) { + firstTime = 0; + } else { + initWifi(); + } } else { + + /* + // If WiFi is in use, disable shutting down the radio. + if (isWifiAvailable()) { + return; + } + */ + + // shutdown wifi + deinitWifi(); + // We have to totally teardown our bluetooth objects to prevent leaks deinitBLE(); - WiFi.mode(WIFI_MODE_NULL); // shutdown wifi + Serial.printf("Shutdown BT: %u heap size\n", ESP.getFreeHeap()); // ESP_ERROR_CHECK( heap_trace_stop() ); // heap_trace_dump(); diff --git a/src/nrf52/wifi-stubs.cpp b/src/nrf52/wifi-stubs.cpp new file mode 100644 index 00000000..30321e5e --- /dev/null +++ b/src/nrf52/wifi-stubs.cpp @@ -0,0 +1,16 @@ +#include "meshwifi/meshhttp.h" +#include "meshwifi/meshwifi.h" + +void initWifi() {} + +void deinitWifi() {} + +bool isWifiAvailable() +{ + return false; +} + +void handleWebResponse() {} + +/// Perform idle loop processing required by the wifi layer +void loopWifi() {} \ No newline at end of file diff --git a/src/portduino/wifi-stubs.cpp b/src/portduino/wifi-stubs.cpp new file mode 120000 index 00000000..b90e6629 --- /dev/null +++ b/src/portduino/wifi-stubs.cpp @@ -0,0 +1 @@ +../nrf52/wifi-stubs.cpp \ No newline at end of file