first commit

This commit is contained in:
Jérôme Delacotte
2025-03-06 11:15:32 +01:00
commit 7b30d6e298
5276 changed files with 2108927 additions and 0 deletions

View File

@@ -0,0 +1,153 @@
extern "C" {
void app_loop();
void restartMCU();
}
#include "Settings.h"
#include <BlynkSimpleEsp32_SSL.h>
#if defined(BLYNK_USE_LITTLEFS)
#include <LittleFS.h>
#define BLYNK_FS LittleFS
#elif defined(BLYNK_USE_SPIFFS)
#if defined(ESP32)
#include <SPIFFS.h>
#elif defined(ESP8266)
#include <FS.h>
#endif
#define BLYNK_FS SPIFFS
#endif
#ifndef BLYNK_NEW_LIBRARY
#error "Old version of Blynk library is in use. Please replace it with the new one."
#endif
#if !defined(BLYNK_TEMPLATE_NAME) && defined(BLYNK_DEVICE_NAME)
#define BLYNK_TEMPLATE_NAME BLYNK_DEVICE_NAME
#endif
#if !defined(BLYNK_TEMPLATE_ID) || !defined(BLYNK_TEMPLATE_NAME)
#error "Please specify your BLYNK_TEMPLATE_ID and BLYNK_TEMPLATE_NAME"
#endif
#if defined(BLYNK_AUTH_TOKEN)
#error "BLYNK_AUTH_TOKEN is assigned automatically when using Blynk.Edgent, please remove it from the configuration"
#endif
BlynkTimer edgentTimer;
#include "BlynkState.h"
#include "ConfigStore.h"
#include "ResetButton.h"
#include "ConfigMode.h"
#include "Indicator.h"
#include "OTA.h"
#include "Console.h"
inline
void BlynkState::set(State m) {
if (state != m && m < MODE_MAX_VALUE) {
DEBUG_PRINT(String(StateStr[state]) + " => " + StateStr[m]);
state = m;
// You can put your state handling here,
// i.e. implement custom indication
}
}
void printDeviceBanner()
{
#ifdef BLYNK_PRINT
Blynk.printBanner();
BLYNK_PRINT.println("----------------------------------------------------");
BLYNK_PRINT.print(" Device: "); BLYNK_PRINT.println(getWiFiName());
BLYNK_PRINT.print(" Firmware: "); BLYNK_PRINT.println(BLYNK_FIRMWARE_VERSION " (build " __DATE__ " " __TIME__ ")");
if (configStore.getFlag(CONFIG_FLAG_VALID)) {
BLYNK_PRINT.print(" Token: ");
BLYNK_PRINT.println(String(configStore.cloudToken).substring(0,4) +
" - •••• - •••• - ••••");
}
BLYNK_PRINT.print(" Platform: "); BLYNK_PRINT.println(String(BLYNK_INFO_DEVICE) + " @ " + ESP.getCpuFreqMHz() + "MHz");
BLYNK_PRINT.print(" Chip rev: "); BLYNK_PRINT.println(ESP.getChipRevision());
BLYNK_PRINT.print(" SDK: "); BLYNK_PRINT.println(ESP.getSdkVersion());
BLYNK_PRINT.print(" Flash: "); BLYNK_PRINT.println(String(ESP.getFlashChipSize() / 1024) + "K");
BLYNK_PRINT.print(" Free mem: "); BLYNK_PRINT.println(ESP.getFreeHeap());
BLYNK_PRINT.println("----------------------------------------------------");
#endif
}
void runBlynkWithChecks() {
Blynk.run();
if (BlynkState::get() == MODE_RUNNING) {
if (!Blynk.connected()) {
if (WiFi.status() == WL_CONNECTED) {
BlynkState::set(MODE_CONNECTING_CLOUD);
} else {
BlynkState::set(MODE_CONNECTING_NET);
}
}
}
}
class Edgent {
public:
void begin()
{
WiFi.persistent(false);
WiFi.enableSTA(true); // Needed to get MAC
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0))
WiFi.setMinSecurity(WIFI_AUTH_WEP);
#endif
#ifdef BLYNK_FS
BLYNK_FS.begin(true);
#endif
indicator_init();
button_init();
config_init();
printDeviceBanner();
console_init();
if (configStore.getFlag(CONFIG_FLAG_VALID)) {
BlynkState::set(MODE_CONNECTING_NET);
} else if (config_load_blnkopt()) {
DEBUG_PRINT("Firmware is preprovisioned");
BlynkState::set(MODE_CONNECTING_NET);
} else {
BlynkState::set(MODE_WAIT_CONFIG);
}
if (!String(BLYNK_TEMPLATE_ID).startsWith("TMPL") ||
!strlen(BLYNK_TEMPLATE_NAME)
) {
DEBUG_PRINT("Invalid configuration of TEMPLATE_ID / TEMPLATE_NAME");
while (true) { delay(100); }
}
}
void run() {
app_loop();
switch (BlynkState::get()) {
case MODE_WAIT_CONFIG:
case MODE_CONFIGURING: enterConfigMode(); break;
case MODE_CONNECTING_NET: enterConnectNet(); break;
case MODE_CONNECTING_CLOUD: enterConnectCloud(); break;
case MODE_RUNNING: runBlynkWithChecks(); break;
case MODE_OTA_UPGRADE: enterOTA(); break;
case MODE_SWITCH_TO_STA: enterSwitchToSTA(); break;
case MODE_RESET_CONFIG: enterResetConfig(); break;
default: enterError(); break;
}
}
} BlynkEdgent;
void app_loop() {
edgentTimer.run();
edgentConsole.run();
}

View File

@@ -0,0 +1,40 @@
enum State {
MODE_WAIT_CONFIG,
MODE_CONFIGURING,
MODE_CONNECTING_NET,
MODE_CONNECTING_CLOUD,
MODE_RUNNING,
MODE_OTA_UPGRADE,
MODE_SWITCH_TO_STA,
MODE_RESET_CONFIG,
MODE_ERROR,
MODE_MAX_VALUE
};
#if defined(APP_DEBUG)
const char* StateStr[MODE_MAX_VALUE+1] = {
"WAIT_CONFIG",
"CONFIGURING",
"CONNECTING_NET",
"CONNECTING_CLOUD",
"RUNNING",
"OTA_UPGRADE",
"SWITCH_TO_STA",
"RESET_CONFIG",
"ERROR",
"INIT"
};
#endif
namespace BlynkState
{
volatile State state = MODE_MAX_VALUE;
State get() { return state; }
bool is (State m) { return (state == m); }
void set(State m);
};

View File

