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

View File

@@ -0,0 +1,155 @@
extern "C" {
#include "user_interface.h"
void app_loop();
void restartMCU();
}
#include "Settings.h"
#include <BlynkSimpleEsp8266_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
#if defined(BLYNK_FS) && defined(ESP8266)
#define BLYNK_FILE_READ "r"
#define BLYNK_FILE_WRITE "w"
#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(" Boot ver: "); BLYNK_PRINT.println(ESP.getBootVersion());
BLYNK_PRINT.print(" SDK: "); BLYNK_PRINT.println(ESP.getSdkVersion());
BLYNK_PRINT.print(" ESP Core: "); BLYNK_PRINT.println(ESP.getCoreVersion());
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()
{
#ifdef BLYNK_FS
BLYNK_FS.begin();
#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,504 @@
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h>
#include <DNSServer.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
ESP8266WebServer server(80);
ESP8266HTTPUpdateServer httpUpdater;
DNSServer dnsServer;
const byte DNS_PORT = 53;
static int connectNetRetries = WIFI_CLOUD_MAX_RETRIES;
static int connectBlynkRetries = WIFI_CLOUD_MAX_RETRIES;
void restartMCU() {
ESP.restart();
ESP.reset();
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)
{
byte mac[6] = { 0, };
WiFi.macAddress(mac);
uint32_t unique = 0;
for (int i=0; i<4; i++) {
unique = BlynkCRC32(&mac, sizeof(mac), 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(uint8_t t) {
switch (t) {
case ENC_TYPE_NONE: return "OPEN";
case ENC_TYPE_WEP: return "WEP";
case ENC_TYPE_TKIP: return "WPA";
case ENC_TYPE_CCMP: return "WPA2";
case ENC_TYPE_AUTO: return "WPA+WPA2";
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_STA);
WiFi.softAPConfig(WIFI_AP_IP, WIFI_AP_IP, WIFI_AP_Subnet);
WiFi.softAP(getWiFiName().c_str());
delay(500);
IPAddress myIP = WiFi.softAPIP();
if (myIP == (uint32_t)0)
{
config_set_last_error(BLYNK_PROV_ERR_INTERNAL);
BlynkState::set(MODE_ERROR);
return;
}
// 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
httpUpdater.setup(&server, "/update");
#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
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "application/json", "[\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,"hidden":%d})json",
WiFi.SSID(id).c_str(),
WiFi.BSSIDstr(id).c_str(),
WiFi.RSSI(id),
wifiSecToStr(WiFi.encryptionType(id)),
WiFi.channel(id),
WiFi.isHidden(id)
);
server.sendContent(buff);
if (i != wifi_nets-1) server.sendContent(",\n");
}
WiFi.scanDelete();
server.sendContent("\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", BLYNK_FS, "/img");
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);
WiFi.mode(WIFI_STA);
String hostname = getWiFiName();
hostname.replace(" ", "-");
WiFi.hostname(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;
}
}
if (!WiFi.begin(configStore.wifiSSID, configStore.wifiPass)) {
config_set_last_error(BLYNK_PROV_ERR_CONFIG);
BlynkState::set(MODE_ERROR);
return;
}
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,147 @@
#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 <EEPROM.h>
#define EEPROM_CONFIG_START 0
void config_load()
{
memset(&configStore, 0, sizeof(configStore));
EEPROM.get(EEPROM_CONFIG_START, configStore);
if (configStore.magic != configDefault.magic) {
DEBUG_PRINT("Using default config.");
configStore = configDefault;
return;
}
}
bool config_save()
{
EEPROM.put(EEPROM_CONFIG_START, configStore);
EEPROM.commit();
DEBUG_PRINT("Configuration stored to flash");
return true;
}
bool config_init()
{
EEPROM.begin(sizeof(ConfigStore));
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,209 @@
#include <Blynk/BlynkConsole.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();
unsigned partSize = sketchSize + ESP.getFreeSketchSpace();
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());
edgentConsole.printf(" ESP Core: %s\n", ESP.getCoreVersion().c_str());
edgentConsole.printf(" App size: %dK (%d%%)\n", sketchSize/1024, (sketchSize*100)/partSize);
edgentConsole.printf(" App MD5: %s\n", ESP.getSketchMD5().c_str());
}
});
edgentConsole.addCommand("status", [](int argc, const char** argv) {
const uint64_t t = micros64() / 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;
uint32_t heap_free; uint16_t heap_max;
uint8_t heap_frag;
ESP.getHeapStats(&heap_free, &heap_max, &heap_frag);
edgentConsole.printf(" Uptime: %dd %dh %dm %ds\n", days, hrs, mins, secs);
edgentConsole.printf(" Reset reason: %s\n", ESP.getResetReason().c_str());
edgentConsole.printf(" Flash: %dK\n", ESP.getFlashChipSize() / 1024);
edgentConsole.printf(" Stack unused: %d\n", ESP.getFreeContStack());
edgentConsole.printf(" Heap free: %d / %d\n", heap_free, heap_max);
edgentConsole.printf(" fragment: %d\n", heap_frag);
edgentConsole.printf(" max alloc: %d\n", ESP.getMaxFreeBlockSize());
#ifdef BLYNK_FS
FSInfo fs_info;
BLYNK_FS.info(fs_info);
edgentConsole.printf(" FS free: %d / %d\n", (fs_info.totalBytes-fs_info.usedBytes), fs_info.totalBytes);
#endif
});
#ifdef BLYNK_FS
edgentConsole.addCommand("ls", [](int argc, const char** argv) {
const char* path = (argc < 1) ? "/" : argv[0];
Dir dir = BLYNK_FS.openDir(path);
while (dir.next()) {
File f = dir.openFile(BLYNK_FILE_READ);
MD5Builder md5;
md5.begin();
md5.addStream(f, f.size());
md5.calculate();
String md5str = md5.toString();
edgentConsole.printf("%8d %-24s %s\n",
f.size(), dir.fileName().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], BLYNK_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], BLYNK_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,53 @@
/*************************************************************
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_SPARKFUN_BLYNK_BOARD
//#define USE_NODE_MCU_BOARD
//#define USE_WITTY_CLOUD_BOARD
//#define USE_WEMOS_D1_MINI
#include "BlynkEdgent.h"
void setup()
{
Serial.begin(115200);
delay(100);
BlynkEdgent.begin();
}
void loop() {
BlynkEdgent.run();
}

View File

@@ -0,0 +1,306 @@
#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() {
pinMode(BOARD_LED_PIN_R, OUTPUT);
pinMode(BOARD_LED_PIN_G, OUTPUT);
pinMode(BOARD_LED_PIN_B, OUTPUT);
}
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
analogWrite(BOARD_LED_PIN_R, TO_PWM(255 - r));
analogWrite(BOARD_LED_PIN_G, TO_PWM(255 - g));
analogWrite(BOARD_LED_PIN_B, TO_PWM(255 - b));
#else
analogWrite(BOARD_LED_PIN_R, TO_PWM(r));
analogWrite(BOARD_LED_PIN_G, TO_PWM(g));
analogWrite(BOARD_LED_PIN_B, TO_PWM(b));
#endif
}
#elif defined(BOARD_LED_PIN) // Single color LED
void initLED() {
pinMode(BOARD_LED_PIN, OUTPUT);
}
void setLED(uint32_t color) {
#if BOARD_LED_INVERSE
analogWrite(BOARD_LED_PIN, TO_PWM(255 - color));
#else
analogWrite(BOARD_LED_PIN, 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,266 @@
#define OTA_FATAL(...) { BLYNK_LOG1(__VA_ARGS__); delay(1000); restartMCU(); }
#define USE_SSL
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);
});
}
#if defined(ESP32)
#include <Update.h>
#include <WiFiClientSecure.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <time.h>
#endif
#if defined(USE_SSL) && defined(ESP8266)
WiFiClient* connectSSL(const String& host, const int port)
{
WiFiUDP::stopAll();
WiFiClient::stopAll();
time_t now = time(nullptr);
if (time(nullptr) < 100000) {
// Synchronize time useing SNTP. This is necessary to verify that
// the TLS certificates offered by the server are currently valid
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
while (now < 100000) {
delay(100);
now = time(nullptr);
}
}
// Reuse Secure WIFI Client on ESP8266
//WiFiClientSecure* clientSSL = &_blynkWifiClient;
WiFiClientSecure* clientSSL = new WiFiClientSecure();
clientSSL->setTrustAnchors(&BlynkCert);
if (!clientSSL->connect(host.c_str(), port)) {
OTA_FATAL(F("Connection failed"));
}
return clientSSL;
}
#elif defined(USE_SSL) && defined(ESP32)
WiFiClient* connectSSL(const String& host, const int port)
{
WiFiUDP::stopAll();
WiFiClient::stopAll();
WiFiClientSecure* clientSSL = new WiFiClientSecure();
clientSSL->setCACert(BLYNK_DEFAULT_ROOT_CA);
if (clientSSL->connect(host.c_str(), port)) {
DEBUG_PRINT(F("Certificate OK"));
} else {
OTA_FATAL(F("Secure connection failed"));
}
return clientSSL;
}
#endif
WiFiClient* connectTCP(const String& host, const int port)
{
WiFiUDP::stopAll();
WiFiClient::stopAll();
WiFiClient* clientTCP = new WiFiClient();
if (!clientTCP->connect(host.c_str(), port)) {
OTA_FATAL(F("Client not connected"));
}
return clientTCP;
}
bool parseURL(String url, String& protocol, String& host, int& port, String& uri)
{
int index = url.indexOf(':');
if(index < 0) {
return false;
}
protocol = url.substring(0, index);
url.remove(0, (index + 3)); // remove protocol part
index = url.indexOf('/');
String server = url.substring(0, index);
url.remove(0, index); // remove server part
index = server.indexOf(':');
if(index >= 0) {
host = server.substring(0, index); // hostname
port = server.substring(index + 1).toInt(); // port
} else {
host = server;
if (protocol == "http") {
port = 80;
} else if (protocol == "https") {
port = 443;
}
}
if (url.length()) {
uri = url;
} else {
uri = "/";
}
return true;
}
void enterOTA() {
BlynkState::set(MODE_OTA_UPGRADE);
// Disconnect, not to interfere with OTA process
Blynk.disconnect();
String protocol, host, url;
int port;
DEBUG_PRINT(String("OTA: ") + overTheAirURL);
if (!parseURL(overTheAirURL, protocol, host, port, url)) {
OTA_FATAL(F("Cannot parse URL"));
}
DEBUG_PRINT(String("Connecting to ") + host + ":" + port);
Client* client = NULL;
if (protocol == "http") {
client = connectTCP(host, port);
#ifdef USE_SSL
} else if (protocol == "https") {
client = connectSSL(host, port);
#endif
} else {
OTA_FATAL(String("Unsupported protocol: ") + protocol);
}
client->print(String("GET ") + url + " HTTP/1.0\r\n"
+ "Host: " + host + "\r\n"
+ "Connection: keep-alive\r\n"
+ "\r\n");
uint32_t timeout = millis();
while (client->connected() && !client->available()) {
if (millis() - timeout > 10000L) {
OTA_FATAL("Response timeout");
}
delay(10);
}
// Collect headers
String md5;
int contentLength = 0;
while (client->available()) {
String line = client->readStringUntil('\n');
line.trim();
//DEBUG_PRINT(line); // Uncomment this to show response headers
line.toLowerCase();
if (line.startsWith("content-length:")) {
contentLength = line.substring(line.lastIndexOf(':') + 1).toInt();
} else if (line.startsWith("x-md5:")) {
md5 = line.substring(line.lastIndexOf(':') + 1);
} else if (line.length() == 0) {
break;
}
delay(10);
}
if (contentLength <= 0) {
OTA_FATAL("Content-Length not defined");
}
bool canBegin = Update.begin(contentLength);
if (!canBegin) {
#ifdef BLYNK_PRINT
Update.printError(BLYNK_PRINT);
#endif
OTA_FATAL("OTA begin failed");
}
if (md5.length()) {
md5.trim();
md5.toLowerCase();
DEBUG_PRINT(String("Expected MD5: ") + md5);
if(!Update.setMD5(md5.c_str())) {
OTA_FATAL("Cannot set MD5");
}
}
DEBUG_PRINT("Flashing...");
// The next loop does approx. the same thing as Update.writeStream(http) or Update.write(http)
int written = 0;
int prevProgress = 0;
uint8_t buff[256];
while (client->connected() && written < contentLength) {
delay(10);
timeout = millis();
while (client->connected() && !client->available()) {
delay(1);
if (millis() - timeout > 10000L) {
OTA_FATAL("Timeout");
}
}
int len = client->read(buff, sizeof(buff));
if (len <= 0) continue;
Update.write(buff, len);
written += len;
const int progress = (written*100)/contentLength;
if (progress - prevProgress >= 10 || progress == 100) {
#ifdef BLYNK_PRINT
BLYNK_PRINT.print(String("\r ") + progress + "%");
#endif
prevProgress = progress;
}
}
#ifdef BLYNK_PRINT
BLYNK_PRINT.println();
#endif
client->stop();
if (written != contentLength) {
#ifdef BLYNK_PRINT
Update.printError(BLYNK_PRINT);
#endif
OTA_FATAL(String("Write failed. Written ") + written + " / " + contentLength + " bytes");
}
if (!Update.end()) {
#ifdef BLYNK_PRINT
Update.printError(BLYNK_PRINT);
#endif
OTA_FATAL(F("Update not ended"));
}
if (!Update.isFinished()) {
OTA_FATAL(F("Update not finished"));
}
DEBUG_PRINT("=== Update successfully completed. Rebooting.");
restartMCU();
}

View File

@@ -0,0 +1,54 @@
#ifdef BOARD_BUTTON_PIN
volatile bool g_buttonPressed = false;
volatile uint32_t g_buttonPressTime = -1;
void button_action(void)
{
BlynkState::set(MODE_RESET_CONFIG);
}
IRAM_ATTR
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);
#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,101 @@
/*
* Board configuration (see examples below).
*/
#if defined(USE_NODE_MCU_BOARD) || defined(USE_WEMOS_D1_MINI)
#if defined(USE_WEMOS_D1_MINI)
#warning "This board does not have a button. Connect a button to gpio0 <> GND"
#endif
#define BOARD_BUTTON_PIN 0
#define BOARD_BUTTON_ACTIVE_LOW true
#define BOARD_LED_PIN 2
#define BOARD_LED_INVERSE true
#define BOARD_LED_BRIGHTNESS 255
#elif defined(USE_SPARKFUN_BLYNK_BOARD)
#define BOARD_BUTTON_PIN 0
#define BOARD_BUTTON_ACTIVE_LOW true
#define BOARD_LED_PIN_WS2812 4
#define BOARD_LED_BRIGHTNESS 64
#elif defined(USE_WITTY_CLOUD_BOARD)
#define BOARD_BUTTON_PIN 4
#define BOARD_BUTTON_ACTIVE_LOW true
#define BOARD_LED_PIN_R 15
#define BOARD_LED_PIN_G 12
#define BOARD_LED_PIN_B 13
#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 BUTTON_PRESS_TIME_ACTION 50
#define BOARD_PWM_MAX 1023
#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

View File

@@ -0,0 +1,6 @@
/*
* Arduino 33 IoT and Arduino MKR1010 are no longer supported by Blynk.Edgent.
* Blynk now offers a better solution for Dual-MCU boards. Please use Blynk.NCP:
* https://github.com/blynkkk/BlynkNcpExample
*
*/

View File

@@ -0,0 +1,97 @@
/*************************************************************
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
*************************************************************
NOTE: This example requires the connectivity module on your board to be
flashed using Blynk.NCP firmware.
The easiest way to install the NCP firmware is using this PlatformIO project:
https://github.com/blynkkk/BlynkNcpExample
*************************************************************/
/* Fill in information from your Blynk Template here */
/* Read more: https://bit.ly/BlynkInject */
//#define BLYNK_TEMPLATE_ID "TMPxxxxxx"
//#define BLYNK_TEMPLATE_NAME "Device"
/* The firmware version of the Primary MCU (used for OTA updates) */
#define BLYNK_FIRMWARE_VERSION "0.1.0"
// Debug output
#define BLYNK_PRINT Serial
// Redefine NCP connection port settings, if needed
//#define BLYNK_NCP_SERIAL Serial1
//#define BLYNK_NCP_BAUD 2000000
#include <BlynkEdgentNCP.h>
BlynkTimer timer;
BLYNK_CONNECTED() {
BLYNK_LOG("Connected to Blynk 🙌");
}
BLYNK_DISCONNECTED() {
BLYNK_LOG("Blynk disconnected");
}
void setup() {
Serial.begin(115200);
Serial.println();
// Give Serial Monitor some time to connect
delay(3000);
BLYNK_LOG("Main firmware: %s", BLYNK_FIRMWARE_VERSION);
BLYNK_LOG("Build: %s", __DATE__ " " __TIME__);
// Initialize the Blynk.NCP hardware
if (Blynk.initNCP()) {
String ver = Blynk.getNcpVersion();
BLYNK_LOG("Blynk.NCP firmware: %s", ver.c_str());
} else {
BLYNK_LOG("Cannot communicate to Blynk.NCP");
BLYNK_LOG(" Please ensure you have flashed your board with the Blynk.NCP firmware, before running this example.");
BLYNK_LOG(" See: https://github.com/blynkkk/BlynkNcpExample");
return;
}
// Print state changes
Blynk.onStateChange([]() {
BLYNK_LOG("State: %s", Blynk.getStateString());
});
// Set config mode timeout to 30 minutes, for testing purposes
Blynk.setConfigTimeout(30*60);
// White labeling (use this ONLY if you have a branded Blynk App)
//Blynk.setVendorPrefix("MyCompany");
//Blynk.setVendorServer("dashboard.mycompany.com");
// Product setup
Blynk.begin(BLYNK_TEMPLATE_ID, BLYNK_TEMPLATE_NAME);
// Publish some data periodically
timer.setInterval(1000, []() {
Blynk.virtualWrite(V0, millis() / 1000);
});
}
void loop() {
timer.run();
Blynk.run();
delay(1);
}

View File

@@ -0,0 +1,128 @@
extern "C" {
void app_loop();
void restartMCU();
}
#include "Settings.h"
#include <BlynkSimpleWioTerminal_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_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) + " @ " + (F_CPU/1000000) + "MHz");
BLYNK_PRINT.print(" WiFi FW: "); BLYNK_PRINT.println(rpc_system_version());
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()
{
//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,480 @@
#include <WiFiClient.h>
#include <WebServer.h>
#include <DNSServer.h>
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";
WebServer server(80);
DNSServer dnsServer;
const byte DNS_PORT = 53;
static int connectNetRetries = WIFI_CLOUD_MAX_RETRIES;
static int connectBlynkRetries = WIFI_CLOUD_MAX_RETRIES;
void restartMCU() {
NVIC_SystemReset();
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)
{
static byte mac[6] = { 0, };
static bool needMac = true;
if (needMac) {
WiFi.macAddress(mac);
needMac = false;
}
uint32_t unique = 0;
for (int i=0; i<4; i++) {
unique = BlynkCRC32(&mac, sizeof(mac), 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
String getWiFiMacAddress() {
return WiFi.macAddress();
}
static
String getWiFiApBSSID() {
return WiFi.softAPmacAddress();
}
static
String getWiFiNetworkSSID() {
return WiFi.SSID();
}
static
String getWiFiNetworkBSSID() {
return WiFi.BSSIDstr();
}
String scanNetworks()
{
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";
}
return result + "\n]";
} else {
return "[]";
}
}
void handleRoot() {
server.send(200, "text/html", config_form);
}
String networks = "[]";
void enterConfigMode()
{
networks = scanNetworks();
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("/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,"5ghz":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", []() {
server.send(200, "application/json", networks);
});
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();
});
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);
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;
}
}
if (strlen(configStore.wifiPass)) {
WiFi.begin(configStore.wifiSSID, configStore.wifiPass);
} else {
WiFi.begin(configStore.wifiSSID);
}
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,162 @@
#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 <sfud.h>
const sfud_flash *_flash = sfud_get_device_table() + 0;
void config_load()
{
memset(&configStore, 0, sizeof(configStore));
sfud_err result = sfud_read(_flash, 0, sizeof(configStore), (uint8_t*)&configStore);
if (result != SFUD_SUCCESS || configStore.magic != 0x626C6E6B)
{
DEBUG_PRINT("Using default config.");
configStore = configDefault;
return;
}
}
bool config_save()
{
sfud_err result = sfud_erase(_flash, 0, sizeof(configStore));
delay(100);
if (!result == SFUD_SUCCESS) { DEBUG_PRINT("Erase flash data failed"); return false; }
result = sfud_write(_flash, 0, sizeof(configStore), (uint8_t*)&configStore);
delay(50);
if (!result == SFUD_SUCCESS) { DEBUG_PRINT("Write the flash data failed"); return false; }
DEBUG_PRINT("Configuration stored to flash");
return true;
}
bool config_init()
{
if (sfud_init() != SFUD_SUCCESS) { DEBUG_PRINT("SFUD init failed"); return false; }
sfud_qspi_fast_read_enable(sfud_get_device(SFUD_W25Q32_DEVICE_INDEX), 2);
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;
sfud_err result = sfud_erase(_flash, 0, 1);
configStore.last_error = error;
BLYNK_LOG2("Last error code: ", error);
config_save();
}
}

