first commit
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
|
||||
151
libraries/Blynk/examples/Blynk.Edgent/Edgent_Wio_Terminal/OTA.h
Normal file
151
libraries/Blynk/examples/Blynk.Edgent/Edgent_Wio_Terminal/OTA.h
Normal 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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user