@@ -0,0 +1,544 @@
#include <WiFiClient.h>
#include <WebServer.h>
#include <DNSServer.h>
#include <Update.h>
#ifndef BLYNK_FS
const char* config_form = R"html(
<!DOCTYPE HTML>
<html>
<head>
<title>WiFi setup</title>
<style>
body {
background-color: #fcfcfc;
box-sizing: border-box;
}
body, input {
font-family: Roboto, sans-serif;
font-weight: 400;
font-size: 16px;
}
.centered {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
background-color: #ccc;
border-radius: 4px;
}
td { padding:0 0 0 5px; }
label { white-space:nowrap; }
input { width: 20em; }
input[name="port"] { width: 5em; }
input[type="submit"], img { margin: auto; display: block; width: 30%; }
</style>
</head>
<body>
<div class="centered">
<form method="get" action="config">
<table>
<tr><td><label for="ssid">WiFi SSID:</label></td> <td><input type="text" name="ssid" length=64 required="required"></td></tr>
<tr><td><label for="pass">Password:</label></td> <td><input type="text" name="pass" length=64></td></tr>
<tr><td><label for="blynk">Auth token:</label></td><td><input type="text" name="blynk" placeholder="a0b1c2d..." pattern="[-_a-zA-Z0-9]{32}" maxlength="32" required="required"></td></tr>
<tr><td><label for="host">Host:</label></td> <td><input type="text" name="host" value="blynk.cloud" length=64></td></tr>
<tr><td><label for="port_ssl">Port:</label></td> <td><input type="number" name="port_ssl" value="443" min="1" max="65535"></td></tr>
</table><br/>
<input type="submit" value="Apply">
</form>
</div>
</body>
</html>
)html";
#endif
WebServer server(80);
DNSServer dnsServer;
const byte DNS_PORT = 53;
static int connectNetRetries = WIFI_CLOUD_MAX_RETRIES;
static int connectBlynkRetries = WIFI_CLOUD_MAX_RETRIES;
static const char serverUpdateForm[] PROGMEM =
R"(<html><body>
<form method='POST' action='' enctype='multipart/form-data'>
<input type='file' name='update'>
<input type='submit' value='Update'>
</form>
</body></html>)";
void restartMCU() {
ESP.restart();
while(1) {};
}
static
String encodeUniquePart(uint32_t n, unsigned len)
{
static constexpr char alphabet[] = { "0W8N4Y1HP5DF9K6JM3C2UA7R" };
static constexpr int base = sizeof(alphabet)-1;
char buf[16] = { 0, };
char prev = 0;
for (unsigned i = 0; i < len; n /= base) {
char c = alphabet[n % base];
if (c == prev) {
c = alphabet[(n+1) % base];
}
prev = buf[i++] = c;
}
return String(buf);
}
static
String getWiFiName(bool withPrefix = true)
{
const uint64_t chipId = ESP.getEfuseMac();
uint32_t unique = 0;
for (int i=0; i<4; i++) {
unique = BlynkCRC32(&chipId, sizeof(chipId), unique);
}
String devUnique = encodeUniquePart(unique, 4);
String devPrefix = CONFIG_DEVICE_PREFIX;
String devName = String(BLYNK_TEMPLATE_NAME).substring(0, 31-6-devPrefix.length());
if (withPrefix) {
return devPrefix + " " + devName + "-" + devUnique;
} else {
return devName + "-" + devUnique;
}
}
static inline
String macToString(byte mac[6]) {
char buff[20];
snprintf(buff, sizeof(buff), "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return String(buff);
}
static inline
const char* wifiSecToStr(wifi_auth_mode_t t) {
switch (t) {
case WIFI_AUTH_OPEN: return "OPEN";
case WIFI_AUTH_WEP: return "WEP";
case WIFI_AUTH_WPA_PSK: return "WPA";
case WIFI_AUTH_WPA2_PSK: return "WPA2";
case WIFI_AUTH_WPA_WPA2_PSK: return "WPA+WPA2";
case WIFI_AUTH_WPA2_ENTERPRISE: return "WPA2-EAP";
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0))
case WIFI_AUTH_WPA3_PSK: return "WPA3";
case WIFI_AUTH_WPA2_WPA3_PSK: return "WPA2+WPA3";
case WIFI_AUTH_WAPI_PSK: return "WAPI";
#endif
default: return "unknown";
}
}
static
String getWiFiMacAddress() {
return WiFi.macAddress();
}
static
String getWiFiApBSSID() {
return WiFi.softAPmacAddress();
}
static
String getWiFiNetworkSSID() {
return WiFi.SSID();
}
static
String getWiFiNetworkBSSID() {
return WiFi.BSSIDstr();
}
void enterConfigMode()
{
WiFi.mode(WIFI_OFF);
delay(100);
WiFi.mode(WIFI_AP);
delay(2000);
WiFi.softAPConfig(WIFI_AP_IP, WIFI_AP_IP, WIFI_AP_Subnet);
WiFi.softAP(getWiFiName().c_str());
delay(500);
// Set up DNS Server
dnsServer.setTTL(300); // Time-to-live 300s
dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure); // Return code for non-accessible domains
#ifdef WIFI_CAPTIVE_PORTAL_ENABLE
dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); // Point all to our IP
server.onNotFound(handleRoot);
#else
dnsServer.start(DNS_PORT, CONFIG_AP_URL, WiFi.softAPIP());
DEBUG_PRINT(String("AP URL: ") + CONFIG_AP_URL);
#endif
server.on("/update", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", serverUpdateForm);
});
server.on("/update", HTTP_POST, []() {
server.sendHeader("Connection", "close");
if (!Update.hasError()) {
server.send(200, "text/plain", "OK");
} else {
server.send(500, "text/plain", "FAIL");
}
delay(1000);
restartMCU();
}, []() {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
DEBUG_PRINT(String("Update: ") + upload.filename);
//WiFiUDP::stop();
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
DEBUG_PRINT(Update.errorString());
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
/* flashing firmware to ESP*/
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
DEBUG_PRINT(Update.errorString());
}
#ifdef BLYNK_PRINT
BLYNK_PRINT.print(".");
#endif
} else if (upload.status == UPLOAD_FILE_END) {
#ifdef BLYNK_PRINT
BLYNK_PRINT.println();
#endif
DEBUG_PRINT("Finishing...");
if (Update.end(true)) { //true to set the size to the current progress
DEBUG_PRINT("Update Success. Rebooting");
} else {
DEBUG_PRINT(Update.errorString());
}
}
});
#ifndef BLYNK_FS
server.on("/", []() {
server.send(200, "text/html", config_form);
});
#endif
server.on("/config", []() {
DEBUG_PRINT("Applying configuration...");
String ssid = server.arg("ssid");
String ssidManual = server.arg("ssidManual");
String pass = server.arg("pass");
if (ssidManual != "") {
ssid = ssidManual;
}
String token = server.arg("blynk");
String host = server.arg("host");
String port = server.arg("port_ssl");
String ip = server.arg("ip");
String mask = server.arg("mask");
String gw = server.arg("gw");
String dns = server.arg("dns");
String dns2 = server.arg("dns2");
bool forceSave = server.arg("save").toInt();
String content;
DEBUG_PRINT(String("WiFi SSID: ") + ssid + " Pass: " + pass);
DEBUG_PRINT(String("Blynk cloud: ") + token + " @ " + host + ":" + port);
if (token.length() == 32 && ssid.length() > 0) {
configStore = configDefault;
CopyString(ssid, configStore.wifiSSID);
CopyString(pass, configStore.wifiPass);
CopyString(token, configStore.cloudToken);
if (host.length()) {
CopyString(host, configStore.cloudHost);
}
if (port.length()) {
configStore.cloudPort = port.toInt();
}
IPAddress addr;
if (ip.length() && addr.fromString(ip)) {
configStore.staticIP = addr;
configStore.setFlag(CONFIG_FLAG_STATIC_IP, true);
} else {
configStore.setFlag(CONFIG_FLAG_STATIC_IP, false);
}
if (mask.length() && addr.fromString(mask)) {
configStore.staticMask = addr;
}
if (gw.length() && addr.fromString(gw)) {
configStore.staticGW = addr;
}
if (dns.length() && addr.fromString(dns)) {
configStore.staticDNS = addr;
}
if (dns2.length() && addr.fromString(dns2)) {
configStore.staticDNS2 = addr;
}
if (forceSave) {
configStore.setFlag(CONFIG_FLAG_VALID, true);
config_save();
content = R"json({"status":"ok","msg":"Configuration saved"})json";
} else {
content = R"json({"status":"ok","msg":"Trying to connect..."})json";
}
server.send(200, "application/json", content);
connectNetRetries = connectBlynkRetries = 1;
BlynkState::set(MODE_SWITCH_TO_STA);
} else {
DEBUG_PRINT("Configuration invalid");
content = R"json({"status":"error","msg":"Configuration invalid"})json";
server.send(500, "application/json", content);
}
});
server.on("/board_info.json", []() {
// Configuring starts with board info request (may impact indication)
BlynkState::set(MODE_CONFIGURING);
DEBUG_PRINT("Sending board info...");
const char* tmpl = BLYNK_TEMPLATE_ID;
char buff[512];
snprintf(buff, sizeof(buff),
R"json({"board":"%s","tmpl_id":"%s","fw_type":"%s","fw_ver":"%s","ssid":"%s","bssid":"%s","mac":"%s","last_error":%d,"wifi_scan":true,"static_ip":true})json",
BLYNK_TEMPLATE_NAME,
tmpl ? tmpl : "Unknown",
BLYNK_FIRMWARE_TYPE,
BLYNK_FIRMWARE_VERSION,
getWiFiName().c_str(),
getWiFiApBSSID().c_str(),
getWiFiMacAddress().c_str(),
configStore.last_error
);
server.send(200, "application/json", buff);
});
server.on("/wifi_scan.json", []() {
DEBUG_PRINT("Scanning networks...");
int wifi_nets = WiFi.scanNetworks(true, true);
const uint32_t t = millis();
while (wifi_nets < 0 &&
millis() - t < 20000)
{
delay(20);
wifi_nets = WiFi.scanComplete();
}
DEBUG_PRINT(String("Found networks: ") + wifi_nets);
if (wifi_nets > 0) {
// Sort networks
int indices[wifi_nets];
for (int i = 0; i < wifi_nets; i++) {
indices[i] = i;
}
for (int i = 0; i < wifi_nets; i++) {
for (int j = i + 1; j < wifi_nets; j++) {
if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) {
std::swap(indices[i], indices[j]);
}
}
}
wifi_nets = BlynkMin(15, wifi_nets); // Show top 15 networks
// TODO: skip empty names
String result = "[\n";
char buff[256];
for (int i = 0; i < wifi_nets; i++){
int id = indices[i];
snprintf(buff, sizeof(buff),
R"json( {"ssid":"%s","bssid":"%s","rssi":%i,"sec":"%s","ch":%i})json",
WiFi.SSID(id).c_str(),
WiFi.BSSIDstr(id).c_str(),
WiFi.RSSI(id),
wifiSecToStr(WiFi.encryptionType(id)),
WiFi.channel(id)
);
result += buff;
if (i != wifi_nets-1) result += ",\n";
}
WiFi.scanDelete();
server.send(200, "application/json", result + "\n]");
} else {
server.send(200, "application/json", "[]");
}
});
server.on("/reset", []() {
BlynkState::set(MODE_RESET_CONFIG);
server.send(200, "application/json", R"json({"status":"ok","msg":"Configuration reset"})json");
});
server.on("/reboot", []() {
restartMCU();
});
#ifdef BLYNK_FS
server.serveStatic("/img/favicon.png", BLYNK_FS, "/img/favicon.png");
server.serveStatic("/img/logo.png", BLYNK_FS, "/img/logo.png");
server.serveStatic("/", BLYNK_FS, "/index.html");
#endif
server.begin();
while (BlynkState::is(MODE_WAIT_CONFIG) || BlynkState::is(MODE_CONFIGURING)) {
delay(10);
dnsServer.processNextRequest();
server.handleClient();
app_loop();
if (BlynkState::is(MODE_CONFIGURING) && WiFi.softAPgetStationNum() == 0) {
BlynkState::set(MODE_WAIT_CONFIG);
}
}
server.stop();
}
void enterConnectNet() {
BlynkState::set(MODE_CONNECTING_NET);
DEBUG_PRINT(String("Connecting to WiFi: ") + configStore.wifiSSID);
// Needed for setHostname to work
WiFi.enableSTA(false);
String hostname = getWiFiName();
hostname.replace(" ", "-");
WiFi.setHostname(hostname.c_str());
if (configStore.getFlag(CONFIG_FLAG_STATIC_IP)) {
if (!WiFi.config(configStore.staticIP,
configStore.staticGW,
configStore.staticMask,
configStore.staticDNS,
configStore.staticDNS2)
) {
DEBUG_PRINT("Failed to configure Static IP");
config_set_last_error(BLYNK_PROV_ERR_CONFIG);
BlynkState::set(MODE_ERROR);
return;
}
}
WiFi.begin(configStore.wifiSSID, configStore.wifiPass);
unsigned long timeoutMs = millis() + WIFI_NET_CONNECT_TIMEOUT;
while ((timeoutMs > millis()) && (WiFi.status() != WL_CONNECTED))
{
delay(10);
app_loop();
if (!BlynkState::is(MODE_CONNECTING_NET)) {
WiFi.disconnect();
return;
}
}
if (WiFi.status() == WL_CONNECTED) {
IPAddress localip = WiFi.localIP();
if (configStore.getFlag(CONFIG_FLAG_STATIC_IP)) {
BLYNK_LOG_IP("Using Static IP: ", localip);
} else {
BLYNK_LOG_IP("Using Dynamic IP: ", localip);
}
connectNetRetries = WIFI_CLOUD_MAX_RETRIES;
BlynkState::set(MODE_CONNECTING_CLOUD);
} else if (--connectNetRetries <= 0) {
config_set_last_error(BLYNK_PROV_ERR_NETWORK);
BlynkState::set(MODE_ERROR);
}
}
void enterConnectCloud() {
BlynkState::set(MODE_CONNECTING_CLOUD);
Blynk.config(configStore.cloudToken, configStore.cloudHost, configStore.cloudPort);
Blynk.connect(0);
unsigned long timeoutMs = millis() + WIFI_CLOUD_CONNECT_TIMEOUT;
while ((timeoutMs > millis()) &&
(WiFi.status() == WL_CONNECTED) &&
(!Blynk.isTokenInvalid()) &&
(Blynk.connected() == false))
{
delay(10);
Blynk.run();
app_loop();
if (!BlynkState::is(MODE_CONNECTING_CLOUD)) {
Blynk.disconnect();
return;
}
}
if (millis() > timeoutMs) {
DEBUG_PRINT("Timeout");
}
if (Blynk.isTokenInvalid()) {
config_set_last_error(BLYNK_PROV_ERR_TOKEN);
BlynkState::set(MODE_WAIT_CONFIG); // TODO: retry after timeout
} else if (WiFi.status() != WL_CONNECTED) {
BlynkState::set(MODE_CONNECTING_NET);
} else if (Blynk.connected()) {
BlynkState::set(MODE_RUNNING);
connectBlynkRetries = WIFI_CLOUD_MAX_RETRIES;
if (!configStore.getFlag(CONFIG_FLAG_VALID)) {
configStore.last_error = BLYNK_PROV_ERR_NONE;
configStore.setFlag(CONFIG_FLAG_VALID, true);
config_save();
Blynk.sendInternal("meta", "set", "Hotspot Name", getWiFiName());
}
} else if (--connectBlynkRetries <= 0) {
config_set_last_error(BLYNK_PROV_ERR_CLOUD);
BlynkState::set(MODE_ERROR);
}
}
void enterSwitchToSTA() {
BlynkState::set(MODE_SWITCH_TO_STA);
DEBUG_PRINT("Switching to STA...");
delay(1000);
WiFi.mode(WIFI_OFF);
delay(100);
WiFi.mode(WIFI_STA);
BlynkState::set(MODE_CONNECTING_NET);
}
void enterError() {
BlynkState::set(MODE_ERROR);
unsigned long timeoutMs = millis() + 10000;
while (timeoutMs > millis() || g_buttonPressed)
{
delay(10);
app_loop();
if (!BlynkState::is(MODE_ERROR)) {
return;
}
}
DEBUG_PRINT("Restarting after error.");
delay(10);
restartMCU();
}