View File

@@ -0,0 +1,69 @@
#include <Blynk/BlynkConsole.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);
});
}
BLYNK_WRITE(InternalPinDBG) {
String cmd = String(param.asStr()) + "\n";
edgentConsole.runCommand((char*)cmd.c_str());
}

View File

@@ -0,0 +1,59 @@
/*************************************************************
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
Required libraries:
- Seeed Arduino rpcUnified
- Seeed Arduino rpcWiFi
- Seeed Arduino SFUD
- Seeed Arduino FS
- Seeed Arduino mbedtls
- Seeed Arduino FreeRTOS
- ArduinoOTA
- ArduinoHttpClient
NOTE: Please also update the WiFi module firmware:
https://wiki.seeedstudio.com/Wio-Terminal-Network-Overview
*************************************************************/
/* 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
#include "BlynkEdgent.h"
void setup()
{
Serial.begin(115200);
delay(100);
BlynkEdgent.begin();
}
void loop() {
BlynkEdgent.run();
}

View File

@@ -0,0 +1,250 @@
#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() {
pinMode(BOARD_LED_PIN_R, OUTPUT);
pinMode(BOARD_LED_PIN_G, OUTPUT);
pinMode(BOARD_LED_PIN_B, OUTPUT);
}
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
analogWrite(BOARD_LED_PIN_R, TO_PWM(255 - r));
analogWrite(BOARD_LED_PIN_G, TO_PWM(255 - g));
analogWrite(BOARD_LED_PIN_B, TO_PWM(255 - b));
#else
analogWrite(BOARD_LED_PIN_R, TO_PWM(r));
analogWrite(BOARD_LED_PIN_G, TO_PWM(g));
analogWrite(BOARD_LED_PIN_B, TO_PWM(b));
#endif
}
#elif defined(BOARD_LED_PIN) // Single color LED
void initLED() {
pinMode(BOARD_LED_PIN, OUTPUT);
}
void setLED(uint32_t color) {
#if BOARD_LED_INVERSE
analogWrite(BOARD_LED_PIN, TO_PWM(255 - color));
#else
analogWrite(BOARD_LED_PIN, 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_TC3)
#include <TimerTC3.h>
void indicator_run() {
uint32_t returnTime = indicator.run();
if (returnTime) {
TimerTc3.initialize(returnTime*1000);
}
}
void indicator_init() {
indicator.init();
TimerTc3.initialize(100*1000);
TimerTc3.attachInterrupt(indicator_run);
}
#elif defined(USE_TCC0)
#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);
}
#else
#warning LED indicator needs a functional timer!
void indicator_run() {}
void indicator_init() {}
#endif

View File

@@ -0,0 +1,151 @@
#include <ArduinoOTA.h> // only for InternalStorage
#include <ArduinoHttpClient.h>
#define OTA_FATAL(...) { BLYNK_LOG1(__VA_ARGS__); delay(1000); restartMCU(); }
#define USE_SSL
String overTheAirURL;
extern BlynkTimer edgentTimer;
BLYNK_WRITE(InternalPinOTA) {
overTheAirURL = param.asString();
// Force HTTP update
overTheAirURL.replace("https://", "http://");
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);
});
}
bool parseURL(String url, String& protocol, String& host, int& port, String& uri)
{
int index = url.indexOf(':');
if(index < 0) {
return false;
}
protocol = url.substring(0, index);
url.remove(0, (index + 3)); // remove protocol part
index = url.indexOf('/');
String server = url.substring(0, index);
url.remove(0, index); // remove server part
index = server.indexOf(':');
if(index >= 0) {
host = server.substring(0, index); // hostname
port = server.substring(index + 1).toInt(); // port
} else {
host = server;
if (protocol == "http") {
port = 80;
} else if (protocol == "https") {
port = 443;
}
}
if (url.length()) {
uri = url;
} else {
uri = "/";
}
return true;
}
void enterOTA() {
BlynkState::set(MODE_OTA_UPGRADE);
// Disconnect, not to interfere with OTA process
Blynk.disconnect();
String protocol, host, url;
int port;
DEBUG_PRINT(String("OTA: ") + overTheAirURL);
if (!parseURL(overTheAirURL, protocol, host, port, url)) {
OTA_FATAL(F("Cannot parse URL"));
}
DEBUG_PRINT(String("Connecting to ") + host + ":" + port);
Client* client = NULL;
if (protocol == "http") {
client = new WiFiClient();
#ifdef USE_SSL
} else if (protocol == "https") {
client = &_blynkWifiClient;
//client = new WiFiClientSecure();
#endif
} else {
OTA_FATAL(String("Unsupported protocol: ") + protocol);
}
HttpClient http(*client, host, port);
http.get(url);
int statusCode = http.responseStatusCode();
if (statusCode != 200) {
http.stop();
OTA_FATAL(String("HTTP status code: ") + statusCode);
}
int contentLength = http.contentLength();
if (contentLength == HttpClient::kNoContentLengthHeader) {
http.stop();
OTA_FATAL("Content-Length not defined");
}
if (!InternalStorage.open(contentLength)) {
http.stop();
OTA_FATAL("Not enough space to store the update");
}
//InternalStorage.debugPrint();
DEBUG_PRINT("Flashing...");
int written = 0;
int prevProgress = 0;
uint8_t buff[256];
while (client->connected() && written < contentLength) {
int len = http.readBytes(buff, sizeof(buff));
if (len <= 0) continue;
for (int i = 0; i<len; i++) {
InternalStorage.write(buff[i]);
}
written += len;
const int progress = (written*100)/contentLength;
if (progress - prevProgress >= 10 || progress == 100) {
#ifdef BLYNK_PRINT
BLYNK_PRINT.print(String("\r ") + progress + "%");
#endif
prevProgress = progress;
}
}
#ifdef BLYNK_PRINT
BLYNK_PRINT.println();
#endif
InternalStorage.close();
http.stop();
if (written != contentLength) {
OTA_FATAL(String("Interrupted at ") + written + " / " + contentLength + " bytes");
}
DEBUG_PRINT("=== Update successfully completed. Rebooting.");
InternalStorage.apply();
}

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,61 @@
/*
* Board configuration (see examples below).
*/
// Example configuration for Wio Terminal Board
#define BOARD_BUTTON_PIN WIO_KEY_A // Pin where user button is attached
#define BOARD_BUTTON_ACTIVE_LOW true // true if button is "active-low"
#define BOARD_LED_PIN LED_BUILTIN // Set LED pin - if you have a single-color LED attached
//#define BOARD_LED_PIN_R 27 // Set R,G,B pins - if your LED is PWM RGB
//#define BOARD_LED_PIN_G 26
//#define BOARD_LED_PIN_B 25
//#define BOARD_LED_PIN_WS2812 33 // 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 255 // 0..255 brightness control
/*
* 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
#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 1
//#define USE_TC3
//#define USE_TCC0
#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