mirror of
https://github.com/esphome/esphome.git
synced 2024-11-10 01:07:45 +01:00
Add Captive Portal (#624)
* WIP: Captive Portal * Updates * Updates * Lint * Fixes
This commit is contained in:
parent
8db6f3129c
commit
36f47ade70
38 changed files with 846 additions and 326 deletions
26
esphome/components/captive_portal/__init__.py
Normal file
26
esphome/components/captive_portal/__init__.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import web_server_base
|
||||||
|
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
|
AUTO_LOAD = ['web_server_base']
|
||||||
|
DEPENDENCIES = ['wifi']
|
||||||
|
|
||||||
|
captive_portal_ns = cg.esphome_ns.namespace('captive_portal')
|
||||||
|
CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
|
cv.GenerateID(): cv.declare_id(CaptivePortal),
|
||||||
|
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(web_server_base.WebServerBase),
|
||||||
|
}).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(64.0)
|
||||||
|
def to_code(config):
|
||||||
|
paren = yield cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
||||||
|
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
cg.add_define('USE_CAPTIVE_PORTAL')
|
173
esphome/components/captive_portal/captive_portal.cpp
Normal file
173
esphome/components/captive_portal/captive_portal.cpp
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
#include "captive_portal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/components/wifi/wifi_component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace captive_portal {
|
||||||
|
|
||||||
|
static const char *TAG = "captive_portal";
|
||||||
|
|
||||||
|
void CaptivePortal::handle_index(AsyncWebServerRequest *request) {
|
||||||
|
AsyncResponseStream *stream = request->beginResponseStream("text/html");
|
||||||
|
stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" "
|
||||||
|
"content=\"width=device-width,initial-scale=1,user-scalable=no\"/><title>"));
|
||||||
|
stream->print(App.get_name().c_str());
|
||||||
|
stream->print(F("</title><link rel=\"stylesheet\" href=\"/stylesheet.css\">"));
|
||||||
|
stream->print(F("<script>function c(l){document.getElementById('ssid').value=l.innerText||l.textContent; "
|
||||||
|
"document.getElementById('psk').focus();}</script>"));
|
||||||
|
stream->print(F("</head>"));
|
||||||
|
stream->print(F("<body><div class=\"main\"><h1>WiFi Networks</h1>"));
|
||||||
|
|
||||||
|
if (request->hasArg("save")) {
|
||||||
|
stream->print(F("<div class=\"info\">The ESP will now try to connect to the network...<br/>Please give it some "
|
||||||
|
"time to connect.<br/>Note: Copy the changed network to your YAML file - the next OTA update will "
|
||||||
|
"overwrite these settings.</div>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
|
||||||
|
if (scan.get_is_hidden())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
stream->print(F("<div class=\"network\" onclick=\"c(this)\"><a href=\"#\" class=\"network-left\">"));
|
||||||
|
|
||||||
|
if (scan.get_rssi() >= -50) {
|
||||||
|
stream->print(F("<img src=\"/wifi-strength-4.svg\">"));
|
||||||
|
} else if (scan.get_rssi() >= -65) {
|
||||||
|
stream->print(F("<img src=\"/wifi-strength-3.svg\">"));
|
||||||
|
} else if (scan.get_rssi() >= -85) {
|
||||||
|
stream->print(F("<img src=\"/wifi-strength-2.svg\">"));
|
||||||
|
} else {
|
||||||
|
stream->print(F("<img src=\"/wifi-strength-1.svg\">"));
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->print(F("<span class=\"network-ssid\">"));
|
||||||
|
stream->print(scan.get_ssid().c_str());
|
||||||
|
stream->print(F("</span></a>"));
|
||||||
|
if (scan.get_with_auth()) {
|
||||||
|
stream->print(F("<img src=\"/lock.svg\">"));
|
||||||
|
}
|
||||||
|
stream->print(F("</div>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->print(F("<h3>WiFi Settings</h3><form method=\"GET\" action=\"/wifisave\"><input id=\"ssid\" name=\"ssid\" "
|
||||||
|
"length=32 placeholder=\"SSID\"><br/><input id=\"psk\" name=\"psk\" length=64 type=\"password\" "
|
||||||
|
"placeholder=\"Password\"><br/><br/><button type=\"submit\">Save</button></form><br><hr><br>"));
|
||||||
|
stream->print(F("<h1>OTA Update</h1><form method=\"POST\" action=\"/update\" enctype=\"multipart/form-data\"><input "
|
||||||
|
"type=\"file\" name=\"update\"><button type=\"submit\">Update</button></form>"));
|
||||||
|
stream->print(F("</div></body></html>"));
|
||||||
|
request->send(stream);
|
||||||
|
}
|
||||||
|
void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
|
||||||
|
std::string ssid = request->arg("ssid").c_str();
|
||||||
|
std::string psk = request->arg("psk").c_str();
|
||||||
|
ESP_LOGI(TAG, "Captive Portal Requested WiFi Settings Change:");
|
||||||
|
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
|
||||||
|
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
|
||||||
|
this->override_sta_(ssid, psk);
|
||||||
|
request->redirect("/?save=true");
|
||||||
|
}
|
||||||
|
void CaptivePortal::override_sta_(const std::string &ssid, const std::string &password) {
|
||||||
|
CaptivePortalSettings save{};
|
||||||
|
strcpy(save.ssid, ssid.c_str());
|
||||||
|
strcpy(save.password, password.c_str());
|
||||||
|
this->pref_.save(&save);
|
||||||
|
|
||||||
|
wifi::WiFiAP sta{};
|
||||||
|
sta.set_ssid(ssid);
|
||||||
|
sta.set_password(password);
|
||||||
|
wifi::global_wifi_component->set_sta(sta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptivePortal::setup() {
|
||||||
|
// Hash with compilation time
|
||||||
|
// This ensures the AP override is not applied for OTA
|
||||||
|
uint32_t hash = fnv1_hash(App.get_compilation_time());
|
||||||
|
this->pref_ = global_preferences.make_preference<CaptivePortalSettings>(hash, true);
|
||||||
|
|
||||||
|
CaptivePortalSettings save{};
|
||||||
|
if (this->pref_.load(&save)) {
|
||||||
|
this->override_sta_(save.ssid, save.password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void CaptivePortal::start() {
|
||||||
|
this->base_->init();
|
||||||
|
if (!this->initialized_) {
|
||||||
|
this->base_->add_handler(this);
|
||||||
|
this->base_->add_ota_handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->dns_server_ = new DNSServer();
|
||||||
|
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
|
||||||
|
IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
||||||
|
this->dns_server_->start(53, "*", ip);
|
||||||
|
|
||||||
|
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
|
||||||
|
bool not_found = false;
|
||||||
|
if (!this->active_) {
|
||||||
|
not_found = true;
|
||||||
|
} else if (req->host() == wifi::global_wifi_component->wifi_soft_ap_ip().toString()) {
|
||||||
|
not_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (not_found) {
|
||||||
|
req->send(404, "text/html", "File not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().toString();
|
||||||
|
req->redirect(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
this->initialized_ = true;
|
||||||
|
this->active_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char STYLESHEET_CSS[] PROGMEM =
|
||||||
|
R"(*{box-sizing:inherit}div,input{padding:5px;font-size:1em}input{width:95%}body{text-align:center;font-family:sans-serif}button{border:0;border-radius:.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;padding:0}.main{text-align:left;display:inline-block;min-width:260px}.network{display:flex;justify-content:space-between;align-items:center}.network-left{display:flex;align-items:center}.network-ssid{margin-bottom:-7px;margin-left:10px}.info{border:1px solid;margin:10px 0;padding:15px 10px;color:#4f8a10;background-color:#dff2bf})";
|
||||||
|
const char LOCK_SVG[] PROGMEM =
|
||||||
|
R"(<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path d="M12 17a2 2 0 0 0 2-2 2 2 0 0 0-2-2 2 2 0 0 0-2 2 2 2 0 0 0 2 2m6-9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2h1V6a5 5 0 0 1 5-5 5 5 0 0 1 5 5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3z"/></svg>)";
|
||||||
|
|
||||||
|
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
|
||||||
|
if (req->url() == "/") {
|
||||||
|
this->handle_index(req);
|
||||||
|
return;
|
||||||
|
} else if (req->url() == "/wifisave") {
|
||||||
|
this->handle_wifisave(req);
|
||||||
|
return;
|
||||||
|
} else if (req->url() == "/stylesheet.css") {
|
||||||
|
req->send_P(200, "text/css", STYLESHEET_CSS);
|
||||||
|
return;
|
||||||
|
} else if (req->url() == "/lock.svg") {
|
||||||
|
req->send_P(200, "image/svg+xml", LOCK_SVG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncResponseStream *stream = req->beginResponseStream("image/svg+xml");
|
||||||
|
stream->print(F("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\"><path d=\"M12 3A18.9 18.9 0 0 "
|
||||||
|
"0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 "));
|
||||||
|
if (req->url() == "/wifi-strength-4.svg") {
|
||||||
|
stream->print(F("3z"));
|
||||||
|
} else {
|
||||||
|
if (req->url() == "/wifi-strength-1.svg") {
|
||||||
|
stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-5.1 6.36a8.43 8.43 0 0 0-7.22-.01L3.27 7.4"));
|
||||||
|
} else if (req->url() == "/wifi-strength-2.svg") {
|
||||||
|
stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-3.21 3.98a11.32 11.32 0 0 0-11 0L3.27 7.4"));
|
||||||
|
} else if (req->url() == "/wifi-strength-3.svg") {
|
||||||
|
stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-1.94 2.43A13.6 13.6 0 0 0 12 8C9 8 6.68 9 5.21 9.84l-1.94-2."));
|
||||||
|
}
|
||||||
|
stream->print(F("4A16.94 16.94 0 0 1 12 5z"));
|
||||||
|
}
|
||||||
|
stream->print(F("\"/></svg>"));
|
||||||
|
req->send(stream);
|
||||||
|
}
|
||||||
|
CaptivePortal::CaptivePortal(web_server_base::WebServerBase *base) : base_(base) { global_captive_portal = this; }
|
||||||
|
float CaptivePortal::get_setup_priority() const {
|
||||||
|
// Before WiFi
|
||||||
|
return setup_priority::WIFI + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptivePortal *global_captive_portal = nullptr;
|
||||||
|
|
||||||
|
} // namespace captive_portal
|
||||||
|
} // namespace esphome
|
80
esphome/components/captive_portal/captive_portal.h
Normal file
80
esphome/components/captive_portal/captive_portal.h
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <DNSServer.h>
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
#include "esphome/components/web_server_base/web_server_base.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
|
||||||
|
namespace captive_portal {
|
||||||
|
|
||||||
|
struct CaptivePortalSettings {
|
||||||
|
char ssid[33];
|
||||||
|
char password[65];
|
||||||
|
} PACKED; // NOLINT
|
||||||
|
|
||||||
|
class CaptivePortal : public AsyncWebHandler, public Component {
|
||||||
|
public:
|
||||||
|
CaptivePortal(web_server_base::WebServerBase *base);
|
||||||
|
void setup() override;
|
||||||
|
void loop() override {
|
||||||
|
if (this->dns_server_ != nullptr)
|
||||||
|
this->dns_server_->processNextRequest();
|
||||||
|
}
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void start();
|
||||||
|
bool is_active() const { return this->active_; }
|
||||||
|
void end() {
|
||||||
|
this->active_ = false;
|
||||||
|
this->base_->deinit();
|
||||||
|
this->dns_server_->stop();
|
||||||
|
delete this->dns_server_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canHandle(AsyncWebServerRequest *request) override {
|
||||||
|
if (!this->active_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (request->method() == HTTP_GET) {
|
||||||
|
if (request->url() == "/")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/stylesheet.css")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/wifi-strength-1.svg")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/wifi-strength-2.svg")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/wifi-strength-3.svg")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/wifi-strength-4.svg")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/lock.svg")
|
||||||
|
return true;
|
||||||
|
if (request->url() == "/wifisave")
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_index(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
void handle_wifisave(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
void handleRequest(AsyncWebServerRequest *req) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void override_sta_(const std::string &ssid, const std::string &password);
|
||||||
|
|
||||||
|
web_server_base::WebServerBase *base_;
|
||||||
|
bool initialized_{false};
|
||||||
|
bool active_{false};
|
||||||
|
ESPPreferenceObject pref_;
|
||||||
|
DNSServer *dns_server_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
extern CaptivePortal *global_captive_portal;
|
||||||
|
|
||||||
|
} // namespace captive_portal
|
||||||
|
} // namespace esphome
|
55
esphome/components/captive_portal/index.html
Normal file
55
esphome/components/captive_portal/index.html
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<!-- HTTP_HEAD -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
|
||||||
|
<title>{{ App.get_name() }}</title>
|
||||||
|
<link rel="stylesheet" href="./stylesheet.css">
|
||||||
|
<script>
|
||||||
|
function c(l) {
|
||||||
|
document.getElementById('ssid').value = l.innerText || l.textContent;
|
||||||
|
document.getElementById('psk').focus();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="main">
|
||||||
|
<h1>WiFi Networks</h1>
|
||||||
|
<div class="info">
|
||||||
|
The ESP will now try to connect to the network...<br/>
|
||||||
|
Please give it some time to connect.<br/>
|
||||||
|
Note: Copy the changed network to your YAML file - the next OTA update will overwrite these settings.
|
||||||
|
</div>
|
||||||
|
<div class="network" onclick="c(this)">
|
||||||
|
<a href="#" class="network-left">
|
||||||
|
<img src="./wifi-strength-4.svg">
|
||||||
|
<span class="network-ssid">AP1</span>
|
||||||
|
</a>
|
||||||
|
<img src="./lock.svg">
|
||||||
|
</div>
|
||||||
|
<div class="network" onclick="c(this)">
|
||||||
|
<a href="#" class="network-left">
|
||||||
|
<img src="./wifi-strength-2.svg">
|
||||||
|
<span class="network-ssid">AP2</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>WiFi Settings</h3>
|
||||||
|
<form method="GET" action="/wifisave">
|
||||||
|
<input id="ssid" name="ssid" length=32 placeholder="SSID"><br/>
|
||||||
|
<input id="psk" name="psk" length=64 type="password" placeholder="Password"><br/>
|
||||||
|
<br/>
|
||||||
|
<button type="submit">Save</button>
|
||||||
|
</form>
|
||||||
|
<br><hr>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<h1>OTA Update</h1>
|
||||||
|
<form method="POST" action="/update" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="update">
|
||||||
|
<button type="submit">Update</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
esphome/components/captive_portal/lock.svg
Normal file
1
esphome/components/captive_portal/lock.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path d="M12 17a2 2 0 0 0 2-2 2 2 0 0 0-2-2 2 2 0 0 0-2 2 2 2 0 0 0 2 2m6-9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2h1V6a5 5 0 0 1 5-5 5 5 0 0 1 5 5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3z"/></svg>
|
After Width: | Height: | Size: 307 B |
58
esphome/components/captive_portal/stylesheet.css
Normal file
58
esphome/components/captive_portal/stylesheet.css
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
* {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
div, input {
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
text-align: center;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
background-color: #1fa3ec;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 2.4rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
text-align: left;
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-ssid {
|
||||||
|
margin-bottom: -7px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
border: 1px solid;
|
||||||
|
margin: 10px 0px;
|
||||||
|
padding: 15px 10px;
|
||||||
|
color: #4f8a10;
|
||||||
|
background-color: #dff2bf;
|
||||||
|
}
|
1
esphome/components/captive_portal/wifi-strength-1.svg
Normal file
1
esphome/components/captive_portal/wifi-strength-1.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-5.1 6.36a8.43 8.43 0 0 0-7.22-.01L3.27 7.44A16.94 16.94 0 0 1 12 5z"/></svg>
|
After Width: | Height: | Size: 268 B |
1
esphome/components/captive_portal/wifi-strength-2.svg
Normal file
1
esphome/components/captive_portal/wifi-strength-2.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-3.21 3.98a11.32 11.32 0 0 0-11 0L3.27 7.44A16.94 16.94 0 0 1 12 5z"/></svg>
|
After Width: | Height: | Size: 267 B |
1
esphome/components/captive_portal/wifi-strength-3.svg
Normal file
1
esphome/components/captive_portal/wifi-strength-3.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-1.94 2.43A13.6 13.6 0 0 0 12 8C9 8 6.68 9 5.21 9.84l-1.94-2.4A16.94 16.94 0 0 1 12 5z"/></svg>
|
After Width: | Height: | Size: 286 B |
1
esphome/components/captive_portal/wifi-strength-4.svg
Normal file
1
esphome/components/captive_portal/wifi-strength-4.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M12 3A18.9 18.9 0 0 0 .38 7C4.41 12.06 7.89 16.37 12 21.5L23.65 7C20.32 4.41 16.22 3 12 3z"/></svg>
|
After Width: | Height: | Size: 171 B |
|
@ -162,7 +162,6 @@ int32_t HOT interpret_index(int32_t index, int32_t size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressableLight::call_setup() {
|
void AddressableLight::call_setup() {
|
||||||
this->setup_internal_();
|
|
||||||
this->setup();
|
this->setup();
|
||||||
|
|
||||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||||
|
|
|
@ -149,9 +149,6 @@ void MQTTComponent::set_availability(std::string topic, std::string payload_avai
|
||||||
}
|
}
|
||||||
void MQTTComponent::disable_availability() { this->set_availability("", "", ""); }
|
void MQTTComponent::disable_availability() { this->set_availability("", "", ""); }
|
||||||
void MQTTComponent::call_setup() {
|
void MQTTComponent::call_setup() {
|
||||||
// Call component internal setup.
|
|
||||||
this->setup_internal_();
|
|
||||||
|
|
||||||
if (this->is_internal())
|
if (this->is_internal())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -173,8 +170,6 @@ void MQTTComponent::call_setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MQTTComponent::call_loop() {
|
void MQTTComponent::call_loop() {
|
||||||
this->loop_internal_();
|
|
||||||
|
|
||||||
if (this->is_internal())
|
if (this->is_internal())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ def to_code(config):
|
||||||
cg.add(var.set_port(config[CONF_PORT]))
|
cg.add(var.set_port(config[CONF_PORT]))
|
||||||
cg.add(var.set_auth_password(config[CONF_PASSWORD]))
|
cg.add(var.set_auth_password(config[CONF_PASSWORD]))
|
||||||
|
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
|
||||||
if config[CONF_SAFE_MODE]:
|
if config[CONF_SAFE_MODE]:
|
||||||
cg.add(var.start_safe_mode())
|
cg.add(var.start_safe_mode())
|
||||||
|
|
||||||
|
@ -29,6 +31,3 @@ def to_code(config):
|
||||||
cg.add_library('Update', None)
|
cg.add_library('Update', None)
|
||||||
elif CORE.is_esp32:
|
elif CORE.is_esp32:
|
||||||
cg.add_library('Hash', None)
|
cg.add_library('Hash', None)
|
||||||
|
|
||||||
# Register at end for safe mode
|
|
||||||
yield cg.register_component(var, config)
|
|
||||||
|
|
|
@ -358,7 +358,7 @@ void OTAComponent::start_safe_mode(uint8_t num_attempts, uint32_t enable_time) {
|
||||||
this->safe_mode_start_time_ = millis();
|
this->safe_mode_start_time_ = millis();
|
||||||
this->safe_mode_enable_time_ = enable_time;
|
this->safe_mode_enable_time_ = enable_time;
|
||||||
this->safe_mode_num_attempts_ = num_attempts;
|
this->safe_mode_num_attempts_ = num_attempts;
|
||||||
this->rtc_ = global_preferences.make_preference<uint32_t>(233825507UL);
|
this->rtc_ = global_preferences.make_preference<uint32_t>(233825507UL, false);
|
||||||
this->safe_mode_rtc_value_ = this->read_rtc_();
|
this->safe_mode_rtc_value_ = this->read_rtc_();
|
||||||
|
|
||||||
ESP_LOGCONFIG(TAG, "There have been %u suspected unsuccessful boot attempts.", this->safe_mode_rtc_value_);
|
ESP_LOGCONFIG(TAG, "There have been %u suspected unsuccessful boot attempts.", this->safe_mode_rtc_value_);
|
||||||
|
@ -369,19 +369,18 @@ void OTAComponent::start_safe_mode(uint8_t num_attempts, uint32_t enable_time) {
|
||||||
ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode.");
|
ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode.");
|
||||||
|
|
||||||
this->status_set_error();
|
this->status_set_error();
|
||||||
network_setup();
|
this->set_timeout(enable_time, []() {
|
||||||
this->call_setup();
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Waiting for OTA attempt.");
|
|
||||||
uint32_t begin = millis();
|
|
||||||
while ((millis() - begin) < enable_time) {
|
|
||||||
this->call_loop();
|
|
||||||
network_tick();
|
|
||||||
App.feed_wdt();
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
ESP_LOGE(TAG, "No OTA attempt made, restarting.");
|
ESP_LOGE(TAG, "No OTA attempt made, restarting.");
|
||||||
App.reboot();
|
App.reboot();
|
||||||
|
});
|
||||||
|
|
||||||
|
App.setup();
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Waiting for OTA attempt.");
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
App.loop();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// increment counter
|
// increment counter
|
||||||
this->write_rtc_(this->safe_mode_rtc_value_ + 1);
|
this->write_rtc_(this->safe_mode_rtc_value_ + 1);
|
||||||
|
|
|
@ -12,7 +12,6 @@ static const char *TAG = "time";
|
||||||
|
|
||||||
RealTimeClock::RealTimeClock() = default;
|
RealTimeClock::RealTimeClock() = default;
|
||||||
void RealTimeClock::call_setup() {
|
void RealTimeClock::call_setup() {
|
||||||
this->setup_internal_();
|
|
||||||
setenv("TZ", this->timezone_.c_str(), 1);
|
setenv("TZ", this->timezone_.c_str(), 1);
|
||||||
tzset();
|
tzset();
|
||||||
this->setup();
|
this->setup();
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import web_server_base
|
||||||
|
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
|
||||||
from esphome.const import CONF_CSS_URL, CONF_ID, CONF_JS_URL, CONF_PORT
|
from esphome.const import CONF_CSS_URL, CONF_ID, CONF_JS_URL, CONF_PORT
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
DEPENDENCIES = ['network']
|
AUTO_LOAD = ['json', 'web_server_base']
|
||||||
AUTO_LOAD = ['json']
|
|
||||||
|
|
||||||
web_server_ns = cg.esphome_ns.namespace('web_server')
|
web_server_ns = cg.esphome_ns.namespace('web_server')
|
||||||
WebServer = web_server_ns.class_('WebServer', cg.Component, cg.Controller)
|
WebServer = web_server_ns.class_('WebServer', cg.Component, cg.Controller)
|
||||||
|
@ -14,18 +15,18 @@ CONFIG_SCHEMA = cv.Schema({
|
||||||
cv.Optional(CONF_PORT, default=80): cv.port,
|
cv.Optional(CONF_PORT, default=80): cv.port,
|
||||||
cv.Optional(CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css"): cv.string,
|
cv.Optional(CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css"): cv.string,
|
||||||
cv.Optional(CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js"): cv.string,
|
cv.Optional(CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js"): cv.string,
|
||||||
|
|
||||||
|
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(web_server_base.WebServerBase),
|
||||||
}).extend(cv.COMPONENT_SCHEMA)
|
}).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(40.0)
|
@coroutine_with_priority(40.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
paren = yield cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
||||||
|
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||||
yield cg.register_component(var, config)
|
yield cg.register_component(var, config)
|
||||||
|
|
||||||
cg.add(var.set_port(config[CONF_PORT]))
|
cg.add(paren.set_port(config[CONF_PORT]))
|
||||||
cg.add(var.set_css_url(config[CONF_CSS_URL]))
|
cg.add(var.set_css_url(config[CONF_CSS_URL]))
|
||||||
cg.add(var.set_js_url(config[CONF_JS_URL]))
|
cg.add(var.set_js_url(config[CONF_JS_URL]))
|
||||||
|
|
||||||
if CORE.is_esp32:
|
|
||||||
cg.add_library('FS', None)
|
|
||||||
cg.add_library('ESP Async WebServer', '1.1.1')
|
|
||||||
|
|
|
@ -6,13 +6,6 @@
|
||||||
|
|
||||||
#include "StreamString.h"
|
#include "StreamString.h"
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
#include <Update.h>
|
|
||||||
#endif
|
|
||||||
#ifdef ARDUINO_ARCH_ESP8266
|
|
||||||
#include <Updater.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <esphome/components/logger/logger.h>
|
#include <esphome/components/logger/logger.h>
|
||||||
|
|
||||||
|
@ -66,7 +59,7 @@ void WebServer::set_js_url(const char *js_url) { this->js_url_ = js_url; }
|
||||||
|
|
||||||
void WebServer::setup() {
|
void WebServer::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up web server...");
|
ESP_LOGCONFIG(TAG, "Setting up web server...");
|
||||||
this->server_ = new AsyncWebServer(this->port_);
|
this->base_->init();
|
||||||
|
|
||||||
this->events_.onConnect([this](AsyncEventSourceClient *client) {
|
this->events_.onConnect([this](AsyncEventSourceClient *client) {
|
||||||
// Configure reconnect timeout
|
// Configure reconnect timeout
|
||||||
|
@ -114,91 +107,18 @@ void WebServer::setup() {
|
||||||
logger::global_logger->add_on_log_callback(
|
logger::global_logger->add_on_log_callback(
|
||||||
[this](int level, const char *tag, const char *message) { this->events_.send(message, "log", millis()); });
|
[this](int level, const char *tag, const char *message) { this->events_.send(message, "log", millis()); });
|
||||||
#endif
|
#endif
|
||||||
this->server_->addHandler(this);
|
this->base_->add_handler(&this->events_);
|
||||||
this->server_->addHandler(&this->events_);
|
this->base_->add_handler(this);
|
||||||
|
this->base_->add_ota_handler();
|
||||||
this->server_->begin();
|
|
||||||
|
|
||||||
this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); });
|
this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); });
|
||||||
}
|
}
|
||||||
void WebServer::dump_config() {
|
void WebServer::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "Web Server:");
|
ESP_LOGCONFIG(TAG, "Web Server:");
|
||||||
ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_);
|
ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->base_->get_port());
|
||||||
}
|
}
|
||||||
float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; }
|
float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; }
|
||||||
|
|
||||||
void WebServer::handle_update_request(AsyncWebServerRequest *request) {
|
|
||||||
AsyncWebServerResponse *response;
|
|
||||||
if (!Update.hasError()) {
|
|
||||||
response = request->beginResponse(200, "text/plain", "Update Successful!");
|
|
||||||
} else {
|
|
||||||
StreamString ss;
|
|
||||||
ss.print("Update Failed: ");
|
|
||||||
Update.printError(ss);
|
|
||||||
response = request->beginResponse(200, "text/plain", ss);
|
|
||||||
}
|
|
||||||
response->addHeader("Connection", "close");
|
|
||||||
request->send(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void report_ota_error() {
|
|
||||||
StreamString ss;
|
|
||||||
Update.printError(ss);
|
|
||||||
ESP_LOGW(TAG, "OTA Update failed! Error: %s", ss.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data,
|
|
||||||
size_t len, bool final) {
|
|
||||||
bool success;
|
|
||||||
if (index == 0) {
|
|
||||||
ESP_LOGI(TAG, "OTA Update Start: %s", filename.c_str());
|
|
||||||
this->ota_read_length_ = 0;
|
|
||||||
#ifdef ARDUINO_ARCH_ESP8266
|
|
||||||
Update.runAsync(true);
|
|
||||||
success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
|
||||||
#endif
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
if (Update.isRunning())
|
|
||||||
Update.abort();
|
|
||||||
success = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH);
|
|
||||||
#endif
|
|
||||||
if (!success) {
|
|
||||||
report_ota_error();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (Update.hasError()) {
|
|
||||||
// don't spam logs with errors if something failed at start
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
success = Update.write(data, len) == len;
|
|
||||||
if (!success) {
|
|
||||||
report_ota_error();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->ota_read_length_ += len;
|
|
||||||
|
|
||||||
const uint32_t now = millis();
|
|
||||||
if (now - this->last_ota_progress_ > 1000) {
|
|
||||||
if (request->contentLength() != 0) {
|
|
||||||
float percentage = (this->ota_read_length_ * 100.0f) / request->contentLength();
|
|
||||||
ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage);
|
|
||||||
} else {
|
|
||||||
ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_);
|
|
||||||
}
|
|
||||||
this->last_ota_progress_ = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (final) {
|
|
||||||
if (Update.end(true)) {
|
|
||||||
ESP_LOGI(TAG, "OTA update successful!");
|
|
||||||
this->set_timeout(100, []() { App.safe_reboot(); });
|
|
||||||
} else {
|
|
||||||
report_ota_error();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||||
AsyncResponseStream *stream = request->beginResponseStream("text/html");
|
AsyncResponseStream *stream = request->beginResponseStream("text/html");
|
||||||
std::string title = App.get_name() + " Web Server";
|
std::string title = App.get_name() + " Web Server";
|
||||||
|
@ -248,7 +168,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||||
|
|
||||||
stream->print(F("</tbody></table><p>See <a href=\"https://esphome.io/web-api/index.html\">ESPHome Web API</a> for "
|
stream->print(F("</tbody></table><p>See <a href=\"https://esphome.io/web-api/index.html\">ESPHome Web API</a> for "
|
||||||
"REST API documentation.</p>"
|
"REST API documentation.</p>"
|
||||||
"<h2>OTA Update</h2><form method='POST' action=\"/update\" enctype=\"multipart/form-data\"><input "
|
"<h2>OTA Update</h2><form method=\"POST\" action=\"/update\" enctype=\"multipart/form-data\"><input "
|
||||||
"type=\"file\" name=\"update\"><input type=\"submit\" value=\"Update\"></form>"
|
"type=\"file\" name=\"update\"><input type=\"submit\" value=\"Update\"></form>"
|
||||||
"<h2>Debug Log</h2><pre id=\"log\"></pre>"
|
"<h2>Debug Log</h2><pre id=\"log\"></pre>"
|
||||||
"<script src=\""));
|
"<script src=\""));
|
||||||
|
@ -531,9 +451,6 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
|
||||||
if (request->url() == "/")
|
if (request->url() == "/")
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (request->url() == "/update" && request->method() == HTTP_POST)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
UrlMatch match = match_url(request->url().c_str(), true);
|
UrlMatch match = match_url(request->url().c_str(), true);
|
||||||
if (!match.valid)
|
if (!match.valid)
|
||||||
return false;
|
return false;
|
||||||
|
@ -575,11 +492,6 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request->url() == "/update") {
|
|
||||||
this->handle_update_request(request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UrlMatch match = match_url(request->url().c_str());
|
UrlMatch match = match_url(request->url().c_str());
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
if (match.domain == "sensor") {
|
if (match.domain == "sensor") {
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/controller.h"
|
#include "esphome/core/controller.h"
|
||||||
|
#include "esphome/components/web_server_base/web_server_base.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace web_server {
|
namespace web_server {
|
||||||
|
@ -28,8 +28,7 @@ struct UrlMatch {
|
||||||
*/
|
*/
|
||||||
class WebServer : public Controller, public Component, public AsyncWebHandler {
|
class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||||
public:
|
public:
|
||||||
void set_port(uint16_t port) { port_ = port; }
|
WebServer(web_server_base::WebServerBase *base) : base_(base) {}
|
||||||
|
|
||||||
/** Set the URL to the CSS <link> that's sent to each client. Defaults to
|
/** Set the URL to the CSS <link> that's sent to each client. Defaults to
|
||||||
* https://esphome.io/_static/webserver-v1.min.css
|
* https://esphome.io/_static/webserver-v1.min.css
|
||||||
*
|
*
|
||||||
|
@ -57,8 +56,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||||
/// Handle an index request under '/'.
|
/// Handle an index request under '/'.
|
||||||
void handle_index_request(AsyncWebServerRequest *request);
|
void handle_index_request(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
void handle_update_request(AsyncWebServerRequest *request);
|
|
||||||
|
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
void on_sensor_update(sensor::Sensor *obj, float state) override;
|
void on_sensor_update(sensor::Sensor *obj, float state) override;
|
||||||
/// Handle a sensor request under '/sensor/<id>'.
|
/// Handle a sensor request under '/sensor/<id>'.
|
||||||
|
@ -122,19 +119,14 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||||
bool canHandle(AsyncWebServerRequest *request) override;
|
bool canHandle(AsyncWebServerRequest *request) override;
|
||||||
/// Override the web handler's handleRequest method.
|
/// Override the web handler's handleRequest method.
|
||||||
void handleRequest(AsyncWebServerRequest *request) override;
|
void handleRequest(AsyncWebServerRequest *request) override;
|
||||||
void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len,
|
|
||||||
bool final) override;
|
|
||||||
/// This web handle is not trivial.
|
/// This web handle is not trivial.
|
||||||
bool isRequestHandlerTrivial() override;
|
bool isRequestHandlerTrivial() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint16_t port_;
|
web_server_base::WebServerBase *base_;
|
||||||
AsyncWebServer *server_;
|
|
||||||
AsyncEventSource events_{"/events"};
|
AsyncEventSource events_{"/events"};
|
||||||
const char *css_url_{nullptr};
|
const char *css_url_{nullptr};
|
||||||
const char *js_url_{nullptr};
|
const char *js_url_{nullptr};
|
||||||
uint32_t last_ota_progress_{0};
|
|
||||||
uint32_t ota_read_length_{0};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace web_server
|
} // namespace web_server
|
||||||
|
|
24
esphome/components/web_server_base/__init__.py
Normal file
24
esphome/components/web_server_base/__init__.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
from esphome.core import coroutine_with_priority, CORE
|
||||||
|
|
||||||
|
DEPENDENCIES = ['network']
|
||||||
|
|
||||||
|
web_server_base_ns = cg.esphome_ns.namespace('web_server_base')
|
||||||
|
WebServerBase = web_server_base_ns.class_('WebServerBase', cg.Component)
|
||||||
|
|
||||||
|
CONF_WEB_SERVER_BASE_ID = 'web_server_base_id'
|
||||||
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
|
cv.GenerateID(): cv.declare_id(WebServerBase),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(65.0)
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
|
||||||
|
if CORE.is_esp32:
|
||||||
|
cg.add_library('FS', None)
|
||||||
|
cg.add_library('ESP Async WebServer', '1.1.1')
|
96
esphome/components/web_server_base/web_server_base.cpp
Normal file
96
esphome/components/web_server_base/web_server_base.cpp
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
#include "web_server_base.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include <StreamString.h>
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
#include <Update.h>
|
||||||
|
#endif
|
||||||
|
#ifdef ARDUINO_ARCH_ESP8266
|
||||||
|
#include <Updater.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace web_server_base {
|
||||||
|
|
||||||
|
static const char *TAG = "web_server_base";
|
||||||
|
|
||||||
|
void report_ota_error() {
|
||||||
|
StreamString ss;
|
||||||
|
Update.printError(ss);
|
||||||
|
ESP_LOGW(TAG, "OTA Update failed! Error: %s", ss.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index,
|
||||||
|
uint8_t *data, size_t len, bool final) {
|
||||||
|
bool success;
|
||||||
|
if (index == 0) {
|
||||||
|
ESP_LOGI(TAG, "OTA Update Start: %s", filename.c_str());
|
||||||
|
this->ota_read_length_ = 0;
|
||||||
|
#ifdef ARDUINO_ARCH_ESP8266
|
||||||
|
Update.runAsync(true);
|
||||||
|
success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
||||||
|
#endif
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
if (Update.isRunning())
|
||||||
|
Update.abort();
|
||||||
|
success = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH);
|
||||||
|
#endif
|
||||||
|
if (!success) {
|
||||||
|
report_ota_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (Update.hasError()) {
|
||||||
|
// don't spam logs with errors if something failed at start
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
success = Update.write(data, len) == len;
|
||||||
|
if (!success) {
|
||||||
|
report_ota_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->ota_read_length_ += len;
|
||||||
|
|
||||||
|
const uint32_t now = millis();
|
||||||
|
if (now - this->last_ota_progress_ > 1000) {
|
||||||
|
if (request->contentLength() != 0) {
|
||||||
|
float percentage = (this->ota_read_length_ * 100.0f) / request->contentLength();
|
||||||
|
ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage);
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_);
|
||||||
|
}
|
||||||
|
this->last_ota_progress_ = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (final) {
|
||||||
|
if (Update.end(true)) {
|
||||||
|
ESP_LOGI(TAG, "OTA update successful!");
|
||||||
|
this->parent_->set_timeout(100, []() { App.safe_reboot(); });
|
||||||
|
} else {
|
||||||
|
report_ota_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||||
|
AsyncWebServerResponse *response;
|
||||||
|
if (!Update.hasError()) {
|
||||||
|
response = request->beginResponse(200, "text/plain", "Update Successful!");
|
||||||
|
} else {
|
||||||
|
StreamString ss;
|
||||||
|
ss.print("Update Failed: ");
|
||||||
|
Update.printError(ss);
|
||||||
|
response = request->beginResponse(200, "text/plain", ss);
|
||||||
|
}
|
||||||
|
response->addHeader("Connection", "close");
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerBase::add_ota_handler() { this->add_handler(new OTARequestHandler(this)); }
|
||||||
|
float WebServerBase::get_setup_priority() const {
|
||||||
|
// Before WiFi (captive portal)
|
||||||
|
return setup_priority::WIFI + 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace web_server_base
|
||||||
|
} // namespace esphome
|
76
esphome/components/web_server_base/web_server_base.h
Normal file
76
esphome/components/web_server_base/web_server_base.h
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace web_server_base {
|
||||||
|
|
||||||
|
class WebServerBase : public Component {
|
||||||
|
public:
|
||||||
|
void init() {
|
||||||
|
if (this->initialized_) {
|
||||||
|
this->initialized_++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->server_ = new AsyncWebServer(this->port_);
|
||||||
|
this->server_->begin();
|
||||||
|
|
||||||
|
for (auto *handler : this->handlers_)
|
||||||
|
this->server_->addHandler(handler);
|
||||||
|
|
||||||
|
this->initialized_++;
|
||||||
|
}
|
||||||
|
void deinit() {
|
||||||
|
this->initialized_--;
|
||||||
|
if (this->initialized_ == 0) {
|
||||||
|
delete this->server_;
|
||||||
|
this->server_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AsyncWebServer *get_server() const { return server_; }
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
|
void add_handler(AsyncWebHandler *handler) {
|
||||||
|
// remove all handlers
|
||||||
|
|
||||||
|
this->handlers_.push_back(handler);
|
||||||
|
if (this->server_ != nullptr)
|
||||||
|
this->server_->addHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_ota_handler();
|
||||||
|
|
||||||
|
void set_port(uint16_t port) { port_ = port; }
|
||||||
|
uint16_t get_port() const { return port_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend class OTARequestHandler;
|
||||||
|
|
||||||
|
int initialized_{0};
|
||||||
|
uint16_t port_{80};
|
||||||
|
AsyncWebServer *server_{nullptr};
|
||||||
|
std::vector<AsyncWebHandler *> handlers_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OTARequestHandler : public AsyncWebHandler {
|
||||||
|
public:
|
||||||
|
OTARequestHandler(WebServerBase *parent) : parent_(parent) {}
|
||||||
|
void handleRequest(AsyncWebServerRequest *request) override;
|
||||||
|
void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len,
|
||||||
|
bool final) override;
|
||||||
|
bool canHandle(AsyncWebServerRequest *request) override {
|
||||||
|
return request->url() == "/update" && request->method() == HTTP_POST;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isRequestHandlerTrivial() override { return false; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t last_ota_progress_{0};
|
||||||
|
uint32_t ota_read_length_{0};
|
||||||
|
WebServerBase *parent_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace web_server_base
|
||||||
|
} // namespace esphome
|
|
@ -64,8 +64,9 @@ WIFI_NETWORK_BASE = cv.Schema({
|
||||||
cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA,
|
cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
CONF_AP_TIMEOUT = 'ap_timeout'
|
||||||
WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend({
|
WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend({
|
||||||
|
cv.Optional(CONF_AP_TIMEOUT, default='1min'): cv.positive_time_period_milliseconds,
|
||||||
})
|
})
|
||||||
|
|
||||||
WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend({
|
WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend({
|
||||||
|
@ -118,7 +119,7 @@ CONFIG_SCHEMA = cv.All(cv.Schema({
|
||||||
|
|
||||||
cv.Optional(CONF_AP): WIFI_NETWORK_AP,
|
cv.Optional(CONF_AP): WIFI_NETWORK_AP,
|
||||||
cv.Optional(CONF_DOMAIN, default='.local'): cv.domain_name,
|
cv.Optional(CONF_DOMAIN, default='.local'): cv.domain_name,
|
||||||
cv.Optional(CONF_REBOOT_TIMEOUT, default='5min'): cv.positive_time_period_milliseconds,
|
cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds,
|
||||||
cv.Optional(CONF_POWER_SAVE_MODE, default='NONE'): cv.enum(WIFI_POWER_SAVE_MODES, upper=True),
|
cv.Optional(CONF_POWER_SAVE_MODE, default='NONE'): cv.enum(WIFI_POWER_SAVE_MODES, upper=True),
|
||||||
cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean,
|
cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||||
|
@ -166,19 +167,20 @@ def wifi_network(config, static_ip):
|
||||||
|
|
||||||
@coroutine_with_priority(60.0)
|
@coroutine_with_priority(60.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
rhs = WiFiComponent.new()
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
wifi = cg.Pvariable(config[CONF_ID], rhs)
|
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||||
cg.add(wifi.set_use_address(config[CONF_USE_ADDRESS]))
|
|
||||||
|
|
||||||
for network in config.get(CONF_NETWORKS, []):
|
for network in config.get(CONF_NETWORKS, []):
|
||||||
cg.add(wifi.add_sta(wifi_network(network, config.get(CONF_MANUAL_IP))))
|
cg.add(var.add_sta(wifi_network(network, config.get(CONF_MANUAL_IP))))
|
||||||
|
|
||||||
if CONF_AP in config:
|
if CONF_AP in config:
|
||||||
cg.add(wifi.set_ap(wifi_network(config[CONF_AP], config.get(CONF_MANUAL_IP))))
|
conf = config[CONF_AP]
|
||||||
|
cg.add(var.set_ap(wifi_network(conf, config.get(CONF_MANUAL_IP))))
|
||||||
|
cg.add(var.set_ap_timeout(conf[CONF_AP_TIMEOUT]))
|
||||||
|
|
||||||
cg.add(wifi.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||||
cg.add(wifi.set_power_save_mode(config[CONF_POWER_SAVE_MODE]))
|
cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE]))
|
||||||
cg.add(wifi.set_fast_connect(config[CONF_FAST_CONNECT]))
|
cg.add(var.set_fast_connect(config[CONF_FAST_CONNECT]))
|
||||||
|
|
||||||
if CORE.is_esp8266:
|
if CORE.is_esp8266:
|
||||||
cg.add_library('ESP8266WiFi', None)
|
cg.add_library('ESP8266WiFi', None)
|
||||||
|
@ -186,7 +188,7 @@ def to_code(config):
|
||||||
cg.add_define('USE_WIFI')
|
cg.add_define('USE_WIFI')
|
||||||
|
|
||||||
# Register at end for OTA safe mode
|
# Register at end for OTA safe mode
|
||||||
yield cg.register_component(wifi, config)
|
yield cg.register_component(var, config)
|
||||||
|
|
||||||
|
|
||||||
@automation.register_condition('wifi.connected', WiFiConnectedCondition, cv.Schema({}))
|
@automation.register_condition('wifi.connected', WiFiConnectedCondition, cv.Schema({}))
|
||||||
|
|
|
@ -18,6 +18,10 @@
|
||||||
#include "esphome/core/util.h"
|
#include "esphome/core/util.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
|
#ifdef USE_CAPTIVE_PORTAL
|
||||||
|
#include "esphome/components/captive_portal/captive_portal.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace wifi {
|
namespace wifi {
|
||||||
|
|
||||||
|
@ -28,6 +32,8 @@ float WiFiComponent::get_setup_priority() const { return setup_priority::WIFI; }
|
||||||
void WiFiComponent::setup() {
|
void WiFiComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up WiFi...");
|
ESP_LOGCONFIG(TAG, "Setting up WiFi...");
|
||||||
|
|
||||||
|
this->last_connected_ = millis();
|
||||||
|
|
||||||
this->wifi_register_callbacks_();
|
this->wifi_register_callbacks_();
|
||||||
|
|
||||||
bool ret = this->wifi_mode_(this->has_sta(), false);
|
bool ret = this->wifi_mode_(this->has_sta(), false);
|
||||||
|
@ -99,6 +105,16 @@ void WiFiComponent::loop() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this->has_ap() && !this->ap_setup_) {
|
||||||
|
if (now - this->last_connected_ > this->ap_timeout_) {
|
||||||
|
ESP_LOGI(TAG, "Starting fallback AP!");
|
||||||
|
this->setup_ap_config_();
|
||||||
|
#ifdef USE_CAPTIVE_PORTAL
|
||||||
|
captive_portal::global_captive_portal->start();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!this->has_ap() && this->reboot_timeout_ != 0) {
|
if (!this->has_ap() && this->reboot_timeout_ != 0) {
|
||||||
if (now - this->last_connected_ > this->reboot_timeout_) {
|
if (now - this->last_connected_ > this->reboot_timeout_) {
|
||||||
ESP_LOGE(TAG, "Can't connect to WiFi, rebooting...");
|
ESP_LOGE(TAG, "Can't connect to WiFi, rebooting...");
|
||||||
|
@ -119,7 +135,7 @@ IPAddress WiFiComponent::get_ip_address() {
|
||||||
if (this->has_sta())
|
if (this->has_sta())
|
||||||
return this->wifi_sta_ip_();
|
return this->wifi_sta_ip_();
|
||||||
if (this->has_ap())
|
if (this->has_ap())
|
||||||
return this->wifi_soft_ap_ip_();
|
return this->wifi_soft_ap_ip();
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
std::string WiFiComponent::get_use_address() const {
|
std::string WiFiComponent::get_use_address() const {
|
||||||
|
@ -147,7 +163,7 @@ void WiFiComponent::setup_ap_config_() {
|
||||||
}
|
}
|
||||||
|
|
||||||
this->ap_setup_ = this->wifi_start_ap_(this->ap_);
|
this->ap_setup_ = this->wifi_start_ap_(this->ap_);
|
||||||
ESP_LOGCONFIG(TAG, " IP Address: %s", this->wifi_soft_ap_ip_().toString().c_str());
|
ESP_LOGCONFIG(TAG, " IP Address: %s", this->wifi_soft_ap_ip().toString().c_str());
|
||||||
|
|
||||||
if (!this->has_sta()) {
|
if (!this->has_sta()) {
|
||||||
this->state_ = WIFI_COMPONENT_STATE_AP;
|
this->state_ = WIFI_COMPONENT_STATE_AP;
|
||||||
|
@ -159,6 +175,10 @@ float WiFiComponent::get_loop_priority() const {
|
||||||
}
|
}
|
||||||
void WiFiComponent::set_ap(const WiFiAP &ap) { this->ap_ = ap; }
|
void WiFiComponent::set_ap(const WiFiAP &ap) { this->ap_ = ap; }
|
||||||
void WiFiComponent::add_sta(const WiFiAP &ap) { this->sta_.push_back(ap); }
|
void WiFiComponent::add_sta(const WiFiAP &ap) { this->sta_.push_back(ap); }
|
||||||
|
void WiFiComponent::set_sta(const WiFiAP &ap) {
|
||||||
|
this->sta_.clear();
|
||||||
|
this->add_sta(ap);
|
||||||
|
}
|
||||||
|
|
||||||
void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) {
|
void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) {
|
||||||
ESP_LOGI(TAG, "WiFi Connecting to '%s'...", ap.get_ssid().c_str());
|
ESP_LOGI(TAG, "WiFi Connecting to '%s'...", ap.get_ssid().c_str());
|
||||||
|
@ -377,10 +397,15 @@ void WiFiComponent::check_connecting_finished() {
|
||||||
wl_status_t status = this->wifi_sta_status_();
|
wl_status_t status = this->wifi_sta_status_();
|
||||||
|
|
||||||
if (status == WL_CONNECTED) {
|
if (status == WL_CONNECTED) {
|
||||||
ESP_LOGI(TAG, "WiFi connected!");
|
ESP_LOGI(TAG, "WiFi Connected!");
|
||||||
this->print_connect_params_();
|
this->print_connect_params_();
|
||||||
|
|
||||||
if (this->has_ap()) {
|
if (this->has_ap()) {
|
||||||
|
#ifdef USE_CAPTIVE_PORTAL
|
||||||
|
if (this->is_captive_portal_active_()) {
|
||||||
|
captive_portal::global_captive_portal->end();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
ESP_LOGD(TAG, "Disabling AP...");
|
ESP_LOGD(TAG, "Disabling AP...");
|
||||||
this->wifi_mode_({}, false);
|
this->wifi_mode_({}, false);
|
||||||
}
|
}
|
||||||
|
@ -426,7 +451,7 @@ void WiFiComponent::check_connecting_finished() {
|
||||||
|
|
||||||
void WiFiComponent::retry_connect() {
|
void WiFiComponent::retry_connect() {
|
||||||
delay(10);
|
delay(10);
|
||||||
if (this->num_retried_ > 5 || this->error_from_callback_) {
|
if (!this->is_captive_portal_active_() && (this->num_retried_ > 5 || this->error_from_callback_)) {
|
||||||
// If retry failed for more than 5 times, let's restart STA
|
// If retry failed for more than 5 times, let's restart STA
|
||||||
ESP_LOGW(TAG, "Restarting WiFi adapter...");
|
ESP_LOGW(TAG, "Restarting WiFi adapter...");
|
||||||
this->wifi_mode_(false, {});
|
this->wifi_mode_(false, {});
|
||||||
|
@ -443,9 +468,6 @@ void WiFiComponent::retry_connect() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->has_ap()) {
|
|
||||||
this->setup_ap_config_();
|
|
||||||
}
|
|
||||||
this->state_ = WIFI_COMPONENT_STATE_COOLDOWN;
|
this->state_ = WIFI_COMPONENT_STATE_COOLDOWN;
|
||||||
this->action_started_ = millis();
|
this->action_started_ = millis();
|
||||||
}
|
}
|
||||||
|
@ -461,11 +483,6 @@ bool WiFiComponent::is_connected() {
|
||||||
return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED && this->wifi_sta_status_() == WL_CONNECTED &&
|
return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED && this->wifi_sta_status_() == WL_CONNECTED &&
|
||||||
!this->error_from_callback_;
|
!this->error_from_callback_;
|
||||||
}
|
}
|
||||||
bool WiFiComponent::ready_for_ota() {
|
|
||||||
if (this->has_ap())
|
|
||||||
return true;
|
|
||||||
return this->is_connected();
|
|
||||||
}
|
|
||||||
void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->power_save_ = power_save; }
|
void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->power_save_ = power_save; }
|
||||||
|
|
||||||
std::string WiFiComponent::format_mac_addr(const uint8_t *mac) {
|
std::string WiFiComponent::format_mac_addr(const uint8_t *mac) {
|
||||||
|
@ -473,20 +490,12 @@ std::string WiFiComponent::format_mac_addr(const uint8_t *mac) {
|
||||||
sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
bool WiFiComponent::is_captive_portal_active_() {
|
||||||
bool sta_field_equal(const uint8_t *field_a, const uint8_t *field_b, int len) {
|
#ifdef USE_CAPTIVE_PORTAL
|
||||||
for (int i = 0; i < len; i++) {
|
return captive_portal::global_captive_portal != nullptr && captive_portal::global_captive_portal->is_active();
|
||||||
uint8_t a = field_a[i];
|
#else
|
||||||
uint8_t b = field_b[i];
|
|
||||||
if (a == b && a == 0)
|
|
||||||
break;
|
|
||||||
if (a == b)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
#endif
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = ssid; }
|
void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = ssid; }
|
||||||
|
|
|
@ -122,6 +122,7 @@ class WiFiComponent : public Component {
|
||||||
/// Construct a WiFiComponent.
|
/// Construct a WiFiComponent.
|
||||||
WiFiComponent();
|
WiFiComponent();
|
||||||
|
|
||||||
|
void set_sta(const WiFiAP &ap);
|
||||||
void add_sta(const WiFiAP &ap);
|
void add_sta(const WiFiAP &ap);
|
||||||
|
|
||||||
/** Setup an Access Point that should be created if no connection to a station can be made.
|
/** Setup an Access Point that should be created if no connection to a station can be made.
|
||||||
|
@ -137,6 +138,7 @@ class WiFiComponent : public Component {
|
||||||
void check_scanning_finished();
|
void check_scanning_finished();
|
||||||
void start_connecting(const WiFiAP &ap, bool two);
|
void start_connecting(const WiFiAP &ap, bool two);
|
||||||
void set_fast_connect(bool fast_connect);
|
void set_fast_connect(bool fast_connect);
|
||||||
|
void set_ap_timeout(uint32_t ap_timeout) { ap_timeout_ = ap_timeout; }
|
||||||
|
|
||||||
void check_connecting_finished();
|
void check_connecting_finished();
|
||||||
|
|
||||||
|
@ -144,8 +146,6 @@ class WiFiComponent : public Component {
|
||||||
|
|
||||||
bool can_proceed() override;
|
bool can_proceed() override;
|
||||||
|
|
||||||
bool ready_for_ota();
|
|
||||||
|
|
||||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||||
|
|
||||||
bool is_connected();
|
bool is_connected();
|
||||||
|
@ -171,6 +171,10 @@ class WiFiComponent : public Component {
|
||||||
std::string get_use_address() const;
|
std::string get_use_address() const;
|
||||||
void set_use_address(const std::string &use_address);
|
void set_use_address(const std::string &use_address);
|
||||||
|
|
||||||
|
const std::vector<WiFiScanResult> &get_scan_result() const { return scan_result_; }
|
||||||
|
|
||||||
|
IPAddress wifi_soft_ap_ip();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static std::string format_mac_addr(const uint8_t mac[6]);
|
static std::string format_mac_addr(const uint8_t mac[6]);
|
||||||
void setup_ap_config_();
|
void setup_ap_config_();
|
||||||
|
@ -188,7 +192,8 @@ class WiFiComponent : public Component {
|
||||||
bool wifi_scan_start_();
|
bool wifi_scan_start_();
|
||||||
bool wifi_ap_ip_config_(optional<ManualIP> manual_ip);
|
bool wifi_ap_ip_config_(optional<ManualIP> manual_ip);
|
||||||
bool wifi_start_ap_(const WiFiAP &ap);
|
bool wifi_start_ap_(const WiFiAP &ap);
|
||||||
IPAddress wifi_soft_ap_ip_();
|
|
||||||
|
bool is_captive_portal_active_();
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP8266
|
#ifdef ARDUINO_ARCH_ESP8266
|
||||||
static void wifi_event_callback(System_Event_t *event);
|
static void wifi_event_callback(System_Event_t *event);
|
||||||
|
@ -211,7 +216,8 @@ class WiFiComponent : public Component {
|
||||||
uint32_t action_started_;
|
uint32_t action_started_;
|
||||||
uint8_t num_retried_{0};
|
uint8_t num_retried_{0};
|
||||||
uint32_t last_connected_{0};
|
uint32_t last_connected_{0};
|
||||||
uint32_t reboot_timeout_{300000};
|
uint32_t reboot_timeout_{};
|
||||||
|
uint32_t ap_timeout_{};
|
||||||
WiFiPowerSaveMode power_save_{WIFI_POWER_SAVE_NONE};
|
WiFiPowerSaveMode power_save_{WIFI_POWER_SAVE_NONE};
|
||||||
bool error_from_callback_{false};
|
bool error_from_callback_{false};
|
||||||
std::vector<WiFiScanResult> scan_result_;
|
std::vector<WiFiScanResult> scan_result_;
|
||||||
|
|
|
@ -359,7 +359,7 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i
|
||||||
}
|
}
|
||||||
case SYSTEM_EVENT_AP_PROBEREQRECVED: {
|
case SYSTEM_EVENT_AP_PROBEREQRECVED: {
|
||||||
auto it = info.ap_probereqrecved;
|
auto it = info.ap_probereqrecved;
|
||||||
ESP_LOGV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
|
ESP_LOGVV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -517,7 +517,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
IPAddress WiFiComponent::wifi_soft_ap_ip_() {
|
IPAddress WiFiComponent::wifi_soft_ap_ip() {
|
||||||
tcpip_adapter_ip_info_t ip;
|
tcpip_adapter_ip_info_t ip;
|
||||||
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip);
|
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip);
|
||||||
return IPAddress(ip.ip.addr);
|
return IPAddress(ip.ip.addr);
|
||||||
|
|
|
@ -382,7 +382,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||||
}
|
}
|
||||||
case EVENT_SOFTAPMODE_PROBEREQRECVED: {
|
case EVENT_SOFTAPMODE_PROBEREQRECVED: {
|
||||||
auto it = event->event_info.ap_probereqrecved;
|
auto it = event->event_info.ap_probereqrecved;
|
||||||
ESP_LOGV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
|
ESP_LOGVV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0
|
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0
|
||||||
|
@ -583,7 +583,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
IPAddress WiFiComponent::wifi_soft_ap_ip_() {
|
IPAddress WiFiComponent::wifi_soft_ap_ip() {
|
||||||
struct ip_info ip {};
|
struct ip_info ip {};
|
||||||
wifi_get_ip_info(SOFTAP_IF, &ip);
|
wifi_get_ip_info(SOFTAP_IF, &ip);
|
||||||
return {ip.ip.addr};
|
return {ip.ip.addr};
|
||||||
|
|
|
@ -33,10 +33,8 @@ void Application::setup() {
|
||||||
|
|
||||||
for (uint32_t i = 0; i < this->components_.size(); i++) {
|
for (uint32_t i = 0; i < this->components_.size(); i++) {
|
||||||
Component *component = this->components_[i];
|
Component *component = this->components_[i];
|
||||||
if (component->is_failed())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
component->call_setup();
|
component->call();
|
||||||
this->scheduler.process_to_add();
|
this->scheduler.process_to_add();
|
||||||
if (component->can_proceed())
|
if (component->can_proceed())
|
||||||
continue;
|
continue;
|
||||||
|
@ -48,9 +46,7 @@ void Application::setup() {
|
||||||
uint32_t new_app_state = STATUS_LED_WARNING;
|
uint32_t new_app_state = STATUS_LED_WARNING;
|
||||||
this->scheduler.call();
|
this->scheduler.call();
|
||||||
for (uint32_t j = 0; j <= i; j++) {
|
for (uint32_t j = 0; j <= i; j++) {
|
||||||
if (!this->components_[j]->is_failed()) {
|
this->components_[j]->call();
|
||||||
this->components_[j]->call_loop();
|
|
||||||
}
|
|
||||||
new_app_state |= this->components_[j]->get_component_state();
|
new_app_state |= this->components_[j]->get_component_state();
|
||||||
this->app_state_ |= new_app_state;
|
this->app_state_ |= new_app_state;
|
||||||
}
|
}
|
||||||
|
@ -68,9 +64,7 @@ void Application::loop() {
|
||||||
|
|
||||||
this->scheduler.call();
|
this->scheduler.call();
|
||||||
for (Component *component : this->components_) {
|
for (Component *component : this->components_) {
|
||||||
if (!component->is_failed()) {
|
component->call();
|
||||||
component->call_loop();
|
|
||||||
}
|
|
||||||
new_app_state |= component->get_component_state();
|
new_app_state |= component->get_component_state();
|
||||||
this->app_state_ |= new_app_state;
|
this->app_state_ |= new_app_state;
|
||||||
this->feed_wdt();
|
this->feed_wdt();
|
||||||
|
@ -125,7 +119,7 @@ void ICACHE_RAM_ATTR HOT Application::feed_wdt() {
|
||||||
LAST_FEED = now;
|
LAST_FEED = now;
|
||||||
#ifdef USE_STATUS_LED
|
#ifdef USE_STATUS_LED
|
||||||
if (status_led::global_status_led != nullptr) {
|
if (status_led::global_status_led != nullptr) {
|
||||||
status_led::global_status_led->call_loop();
|
status_led::global_status_led->call();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,23 +58,35 @@ bool Component::cancel_timeout(const std::string &name) { // NOLINT
|
||||||
return App.scheduler.cancel_timeout(this, name);
|
return App.scheduler.cancel_timeout(this, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Component::call_loop() {
|
void Component::call_loop() { this->loop(); }
|
||||||
this->loop_internal_();
|
|
||||||
this->loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Component::call_setup() {
|
void Component::call_setup() { this->setup(); }
|
||||||
this->setup_internal_();
|
|
||||||
this->setup();
|
|
||||||
}
|
|
||||||
uint32_t Component::get_component_state() const { return this->component_state_; }
|
uint32_t Component::get_component_state() const { return this->component_state_; }
|
||||||
void Component::loop_internal_() {
|
void Component::call() {
|
||||||
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
uint32_t state = this->component_state_ & COMPONENT_STATE_MASK;
|
||||||
this->component_state_ |= COMPONENT_STATE_LOOP;
|
switch (state) {
|
||||||
}
|
case COMPONENT_STATE_CONSTRUCTION:
|
||||||
void Component::setup_internal_() {
|
// State Construction: Call setup and set state to setup
|
||||||
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
||||||
this->component_state_ |= COMPONENT_STATE_SETUP;
|
this->component_state_ |= COMPONENT_STATE_SETUP;
|
||||||
|
this->call_setup();
|
||||||
|
break;
|
||||||
|
case COMPONENT_STATE_SETUP:
|
||||||
|
// State setup: Call first loop and set state to loop
|
||||||
|
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
||||||
|
this->component_state_ |= COMPONENT_STATE_LOOP;
|
||||||
|
this->call_loop();
|
||||||
|
break;
|
||||||
|
case COMPONENT_STATE_LOOP:
|
||||||
|
// State loop: Call loop
|
||||||
|
this->call_loop();
|
||||||
|
break;
|
||||||
|
case COMPONENT_STATE_FAILED:
|
||||||
|
// State failed: Do nothing
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void Component::mark_failed() {
|
void Component::mark_failed() {
|
||||||
ESP_LOGE(TAG, "Component was marked as failed.");
|
ESP_LOGE(TAG, "Component was marked as failed.");
|
||||||
|
@ -130,9 +142,6 @@ void Component::set_setup_priority(float priority) { this->setup_priority_overri
|
||||||
PollingComponent::PollingComponent(uint32_t update_interval) : Component(), update_interval_(update_interval) {}
|
PollingComponent::PollingComponent(uint32_t update_interval) : Component(), update_interval_(update_interval) {}
|
||||||
|
|
||||||
void PollingComponent::call_setup() {
|
void PollingComponent::call_setup() {
|
||||||
// Call component internal setup.
|
|
||||||
this->setup_internal_();
|
|
||||||
|
|
||||||
// Let the polling component subclass setup their HW.
|
// Let the polling component subclass setup their HW.
|
||||||
this->setup();
|
this->setup();
|
||||||
|
|
||||||
|
|
|
@ -91,18 +91,7 @@ class Component {
|
||||||
*/
|
*/
|
||||||
virtual float get_loop_priority() const;
|
virtual float get_loop_priority() const;
|
||||||
|
|
||||||
/** Public loop() functions. These will be called by the Application instance.
|
void call();
|
||||||
*
|
|
||||||
* Note: This should normally not be overriden, unless you know what you're doing.
|
|
||||||
* They're basically to make creating custom components easier. For example the
|
|
||||||
* SensorComponent can override these methods to not have the user call some super
|
|
||||||
* methods within their custom sensors. These methods should ALWAYS call the loop_internal()
|
|
||||||
* and setup_internal() methods.
|
|
||||||
*
|
|
||||||
* Basically, it handles stuff like interval/timeout functions and eventually calls loop().
|
|
||||||
*/
|
|
||||||
virtual void call_loop();
|
|
||||||
virtual void call_setup();
|
|
||||||
|
|
||||||
virtual void on_shutdown() {}
|
virtual void on_shutdown() {}
|
||||||
virtual void on_safe_shutdown() {}
|
virtual void on_safe_shutdown() {}
|
||||||
|
@ -138,6 +127,8 @@ class Component {
|
||||||
void status_momentary_error(const std::string &name, uint32_t length = 5000);
|
void status_momentary_error(const std::string &name, uint32_t length = 5000);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
virtual void call_loop();
|
||||||
|
virtual void call_setup();
|
||||||
/** Set an interval function with a unique name. Empty name means no cancelling possible.
|
/** Set an interval function with a unique name. Empty name means no cancelling possible.
|
||||||
*
|
*
|
||||||
* This will call f every interval ms. Can be cancelled via CancelInterval().
|
* This will call f every interval ms. Can be cancelled via CancelInterval().
|
||||||
|
@ -204,9 +195,6 @@ class Component {
|
||||||
/// Cancel a defer callback using the specified name, name must not be empty.
|
/// Cancel a defer callback using the specified name, name must not be empty.
|
||||||
bool cancel_defer(const std::string &name); // NOLINT
|
bool cancel_defer(const std::string &name); // NOLINT
|
||||||
|
|
||||||
void loop_internal_();
|
|
||||||
void setup_internal_();
|
|
||||||
|
|
||||||
uint32_t component_state_{0x0000}; ///< State of this component.
|
uint32_t component_state_{0x0000}; ///< State of this component.
|
||||||
float setup_priority_override_{NAN};
|
float setup_priority_override_{NAN};
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,3 +22,4 @@
|
||||||
#endif
|
#endif
|
||||||
#define USE_TIME
|
#define USE_TIME
|
||||||
#define USE_DEEP_SLEEP
|
#define USE_DEEP_SLEEP
|
||||||
|
#define USE_CAPTIVE_PORTAL
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
#ifdef USE_ESP8266_PREFERENCES_FLASH
|
#ifdef ARDUINO_ARCH_ESP8266
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "spi_flash.h"
|
#include "spi_flash.h"
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,9 @@ namespace esphome {
|
||||||
|
|
||||||
static const char *TAG = "preferences";
|
static const char *TAG = "preferences";
|
||||||
|
|
||||||
ESPPreferenceObject::ESPPreferenceObject() : rtc_offset_(0), length_words_(0), type_(0), data_(nullptr) {}
|
ESPPreferenceObject::ESPPreferenceObject() : offset_(0), length_words_(0), type_(0), data_(nullptr) {}
|
||||||
ESPPreferenceObject::ESPPreferenceObject(size_t rtc_offset, size_t length, uint32_t type)
|
ESPPreferenceObject::ESPPreferenceObject(size_t offset, size_t length, uint32_t type)
|
||||||
: rtc_offset_(rtc_offset), length_words_(length), type_(type) {
|
: offset_(offset), length_words_(length), type_(type) {
|
||||||
this->data_ = new uint32_t[this->length_words_ + 1];
|
this->data_ = new uint32_t[this->length_words_ + 1];
|
||||||
for (uint32_t i = 0; i < this->length_words_ + 1; i++)
|
for (uint32_t i = 0; i < this->length_words_ + 1; i++)
|
||||||
this->data_[i] = 0;
|
this->data_[i] = 0;
|
||||||
|
@ -29,7 +29,7 @@ bool ESPPreferenceObject::load_() {
|
||||||
|
|
||||||
bool valid = this->data_[this->length_words_] == this->calculate_crc_();
|
bool valid = this->data_[this->length_words_] == this->calculate_crc_();
|
||||||
|
|
||||||
ESP_LOGVV(TAG, "LOAD %u: valid=%s, 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->rtc_offset_, // NOLINT
|
ESP_LOGVV(TAG, "LOAD %u: valid=%s, 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->offset_, // NOLINT
|
||||||
YESNO(valid), this->data_[0], this->data_[1], this->type_, this->calculate_crc_());
|
YESNO(valid), this->data_[0], this->data_[1], this->type_, this->calculate_crc_());
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ bool ESPPreferenceObject::save_() {
|
||||||
this->data_[this->length_words_] = this->calculate_crc_();
|
this->data_[this->length_words_] = this->calculate_crc_();
|
||||||
if (!this->save_internal_())
|
if (!this->save_internal_())
|
||||||
return false;
|
return false;
|
||||||
ESP_LOGVV(TAG, "SAVE %u: 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->rtc_offset_, // NOLINT
|
ESP_LOGVV(TAG, "SAVE %u: 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->offset_, // NOLINT
|
||||||
this->data_[0], this->data_[1], this->type_, this->calculate_crc_());
|
this->data_[0], this->data_[1], this->type_, this->calculate_crc_());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,12 @@ bool ESPPreferenceObject::save_() {
|
||||||
#define ESP_RTC_USER_MEM_SIZE_WORDS 128
|
#define ESP_RTC_USER_MEM_SIZE_WORDS 128
|
||||||
#define ESP_RTC_USER_MEM_SIZE_BYTES ESP_RTC_USER_MEM_SIZE_WORDS * 4
|
#define ESP_RTC_USER_MEM_SIZE_BYTES ESP_RTC_USER_MEM_SIZE_WORDS * 4
|
||||||
|
|
||||||
|
#ifdef USE_ESP8266_PREFERENCES_FLASH
|
||||||
|
#define ESP8266_FLASH_STORAGE_SIZE 128
|
||||||
|
#else
|
||||||
|
#define ESP8266_FLASH_STORAGE_SIZE 64
|
||||||
|
#endif
|
||||||
|
|
||||||
static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
|
static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
|
||||||
if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
|
if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -62,9 +68,7 @@ static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ESP8266_PREFERENCES_FLASH
|
static bool esp8266_flash_dirty = false;
|
||||||
static bool esp8266_preferences_modified = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
|
static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
|
||||||
if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
|
if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
|
||||||
|
@ -75,29 +79,24 @@ static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *ptr = &ESP_RTC_USER_MEM[index];
|
auto *ptr = &ESP_RTC_USER_MEM[index];
|
||||||
#ifdef USE_ESP8266_PREFERENCES_FLASH
|
|
||||||
if (*ptr != value) {
|
|
||||||
esp8266_preferences_modified = true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
*ptr = value;
|
*ptr = value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ESP8266_PREFERENCES_FLASH
|
|
||||||
extern "C" uint32_t _SPIFFS_end;
|
extern "C" uint32_t _SPIFFS_end;
|
||||||
|
|
||||||
static const uint32_t get_esp8266_flash_sector() { return (uint32_t(&_SPIFFS_end) - 0x40200000) / SPI_FLASH_SEC_SIZE; }
|
static const uint32_t get_esp8266_flash_sector() {
|
||||||
|
union {
|
||||||
|
uint32_t *ptr;
|
||||||
|
uint32_t uint;
|
||||||
|
} data{};
|
||||||
|
data.ptr = &_SPIFFS_end;
|
||||||
|
return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE;
|
||||||
|
}
|
||||||
static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
|
static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
|
||||||
|
|
||||||
static void load_esp8266_flash() {
|
void ESPPreferences::save_esp8266_flash_() {
|
||||||
ESP_LOGVV(TAG, "Loading preferences from flash...");
|
if (!esp8266_flash_dirty)
|
||||||
disable_interrupts();
|
|
||||||
spi_flash_read(get_esp8266_flash_address(), ESP_RTC_USER_MEM, ESP_RTC_USER_MEM_SIZE_BYTES);
|
|
||||||
enable_interrupts();
|
|
||||||
}
|
|
||||||
static void save_esp8266_flash() {
|
|
||||||
if (!esp8266_preferences_modified)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ESP_LOGVV(TAG, "Saving preferences to flash...");
|
ESP_LOGVV(TAG, "Saving preferences to flash...");
|
||||||
|
@ -109,31 +108,53 @@ static void save_esp8266_flash() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto write_res = spi_flash_write(get_esp8266_flash_address(), ESP_RTC_USER_MEM, ESP_RTC_USER_MEM_SIZE_BYTES);
|
auto write_res = spi_flash_write(get_esp8266_flash_address(), this->flash_storage_, ESP8266_FLASH_STORAGE_SIZE * 4);
|
||||||
enable_interrupts();
|
enable_interrupts();
|
||||||
if (write_res != SPI_FLASH_RESULT_OK) {
|
if (write_res != SPI_FLASH_RESULT_OK) {
|
||||||
ESP_LOGV(TAG, "Write ESP8266 flash failed!");
|
ESP_LOGV(TAG, "Write ESP8266 flash failed!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp8266_preferences_modified = false;
|
esp8266_flash_dirty = false;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
bool ESPPreferenceObject::save_internal_() {
|
bool ESPPreferenceObject::save_internal_() {
|
||||||
|
if (this->in_flash_) {
|
||||||
for (uint32_t i = 0; i <= this->length_words_; i++) {
|
for (uint32_t i = 0; i <= this->length_words_; i++) {
|
||||||
if (!esp_rtc_user_mem_write(this->rtc_offset_ + i, this->data_[i]))
|
uint32_t j = this->offset_ + i;
|
||||||
|
if (j >= ESP8266_FLASH_STORAGE_SIZE)
|
||||||
|
return false;
|
||||||
|
uint32_t v = this->data_[i];
|
||||||
|
uint32_t *ptr = &global_preferences.flash_storage_[j];
|
||||||
|
if (*ptr != v)
|
||||||
|
esp8266_flash_dirty = true;
|
||||||
|
*ptr = v;
|
||||||
|
}
|
||||||
|
global_preferences.save_esp8266_flash_();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i <= this->length_words_; i++) {
|
||||||
|
if (!esp_rtc_user_mem_write(this->offset_ + i, this->data_[i]))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ESP8266_PREFERENCES_FLASH
|
|
||||||
save_esp8266_flash();
|
|
||||||
#endif
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool ESPPreferenceObject::load_internal_() {
|
bool ESPPreferenceObject::load_internal_() {
|
||||||
|
if (this->in_flash_) {
|
||||||
for (uint32_t i = 0; i <= this->length_words_; i++) {
|
for (uint32_t i = 0; i <= this->length_words_; i++) {
|
||||||
if (!esp_rtc_user_mem_read(this->rtc_offset_ + i, &this->data_[i]))
|
uint32_t j = this->offset_ + i;
|
||||||
|
if (j >= ESP8266_FLASH_STORAGE_SIZE)
|
||||||
|
return false;
|
||||||
|
this->data_[i] = global_preferences.flash_storage_[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i <= this->length_words_; i++) {
|
||||||
|
if (!esp_rtc_user_mem_read(this->offset_ + i, &this->data_[i]))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -145,12 +166,25 @@ ESPPreferences::ESPPreferences()
|
||||||
: current_offset_(0) {}
|
: current_offset_(0) {}
|
||||||
|
|
||||||
void ESPPreferences::begin(const std::string &name) {
|
void ESPPreferences::begin(const std::string &name) {
|
||||||
#ifdef USE_ESP8266_PREFERENCES_FLASH
|
this->flash_storage_ = new uint32_t[ESP8266_FLASH_STORAGE_SIZE];
|
||||||
load_esp8266_flash();
|
ESP_LOGVV(TAG, "Loading preferences from flash...");
|
||||||
#endif
|
disable_interrupts();
|
||||||
|
spi_flash_read(get_esp8266_flash_address(), this->flash_storage_, ESP8266_FLASH_STORAGE_SIZE * 4);
|
||||||
|
enable_interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type) {
|
ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type, bool in_flash) {
|
||||||
|
if (in_flash) {
|
||||||
|
uint32_t start = this->current_flash_offset_;
|
||||||
|
uint32_t end = start + length + 1;
|
||||||
|
if (end > ESP8266_FLASH_STORAGE_SIZE)
|
||||||
|
return {};
|
||||||
|
auto pref = ESPPreferenceObject(start, length, type);
|
||||||
|
pref.in_flash_ = true;
|
||||||
|
this->current_flash_offset_ = end;
|
||||||
|
return pref;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t start = this->current_offset_;
|
uint32_t start = this->current_offset_;
|
||||||
uint32_t end = start + length + 1;
|
uint32_t end = start + length + 1;
|
||||||
bool in_normal = start < 96;
|
bool in_normal = start < 96;
|
||||||
|
@ -165,7 +199,7 @@ ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type
|
||||||
|
|
||||||
if (end > 128) {
|
if (end > 128) {
|
||||||
// Doesn't fit in data, return uninitialized preference obj.
|
// Doesn't fit in data, return uninitialized preference obj.
|
||||||
return ESPPreferenceObject();
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t rtc_offset;
|
uint32_t rtc_offset;
|
||||||
|
@ -186,7 +220,7 @@ bool ESPPreferences::is_prevent_write() { return this->prevent_write_; }
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
bool ESPPreferenceObject::save_internal_() {
|
bool ESPPreferenceObject::save_internal_() {
|
||||||
char key[32];
|
char key[32];
|
||||||
sprintf(key, "%u", this->rtc_offset_);
|
sprintf(key, "%u", this->offset_);
|
||||||
uint32_t len = (this->length_words_ + 1) * 4;
|
uint32_t len = (this->length_words_ + 1) * 4;
|
||||||
size_t ret = global_preferences.preferences_.putBytes(key, this->data_, len);
|
size_t ret = global_preferences.preferences_.putBytes(key, this->data_, len);
|
||||||
if (ret != len) {
|
if (ret != len) {
|
||||||
|
@ -197,7 +231,7 @@ bool ESPPreferenceObject::save_internal_() {
|
||||||
}
|
}
|
||||||
bool ESPPreferenceObject::load_internal_() {
|
bool ESPPreferenceObject::load_internal_() {
|
||||||
char key[32];
|
char key[32];
|
||||||
sprintf(key, "%u", this->rtc_offset_);
|
sprintf(key, "%u", this->offset_);
|
||||||
uint32_t len = (this->length_words_ + 1) * 4;
|
uint32_t len = (this->length_words_ + 1) * 4;
|
||||||
size_t ret = global_preferences.preferences_.getBytes(key, this->data_, len);
|
size_t ret = global_preferences.preferences_.getBytes(key, this->data_, len);
|
||||||
if (ret != len) {
|
if (ret != len) {
|
||||||
|
@ -213,7 +247,7 @@ void ESPPreferences::begin(const std::string &name) {
|
||||||
this->preferences_.begin(key.c_str());
|
this->preferences_.begin(key.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type) {
|
ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type, bool in_flash) {
|
||||||
auto pref = ESPPreferenceObject(this->current_offset_, length, type);
|
auto pref = ESPPreferenceObject(this->current_offset_, length, type);
|
||||||
this->current_offset_++;
|
this->current_offset_++;
|
||||||
return pref;
|
return pref;
|
||||||
|
|
|
@ -7,13 +7,14 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "esphome/core/esphal.h"
|
#include "esphome/core/esphal.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
class ESPPreferenceObject {
|
class ESPPreferenceObject {
|
||||||
public:
|
public:
|
||||||
ESPPreferenceObject();
|
ESPPreferenceObject();
|
||||||
ESPPreferenceObject(size_t rtc_offset, size_t length, uint32_t type);
|
ESPPreferenceObject(size_t offset, size_t length, uint32_t type);
|
||||||
|
|
||||||
template<typename T> bool save(T *src);
|
template<typename T> bool save(T *src);
|
||||||
|
|
||||||
|
@ -22,6 +23,8 @@ class ESPPreferenceObject {
|
||||||
bool is_initialized() const;
|
bool is_initialized() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
friend class ESPPreferences;
|
||||||
|
|
||||||
bool save_();
|
bool save_();
|
||||||
bool load_();
|
bool load_();
|
||||||
bool save_internal_();
|
bool save_internal_();
|
||||||
|
@ -29,18 +32,33 @@ class ESPPreferenceObject {
|
||||||
|
|
||||||
uint32_t calculate_crc_() const;
|
uint32_t calculate_crc_() const;
|
||||||
|
|
||||||
size_t rtc_offset_;
|
size_t offset_;
|
||||||
size_t length_words_;
|
size_t length_words_;
|
||||||
uint32_t type_;
|
uint32_t type_;
|
||||||
uint32_t *data_;
|
uint32_t *data_;
|
||||||
|
#ifdef ARDUINO_ARCH_ESP8266
|
||||||
|
bool in_flash_{false};
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP8266
|
||||||
|
#ifdef USE_ESP8266_PREFERENCES_FLASH
|
||||||
|
static bool DEFAULT_IN_FLASH = true;
|
||||||
|
#else
|
||||||
|
static bool DEFAULT_IN_FLASH = false;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
static bool DEFAULT_IN_FLASH = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
class ESPPreferences {
|
class ESPPreferences {
|
||||||
public:
|
public:
|
||||||
ESPPreferences();
|
ESPPreferences();
|
||||||
void begin(const std::string &name);
|
void begin(const std::string &name);
|
||||||
ESPPreferenceObject make_preference(size_t length, uint32_t type);
|
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash = DEFAULT_IN_FLASH);
|
||||||
template<typename T> ESPPreferenceObject make_preference(uint32_t type);
|
template<typename T> ESPPreferenceObject make_preference(uint32_t type, bool in_flash = DEFAULT_IN_FLASH);
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP8266
|
#ifdef ARDUINO_ARCH_ESP8266
|
||||||
/** On the ESP8266, we can't override the first 128 bytes during OTA uploads
|
/** On the ESP8266, we can't override the first 128 bytes during OTA uploads
|
||||||
|
@ -62,14 +80,17 @@ class ESPPreferences {
|
||||||
Preferences preferences_;
|
Preferences preferences_;
|
||||||
#endif
|
#endif
|
||||||
#ifdef ARDUINO_ARCH_ESP8266
|
#ifdef ARDUINO_ARCH_ESP8266
|
||||||
|
void save_esp8266_flash_();
|
||||||
bool prevent_write_{false};
|
bool prevent_write_{false};
|
||||||
|
uint32_t *flash_storage_;
|
||||||
|
uint32_t current_flash_offset_;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
extern ESPPreferences global_preferences;
|
extern ESPPreferences global_preferences;
|
||||||
|
|
||||||
template<typename T> ESPPreferenceObject ESPPreferences::make_preference(uint32_t type) {
|
template<typename T> ESPPreferenceObject ESPPreferences::make_preference(uint32_t type, bool in_flash) {
|
||||||
return this->make_preference((sizeof(T) + 3) / 4, type);
|
return this->make_preference((sizeof(T) + 3) / 4, type, in_flash);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T> bool ESPPreferenceObject::save(T *src) {
|
template<typename T> bool ESPPreferenceObject::save(T *src) {
|
||||||
|
|
|
@ -38,50 +38,6 @@ bool network_is_connected() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void network_setup() {
|
|
||||||
bool ready = true;
|
|
||||||
#ifdef USE_ETHERNET
|
|
||||||
if (ethernet::global_eth_component != nullptr) {
|
|
||||||
ethernet::global_eth_component->call_setup();
|
|
||||||
ready = false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_WIFI
|
|
||||||
if (wifi::global_wifi_component != nullptr) {
|
|
||||||
wifi::global_wifi_component->call_setup();
|
|
||||||
ready = false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
while (!ready) {
|
|
||||||
#ifdef USE_ETHERNET
|
|
||||||
if (ethernet::global_eth_component != nullptr) {
|
|
||||||
ethernet::global_eth_component->call_loop();
|
|
||||||
ready = ready || ethernet::global_eth_component->can_proceed();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef USE_WIFI
|
|
||||||
if (wifi::global_wifi_component != nullptr) {
|
|
||||||
wifi::global_wifi_component->call_loop();
|
|
||||||
ready = ready || wifi::global_wifi_component->can_proceed();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
App.feed_wdt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void network_tick() {
|
|
||||||
#ifdef USE_ETHERNET
|
|
||||||
if (ethernet::global_eth_component != nullptr)
|
|
||||||
ethernet::global_eth_component->call_loop();
|
|
||||||
#endif
|
|
||||||
#ifdef USE_WIFI
|
|
||||||
if (wifi::global_wifi_component != nullptr)
|
|
||||||
wifi::global_wifi_component->call_loop();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void network_setup_mdns() {
|
void network_setup_mdns() {
|
||||||
MDNS.begin(App.get_name().c_str());
|
MDNS.begin(App.get_name().c_str());
|
||||||
#ifdef USE_API
|
#ifdef USE_API
|
||||||
|
|
|
@ -10,8 +10,6 @@ bool network_is_connected();
|
||||||
std::string network_get_address();
|
std::string network_get_address();
|
||||||
|
|
||||||
/// Manually set up the network stack (outside of the App.setup() loop, for example in OTA safe mode)
|
/// Manually set up the network stack (outside of the App.setup() loop, for example in OTA safe mode)
|
||||||
void network_setup();
|
|
||||||
void network_tick();
|
|
||||||
void network_setup_mdns();
|
void network_setup_mdns();
|
||||||
void network_tick_mdns();
|
void network_tick_mdns();
|
||||||
|
|
||||||
|
|
|
@ -370,14 +370,14 @@ def _list_dashboard_entries():
|
||||||
|
|
||||||
|
|
||||||
class DashboardEntry(object):
|
class DashboardEntry(object):
|
||||||
def __init__(self, filename):
|
def __init__(self, path):
|
||||||
self.filename = filename
|
self.path = path
|
||||||
self._storage = None
|
self._storage = None
|
||||||
self._loaded_storage = False
|
self._loaded_storage = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_path(self): # type: () -> str
|
def filename(self):
|
||||||
return os.path.join(settings.config_dir, self.filename)
|
return os.path.basename(self.path)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def storage(self): # type: () -> Optional[StorageJSON]
|
def storage(self): # type: () -> Optional[StorageJSON]
|
||||||
|
|
|
@ -82,7 +82,7 @@
|
||||||
<span class="status-indicator unknown" data-node="{{ entry.filename }}">
|
<span class="status-indicator unknown" data-node="{{ entry.filename }}">
|
||||||
<span class="status-indicator-icon"></span>
|
<span class="status-indicator-icon"></span>
|
||||||
<span class="status-indicator-text"></span></span>.
|
<span class="status-indicator-text"></span></span>.
|
||||||
Full path: <code class="inlinecode">{{ escape(entry.full_path) }}</code>
|
Full path: <code class="inlinecode">{{ escape(entry.path) }}</code>
|
||||||
</p>
|
</p>
|
||||||
{% if entry.update_available %}
|
{% if entry.update_available %}
|
||||||
<p class="update-available" data-node="{{ entry.filename }}">
|
<p class="update-available" data-node="{{ entry.filename }}">
|
||||||
|
|
|
@ -2,6 +2,8 @@ from __future__ import print_function
|
||||||
|
|
||||||
import codecs
|
import codecs
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
|
import string
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
@ -52,6 +54,13 @@ wifi:
|
||||||
ssid: "{ssid}"
|
ssid: "{ssid}"
|
||||||
password: "{psk}"
|
password: "{psk}"
|
||||||
|
|
||||||
|
# Enable fallback network (captive portal)
|
||||||
|
ap:
|
||||||
|
ssid: "{name} Fallback AP"
|
||||||
|
password: "{fallback_psk}"
|
||||||
|
|
||||||
|
captive_portal:
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logger:
|
logger:
|
||||||
|
|
||||||
|
@ -65,6 +74,9 @@ def sanitize_double_quotes(value):
|
||||||
|
|
||||||
|
|
||||||
def wizard_file(**kwargs):
|
def wizard_file(**kwargs):
|
||||||
|
letters = string.ascii_letters + string.digits
|
||||||
|
kwargs['fallback_psk'] = ''.join(random.choice(letters) for _ in range(12))
|
||||||
|
|
||||||
config = BASE_CONFIG.format(**kwargs)
|
config = BASE_CONFIG.format(**kwargs)
|
||||||
|
|
||||||
if kwargs['password']:
|
if kwargs['password']:
|
||||||
|
@ -117,8 +129,8 @@ def default_input(text, default):
|
||||||
|
|
||||||
|
|
||||||
# From https://stackoverflow.com/a/518232/8924614
|
# From https://stackoverflow.com/a/518232/8924614
|
||||||
def strip_accents(string):
|
def strip_accents(value):
|
||||||
return u''.join(c for c in unicodedata.normalize('NFD', text_type(string))
|
return u''.join(c for c in unicodedata.normalize('NFD', text_type(value))
|
||||||
if unicodedata.category(c) != 'Mn')
|
if unicodedata.category(c) != 'Mn')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ EXECUTABLE_BIT = {
|
||||||
files = [s[3].strip() for s in lines]
|
files = [s[3].strip() for s in lines]
|
||||||
files.sort()
|
files.sort()
|
||||||
|
|
||||||
file_types = ('.h', '.c', '.cpp', '.tcc', '.yaml', '.yml', '.ini', '.txt', '.ico',
|
file_types = ('.h', '.c', '.cpp', '.tcc', '.yaml', '.yml', '.ini', '.txt', '.ico', '.svg',
|
||||||
'.py', '.html', '.js', '.md', '.sh', '.css', '.proto', '.conf', '.cfg')
|
'.py', '.html', '.js', '.md', '.sh', '.css', '.proto', '.conf', '.cfg')
|
||||||
cpp_include = ('*.h', '*.c', '*.cpp', '*.tcc')
|
cpp_include = ('*.h', '*.c', '*.cpp', '*.tcc')
|
||||||
ignore_types = ('.ico',)
|
ignore_types = ('.ico',)
|
||||||
|
@ -104,7 +104,7 @@ def lint_ino(fname):
|
||||||
|
|
||||||
@lint_file_check(exclude=['*{}'.format(f) for f in file_types] + [
|
@lint_file_check(exclude=['*{}'.format(f) for f in file_types] + [
|
||||||
'.clang-*', '.dockerignore', '.editorconfig', '*.gitignore', 'LICENSE', 'pylintrc',
|
'.clang-*', '.dockerignore', '.editorconfig', '*.gitignore', 'LICENSE', 'pylintrc',
|
||||||
'MANIFEST.in', 'docker/Dockerfile*', 'docker/rootfs/*', 'script/*'
|
'MANIFEST.in', 'docker/Dockerfile*', 'docker/rootfs/*', 'script/*',
|
||||||
])
|
])
|
||||||
def lint_ext_check(fname):
|
def lint_ext_check(fname):
|
||||||
return "This file extension is not a registered file type. If this is an error, please " \
|
return "This file extension is not a registered file type. If this is an error, please " \
|
||||||
|
@ -135,7 +135,7 @@ def lint_newline(fname):
|
||||||
return "File contains windows newline. Please set your editor to unix newline mode."
|
return "File contains windows newline. Please set your editor to unix newline mode."
|
||||||
|
|
||||||
|
|
||||||
@lint_content_check()
|
@lint_content_check(exclude=['*.svg'])
|
||||||
def lint_end_newline(fname, content):
|
def lint_end_newline(fname, content):
|
||||||
if content and not content.endswith('\n'):
|
if content and not content.endswith('\n'):
|
||||||
return "File does not end with a newline, please add an empty line at the end of the file."
|
return "File does not end with a newline, please add an empty line at the end of the file."
|
||||||
|
@ -164,7 +164,8 @@ def relative_py_search_text(fname, content):
|
||||||
return 'esphome.components.{}'.format(integration)
|
return 'esphome.components.{}'.format(integration)
|
||||||
|
|
||||||
|
|
||||||
@lint_content_find_check(relative_py_search_text, include=['esphome/components/*.py'])
|
@lint_content_find_check(relative_py_search_text, include=['esphome/components/*.py'],
|
||||||
|
exclude=['esphome/components/web_server/__init__.py'])
|
||||||
def lint_relative_py_import(fname):
|
def lint_relative_py_import(fname):
|
||||||
return ("Component contains absolute import - Components must always use "
|
return ("Component contains absolute import - Components must always use "
|
||||||
"relative imports within the integration.\n"
|
"relative imports within the integration.\n"
|
||||||
|
|
Loading…
Reference in a new issue