View File

@@ -0,0 +1,154 @@
#define CONFIG_FLAG_VALID 0x01
#define CONFIG_FLAG_STATIC_IP 0x02
#define BLYNK_PROV_ERR_NONE 0 // All good
#define BLYNK_PROV_ERR_CONFIG 700 // Invalid config from app (malformed token,etc)
#define BLYNK_PROV_ERR_NETWORK 701 // Could not connect to the router
#define BLYNK_PROV_ERR_CLOUD 702 // Could not connect to the cloud
#define BLYNK_PROV_ERR_TOKEN 703 // Invalid token error (after connection)
#define BLYNK_PROV_ERR_INTERNAL 704 // Other issues (i.e. hardware failure)
struct ConfigStore {
uint32_t magic;
char version[15];
uint8_t flags;
char wifiSSID[34];
char wifiPass[64];
char cloudToken[34];
char cloudHost[34];
uint16_t cloudPort;
uint32_t staticIP;
uint32_t staticMask;
uint32_t staticGW;
uint32_t staticDNS;
uint32_t staticDNS2;
int last_error;
void setFlag(uint8_t mask, bool value) {
if (value) {
flags |= mask;
} else {
flags &= ~mask;
}
}
bool getFlag(uint8_t mask) {
return (flags & mask) == mask;
}
} __attribute__((packed));
ConfigStore configStore;
const ConfigStore configDefault = {
0x626C6E6B,
BLYNK_FIRMWARE_VERSION,
0x00,
"",
"",
"invalid token",
CONFIG_DEFAULT_SERVER,
CONFIG_DEFAULT_PORT,
0,
BLYNK_PROV_ERR_NONE
};
template<typename T, int size>
void CopyString(const String& s, T(&arr)[size]) {
s.toCharArray(arr, size);
}
static bool config_load_blnkopt()
{
static const char blnkopt[] = "blnkopt\0"
BLYNK_PARAM_KV("ssid" , BLYNK_PARAM_PLACEHOLDER_64
BLYNK_PARAM_PLACEHOLDER_64
BLYNK_PARAM_PLACEHOLDER_64
BLYNK_PARAM_PLACEHOLDER_64)
BLYNK_PARAM_KV("host" , CONFIG_DEFAULT_SERVER)
BLYNK_PARAM_KV("port" , BLYNK_TOSTRING(CONFIG_DEFAULT_PORT))
"\0";
BlynkParam prov(blnkopt+8, sizeof(blnkopt)-8-2);
BlynkParam::iterator ssid = prov["ssid"];
BlynkParam::iterator pass = prov["pass"];
BlynkParam::iterator auth = prov["auth"];
BlynkParam::iterator host = prov["host"];
BlynkParam::iterator port = prov["port"];
if (!(ssid.isValid() && auth.isValid())) {
return false;
}
// reset to defaut before loading values from blnkopt
configStore = configDefault;
if (ssid.isValid()) { CopyString(ssid.asStr(), configStore.wifiSSID); }
if (pass.isValid()) { CopyString(pass.asStr(), configStore.wifiPass); }
if (auth.isValid()) { CopyString(auth.asStr(), configStore.cloudToken); }
if (host.isValid()) { CopyString(host.asStr(), configStore.cloudHost); }
if (port.isValid()) { configStore.cloudPort = port.asInt(); }
return true;
}
#include <Preferences.h>
void config_load()
{
Preferences prefs;
if (prefs.begin("blynk", true)) { // read-only
memset(&configStore, 0, sizeof(configStore));
prefs.getBytes("config", &configStore, sizeof(configStore));
if (configStore.magic != configDefault.magic) {
DEBUG_PRINT("Using default config.");
configStore = configDefault;
}
} else {
DEBUG_PRINT("Config read failed");
}
}
bool config_save()
{
Preferences prefs;
if (prefs.begin("blynk", false)) { // writeable
prefs.putBytes("config", &configStore, sizeof(configStore));
DEBUG_PRINT("Configuration stored to flash");
return true;
} else {
DEBUG_PRINT("Config write failed");
return false;
}
}
bool config_init()
{
config_load();
return true;
}
void enterResetConfig()
{
DEBUG_PRINT("Resetting configuration!");
configStore = configDefault;
config_save();
BlynkState::set(MODE_WAIT_CONFIG);
}
void config_set_last_error(int error) {
// Only set error if not provisioned
if (!configStore.getFlag(CONFIG_FLAG_VALID)) {
configStore = configDefault;
configStore.last_error = error;
BLYNK_LOG2("Last error code: ", error);
config_save();
}
}

