diff --git a/.prettierrc b/.prettierrc index 9510464..97b367e 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,5 @@ { - "semi": false, + "semi": true, "singleQuote": true, "quoteProps": "as-needed", "jsxSingleQuote": true, diff --git a/README.md b/README.md index 9e21750..3a61023 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ A template for developing NOT[^1]-devices using micro controllers like the ESP8266 or ESP32 (support pending) using [PlatformIO](https://platformio.org/) and the [Arduino environment](https://github.com/arduino/ArduinoCore-API). +The WebGUI is tested on `Android Browser 2.3.5` (yikes!) and should work with +other outdated browsers, so you can use your old devices to control your NOT! + [^1]: Network Of Things (a.k.a. IOT / marketing buzzword for network / internet enabled devices) diff --git a/TODO.md b/TODO.md index e909923..0a56f5a 100644 --- a/TODO.md +++ b/TODO.md @@ -4,14 +4,15 @@ ## Planned -- [ ] load WiFi configuration from filesystem +- [x] load WiFi STA configuration from filesystem +- [x] HTTPS +- [ ] load WiFi AP configuration from filesystem - [ ] edit WiFi configuration from browser -- [ ] HTTPS - [ ] API & Console configuration - [ ] IPv4 & IPv6 configuration, DNS, captive portal - [ ] physical access validation for reset / recovery - [ ] MQTT / HomeAssistant integration -- [ ] ESP32 support +- [x] ESP32 support - [ ] SPA frontend, live - [ ] Change peripheral / pin configuration - [ ] SD card logging diff --git a/boards/esp32c3-mini.json b/boards/esp32c3-mini.json index b1155f9..c744416 100644 --- a/boards/esp32c3-mini.json +++ b/boards/esp32c3-mini.json @@ -1,35 +1,30 @@ { - "build": { - "arduino":{ - "ldscript": "esp32c3_out.ld" + "build": { + "arduino": { + "ldscript": "esp32c3_out.ld" + }, + "core": "esp32", + "extra_flags": "-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1", + "f_cpu": "160000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "mcu": "esp32c3", + "variant": "esp32c3-mini", + "variants_dir": "variants" }, - "core": "esp32", - "extra_flags": "-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1", - "f_cpu": "160000000L", - "f_flash": "80000000L", - "flash_mode": "qio", - "mcu": "esp32c3", - "variant": "esp32c3-mini", - "variants_dir": "variants" - }, - "connectivity": [ - "wifi" - ], - "debug": { - "openocd_target": "esp32c3.cfg" - }, - "frameworks": [ - "arduino", - "espidf" - ], - "name": "Espressif ESP32-C3-MINI", - "upload": { - "flash_size": "4MB", - "maximum_ram_size": 327680, - "maximum_size": 4194304, - "require_upload_port": true, - "speed": 460800 - }, - "url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/hw-reference/esp32c3/user-guide-devkitm-1.html", - "vendor": "Espressif" + "connectivity": ["wifi"], + "debug": { + "openocd_target": "esp32c3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "Espressif ESP32-C3 Mini", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/hw-reference/esp32c3/user-guide-devkitm-1.html", + "vendor": "Espressif" } diff --git a/data/web/add.html b/data/web/add.html new file mode 100644 index 0000000..153a0d0 --- /dev/null +++ b/data/web/add.html @@ -0,0 +1,74 @@ + + + + + + + + Tiny NOT Add WiFi + + +
+

WiFi configuration

+ +
+

Add configuration

+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ IPv4 Configuration + + + + +
+
+
+
+
+
+ + + + diff --git a/data/web/app.js b/data/web/app.js new file mode 100644 index 0000000..7d1af05 --- /dev/null +++ b/data/web/app.js @@ -0,0 +1,51 @@ +var cssPropertySupported = (function () { + var mem = {}; // save test results for efficient future calls with same parameters + + return function cssPropertySupported(prop, values) { + if (mem[prop + values]) return mem[prop + values]; + + var element = document.createElement('p'), + index = values.length, + value, + result = false; + + try { + while (index--) { + value = values[index]; + element.style.setProperty(prop, value); + + if (element.style.getPropertyValue(prop) === value) { + result = true; + break; + } + } + } catch (pError) {} + + mem[prop + values] = result; + return result; + }; +})(); + +if (!cssPropertySupported('display', ['flex'])) { + fillViewport(); +} + +function fillViewport() { + var vh = window.innerHeight * 0.01; + + var main = document.querySelector('main'); + var footer = document.querySelector('footer'); + main.style.minHeight = window.innerHeight - footer.offsetHeight * 2 + 'px'; + + window.addEventListener('resize', function () { + main.style.minHeight = + window.innerHeight - footer.offsetHeight * 2 + 'px'; + }); + + // also hide early android browser's zoom controls + var meta = document.createElement('meta'); + meta.name = 'viewport'; + meta.content = + 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no'; + document.getElementsByTagName('head')[0].appendChild(meta); +} diff --git a/data/web/index.html b/data/web/index.html new file mode 100644 index 0000000..45f1546 --- /dev/null +++ b/data/web/index.html @@ -0,0 +1,70 @@ + + + + + + + + Tiny NOT setup + + +
+

Start

+ +
+

Status

+ + + + + + + + + + + +
FieldValue
Uptime + +
+
+ +
+

Stored

+
+
    +
  1. + stored SSID - stored BSSID - RSSI + +
  2. +
+
+
+ +
+

Scan results

+ +
+
    +
  1. SSID - BSSID - RSSI +
    + Add + +
    +
  2. +
+
+
+
+ + + + diff --git a/data/web/logo.png b/data/web/logo.png new file mode 100644 index 0000000..628655f Binary files /dev/null and b/data/web/logo.png differ diff --git a/data/web/style.css b/data/web/style.css new file mode 100644 index 0000000..df84f76 --- /dev/null +++ b/data/web/style.css @@ -0,0 +1,116 @@ +* { + box-sizing: border-box; + margin: 0; + font-family: Arial, Helvetica, sans-serif; + color: white; +} + +html { + /* min-height: 100%; */ + background-color: #232323; +} + +h1, +h2 { + text-align: center; + margin-top: 0.5rem; + margin-bottom: 1rem; +} + +[data-symbol]::before { + content: attr(data-symbol); + padding-right: 0.5rem; +} + +input[type='submit'], +a { + background-color: #233c54; +} + +input[type='submit']:hover, +input[type='submit']:focus-visible, +a:hover, +a:focus-visible { + background-color: #61a8ea; +} + +input, +a { + padding: 0.25rem; + border-radius: 4px; + display: inline-block; +} + +ol { + /* list-style-position: inside; */ + display: inline-block; + text-align: left; +} + +table { + display: inline-table; +} + +section { + margin-bottom: 1rem; +} + +.list-actions { + display: inline-block; +} + +.section-actions { + margin: 0.5rem 0; +} + +input { + border: 1px solid grey; + background-color: rgb(40, 40, 40); +} + +body { + min-height: 100vh; + min-height: 100dvh; + display: flex; + flex-direction: column; +} + +main { + flex-grow: 1; + padding-bottom: 40px; + display: block; +} + +input { + display: block; + margin: 0.5rem auto; +} + +input[required] { + border-color: red; +} + +nav, +.align-center { + text-align: center; +} + +form { + background-color: #181818; + border-radius: 8px; + padding: 1rem; + margin: auto; + width: fit-content; + display: inline-block; +} + +form div { + position: relative; +} + +footer { + background-color: #233c54; + text-align: center; + padding: 1rem 0; + border-radius: 8px 8px 0 0; +} diff --git a/lib/WiFiManager/src/WiFiManager.cpp b/lib/WiFiManager/src/WiFiManager.cpp index 3147525..12f1508 100644 --- a/lib/WiFiManager/src/WiFiManager.cpp +++ b/lib/WiFiManager/src/WiFiManager.cpp @@ -10,19 +10,23 @@ #endif #ifdef ESP32 -#include #include #include +#include WiFiMulti wifiMulti; +WiFiEventCb disconnectedEventHandler; +typedef wifi_ap_record_t bss_info; +int scanned_ap_num; #elif defined(ESP8266) #include ESP8266WiFiMulti wifiMulti; +WiFiEventHandler disconnectedEventHandler; +typedef nullptr_t WiFiEventInfo_t; #endif -WiFiEventHandler disconnectedEventHandler; std::vector *connection_queue; JsonDocument doc; -const uint8 *bssid; +const uint8_t *bssid; // keep track of current connection uint currentConfig; @@ -38,7 +42,7 @@ int sortByRSSI(const void *a, const void *b) { bool matches_ssid(const std::vector &networks, JsonVariant config) { for (auto &&network : networks) { - if (memcmp((char *)network->ssid, config["ssid"].as().c_str(), network->ssid_len) == 0) { + if (memcmp((char *)network->ssid, config["ssid"].as().c_str(), strlen((const char *)network->ssid)) == 0) { return true; } } @@ -60,11 +64,23 @@ bool matches_bssid(const std::vector &networks, JsonVariant co return false; } -void disconnectHandler(const WiFiEventStationModeDisconnected &event = {}) { +void startRecoveryAP() { + inRecovery = true; + WiFi.disconnect(); + WiFi.softAP("ESP reached recovery state", NULL); +} + +void disconnectHandler(WiFiEvent_t event, WiFiEventInfo_t info) { if (inRecovery) { return; } + if (connection_queue->size() == 0) { + Log.noticeln("No configurations found to try. Starting recovery sequence"); + startRecoveryAP(); + return; + } + Log.noticeln("In disc-handler"); JsonVariant config = connection_queue->at(currentConfig); if (currentTry < config["retries"]) { @@ -73,9 +89,7 @@ void disconnectHandler(const WiFiEventStationModeDisconnected &event = {}) { } else { if (currentConfig + 1 == connection_queue->size()) { Log.noticeln("Station disconnected. No more configurations to try. Starting recovery sequence"); - inRecovery = true; - WiFi.disconnect(); - WiFi.softAP("ESP reached recovery state", "tempPW12345"); + startRecoveryAP(); return; } else { currentTry = 0; @@ -102,13 +116,22 @@ void disconnectHandler(const WiFiEventStationModeDisconnected &event = {}) { tryStartMillis = millis(); if (!config["hostname"].isNull()) { Log.noticeln("Set hostname %s", config["hostname"].as().c_str()); +#ifdef ESP32 + WiFi.mode(WIFI_MODE_NULL); +#endif WiFi.setHostname(config["hostname"].as().c_str()); } - WiFi.begin(config["ssid"].as(), psk, 0); - Log.noticeln("Set Wifi to ssid: '%s' psk: '%s' bssid: %s", WiFi.SSID().c_str(), WiFi.psk().c_str(), WiFi.BSSIDstr()); + WiFi.begin(config["ssid"].as(), psk, 0, bssid); + Log.noticeln("Set Wifi to ssid: '%s' psk: '%s' bssid: %s", WiFi.SSID().c_str(), WiFi.psk().c_str(), WiFi.BSSIDstr().c_str()); } +#ifdef ESP8266 +void disconnectHandler_ESP8266(const WiFiEventStationModeDisconnected &event = {}) { + disconnectHandler(WiFiEvent_t::WIFI_EVENT_ANY, nullptr); +} +#endif + void getScanResults(int count) { Log.noticeln("Scan done. Found %i radios", count); @@ -116,10 +139,14 @@ void getScanResults(int count) { std::vector networks; networks.resize(count); - uint8 max_ssid_length = 0; + size_t max_ssid_length = 0; for (int i = 0; i < count; i++) { +#ifdef ESP32 + networks.at(i) = (bss_info *)WiFiScanClass::getScanInfoByIndex(i); +#elif defined(ESP8266) networks.at(i) = WiFi.getScanInfoByIndex(i); - max_ssid_length = max(networks.at(i)->ssid_len, max_ssid_length); +#endif + max_ssid_length = max(strlen((const char *)networks.at(i)->ssid), max_ssid_length); } std::sort(networks.begin(), networks.end(), sortByRSSI); @@ -129,7 +156,14 @@ void getScanResults(int count) { for (auto &network : networks) { bssid = network->bssid; snprintf_P(bssid_char, sizeof(bssid_char), "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); - snprintf_P(buffer, sizeof(buffer), "SSID: %-*s, RSSI: %i, BSSID: %s, hidden: %i", max_ssid_length, network->ssid, network->rssi, bssid_char, network->is_hidden); +#ifdef ESP32 + const bool ssid_is_hidden = strlen((const char *)network->ssid) == 0; + const uint8_t channel = network->primary; +#elif defined(ESP8266) + const bool ssid_is_hidden = network->is_hidden; + const uint8_t channel = network->channel; +#endif + snprintf_P(buffer, sizeof(buffer), "SSID: %-*s, RSSI: %i, BSSID: %s, hidden: %i, channel: %i", max_ssid_length, network->ssid, network->rssi, bssid_char, ssid_is_hidden, channel); Log.noticeln("%s", buffer); } @@ -145,7 +179,17 @@ void getScanResults(int count) { Log.noticeln("Adding %s to queue", config["name"].as().c_str()); } } - disconnectHandler(); // start connecting to configurations + +#ifdef ESP32 + disconnectHandler(WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_START, arduino_event_info_t{}); // start connecting to configurations +#endif +#ifdef ESP8266 + disconnectHandler(WiFiEvent_t::WIFI_EVENT_ANY, nullptr); // start connecting to configurations +#endif +} + +void getScanResultsESP32(WiFiEvent_t event, WiFiEventInfo_t info) { + getScanResults(WiFi.scanComplete()); // assume scan always completed since we are in scan complete CB } wl_status_t WiFiManager::tick(unsigned long update_interval = 100) { @@ -198,15 +242,19 @@ bool WiFiManager::loadFromJson(File file) { setupAP = doc["SetupAP"].as(); for (JsonVariant i : doc["WiFiSta"].as()) { - Serial.println(i["name"].as() + i["ssid"].as() + i["psk"].as()); Log.noticeln("Name: %s", i["name"].as().c_str()); Log.noticeln("SSID: %s", i["ssid"].as().c_str()); Log.noticeln("PSK: %s", i["psk"].as().c_str()); } - disconnectedEventHandler = WiFi.onStationModeDisconnected(disconnectHandler); - +#ifdef ESP32 + WiFi.onEvent(disconnectHandler, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); + WiFi.onEvent(getScanResultsESP32, WiFiEvent_t::ARDUINO_EVENT_WIFI_SCAN_DONE); + WiFi.scanNetworks(true, true); +#elif defined(ESP8266) + disconnectedEventHandler = WiFi.onStationModeDisconnected(disconnectHandler_ESP8266); WiFi.scanNetworksAsync(getScanResults, true); +#endif loadConnections(); @@ -216,7 +264,7 @@ bool WiFiManager::loadFromJson(File file) { bool WiFiManager::loadConnections() { WiFi.persistent(false); - WiFi.mode(WIFI_AP_STA); + WiFi.mode(WIFI_STA); IPAddress local_ip, gateway, subnet; local_ip.fromString("10.0.0.1"); @@ -225,8 +273,9 @@ bool WiFiManager::loadConnections() { uint8_t newMACAddress[WL_MAC_ADDR_LENGTH] = {0x32, 0xAE, 0xA4, 0x07, 0x0D, 0x66}; - const bool ap = true; + const bool ap = false; + // TODO: can't set AP channel, can't scan AP channel -> set ap, then start async scan seems to work if (ap) { if (setApMac(newMACAddress)) @@ -243,7 +292,11 @@ bool WiFiManager::loadConnections() { } bool WiFiManager::setApMac(uint8_t mac[WL_MAC_ADDR_LENGTH]) { +#ifdef ESP32 + esp_wifi_set_mac(WIFI_IF_AP, &mac[0]); +#elif defined(ESP8266) wifi_set_macaddr(SOFTAP_IF, &mac[0]); +#endif char charMacAddress[(sizeof(*mac) * 3)]; snprintf_P(charMacAddress, sizeof(charMacAddress), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); diff --git a/lib/WiFiManager/src/WiFiManager.h b/lib/WiFiManager/src/WiFiManager.h index c3a7c6b..7c0409c 100644 --- a/lib/WiFiManager/src/WiFiManager.h +++ b/lib/WiFiManager/src/WiFiManager.h @@ -1,15 +1,22 @@ #ifdef ESP32 -#include +#include #include -#include #elif defined(ESP8266) #include #endif +#ifndef WL_IPV4_LENGTH +#define WL_IPV4_LENGTH 4 +#endif + #ifndef WL_IPV6_LENGTH #define WL_IPV6_LENGTH 16 #endif +#ifndef WL_MAC_ADDR_LENGTH +#define WL_MAC_ADDR_LENGTH 6 +#endif + struct WiFiConnection { char *ssid; char *bssid; diff --git a/platformio.ini b/platformio.ini index 31b146b..4431208 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,6 +15,13 @@ default_envs = nodemcuv2 build_flags = '-D BUILD_VERSION="0.0.1"' -D DEBUG_ESP_PORT=Serial -D NDEBUG extra_scripts = ./extra_scripts/reset_board.py +lib_deps = miguel5612/MQUnifiedsensor @ ^3.0.0 + adafruit/DHT sensor library @ ^1.4.6 + dawidchyrzynski/home-assistant-integration @ ^2.1.0 + arduinogetstarted/ezLED @ ^1.0.1 + https://github.com/gilex-dev/rc-switch.git#SKL-W1B + thijse/ArduinoLog @ ^1.1.1 + bblanchon/ArduinoJson @ ^7.4.2 [env:nodemcuv2] platform = espressif8266 @@ -28,6 +35,28 @@ build_flags = ${common_env_data.build_flags} -D SERIAL_BAUD_RATE=${this.monitor_ extra_scripts = ${common_env_data.extra_scripts} lib_compat_mode = strict lib_ldf_mode = chain -lib_deps = - thijse/ArduinoLog @ ^1.1.1 - bblanchon/ArduinoJson @ ^7.4.2 +lib_deps = ${common_env_data.lib_deps} + +[env:nodemcuv2_ota] +extends = env:nodemcuv2 +upload_protocol = espota +[env:esp32-dev] +upload_port = /dev/ttyUSB0 +framework = arduino +platform = espressif32 +board = esp32dev +upload_speed = 921600 +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +board_build.filesystem = littlefs +build_flags = ${common_env_data.build_flags} -D SERIAL_BAUD_RATE=${this.monitor_speed} -D BUILD_DEBUG=false +extra_scripts = ${common_env_data.extra_scripts} +lib_compat_mode = strict +lib_ldf_mode = chain +lib_deps = ${common_env_data.lib_deps} + hoeken/PsychicHttp @ ^1.2.1 + +[env:esp32c3-mini] +platform = espressif32 +extends = env:esp32dev +board = esp32c3-mini diff --git a/src/main.cpp b/src/main.cpp index 57b1b4a..4ef216a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,32 @@ #include #include +static const char serverCert[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- +-----END CERTIFICATE----- +)EOF"; + +static const char serverKey[] PROGMEM = R"EOF( +-----BEGIN RSA PRIVATE KEY----- +-----END RSA PRIVATE KEY----- +)EOF"; + +#ifdef ESP32 +#include +#include +#include +#ifdef PSY_ENABLE_SSL +PsychicHttpsServer server; +#else +PsychicHttpServer server; +#endif + +#elif defined(ESP8266) +#include +BearSSL::ESP8266WebServerSecure server(443); +BearSSL::ServerSessions serverCache(5); +#endif + #ifndef SERIAL_BAUD_RATE #define SERIAL_BAUD_RATE 115200 #endif @@ -16,6 +42,25 @@ WiFiManager manager; unsigned long currentMillis, oldMillis; void setup() { + WiFi.persistent(false); +#ifdef ESP8266 + server.getServer().setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey)); + + // Cache SSL sessions to accelerate the TLS handshake. + server.getServer().setCache(&serverCache); + + server.on("/", []() { + server.send(200, "text/plain", "Hello from esp8266 over HTTPS!"); + }); + server.begin(); +#elif ESP32 + WiFi.mode(WIFI_MODE_STA); // required by PsychicHttp + server.listen(443, serverCert, serverKey); + server.on("/", [](PsychicRequest *request) { + return request->reply(200, "text/plain", "Hello from esp32 over HTTPS!"); + }); +#endif + Serial.begin(SERIAL_BAUD_RATE); Log.setPrefix(printPrefix); Log.setSuffix(printSuffix); @@ -42,7 +87,6 @@ void setup() { } void loop() { - currentMillis = millis(); if (currentMillis - oldMillis >= 1000) { @@ -52,5 +96,10 @@ void loop() { if (manager.tick(1000) == WL_CONNECTED) { // regularOperationStuff(); } + +#ifdef ESP8266 + esp_yield(); + server.handleClient(); +#endif // importantStuff(); } diff --git a/src/main.h b/src/main.h index e69de29..5367fac 100644 --- a/src/main.h +++ b/src/main.h @@ -0,0 +1,3 @@ +#ifndef WL_MAC_ADDR_LENGTH +#define WL_MAC_ADDR_LENGTH 6 +#endif