10 changed files with 1428 additions and 0 deletions
@ -0,0 +1,117 @@ |
|||||
|
|
||||
|
extern "C" { |
||||
|
void app_loop(); |
||||
|
void eraseMcuConfig(); |
||||
|
void restartMCU(); |
||||
|
} |
||||
|
|
||||
|
#include "Settings.h" |
||||
|
#include <BlynkSimpleEsp32_SSL.h> |
||||
|
|
||||
|
#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_ID) || !defined(BLYNK_DEVICE_NAME) |
||||
|
#error "Please specify your BLYNK_TEMPLATE_ID and BLYNK_DEVICE_NAME" |
||||
|
#endif |
||||
|
|
||||
|
#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() |
||||
|
{ |
||||
|
Blynk.printBanner(); |
||||
|
DEBUG_PRINT("--------------------------"); |
||||
|
DEBUG_PRINT(String("Product: ") + BLYNK_DEVICE_NAME); |
||||
|
DEBUG_PRINT(String("Firmware: ") + BLYNK_FIRMWARE_VERSION " (build " __DATE__ " " __TIME__ ")"); |
||||
|
if (configStore.getFlag(CONFIG_FLAG_VALID)) { |
||||
|
DEBUG_PRINT(String("Token: ...") + (configStore.cloudToken+28)); |
||||
|
} |
||||
|
DEBUG_PRINT(String("Device: ") + BLYNK_INFO_DEVICE + " @ " + ESP.getCpuFreqMHz() + "MHz"); |
||||
|
DEBUG_PRINT(String("MAC: ") + WiFi.macAddress()); |
||||
|
DEBUG_PRINT(String("Flash: ") + ESP.getFlashChipSize() / 1024 + "K"); |
||||
|
DEBUG_PRINT(String("ESP sdk: ") + ESP.getSdkVersion()); |
||||
|
DEBUG_PRINT(String("Chip rev: ") + ESP.getChipRevision()); |
||||
|
DEBUG_PRINT(String("Free mem: ") + ESP.getFreeHeap()); |
||||
|
DEBUG_PRINT("--------------------------"); |
||||
|
} |
||||
|
|
||||
|
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
|
||||
|
|
||||
|
indicator_init(); |
||||
|
button_init(); |
||||
|
config_init(); |
||||
|
console_init(); |
||||
|
|
||||
|
printDeviceBanner(); |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
}; |
||||
|
|
||||
|
Edgent BlynkEdgent; |
||||
|
BlynkTimer edgentTimer; |
||||
|
|
||||
|
void app_loop() { |
||||
|
edgentTimer.run(); |
||||
|
edgentConsole.run(); |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
|
||||
|
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); |
||||
|
}; |
||||
@ -0,0 +1,77 @@ |
|||||
|
#define BLYNK_TEMPLATE_ID "TMPLsdSzyUnw" |
||||
|
#define BLYNK_DEVICE_NAME "CO2 100" |
||||
|
#define BLYNK_AUTH_TOKEN "28OeDXtCtS5dnMICcx2LuT3xO0Kt4myz" |
||||
|
|
||||
|
#define BLYNK_FIRMWARE_VERSION "0.1.0" |
||||
|
#define BLYNK_PRINT Serial |
||||
|
//#define BLYNK_DEBUG
|
||||
|
|
||||
|
#define APP_DEBUG |
||||
|
|
||||
|
#include <MHZ19.h> |
||||
|
#include <SoftwareSerial.h> |
||||
|
#include "BlynkEdgent.h" |
||||
|
// Pin RX Arduino conectado al pin TX del MHZ19
|
||||
|
#define RX_PIN 16 |
||||
|
// Pin TX Arduino conectado al pin RX del MHZ19
|
||||
|
#define TX_PIN 17 |
||||
|
|
||||
|
// Objeto para sensor MHZ19
|
||||
|
MHZ19 myMHZ19; |
||||
|
// Serial requerido por el MHZ19
|
||||
|
SoftwareSerial mySerial(RX_PIN, TX_PIN); |
||||
|
|
||||
|
|
||||
|
// Contador para temporizar las mediciones
|
||||
|
unsigned long timer = 0; |
||||
|
|
||||
|
void setup() { |
||||
|
Serial.begin(9600); |
||||
|
mySerial.begin(9600); |
||||
|
myMHZ19.begin(mySerial); |
||||
|
// Turn auto calibration ON (OFF autoCalibration(false))
|
||||
|
myMHZ19.autoCalibration(); |
||||
|
BlynkEdgent.begin(); |
||||
|
} |
||||
|
|
||||
|
void loop() { |
||||
|
BlynkEdgent.run(); |
||||
|
Blynk.virtualWrite(V24, WiFi.SSID()); |
||||
|
Blynk.virtualWrite(V22, generateChipID()); |
||||
|
medicionCO2(); |
||||
|
} |
||||
|
|
||||
|
void medicionCO2(){ |
||||
|
if (millis() - timer >= 10000) { |
||||
|
|
||||
|
// Obtener la medición de CO2 actual como ppm
|
||||
|
int nivelCO2 = myMHZ19.getCO2(); |
||||
|
|
||||
|
if(nivelCO2<900){ |
||||
|
Blynk.setProperty(V6, "color", "#7cff00"); |
||||
|
}else{ |
||||
|
Blynk.setProperty(V6, "color", "#ff1e1e"); |
||||
|
} |
||||
|
|
||||
|
// Mostrar el nivel de CO2 en el monitor serie
|
||||
|
Serial.print("CO2 (ppm): "); |
||||
|
Serial.println(nivelCO2); |
||||
|
Blynk.virtualWrite(V6, nivelCO2); |
||||
|
|
||||
|
timer = millis(); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
String generateChipID() |
||||
|
{ |
||||
|
String aux; |
||||
|
uint64_t chipid = ESP.getEfuseMac(); |
||||
|
char chip[14]; |
||||
|
chip[0] = NULL; |
||||
|
for (int i = 0; i < 6; i++) |
||||
|
sprintf(chip, "%s%02X", chip, (uint8_t)(chipid >> 8 * i)); |
||||
|
aux = String(chip); |
||||
|
return aux; |
||||
|
} |
||||
@ -0,0 +1,488 @@ |
|||||
|
|
||||
|
#include <WiFiClient.h> |
||||
|
#include <WebServer.h> |
||||
|
#include <DNSServer.h> |
||||
|
#include <Update.h> |
||||
|
|
||||
|
#include <nvs.h> |
||||
|
#include <nvs_flash.h> |
||||
|
|
||||
|
WebServer server(80); |
||||
|
DNSServer dnsServer; |
||||
|
const byte DNS_PORT = 53; |
||||
|
|
||||
|
#ifdef BLYNK_USE_SPIFFS |
||||
|
#include "SPIFFS.h" |
||||
|
#else |
||||
|
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 |
||||
|
|
||||
|
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) {}; |
||||
|
} |
||||
|
|
||||
|
void eraseMcuConfig() { |
||||
|
// Erase ESP32 NVS
|
||||
|
int err; |
||||
|
//err=nvs_flash_init();
|
||||
|
//BLYNK_LOG2("nvs_flash_init: ", err ? String(err) : "Success");
|
||||
|
err=nvs_flash_erase(); |
||||
|
BLYNK_LOG2("nvs_flash_erase: ", err ? String(err) : "Success"); |
||||
|
} |
||||
|
|
||||
|
void getWiFiName(char* buff, size_t len, 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); |
||||
|
} |
||||
|
unique &= 0xFFFFF; |
||||
|
|
||||
|
if (withPrefix) { |
||||
|
snprintf(buff, len, "Blynk %s-%05X", BLYNK_DEVICE_NAME, unique); |
||||
|
} else { |
||||
|
snprintf(buff, len, "%s-%05X", BLYNK_DEVICE_NAME, unique); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void enterConfigMode() |
||||
|
{ |
||||
|
char ssidBuff[64]; |
||||
|
getWiFiName(ssidBuff, sizeof(ssidBuff)); |
||||
|
|
||||
|
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(ssidBuff); |
||||
|
delay(500); |
||||
|
|
||||
|
IPAddress myIP = WiFi.softAPIP(); |
||||
|
DEBUG_PRINT(String("AP SSID: ") + ssidBuff); |
||||
|
DEBUG_PRINT(String("AP IP: ") + myIP[0] + "." + myIP[1] + "." + myIP[2] + "." + myIP[3]); |
||||
|
|
||||
|
// 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
|
||||
|
Update.printError(BLYNK_PRINT); |
||||
|
} |
||||
|
} else if (upload.status == UPLOAD_FILE_WRITE) { |
||||
|
/* flashing firmware to ESP*/ |
||||
|
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { |
||||
|
Update.printError(BLYNK_PRINT); |
||||
|
} |
||||
|
BLYNK_PRINT.print("."); |
||||
|
} else if (upload.status == UPLOAD_FILE_END) { |
||||
|
BLYNK_PRINT.println(); |
||||
|
DEBUG_PRINT("Finishing..."); |
||||
|
if (Update.end(true)) { //true to set the size to the current progress
|
||||
|
DEBUG_PRINT("Update Success. Rebooting"); |
||||
|
} else { |
||||
|
Update.printError(BLYNK_PRINT); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
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 save = 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.setFlag(CONFIG_FLAG_VALID, false); |
||||
|
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 (save) { |
||||
|
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); |
||||
|
|
||||
|
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", []() { |
||||
|
DEBUG_PRINT("Sending board info..."); |
||||
|
const char* tmpl = BLYNK_TEMPLATE_ID; |
||||
|
char ssidBuff[64]; |
||||
|
getWiFiName(ssidBuff, sizeof(ssidBuff)); |
||||
|
char buff[512]; |
||||
|
snprintf(buff, sizeof(buff), |
||||
|
R"json({"board":"%s","tmpl_id":"%s","fw_type":"%s","fw_ver":"%s","ssid":"%s","bssid":"%s","last_error":%d,"wifi_scan":true,"static_ip":true})json", |
||||
|
BLYNK_DEVICE_NAME, |
||||
|
tmpl ? tmpl : "Unknown", |
||||
|
BLYNK_FIRMWARE_TYPE, |
||||
|
BLYNK_FIRMWARE_VERSION, |
||||
|
ssidBuff, |
||||
|
WiFi.softAPmacAddress().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]; |
||||
|
|
||||
|
const char* sec; |
||||
|
switch (WiFi.encryptionType(id)) { |
||||
|
case WIFI_AUTH_WEP: sec = "WEP"; break; |
||||
|
case WIFI_AUTH_WPA_PSK: sec = "WPA/PSK"; break; |
||||
|
case WIFI_AUTH_WPA2_PSK: sec = "WPA2/PSK"; break; |
||||
|
case WIFI_AUTH_WPA_WPA2_PSK: sec = "WPA/WPA2/PSK"; break; |
||||
|
case WIFI_AUTH_OPEN: sec = "OPEN"; break; |
||||
|
default: sec = "unknown"; break; |
||||
|
} |
||||
|
|
||||
|
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), |
||||
|
sec, |
||||
|
WiFi.channel(id) |
||||
|
); |
||||
|
|
||||
|
result += buff; |
||||
|
if (i != wifi_nets-1) result += ",\n"; |
||||
|
} |
||||
|
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_USE_SPIFFS |
||||
|
if (SPIFFS.begin()) { |
||||
|
server.serveStatic("/img/favicon.png", SPIFFS, "/img/favicon.png"); |
||||
|
server.serveStatic("/img/logo.png", SPIFFS, "/img/logo.png"); |
||||
|
server.serveStatic("/", SPIFFS, "/index.html"); |
||||
|
} else { |
||||
|
DEBUG_PRINT("Webpage: No SPIFFS"); |
||||
|
} |
||||
|
#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_WAIT_CONFIG) && WiFi.softAPgetStationNum() > 0) { |
||||
|
BlynkState::set(MODE_CONFIGURING); |
||||
|
} else if (BlynkState::is(MODE_CONFIGURING) && WiFi.softAPgetStationNum() == 0) { |
||||
|
BlynkState::set(MODE_WAIT_CONFIG); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
server.stop(); |
||||
|
|
||||
|
#ifdef BLYNK_USE_SPIFFS |
||||
|
SPIFFS.end(); |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
void enterConnectNet() { |
||||
|
BlynkState::set(MODE_CONNECTING_NET); |
||||
|
DEBUG_PRINT(String("Connecting to WiFi: ") + configStore.wifiSSID); |
||||
|
|
||||
|
char ssidBuff[64]; |
||||
|
getWiFiName(ssidBuff, sizeof(ssidBuff)); |
||||
|
String hostname(ssidBuff); |
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
BlynkState::set(MODE_CONNECTING_CLOUD); |
||||
|
} else { |
||||
|
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()) && |
||||
|
(!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); |
||||
|
} else if (Blynk.connected()) { |
||||
|
BlynkState::set(MODE_RUNNING); |
||||
|
|
||||
|
if (!configStore.getFlag(CONFIG_FLAG_VALID)) { |
||||
|
configStore.last_error = BLYNK_PROV_ERR_NONE; |
||||
|
configStore.setFlag(CONFIG_FLAG_VALID, true); |
||||
|
config_save(); |
||||
|
} |
||||
|
} else { |
||||
|
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(); |
||||
|
} |
||||
@ -0,0 +1,146 @@ |
|||||
|
|
||||
|
#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> |
||||
|
Preferences preferences; |
||||
|
|
||||
|
void config_load() |
||||
|
{ |
||||
|
memset(&configStore, 0, sizeof(configStore)); |
||||
|
preferences.getBytes("config", &configStore, sizeof(configStore)); |
||||
|
if (configStore.magic != configDefault.magic) { |
||||
|
DEBUG_PRINT("Using default config."); |
||||
|
configStore = configDefault; |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool config_save() |
||||
|
{ |
||||
|
preferences.putBytes("config", &configStore, sizeof(configStore)); |
||||
|
DEBUG_PRINT("Configuration stored to flash"); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
bool config_init() |
||||
|
{ |
||||
|
preferences.begin("blynk", false); |
||||
|
config_load(); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void enterResetConfig() |
||||
|
{ |
||||
|
DEBUG_PRINT("Resetting configuration!"); |
||||
|
configStore = configDefault; |
||||
|
config_save(); |
||||
|
eraseMcuConfig(); |
||||
|
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(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
|
||||
|
#include <Blynk/BlynkConsole.h> |
||||
|
|
||||
|
BlynkConsole edgentConsole; |
||||
|
|
||||
|
void console_init() |
||||
|
{ |
||||
|
edgentConsole.init(BLYNK_PRINT); |
||||
|
|
||||
|
edgentConsole.print("\n>"); |
||||
|
|
||||
|
edgentConsole.addCommand("reboot", []() { |
||||
|
edgentConsole.print(R"json({"status":"OK","msg":"resetting device"})json" "\n"); |
||||
|
delay(100); |
||||
|
restartMCU(); |
||||
|
}); |
||||
|
|
||||
|
edgentConsole.addCommand("config", []() { |
||||
|
edgentConsole.print(R"json({"status":"OK","msg":"entering configuration mode"})json" "\n"); |
||||
|
BlynkState::set(MODE_WAIT_CONFIG); |
||||
|
}); |
||||
|
|
||||
|
edgentConsole.addCommand("devinfo", []() { |
||||
|
edgentConsole.printf( |
||||
|
R"json({"board":"%s","tmpl_id":"%s","fw_type":"%s","fw_ver":"%s"})json" "\n", |
||||
|
BLYNK_DEVICE_NAME, |
||||
|
BLYNK_TEMPLATE_ID, |
||||
|
BLYNK_FIRMWARE_TYPE, |
||||
|
BLYNK_FIRMWARE_VERSION |
||||
|
); |
||||
|
}); |
||||
|
|
||||
|
edgentConsole.addCommand("netinfo", []() { |
||||
|
char ssidBuff[64]; |
||||
|
getWiFiName(ssidBuff, sizeof(ssidBuff)); |
||||
|
|
||||
|
byte mac[6] = { 0, }; |
||||
|
WiFi.macAddress(mac); |
||||
|
|
||||
|
edgentConsole.printf( |
||||
|
R"json({"ssid":"%s","bssid":"%02x:%02x:%02x:%02x:%02x:%02x","rssi":%d})json" "\n", |
||||
|
ssidBuff, |
||||
|
mac[5], mac[4], mac[3], mac[2], mac[1], mac[0], |
||||
|
WiFi.RSSI() |
||||
|
); |
||||
|
}); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
BLYNK_WRITE(InternalPinDBG) { |
||||
|
String cmd = String(param.asStr()) + "\n"; |
||||
|
edgentConsole.runCommand((char*)cmd.c_str()); |
||||
|
} |
||||
@ -0,0 +1,309 @@ |
|||||
|
|
||||
|
#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; |
||||
|
} |
||||
|
|
||||
|
if (g_buttonPressed) { |
||||
|
if (millis() - g_buttonPressTime > BUTTON_HOLD_TIME_ACTION) { return beatLED(COLOR_WHITE, (int[]){ 100, 100 }); } |
||||
|
if (millis() - 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 |
||||
@ -0,0 +1,74 @@ |
|||||
|
|
||||
|
#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); |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
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(); |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
|
||||
|
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 { |
||||
|
// 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); |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
|
||||
|
/*
|
||||
|
* 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) |
||||
|
|
||||
|
// This board does not have a built-in 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 |
||||
|
|
||||
|
#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 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 |
||||
|
|
||||
|
#define CONFIG_AP_URL "blynk.setup" |
||||
|
#define CONFIG_DEFAULT_SERVER "blynk.cloud" |
||||
|
#define CONFIG_DEFAULT_PORT 443 |
||||
|
|
||||
|
#define WIFI_NET_CONNECT_TIMEOUT 30000 |
||||
|
#define WIFI_CLOUD_CONNECT_TIMEOUT 30000 |
||||
|
#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__) |
||||
|
#else |
||||
|
#define DEBUG_PRINT(...) |
||||
|
#endif |
||||
Loading…
Reference in new issue