View File

@@ -0,0 +1,225 @@
#include <Blynk/BlynkConsole.h>
extern "C" {
#include "esp_partition.h"
#include "esp_ota_ops.h"
}
BlynkConsole edgentConsole;
void console_init()
{
#ifdef BLYNK_PRINT
edgentConsole.begin(BLYNK_PRINT);
#endif
edgentConsole.print("\n>");
edgentConsole.addCommand("reboot", []() {
edgentConsole.print(R"json({"status":"OK","msg":"rebooting wifi module"})json" "\n");
delay(100);
restartMCU();
});
edgentConsole.addCommand("config", [](int argc, const char** argv) {
if (argc < 1 || 0 == strcmp(argv[0], "start")) {
BlynkState::set(MODE_WAIT_CONFIG);
} else if (0 == strcmp(argv[0], "erase")) {
BlynkState::set(MODE_RESET_CONFIG);
}
});
edgentConsole.addCommand("devinfo", []() {
edgentConsole.printf(
R"json({"name":"%s","board":"%s","tmpl_id":"%s","fw_type":"%s","fw_ver":"%s"})json" "\n",
getWiFiName().c_str(),
BLYNK_TEMPLATE_NAME,
BLYNK_TEMPLATE_ID,
BLYNK_FIRMWARE_TYPE,
BLYNK_FIRMWARE_VERSION
);
});
edgentConsole.addCommand("connect", [](int argc, const char** argv) {
if (argc < 2) {
edgentConsole.print(R"json({"status":"error","msg":"invalid arguments. expected: <auth> <ssid> <pass>"})json" "\n");
return;
}
String auth = argv[0];
String ssid = argv[1];
String pass = (argc >= 3) ? argv[2] : "";
if (auth.length() != 32) {
edgentConsole.print(R"json({"status":"error","msg":"invalid token size"})json" "\n");
return;
}
edgentConsole.print(R"json({"status":"OK","msg":"trying to connect..."})json" "\n");
configStore = configDefault;
CopyString(ssid, configStore.wifiSSID);
CopyString(pass, configStore.wifiPass);
CopyString(auth, configStore.cloudToken);
BlynkState::set(MODE_SWITCH_TO_STA);
});
edgentConsole.addCommand("wifi", [](int argc, const char* argv[]) {
if (argc < 1 || 0 == strcmp(argv[0], "show")) {
edgentConsole.printf(
"mac:%s ip:%s (%s [%s] %ddBm)\n",
getWiFiMacAddress().c_str(),
WiFi.localIP().toString().c_str(),
getWiFiNetworkSSID().c_str(),
getWiFiNetworkBSSID().c_str(),
WiFi.RSSI()
);
} else if (0 == strcmp(argv[0], "scan")) {
int found = WiFi.scanNetworks();
for (int i = 0; i < found; i++) {
bool current = (WiFi.SSID(i) == WiFi.SSID());
edgentConsole.printf(
"%s %s [%s] %s ch:%d rssi:%d\n",
(current ? "*" : " "), WiFi.SSID(i).c_str(),
macToString(WiFi.BSSID(i)).c_str(),
wifiSecToStr(WiFi.encryptionType(i)),
WiFi.channel(i), WiFi.RSSI(i)
);
}
WiFi.scanDelete();
}
});
edgentConsole.addCommand("firmware", [](int argc, const char** argv) {
if (argc < 1 || 0 == strcmp(argv[0], "info")) {
unsigned sketchSize = ESP.getSketchSize();
edgentConsole.printf(" Version: %s (build %s)\n", BLYNK_FIRMWARE_VERSION, __DATE__ " " __TIME__);
edgentConsole.printf(" Type: %s\n", BLYNK_FIRMWARE_TYPE);
edgentConsole.printf(" Platform: %s\n", BLYNK_INFO_DEVICE);
edgentConsole.printf(" SDK: %s\n", ESP.getSdkVersion());
if (const esp_partition_t* running = esp_ota_get_running_partition()) {
edgentConsole.printf(" Partition: %s (%dK)\n", running->label, running->size / 1024);
edgentConsole.printf(" App size: %dK (%d%%)\n", sketchSize/1024, (sketchSize*100)/(running->size));
edgentConsole.printf(" App MD5: %s\n", ESP.getSketchMD5().c_str());
}
} else if (0 == strcmp(argv[0], "rollback")) {
if (Update.rollBack()) {
edgentConsole.print(R"json({"status":"ok"})json" "\n");
edgentTimer.setTimeout(50, restartMCU);
} else {
edgentConsole.print(R"json({"status":"error"})json" "\n");
}
}
});
edgentConsole.addCommand("status", [](int argc, const char** argv) {
const int64_t t = esp_timer_get_time() / 1000000;
unsigned secs = t % BLYNK_SECS_PER_MIN;
unsigned mins = (t / BLYNK_SECS_PER_MIN) % BLYNK_SECS_PER_MIN;
unsigned hrs = (t % BLYNK_SECS_PER_DAY) / BLYNK_SECS_PER_HOUR;
unsigned days = t / BLYNK_SECS_PER_DAY;
edgentConsole.printf(" Uptime: %dd %dh %dm %ds\n", days, hrs, mins, secs);
edgentConsole.printf(" Chip: %s rev %d\n", ESP.getChipModel(), ESP.getChipRevision());
edgentConsole.printf(" Flash: %dK\n", ESP.getFlashChipSize() / 1024);
edgentConsole.printf(" Stack unused: %d\n", uxTaskGetStackHighWaterMark(NULL));
edgentConsole.printf(" Heap free: %d / %d\n", ESP.getFreeHeap(), ESP.getHeapSize());
edgentConsole.printf(" max alloc: %d\n", ESP.getMaxAllocHeap());
edgentConsole.printf(" min free: %d\n", ESP.getMinFreeHeap());
if (ESP.getPsramSize()) {
edgentConsole.printf(" PSRAM free: %d / %d\n", ESP.getFreePsram(), ESP.getPsramSize());
}
#ifdef BLYNK_FS
uint32_t fs_total = BLYNK_FS.totalBytes();
edgentConsole.printf(" FS free: %d / %d\n", (fs_total-BLYNK_FS.usedBytes()), fs_total);
#endif
});
#ifdef BLYNK_FS
edgentConsole.addCommand("ls", [](int argc, const char** argv) {
const char* path = (argc < 1) ? "/" : argv[0];
File rootDir = BLYNK_FS.open(path);
while (File f = rootDir.openNextFile()) {
#if defined(BLYNK_USE_SPIFFS) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 0, 0))
String fn = f.name();
#else
String fn = f.path();
#endif
MD5Builder md5;
md5.begin();
md5.addStream(f, f.size());
md5.calculate();
String md5str = md5.toString();
edgentConsole.printf("%8d %-24s %s\n",
f.size(), fn.c_str(),
md5str.substring(0,8).c_str());
}
});
edgentConsole.addCommand("rm", [](int argc, const char** argv) {
if (argc < 1) return;
for (int i=0; i<argc; i++) {
const char* fn = argv[i];
if (BLYNK_FS.remove(fn)) {
edgentConsole.printf("Removed %s\n", fn);
} else {
edgentConsole.printf("Removing %s failed\n", fn);
}
}
});
edgentConsole.addCommand("mv", [](int argc, const char** argv) {
if (argc != 2) return;
if (!BLYNK_FS.rename(argv[0], argv[1])) {
edgentConsole.print("Rename failed\n");
}
});
edgentConsole.addCommand("cat", [](int argc, const char** argv) {
if (argc != 1) return;
if (!BLYNK_FS.exists(argv[0])) {
edgentConsole.print("File not found\n");
return;
}
if (File f = BLYNK_FS.open(argv[0], FILE_READ)) {
while (f.available()) {
edgentConsole.print((char)f.read());
}
edgentConsole.print("\n");
} else {
edgentConsole.print("Cannot open file\n");
}
});
edgentConsole.addCommand("echo", [](int argc, const char** argv) {
if (argc != 2) return;
if (File f = BLYNK_FS.open(argv[1], FILE_WRITE)) {
if (!f.print(argv[0])) {
edgentConsole.print("Cannot write file\n");
}
} else {
edgentConsole.print("Cannot open file\n");
}
});
#endif
}
BLYNK_WRITE(InternalPinDBG) {
String cmd = String(param.asStr()) + "\n";
edgentConsole.runCommand((char*)cmd.c_str());
}

View File

@@ -0,0 +1,55 @@
/*************************************************************
Blynk is a platform with iOS and Android apps to control
ESP32, Arduino, Raspberry Pi and the likes over the Internet.
You can easily build mobile and web interfaces for any
projects by simply dragging and dropping widgets.
Downloads, docs, tutorials: https://www.blynk.io
Sketch generator: https://examples.blynk.cc
Blynk community: https://community.blynk.cc
Follow us: https://www.fb.com/blynkapp
https://twitter.com/blynk_app
Blynk library is licensed under MIT license
*************************************************************
Blynk.Edgent implements:
- Blynk.Inject - Dynamic WiFi credentials provisioning
- Blynk.Air - Over The Air firmware updates
- Device state indication using a physical LED
- Credentials reset using a physical Button
*************************************************************/
/* Fill in information from your Blynk Template here */
/* Read more: https://bit.ly/BlynkInject */
//#define BLYNK_TEMPLATE_ID "TMPxxxxxx"
//#define BLYNK_TEMPLATE_NAME "Device"
#define BLYNK_FIRMWARE_VERSION "0.1.0"
#define BLYNK_PRINT Serial
//#define BLYNK_DEBUG
#define APP_DEBUG
// Uncomment your board, or configure a custom board in Settings.h
//#define USE_ESP32_DEV_MODULE
//#define USE_ESP32C3_DEV_MODULE
//#define USE_ESP32S2_DEV_KIT
//#define USE_WROVER_BOARD
//#define USE_TTGO_T7
//#define USE_TTGO_T_OI
#include "BlynkEdgent.h"
void setup()
{
Serial.begin(115200);
delay(100);
BlynkEdgent.begin();
}
void loop() {
BlynkEdgent.run();
}

View File

@@ -0,0 +1,311 @@
#if defined(BOARD_LED_PIN_WS2812)
#include <Adafruit_NeoPixel.h> // Library: https://github.com/adafruit/Adafruit_NeoPixel
Adafruit_NeoPixel rgb = Adafruit_NeoPixel(1, BOARD_LED_PIN_WS2812, NEO_GRB + NEO_KHZ800);
#endif
void indicator_run();
#if !defined(BOARD_LED_BRIGHTNESS)
#define BOARD_LED_BRIGHTNESS 255
#endif
#if defined(BOARD_LED_PIN_WS2812) || defined(BOARD_LED_PIN_R)
#define BOARD_LED_IS_RGB
#endif
#define DIMM(x) ((uint32_t)(x)*(BOARD_LED_BRIGHTNESS)/255)
#define RGB(r,g,b) (DIMM(r) << 16 | DIMM(g) << 8 | DIMM(b) << 0)
#define TO_PWM(x) ((uint32_t)(x)*(BOARD_PWM_MAX)/255)
class Indicator {
public:
enum Colors {
COLOR_BLACK = RGB(0x00, 0x00, 0x00),
COLOR_WHITE = RGB(0xFF, 0xFF, 0xE7),
COLOR_BLUE = RGB(0x0D, 0x36, 0xFF),
COLOR_BLYNK = RGB(0x2E, 0xFF, 0xB9),
COLOR_RED = RGB(0xFF, 0x10, 0x08),
COLOR_MAGENTA = RGB(0xA7, 0x00, 0xFF),
};
Indicator() {
}
void init() {
m_Counter = 0;
initLED();
}
uint32_t run() {
State currState = BlynkState::get();
// Reset counter if indicator state changes
if (m_PrevState != currState) {
m_PrevState = currState;
m_Counter = 0;
}
const long t = millis();
if (g_buttonPressed) {
if (t - g_buttonPressTime > BUTTON_HOLD_TIME_ACTION) { return beatLED(COLOR_WHITE, (int[]){ 100, 100 }); }
if (t - g_buttonPressTime > BUTTON_HOLD_TIME_INDICATION) { return waveLED(COLOR_WHITE, 1000); }
}
switch (currState) {
case MODE_RESET_CONFIG:
case MODE_WAIT_CONFIG: return beatLED(COLOR_BLUE, (int[]){ 50, 500 });
case MODE_CONFIGURING: return beatLED(COLOR_BLUE, (int[]){ 200, 200 });
case MODE_CONNECTING_NET: return beatLED(COLOR_BLYNK, (int[]){ 50, 500 });
case MODE_CONNECTING_CLOUD: return beatLED(COLOR_BLYNK, (int[]){ 100, 100 });
case MODE_RUNNING: return waveLED(COLOR_BLYNK, 5000);
case MODE_OTA_UPGRADE: return beatLED(COLOR_MAGENTA, (int[]){ 50, 50 });
default: return beatLED(COLOR_RED, (int[]){ 80, 100, 80, 1000 } );
}
}
protected:
/*
* LED drivers
*/
#if defined(BOARD_LED_PIN_WS2812) // Addressable, NeoPixel RGB LED
void initLED() {
rgb.begin();
setRGB(COLOR_BLACK);
}
void setRGB(uint32_t color) {
rgb.setPixelColor(0, color);
rgb.show();
}
#elif defined(BOARD_LED_PIN_R) // Normal RGB LED (common anode or common cathode)
void initLED() {
ledcAttachPin(BOARD_LED_PIN_R, BOARD_LEDC_CHANNEL_1);
ledcAttachPin(BOARD_LED_PIN_G, BOARD_LEDC_CHANNEL_2);
ledcAttachPin(BOARD_LED_PIN_B, BOARD_LEDC_CHANNEL_3);
ledcSetup(BOARD_LEDC_CHANNEL_1, BOARD_LEDC_BASE_FREQ, BOARD_LEDC_TIMER_BITS);
ledcSetup(BOARD_LEDC_CHANNEL_2, BOARD_LEDC_BASE_FREQ, BOARD_LEDC_TIMER_BITS);
ledcSetup(BOARD_LEDC_CHANNEL_3, BOARD_LEDC_BASE_FREQ, BOARD_LEDC_TIMER_BITS);
}
void setRGB(uint32_t color) {
uint8_t r = (color & 0xFF0000) >> 16;
uint8_t g = (color & 0x00FF00) >> 8;
uint8_t b = (color & 0x0000FF);
#if BOARD_LED_INVERSE
ledcWrite(BOARD_LEDC_CHANNEL_1, TO_PWM(255 - r));
ledcWrite(BOARD_LEDC_CHANNEL_2, TO_PWM(255 - g));
ledcWrite(BOARD_LEDC_CHANNEL_3, TO_PWM(255 - b));
#else
ledcWrite(BOARD_LEDC_CHANNEL_1, TO_PWM(r));
ledcWrite(BOARD_LEDC_CHANNEL_2, TO_PWM(g));
ledcWrite(BOARD_LEDC_CHANNEL_3, TO_PWM(b));
#endif
}
#elif defined(BOARD_LED_PIN) // Single color LED
void initLED() {
ledcSetup(BOARD_LEDC_CHANNEL_1, BOARD_LEDC_BASE_FREQ, BOARD_LEDC_TIMER_BITS);
ledcAttachPin(BOARD_LED_PIN, BOARD_LEDC_CHANNEL_1);
}
void setLED(uint32_t color) {
#if BOARD_LED_INVERSE
ledcWrite(BOARD_LEDC_CHANNEL_1, TO_PWM(255 - color));
#else
ledcWrite(BOARD_LEDC_CHANNEL_1, TO_PWM(color));
#endif
}
#else
#warning Invalid LED configuration.
void initLED() {
}
void setLED(uint32_t color) {
}
#endif
/*
* Animations
*/
uint32_t skipLED() {
return 20;
}
#if defined(BOARD_LED_IS_RGB)
template<typename T>
uint32_t beatLED(uint32_t onColor, const T& beat) {
const uint8_t cnt = sizeof(beat)/sizeof(beat[0]);
setRGB((m_Counter % 2 == 0) ? onColor : (uint32_t)COLOR_BLACK);
uint32_t next = beat[m_Counter % cnt];
m_Counter = (m_Counter+1) % cnt;
return next;
}
uint32_t waveLED(uint32_t colorMax, unsigned breathePeriod) {
uint8_t redMax = (colorMax & 0xFF0000) >> 16;
uint8_t greenMax = (colorMax & 0x00FF00) >> 8;
uint8_t blueMax = (colorMax & 0x0000FF);
// Brightness will rise from 0 to 128, then fall back to 0
uint8_t brightness = (m_Counter < 128) ? m_Counter : 255 - m_Counter;
// Multiply our three colors by the brightness:
redMax *= ((float)brightness / 128.0);
greenMax *= ((float)brightness / 128.0);
blueMax *= ((float)brightness / 128.0);
// And turn the LED to that color:
setRGB((redMax << 16) | (greenMax << 8) | blueMax);
// This function relies on the 8-bit, unsigned m_Counter rolling over.
m_Counter = (m_Counter+1) % 256;
return breathePeriod / 256;
}
#else
template<typename T>
uint32_t beatLED(uint32_t, const T& beat) {
const uint8_t cnt = sizeof(beat)/sizeof(beat[0]);
setLED((m_Counter % 2 == 0) ? BOARD_LED_BRIGHTNESS : 0);
uint32_t next = beat[m_Counter % cnt];
m_Counter = (m_Counter+1) % cnt;
return next;
}
uint32_t waveLED(uint32_t, unsigned breathePeriod) {
uint32_t brightness = (m_Counter < 128) ? m_Counter : 255 - m_Counter;
setLED(DIMM(brightness*2));
// This function relies on the 8-bit, unsigned m_Counter rolling over.
m_Counter = (m_Counter+1) % 256;
return breathePeriod / 256;
}
#endif
private:
uint8_t m_Counter;
State m_PrevState;
};
Indicator indicator;
/*
* Animation timers
*/
#if defined(USE_TICKER)
#include <Ticker.h>
Ticker blinker;
void indicator_run() {
uint32_t returnTime = indicator.run();
if (returnTime) {
blinker.attach_ms(returnTime, indicator_run);
}
}
void indicator_init() {
indicator.init();
blinker.attach_ms(100, indicator_run);
}
#elif defined(USE_PTHREAD)
#include <pthread.h>
pthread_t blinker;
void* indicator_thread(void*) {
while (true) {
uint32_t returnTime = indicator.run();
returnTime = BlynkMathClamp(returnTime, 1, 10000);
vTaskDelay(returnTime);
}
}
void indicator_init() {
indicator.init();
pthread_create(&blinker, NULL, indicator_thread, NULL);
}
#elif defined(USE_TIMER_ONE)
#include <TimerOne.h>
void indicator_run() {
uint32_t returnTime = indicator.run();
if (returnTime) {
Timer1.initialize(returnTime*1000);
}
}
void indicator_init() {
indicator.init();
Timer1.initialize(100*1000);
Timer1.attachInterrupt(indicator_run);
}
#elif defined(USE_TIMER_THREE)
#include <TimerThree.h>
void indicator_run() {
uint32_t returnTime = indicator.run();
if (returnTime) {
Timer3.initialize(returnTime*1000);
}
}
void indicator_init() {
indicator.init();
Timer3.initialize(100*1000);
Timer3.attachInterrupt(indicator_run);
}
#elif defined(USE_TIMER_FIVE)
#include <Timer5.h> // Library: https://github.com/michael71/Timer5
int indicator_counter = -1;
void indicator_run() {
indicator_counter -= 10;
if (indicator_counter < 0) {
indicator_counter = indicator.run();
}
}
void indicator_init() {
indicator.init();
MyTimer5.begin(1000/10);
MyTimer5.attachInterrupt(indicator_run);
MyTimer5.start();
}
#else
#warning LED indicator needs a functional timer!
void indicator_run() {}
void indicator_init() {}
#endif

View File

@@ -0,0 +1,91 @@
#include <WiFi.h>
#include <Update.h>
#include <HTTPClient.h>
String overTheAirURL;
extern BlynkTimer edgentTimer;
BLYNK_WRITE(InternalPinOTA) {
overTheAirURL = param.asString();
edgentTimer.setTimeout(2000L, [](){
// Start OTA
Blynk.logEvent("sys_ota", "OTA started");
// Disconnect, not to interfere with OTA process
Blynk.disconnect();
BlynkState::set(MODE_OTA_UPGRADE);
});
}
void enterOTA() {
BlynkState::set(MODE_OTA_UPGRADE);
DEBUG_PRINT(String("Firmware update URL: ") + overTheAirURL);
HTTPClient http;
http.begin(overTheAirURL);
const char* headerkeys[] = { "x-MD5" };
http.collectHeaders(headerkeys, sizeof(headerkeys)/sizeof(char*));
int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK) {
DEBUG_PRINT("HTTP response should be 200");
BlynkState::set(MODE_ERROR);
return;
}
int contentLength = http.getSize();
if (contentLength <= 0) {
DEBUG_PRINT("Content-Length not defined");
BlynkState::set(MODE_ERROR);
return;
}
bool canBegin = Update.begin(contentLength);
if (!canBegin) {
DEBUG_PRINT("Not enough space to begin OTA");
BlynkState::set(MODE_ERROR);
return;
}
if (http.hasHeader("x-MD5")) {
String md5 = http.header("x-MD5");
if (md5.length() == 32) {
md5.toLowerCase();
DEBUG_PRINT("Expected MD5: " + md5);
Update.setMD5(md5.c_str());
}
}
#ifdef BLYNK_FS
BLYNK_FS.end();
#endif
Client& client = http.getStream();
int written = Update.writeStream(client);
if (written != contentLength) {
DEBUG_PRINT(String("OTA written ") + written + " / " + contentLength + " bytes");
BlynkState::set(MODE_ERROR);
return;
}
if (!Update.end()) {
DEBUG_PRINT("Error #" + String(Update.getError()));
BlynkState::set(MODE_ERROR);
return;
}
if (!Update.isFinished()) {
DEBUG_PRINT("Update failed.");
BlynkState::set(MODE_ERROR);
return;
}
DEBUG_PRINT("=== Update successfully completed. Rebooting.");
restartMCU();
}

View File

@@ -0,0 +1,53 @@
#ifdef BOARD_BUTTON_PIN
volatile bool g_buttonPressed = false;
volatile uint32_t g_buttonPressTime = -1;
void button_action(void)
{
BlynkState::set(MODE_RESET_CONFIG);
}
void button_change(void)
{
#if BOARD_BUTTON_ACTIVE_LOW
bool buttonState = !digitalRead(BOARD_BUTTON_PIN);
#else
bool buttonState = digitalRead(BOARD_BUTTON_PIN);
#endif
if (buttonState && !g_buttonPressed) {
g_buttonPressTime = millis();
g_buttonPressed = true;
DEBUG_PRINT("Hold the button for 10 seconds to reset configuration...");
} else if (!buttonState && g_buttonPressed) {
g_buttonPressed = false;
uint32_t buttonHoldTime = millis() - g_buttonPressTime;
if (buttonHoldTime >= BUTTON_HOLD_TIME_ACTION) {
button_action();
} else if (buttonHoldTime >= BUTTON_PRESS_TIME_ACTION) {
// User action
}
g_buttonPressTime = -1;
}
}
void button_init()
{
#if BOARD_BUTTON_ACTIVE_LOW
pinMode(BOARD_BUTTON_PIN, INPUT_PULLUP);
#else
pinMode(BOARD_BUTTON_PIN, INPUT_PULLDOWN);
#endif
attachInterrupt(BOARD_BUTTON_PIN, button_change, CHANGE);
}
#else
#define g_buttonPressed false
#define g_buttonPressTime 0
void button_init() {}
#endif

View File

@@ -0,0 +1,133 @@
/*
* Board configuration (see examples below).
*/
#if defined(USE_WROVER_BOARD)
#define BOARD_BUTTON_PIN 15
#define BOARD_BUTTON_ACTIVE_LOW true
#define BOARD_LED_PIN_R 0
#define BOARD_LED_PIN_G 2
#define BOARD_LED_PIN_B 4
#define BOARD_LED_INVERSE false
#define BOARD_LED_BRIGHTNESS 128
#elif defined(USE_TTGO_T7)
#warning "This board does not have a button. Connect a button to gpio0 <> GND"
#define BOARD_BUTTON_PIN 0
#define BOARD_BUTTON_ACTIVE_LOW true
#define BOARD_LED_PIN 19
#define BOARD_LED_INVERSE false
#define BOARD_LED_BRIGHTNESS 64
#elif defined(USE_TTGO_T_OI)
#warning "This board does not have a button. Connect a button to gpio0 <> GND"
#define BOARD_BUTTON_PIN 0
#define BOARD_BUTTON_ACTIVE_LOW true
#define BOARD_LED_PIN 3
#define BOARD_LED_INVERSE false
#define BOARD_LED_BRIGHTNESS 64
#elif defined(USE_ESP32_DEV_MODULE)
#warning "The LED of this board is not configured"
#define BOARD_BUTTON_PIN 0
#define BOARD_BUTTON_ACTIVE_LOW true
#elif defined(USE_ESP32C3_DEV_MODULE)
#define BOARD_BUTTON_PIN 9
#define BOARD_BUTTON_ACTIVE_LOW true
#define BOARD_LED_PIN_WS2812 8
#define BOARD_LED_INVERSE false
#define BOARD_LED_BRIGHTNESS 32
#elif defined(USE_ESP32S2_DEV_KIT)
#define BOARD_BUTTON_PIN 0
#define BOARD_BUTTON_ACTIVE_LOW true
#define BOARD_LED_PIN 19
#define BOARD_LED_INVERSE false
#define BOARD_LED_BRIGHTNESS 128
#else
#warning "Custom board configuration is used"
#define BOARD_BUTTON_PIN 0 // Pin where user button is attached
#define BOARD_BUTTON_ACTIVE_LOW true // true if button is "active-low"
//#define BOARD_LED_PIN 4 // Set LED pin - if you have a single-color LED attached
//#define BOARD_LED_PIN_R 15 // Set R,G,B pins - if your LED is PWM RGB
//#define BOARD_LED_PIN_G 12
//#define BOARD_LED_PIN_B 13
//#define BOARD_LED_PIN_WS2812 4 // Set if your LED is WS2812 RGB
#define BOARD_LED_INVERSE false // true if LED is common anode, false if common cathode
#define BOARD_LED_BRIGHTNESS 64 // 0..255 brightness control
#endif
/*
* Advanced options
*/
#define BUTTON_HOLD_TIME_INDICATION 3000
#define BUTTON_HOLD_TIME_ACTION 10000
#define BUTTON_PRESS_TIME_ACTION 50
#define BOARD_PWM_MAX 1023
#define BOARD_LEDC_CHANNEL_1 1
#define BOARD_LEDC_CHANNEL_2 2
#define BOARD_LEDC_CHANNEL_3 3
#define BOARD_LEDC_TIMER_BITS 10
#define BOARD_LEDC_BASE_FREQ 12000
#if !defined(CONFIG_DEVICE_PREFIX)
#define CONFIG_DEVICE_PREFIX "Blynk"
#endif
#if !defined(CONFIG_AP_URL)
#define CONFIG_AP_URL "blynk.setup"
#endif
#if !defined(CONFIG_DEFAULT_SERVER)
#define CONFIG_DEFAULT_SERVER "blynk.cloud"
#endif
#if !defined(CONFIG_DEFAULT_PORT)
#define CONFIG_DEFAULT_PORT 443
#endif
#define WIFI_CLOUD_MAX_RETRIES 500
#define WIFI_NET_CONNECT_TIMEOUT 50000
#define WIFI_CLOUD_CONNECT_TIMEOUT 50000
#define WIFI_AP_IP IPAddress(192, 168, 4, 1)
#define WIFI_AP_Subnet IPAddress(255, 255, 255, 0)
//#define WIFI_CAPTIVE_PORTAL_ENABLE
//#define USE_TICKER
//#define USE_TIMER_ONE
//#define USE_TIMER_THREE
//#define USE_TIMER_FIVE
#define USE_PTHREAD
#define BLYNK_NO_DEFAULT_BANNER
#if defined(APP_DEBUG)
#define DEBUG_PRINT(...) BLYNK_LOG1(__VA_ARGS__)
#define DEBUG_PRINTF(...) BLYNK_LOG(__VA_ARGS__)
#else
#define DEBUG_PRINT(...)
#define DEBUG_PRINTF(...)
#endif