初始化提交
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
#include "Settings.h"
|
||||
#include <BlynkSimpleEsp32.h>
|
||||
#include "BlynkState.h"
|
||||
#include "ConfigStore.h"
|
||||
#include "ResetButton.h"
|
||||
#include "ConfigMode.h"
|
||||
#include "Indicator.h"
|
||||
#include "OTA.h"
|
||||
|
||||
inline
|
||||
void BlynkState::set(State m) {
|
||||
if (state != m) {
|
||||
DEBUG_PRINT(String(StateStr[state]) + " => " + StateStr[m]);
|
||||
state = m;
|
||||
}
|
||||
}
|
||||
|
||||
class Provisioning {
|
||||
|
||||
public:
|
||||
void begin()
|
||||
{
|
||||
DEBUG_PRINT("");
|
||||
DEBUG_PRINT("Hardware v" + String(BOARD_HARDWARE_VERSION));
|
||||
DEBUG_PRINT("Firmware v" + String(BOARD_FIRMWARE_VERSION));
|
||||
|
||||
indicator_init();
|
||||
button_init();
|
||||
config_init();
|
||||
|
||||
if (configStore.flagConfig) {
|
||||
BlynkState::set(MODE_CONNECTING_NET);
|
||||
} else {
|
||||
BlynkState::set(MODE_WAIT_CONFIG);
|
||||
}
|
||||
}
|
||||
|
||||
void run() {
|
||||
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: Blynk.run(); break;
|
||||
case MODE_OTA_UPGRADE: enterOTA(); break;
|
||||
case MODE_SWITCH_TO_STA: enterSwitchToSTA(); break;
|
||||
case MODE_RESET_CONFIG: enterResetConfig(); break;
|
||||
default: enterError(); break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Provisioning BlynkProvisioning;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
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] = {
|
||||
"WAIT_CONFIG",
|
||||
"CONFIGURING",
|
||||
"CONNECTING_NET",
|
||||
"CONNECTING_CLOUD",
|
||||
"RUNNING",
|
||||
"OTA_UPGRADE",
|
||||
"SWITCH_TO_STA",
|
||||
"RESET_CONFIG",
|
||||
"ERROR"
|
||||
};
|
||||
#endif
|
||||
|
||||
namespace BlynkState
|
||||
{
|
||||
volatile State state;
|
||||
|
||||
State get() { return state; }
|
||||
bool is (State m) { return (state == m); }
|
||||
void set(State m);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
#include <WiFiClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <DNSServer.h>
|
||||
|
||||
WebServer server(WIFI_AP_CONFIG_PORT);
|
||||
DNSServer dnsServer;
|
||||
const byte DNS_PORT = 53;
|
||||
|
||||
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" length=64></td></tr>
|
||||
<tr><td><label for="port">Port:</label></td> <td><input type="number" name="port" value="80" min="1" max="65535"></td></tr>
|
||||
</table><br/>
|
||||
<input type="submit" value="Apply">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)html";
|
||||
|
||||
void restartMCU() {
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
void enterConfigMode()
|
||||
{
|
||||
randomSeed(ESP.getEfuseMac() & 0xFFFFFF);
|
||||
const uint32_t unique = random(0xFFFFF);
|
||||
char ssidBuff[64];
|
||||
snprintf(ssidBuff, sizeof(ssidBuff), "%s-%05X", PRODUCT_WIFI_SSID, unique);
|
||||
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(100);
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAPConfig(WIFI_AP_IP, WIFI_AP_IP, WIFI_AP_Subnet);
|
||||
WiFi.softAP(ssidBuff);
|
||||
delay(500);
|
||||
IPAddress myIP = WiFi.softAPIP();
|
||||
DEBUG_PRINT(String("AP SSID: ") + ssidBuff);
|
||||
DEBUG_PRINT(String("AP IP: ") + myIP[0] + "." + myIP[1] + "." + myIP[2] + "." + myIP[3]);
|
||||
|
||||
// Set up DNS Server
|
||||
dnsServer.setTTL(300); // Time-to-live 300s
|
||||
dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure); // Return code for non-accessible domains
|
||||
#ifdef WIFI_CAPTIVE_PORTAL_ENABLE
|
||||
dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); // Point all to our IP
|
||||
server.onNotFound(handleRoot);
|
||||
#else
|
||||
dnsServer.start(DNS_PORT, BOARD_CONFIG_AP_URL, WiFi.softAPIP());
|
||||
DEBUG_PRINT(String("AP URL: ") + BOARD_CONFIG_AP_URL);
|
||||
#endif
|
||||
|
||||
server.on("/", []() {
|
||||
server.send(200, "text/html", config_form);
|
||||
});
|
||||
server.on("/config", []() {
|
||||
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");
|
||||
|
||||
String content;
|
||||
unsigned statusCode;
|
||||
|
||||
DEBUG_PRINT(String("WiFi SSID: ") + ssid + " Pass: " + pass);
|
||||
DEBUG_PRINT(String("Blynk cloud: ") + token + " @ " + host + ":" + port);
|
||||
|
||||
if (token.length() == 32 && ssid.length() > 0) {
|
||||
configStore.flagConfig = false;
|
||||
CopyString(ssid, configStore.wifiSSID);
|
||||
CopyString(pass, configStore.wifiPass);
|
||||
CopyString(token, configStore.cloudToken);
|
||||
if (host.length()) {
|
||||
CopyString(host, configStore.cloudHost);
|
||||
}
|
||||
if (port.length()) {
|
||||
configStore.cloudPort = port.toInt();
|
||||
}
|
||||
|
||||
content = R"json({"status":"ok","msg":"Configuration saved"})json";
|
||||
statusCode = 200;
|
||||
server.send(statusCode, "application/json", content);
|
||||
|
||||
BlynkState::set(MODE_SWITCH_TO_STA);
|
||||
} else {
|
||||
DEBUG_PRINT("Configuration invalid");
|
||||
content = R"json({"status":"error","msg":"Configuration invalid"})json";
|
||||
statusCode = 404;
|
||||
server.send(statusCode, "application/json", content);
|
||||
}
|
||||
});
|
||||
server.on("/board_info.json", []() {
|
||||
char buff[256];
|
||||
snprintf(buff, sizeof(buff),
|
||||
R"json({"board":"%s","vendor":"%s","tmpl_id":"%s","fw_ver":"%s","hw_ver":"%s"})json",
|
||||
BOARD_NAME,
|
||||
BOARD_VENDOR,
|
||||
BOARD_TEMPLATE_ID,
|
||||
BOARD_FIRMWARE_VERSION,
|
||||
BOARD_HARDWARE_VERSION
|
||||
);
|
||||
server.send(200, "application/json", buff);
|
||||
});
|
||||
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)) {
|
||||
dnsServer.processNextRequest();
|
||||
server.handleClient();
|
||||
if (BlynkState::is(MODE_WAIT_CONFIG) && WiFi.softAPgetStationNum() > 0) {
|
||||
BlynkState::set(MODE_CONFIGURING);
|
||||
} else if (BlynkState::is(MODE_CONFIGURING) && WiFi.softAPgetStationNum() == 0) {
|
||||
BlynkState::set(MODE_WAIT_CONFIG);
|
||||
}
|
||||
}
|
||||
|
||||
server.stop();
|
||||
}
|
||||
|
||||
void enterConnectNet() {
|
||||
BlynkState::set(MODE_CONNECTING_NET);
|
||||
DEBUG_PRINT(String("Connecting to WiFi: ") + configStore.wifiSSID);
|
||||
|
||||
WiFi.begin(configStore.wifiSSID, configStore.wifiPass);
|
||||
|
||||
unsigned long timeoutMs = millis() + WIFI_NET_CONNECT_TIMEOUT;
|
||||
while ((timeoutMs > millis()) && (WiFi.status() != WL_CONNECTED))
|
||||
{
|
||||
delay(100);
|
||||
if (!BlynkState::is(MODE_CONNECTING_NET)) {
|
||||
WiFi.disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
BlynkState::set(MODE_CONNECTING_CLOUD);
|
||||
} else {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void enterConnectCloud() {
|
||||
BlynkState::set(MODE_CONNECTING_CLOUD);
|
||||
|
||||
Blynk.disconnect();
|
||||
Blynk.config(configStore.cloudToken, configStore.cloudHost, configStore.cloudPort);
|
||||
Blynk.connect(0);
|
||||
|
||||
unsigned long timeoutMs = millis() + WIFI_CLOUD_CONNECT_TIMEOUT;
|
||||
while ((timeoutMs > millis()) &&
|
||||
(Blynk.connected() == false))
|
||||
{
|
||||
Blynk.run();
|
||||
if (!BlynkState::is(MODE_CONNECTING_CLOUD)) {
|
||||
Blynk.disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Blynk.connected()) {
|
||||
BlynkState::set(MODE_RUNNING);
|
||||
|
||||
if (!configStore.flagConfig) {
|
||||
configStore.flagConfig = true;
|
||||
config_save();
|
||||
DEBUG_PRINT("Configuration stored to flash");
|
||||
}
|
||||
} else {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void enterSwitchToSTA() {
|
||||
BlynkState::set(MODE_SWITCH_TO_STA);
|
||||
|
||||
DEBUG_PRINT("Switching to STA...");
|
||||
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(1000);
|
||||
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);
|
||||
if (!BlynkState::is(MODE_ERROR)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
DEBUG_PRINT("Restarting after error.");
|
||||
delay(10);
|
||||
|
||||
restartMCU();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
struct ConfigStore {
|
||||
uint32_t magic;
|
||||
char version[9];
|
||||
uint8_t flagConfig:1;
|
||||
uint8_t flagApFail:1;
|
||||
uint8_t flagSelfTest:1;
|
||||
|
||||
char wifiSSID[34];
|
||||
char wifiPass[64];
|
||||
|
||||
char cloudToken[34];
|
||||
char cloudHost[34];
|
||||
uint16_t cloudPort;
|
||||
|
||||
uint16_t checksum;
|
||||
} __attribute__((packed));
|
||||
|
||||
ConfigStore configStore;
|
||||
|
||||
const ConfigStore configDefault = {
|
||||
0x626C6E6B,
|
||||
BOARD_FIRMWARE_VERSION,
|
||||
0, 0, 0,
|
||||
|
||||
"",
|
||||
"",
|
||||
|
||||
"invalid token",
|
||||
"blynk-cloud.com", 80,
|
||||
0
|
||||
};
|
||||
|
||||
#include <Preferences.h>
|
||||
Preferences preferences;
|
||||
|
||||
void config_load()
|
||||
{
|
||||
memset(&configStore, 0, sizeof(configStore));
|
||||
preferences.getBytes("config", &configStore, sizeof(configStore));
|
||||
if (configStore.magic != configDefault.magic) {
|
||||
DEBUG_PRINT("Using default config.");
|
||||
configStore = configDefault;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool config_save()
|
||||
{
|
||||
preferences.putBytes("config", &configStore, sizeof(configStore));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool config_init()
|
||||
{
|
||||
preferences.begin("blynk", false);
|
||||
config_load();
|
||||
return true;
|
||||
}
|
||||
|
||||
void enterResetConfig()
|
||||
{
|
||||
DEBUG_PRINT("Resetting configuration!");
|
||||
configStore = configDefault;
|
||||
config_save();
|
||||
BlynkState::set(MODE_WAIT_CONFIG);
|
||||
}
|
||||
|
||||
template<typename T, int size>
|
||||
void CopyString(const String& s, T(&arr)[size]) {
|
||||
s.toCharArray(arr, size);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
#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) ((x)*(BOARD_LED_BRIGHTNESS)/255)
|
||||
#define RGB(r,g,b) (DIMM(r) << 16 | DIMM(g) << 8 | DIMM(b) << 0)
|
||||
|
||||
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() {
|
||||
m_Counter = 0;
|
||||
initLED();
|
||||
}
|
||||
|
||||
uint32_t run() {
|
||||
State currState = BlynkState::get();
|
||||
|
||||
// Reset counter if indicator state changes
|
||||
if (m_PrevState != currState) {
|
||||
m_PrevState = currState;
|
||||
m_Counter = 0;
|
||||
}
|
||||
|
||||
if (g_buttonPressed) {
|
||||
if (millis() - g_buttonPressTime > BUTTON_HOLD_TIME_ACTION) { return beatLED(COLOR_WHITE, (int[]){ 100, 100 }); }
|
||||
if (millis() - g_buttonPressTime > BUTTON_HOLD_TIME_INDICATION) { return waveLED(COLOR_WHITE, 1000); }
|
||||
}
|
||||
switch (currState) {
|
||||
case MODE_RESET_CONFIG:
|
||||
case MODE_WAIT_CONFIG: return beatLED(COLOR_BLUE, (int[]){ 50, 500 });
|
||||
case MODE_CONFIGURING: return beatLED(COLOR_BLUE, (int[]){ 200, 200 });
|
||||
case MODE_CONNECTING_NET: return beatLED(COLOR_BLYNK, (int[]){ 50, 500 });
|
||||
case MODE_CONNECTING_CLOUD: return beatLED(COLOR_BLYNK, (int[]){ 100, 100 });
|
||||
case MODE_RUNNING: return waveLED(COLOR_BLYNK, 5000);
|
||||
case MODE_OTA_UPGRADE: return beatLED(COLOR_MAGENTA, (int[]){ 50, 50 });
|
||||
default: return beatLED(COLOR_RED, (int[]){ 80, 100, 80, 1000 } );
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/*
|
||||
* LED drivers
|
||||
*/
|
||||
|
||||
#if defined(BOARD_LED_PIN_WS2812) // Addressable, NeoPixel RGB LED
|
||||
|
||||
void initLED() {
|
||||
rgb.begin();
|
||||
setRGB(COLOR_BLACK);
|
||||
}
|
||||
|
||||
void setRGB(uint32_t color) {
|
||||
rgb.setPixelColor(0, color);
|
||||
rgb.show();
|
||||
}
|
||||
|
||||
#elif defined(BOARD_LED_PIN_R) // Normal RGB LED (common anode or common cathode)
|
||||
|
||||
void initLED() {
|
||||
ledcAttachPin(BOARD_LED_PIN_R, LEDC_CHANNEL_1);
|
||||
ledcAttachPin(BOARD_LED_PIN_G, LEDC_CHANNEL_2);
|
||||
ledcAttachPin(BOARD_LED_PIN_B, LEDC_CHANNEL_3);
|
||||
|
||||
ledcSetup(LEDC_CHANNEL_1, LEDC_BASE_FREQ, LEDC_TIMER_BITS);
|
||||
ledcSetup(LEDC_CHANNEL_2, LEDC_BASE_FREQ, LEDC_TIMER_BITS);
|
||||
ledcSetup(LEDC_CHANNEL_3, LEDC_BASE_FREQ, 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(LEDC_CHANNEL_1, BOARD_PWM_MAX - r);
|
||||
ledcWrite(LEDC_CHANNEL_2, BOARD_PWM_MAX - g);
|
||||
ledcWrite(LEDC_CHANNEL_3, BOARD_PWM_MAX - b);
|
||||
#else
|
||||
ledcWrite(LEDC_CHANNEL_1, r);
|
||||
ledcWrite(LEDC_CHANNEL_2, g);
|
||||
ledcWrite(LEDC_CHANNEL_3, b);
|
||||
#endif
|
||||
}
|
||||
|
||||
#elif defined(BOARD_LED_PIN) // Single color LED
|
||||
|
||||
void initLED() {
|
||||
ledcSetup(LEDC_CHANNEL_1, LEDC_BASE_FREQ, LEDC_TIMER_BITS);
|
||||
ledcAttachPin(BOARD_LED_PIN, LEDC_CHANNEL_1);
|
||||
}
|
||||
|
||||
void setLED(uint32_t color) {
|
||||
#if BOARD_LED_INVERSE
|
||||
ledcWrite(LEDC_CHANNEL_1, BOARD_PWM_MAX - color);
|
||||
#else
|
||||
ledcWrite(LEDC_CHANNEL_1, color);
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#warning Invalid LED configuration.
|
||||
|
||||
#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_PWM_MAX : 0);
|
||||
uint32_t next = beat[m_Counter % cnt];
|
||||
m_Counter = (m_Counter+1) % cnt;
|
||||
return next;
|
||||
}
|
||||
|
||||
uint32_t waveLED(uint32_t, unsigned breathePeriod) {
|
||||
uint8_t brightness = (m_Counter < 128) ? m_Counter : 255 - m_Counter;
|
||||
|
||||
setLED(BOARD_PWM_MAX * ((float)brightness / (BOARD_PWM_MAX/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() {
|
||||
blinker.attach_ms(100, indicator_run);
|
||||
}
|
||||
|
||||
#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() {
|
||||
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() {
|
||||
Timer3.initialize(100*1000);
|
||||
Timer3.attachInterrupt(indicator_run);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#warning LED indicator needs a functional timer!
|
||||
|
||||
void indicator_run() {}
|
||||
void indicator_init() {}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************
|
||||
*
|
||||
* How to trigger an OTA update?
|
||||
* 1. In Arduino IDE menu: Sketch -> Export compiled Binary
|
||||
* 2. Open console, navigate to the sketch directory
|
||||
* 3.a Trigger update using HTTPS API on local server for specific hardware:
|
||||
* curl -v -F file=@Template_ESP8266.ino.nodemcu.bin --insecure -u admin@blynk.cc:admin https://localhost:9443/admin/ota/start?token=123
|
||||
* 3.b Trigger update using HTTPS API on local server for all hardware:
|
||||
* curl -v -F file=@Template_ESP8266.ino.nodemcu.bin --insecure -u admin@blynk.cc:admin https://localhost:9443/admin/ota/start
|
||||
* 3.c Trigger update using HTTPS API on local server for single user:
|
||||
* curl -v -F file=@Template_ESP8266.ino.nodemcu.bin --insecure -u admin@blynk.cc:admin https://localhost:9443/admin/ota/start?user=pupkin@gmail.com
|
||||
* 3.d Trigger update using HTTPS API on local server for single user and specific project:
|
||||
* curl -v -F file=@Template_ESP8266.ino.nodemcu.bin --insecure -u admin@blynk.cc:admin https://localhost:9443/admin/ota/start?user=pupkin@gmail.com&project=123
|
||||
* More about ESP8266 OTA updates:
|
||||
* https://github.com/esp8266/Arduino/blob/master/doc/ota_updates/readme.md
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <Update.h>
|
||||
#include <HTTPClient.h>
|
||||
|
||||
String overTheAirURL;
|
||||
|
||||
BLYNK_WRITE(InternalPinOTA) {
|
||||
overTheAirURL = param.asString();
|
||||
|
||||
// Disconnect, not to interfere with OTA process
|
||||
Blynk.disconnect();
|
||||
|
||||
// Start OTA
|
||||
BlynkState::set(MODE_OTA_UPGRADE);
|
||||
delay(500);
|
||||
}
|
||||
|
||||
void enterOTA() {
|
||||
BlynkState::set(MODE_OTA_UPGRADE);
|
||||
|
||||
DEBUG_PRINT(String("Firmware update URL: ") + overTheAirURL);
|
||||
|
||||
HTTPClient http;
|
||||
http.begin(overTheAirURL);
|
||||
|
||||
int httpCode = http.GET();
|
||||
if (httpCode != HTTP_CODE_OK) {
|
||||
DEBUG_PRINT("HTTP response should be 200");
|
||||
BlynkState::set(MODE_ERROR);
|
||||
return;
|
||||
}
|
||||
int contentLength = http.getSize();
|
||||
if (contentLength <= 0) {
|
||||
DEBUG_PRINT("Content-Length not defined");
|
||||
BlynkState::set(MODE_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
bool canBegin = Update.begin(contentLength);
|
||||
if (!canBegin) {
|
||||
DEBUG_PRINT("Not enough space to begin OTA");
|
||||
BlynkState::set(MODE_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
Client& client = http.getStream();
|
||||
int written = Update.writeStream(client);
|
||||
if (written != contentLength) {
|
||||
DEBUG_PRINT(String("OTA written ") + written + " / " + contentLength + " bytes");
|
||||
BlynkState::set(MODE_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Update.end()) {
|
||||
DEBUG_PRINT("Error #" + String(Update.getError()));
|
||||
BlynkState::set(MODE_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Update.isFinished()) {
|
||||
DEBUG_PRINT("Update failed.");
|
||||
BlynkState::set(MODE_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_PRINT("Update successfully completed. Rebooting.");
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
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 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();
|
||||
}
|
||||
g_buttonPressTime = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void button_init()
|
||||
{
|
||||
#if BOARD_BUTTON_ACTIVE_LOW
|
||||
pinMode(BOARD_BUTTON_PIN, INPUT_PULLUP);
|
||||
#else
|
||||
pinMode(BOARD_BUTTON_PIN, INPUT_PULLDOWN);
|
||||
#endif
|
||||
attachInterrupt(BOARD_BUTTON_PIN, button_change, CHANGE);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* General options
|
||||
*/
|
||||
|
||||
#define BOARD_FIRMWARE_VERSION "1.0.1"
|
||||
#define BOARD_HARDWARE_VERSION "1.0.0"
|
||||
|
||||
#define BOARD_NAME "Product Name" // Name of your product. Should match App Export request info.
|
||||
#define BOARD_VENDOR "Company Name" // Name of your company. Should match App Export request info.
|
||||
#define BOARD_TEMPLATE_ID "TMPL0000" // ID of the Tile Template. Can be found in Tile Template Settings
|
||||
|
||||
#define PRODUCT_WIFI_SSID "Our Product" // Name of the device, to be displayed during configuration. Should match export request info.
|
||||
#define BOARD_CONFIG_AP_URL "our-product.cc" // Config page will be available in a browser at 'http://our-product.cc/'
|
||||
|
||||
/*
|
||||
* Board configuration (see examples below).
|
||||
*/
|
||||
|
||||
#if defined(USE_CUSTOM_BOARD)
|
||||
|
||||
// Custom board configuration
|
||||
#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 13 // 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 64 // 0..255 brightness control
|
||||
|
||||
#elif defined(USE_WROVER_BOARD)
|
||||
|
||||
// Custom board configuration
|
||||
#define BOARD_BUTTON_PIN 15 // Pin where user button is attached
|
||||
#define BOARD_BUTTON_ACTIVE_LOW true // true if button is "active-low"
|
||||
|
||||
#define BOARD_LED_PIN_R 0 // Set R,G,B pins - if your LED is PWM RGB
|
||||
#define BOARD_LED_PIN_G 2
|
||||
#define BOARD_LED_PIN_B 4
|
||||
#define BOARD_LED_INVERSE false // true if LED is common anode, false if common cathode
|
||||
#define BOARD_LED_BRIGHTNESS 64 // 0..255 brightness control
|
||||
|
||||
#else
|
||||
#error "No board selected"
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Advanced options
|
||||
*/
|
||||
|
||||
#define BUTTON_HOLD_TIME_INDICATION 3000
|
||||
#define BUTTON_HOLD_TIME_ACTION 10000
|
||||
|
||||
#define BOARD_PWM_MAX 1023
|
||||
|
||||
#define LEDC_CHANNEL_1 1
|
||||
#define LEDC_CHANNEL_2 2
|
||||
#define LEDC_CHANNEL_3 3
|
||||
#define LEDC_TIMER_BITS 10
|
||||
#define LEDC_BASE_FREQ 12000
|
||||
|
||||
#define WIFI_NET_CONNECT_TIMEOUT 30000
|
||||
#define WIFI_CLOUD_CONNECT_TIMEOUT 15000
|
||||
#define WIFI_AP_CONFIG_PORT 80
|
||||
#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
|
||||
|
||||
#if defined(APP_DEBUG)
|
||||
#define DEBUG_PRINT(...) BLYNK_LOG1(__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_PRINT(...)
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*************************************************************
|
||||
This is a DEMO. You can use it only for development and testing.
|
||||
You should open Setting.h and modify General options.
|
||||
|
||||
If you would like to add these features to your product,
|
||||
please contact Blynk for Businesses:
|
||||
|
||||
http://www.blynk.io/
|
||||
|
||||
*************************************************************/
|
||||
|
||||
//#define USE_WROVER_BOARD
|
||||
#define USE_CUSTOM_BOARD // See "Custom board configuration" in Settings.h
|
||||
|
||||
#define APP_DEBUG // Comment this out to disable debug prints
|
||||
|
||||
#define BLYNK_PRINT Serial
|
||||
|
||||
#include "BlynkProvisioning.h"
|
||||
|
||||
void setup() {
|
||||
delay(500);
|
||||
Serial.begin(115200);
|
||||
|
||||
BlynkProvisioning.begin();
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// This handles the network and cloud connection
|
||||
BlynkProvisioning.run();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
extern "C" {
|
||||
#include "user_interface.h"
|
||||
}
|
||||
|
||||
#include "Settings.h"
|
||||
#include <BlynkSimpleEsp8266.h>
|
||||
#include "BlynkState.h"
|
||||
#include "ConfigStore.h"
|
||||
#include "ResetButton.h"
|
||||
#include "ConfigMode.h"
|
||||
#include "Indicator.h"
|
||||
#include "OTA.h"
|
||||
|
||||
inline
|
||||
void BlynkState::set(State m) {
|
||||
if (state != m) {
|
||||
DEBUG_PRINT(String(StateStr[state]) + " => " + StateStr[m]);
|
||||
state = m;
|
||||
}
|
||||
}
|
||||
|
||||
class Provisioning {
|
||||
|
||||
public:
|
||||
void begin()
|
||||
{
|
||||
DEBUG_PRINT("");
|
||||
DEBUG_PRINT("Hardware v" + String(BOARD_HARDWARE_VERSION));
|
||||
DEBUG_PRINT("Firmware v" + String(BOARD_FIRMWARE_VERSION));
|
||||
|
||||
indicator_init();
|
||||
button_init();
|
||||
config_init();
|
||||
|
||||
if (configStore.flagConfig) {
|
||||
BlynkState::set(MODE_CONNECTING_NET);
|
||||
} else {
|
||||
BlynkState::set(MODE_WAIT_CONFIG);
|
||||
}
|
||||
}
|
||||
|
||||
void run() {
|
||||
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: Blynk.run(); break;
|
||||
case MODE_OTA_UPGRADE: enterOTA(); break;
|
||||
case MODE_SWITCH_TO_STA: enterSwitchToSTA(); break;
|
||||
case MODE_RESET_CONFIG: enterResetConfig(); break;
|
||||
default: enterError(); break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Provisioning BlynkProvisioning;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
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] = {
|
||||
"WAIT_CONFIG",
|
||||
"CONFIGURING",
|
||||
"CONNECTING_NET",
|
||||
"CONNECTING_CLOUD",
|
||||
"RUNNING",
|
||||
"OTA_UPGRADE",
|
||||
"SWITCH_TO_STA",
|
||||
"RESET_CONFIG",
|
||||
"ERROR"
|
||||
};
|
||||
#endif
|
||||
|
||||
namespace BlynkState
|
||||
{
|
||||
volatile State state;
|
||||
|
||||
State get() { return state; }
|
||||
bool is (State m) { return (state == m); }
|
||||
void set(State m);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <ESP8266HTTPUpdateServer.h>
|
||||
#include <DNSServer.h>
|
||||
|
||||
ESP8266WebServer server(WIFI_AP_CONFIG_PORT);
|
||||
ESP8266HTTPUpdateServer httpUpdater;
|
||||
DNSServer dnsServer;
|
||||
const byte DNS_PORT = 53;
|
||||
|
||||
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" length=64></td></tr>
|
||||
<tr><td><label for="port">Port:</label></td> <td><input type="number" name="port" value="80" min="1" max="65535"></td></tr>
|
||||
</table><br/>
|
||||
<input type="submit" value="Apply">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)html";
|
||||
|
||||
void restartMCU() {
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
void enterConfigMode()
|
||||
{
|
||||
randomSeed(ESP.getChipId());
|
||||
const uint32_t unique = random(0xFFFFF);
|
||||
char ssidBuff[64];
|
||||
snprintf(ssidBuff, sizeof(ssidBuff), "%s-%05X", PRODUCT_WIFI_SSID, unique);
|
||||
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(100);
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAPConfig(WIFI_AP_IP, WIFI_AP_IP, WIFI_AP_Subnet);
|
||||
WiFi.softAP(ssidBuff);
|
||||
delay(500);
|
||||
IPAddress myIP = WiFi.softAPIP();
|
||||
DEBUG_PRINT(String("AP SSID: ") + ssidBuff);
|
||||
DEBUG_PRINT(String("AP IP: ") + myIP[0] + "." + myIP[1] + "." + myIP[2] + "." + myIP[3]);
|
||||
|
||||
if (myIP == (uint32_t)0)
|
||||
{
|
||||
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, BOARD_CONFIG_AP_URL, WiFi.softAPIP());
|
||||
DEBUG_PRINT(String("AP URL: ") + BOARD_CONFIG_AP_URL);
|
||||
#endif
|
||||
|
||||
httpUpdater.setup(&server);
|
||||
|
||||
server.on("/", []() {
|
||||
server.send(200, "text/html", config_form);
|
||||
});
|
||||
server.on("/config", []() {
|
||||
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");
|
||||
|
||||
String content;
|
||||
unsigned statusCode;
|
||||
|
||||
DEBUG_PRINT(String("WiFi SSID: ") + ssid + " Pass: " + pass);
|
||||
DEBUG_PRINT(String("Blynk cloud: ") + token + " @ " + host + ":" + port);
|
||||
|
||||
if (token.length() == 32 && ssid.length() > 0) {
|
||||
configStore.flagConfig = false;
|
||||
CopyString(ssid, configStore.wifiSSID);
|
||||
CopyString(pass, configStore.wifiPass);
|
||||
CopyString(token, configStore.cloudToken);
|
||||
if (host.length()) {
|
||||
CopyString(host, configStore.cloudHost);
|
||||
}
|
||||
if (port.length()) {
|
||||
configStore.cloudPort = port.toInt();
|
||||
}
|
||||
|
||||
content = R"json({"status":"ok","msg":"Configuration saved"})json";
|
||||
statusCode = 200;
|
||||
server.send(statusCode, "application/json", content);
|
||||
|
||||
BlynkState::set(MODE_SWITCH_TO_STA);
|
||||
} else {
|
||||
DEBUG_PRINT("Configuration invalid");
|
||||
content = R"json({"status":"error","msg":"Configuration invalid"})json";
|
||||
statusCode = 404;
|
||||
server.send(statusCode, "application/json", content);
|
||||
}
|
||||
});
|
||||
server.on("/board_info.json", []() {
|
||||
char buff[256];
|
||||
snprintf(buff, sizeof(buff),
|
||||
R"json({"board":"%s","vendor":"%s","tmpl_id":"%s","fw_ver":"%s","hw_ver":"%s"})json",
|
||||
BOARD_NAME,
|
||||
BOARD_VENDOR,
|
||||
BOARD_TEMPLATE_ID,
|
||||
BOARD_FIRMWARE_VERSION,
|
||||
BOARD_HARDWARE_VERSION
|
||||
);
|
||||
server.send(200, "application/json", buff);
|
||||
});
|
||||
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)) {
|
||||
dnsServer.processNextRequest();
|
||||
server.handleClient();
|
||||
if (BlynkState::is(MODE_WAIT_CONFIG) && WiFi.softAPgetStationNum() > 0) {
|
||||
BlynkState::set(MODE_CONFIGURING);
|
||||
} else if (BlynkState::is(MODE_CONFIGURING) && WiFi.softAPgetStationNum() == 0) {
|
||||
BlynkState::set(MODE_WAIT_CONFIG);
|
||||
}
|
||||
}
|
||||
|
||||
server.stop();
|
||||
}
|
||||
|
||||
void enterConnectNet() {
|
||||
BlynkState::set(MODE_CONNECTING_NET);
|
||||
DEBUG_PRINT(String("Connecting to WiFi: ") + configStore.wifiSSID);
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
if (!WiFi.begin(configStore.wifiSSID, configStore.wifiPass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long timeoutMs = millis() + WIFI_NET_CONNECT_TIMEOUT;
|
||||
while ((timeoutMs > millis()) && (WiFi.status() != WL_CONNECTED))
|
||||
{
|
||||
delay(100);
|
||||
if (!BlynkState::is(MODE_CONNECTING_NET)) {
|
||||
WiFi.disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
BlynkState::set(MODE_CONNECTING_CLOUD);
|
||||
} else {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void enterConnectCloud() {
|
||||
BlynkState::set(MODE_CONNECTING_CLOUD);
|
||||
|
||||
Blynk.disconnect();
|
||||
Blynk.config(configStore.cloudToken, configStore.cloudHost, configStore.cloudPort);
|
||||
Blynk.connect(0);
|
||||
|
||||
unsigned long timeoutMs = millis() + WIFI_CLOUD_CONNECT_TIMEOUT;
|
||||
while ((timeoutMs > millis()) &&
|
||||
(Blynk.connected() == false))
|
||||
{
|
||||
Blynk.run();
|
||||
if (!BlynkState::is(MODE_CONNECTING_CLOUD)) {
|
||||
Blynk.disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Blynk.connected()) {
|
||||
BlynkState::set(MODE_RUNNING);
|
||||
|
||||
if (!configStore.flagConfig) {
|
||||
configStore.flagConfig = true;
|
||||
config_save();
|
||||
DEBUG_PRINT("Configuration stored to flash");
|
||||
}
|
||||
} else {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void enterSwitchToSTA() {
|
||||
BlynkState::set(MODE_SWITCH_TO_STA);
|
||||
|
||||
DEBUG_PRINT("Switching to STA...");
|
||||
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(1000);
|
||||
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);
|
||||
if (!BlynkState::is(MODE_ERROR)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
DEBUG_PRINT("Restarting after error.");
|
||||
delay(10);
|
||||
|
||||
restartMCU();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
struct ConfigStore {
|
||||
uint32_t magic;
|
||||
char version[9];
|
||||
uint8_t flagConfig:1;
|
||||
uint8_t flagApFail:1;
|
||||
uint8_t flagSelfTest:1;
|
||||
|
||||
char wifiSSID[34];
|
||||
char wifiPass[64];
|
||||
|
||||
char cloudToken[34];
|
||||
char cloudHost[34];
|
||||
uint16_t cloudPort;
|
||||
|
||||
uint16_t checksum;
|
||||
} __attribute__((packed));
|
||||
|
||||
ConfigStore configStore;
|
||||
|
||||
const ConfigStore configDefault = {
|
||||
0x626C6E6B,
|
||||
BOARD_FIRMWARE_VERSION,
|
||||
0, 0, 0,
|
||||
|
||||
"",
|
||||
"",
|
||||
|
||||
"invalid token",
|
||||
"blynk-cloud.com", 80,
|
||||
0
|
||||
};
|
||||
|
||||
#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();
|
||||
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);
|
||||
}
|
||||
|
||||
template<typename T, int size>
|
||||
void CopyString(const String& s, T(&arr)[size]) {
|
||||
s.toCharArray(arr, size);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
#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) ((x)*(BOARD_LED_BRIGHTNESS)/255)
|
||||
#define RGB(r,g,b) (DIMM(r) << 16 | DIMM(g) << 8 | DIMM(b) << 0)
|
||||
|
||||
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() {
|
||||
m_Counter = 0;
|
||||
initLED();
|
||||
}
|
||||
|
||||
uint32_t run() {
|
||||
State currState = BlynkState::get();
|
||||
|
||||
// Reset counter if indicator state changes
|
||||
if (m_PrevState != currState) {
|
||||
m_PrevState = currState;
|
||||
m_Counter = 0;
|
||||
}
|
||||
|
||||
if (g_buttonPressed) {
|
||||
if (millis() - g_buttonPressTime > BUTTON_HOLD_TIME_ACTION) { return beatLED(COLOR_WHITE, (int[]){ 100, 100 }); }
|
||||
if (millis() - g_buttonPressTime > BUTTON_HOLD_TIME_INDICATION) { return waveLED(COLOR_WHITE, 1000); }
|
||||
}
|
||||
switch (currState) {
|
||||
case MODE_RESET_CONFIG:
|
||||
case MODE_WAIT_CONFIG: return beatLED(COLOR_BLUE, (int[]){ 50, 500 });
|
||||
case MODE_CONFIGURING: return beatLED(COLOR_BLUE, (int[]){ 200, 200 });
|
||||
case MODE_CONNECTING_NET: return beatLED(COLOR_BLYNK, (int[]){ 50, 500 });
|
||||
case MODE_CONNECTING_CLOUD: return beatLED(COLOR_BLYNK, (int[]){ 100, 100 });
|
||||
case MODE_RUNNING: return waveLED(COLOR_BLYNK, 5000);
|
||||
case MODE_OTA_UPGRADE: return beatLED(COLOR_MAGENTA, (int[]){ 50, 50 });
|
||||
default: return beatLED(COLOR_RED, (int[]){ 80, 100, 80, 1000 } );
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/*
|
||||
* LED drivers
|
||||
*/
|
||||
|
||||
#if defined(BOARD_LED_PIN_WS2812) // Addressable, NeoPixel RGB LED
|
||||
|
||||
void initLED() {
|
||||
rgb.begin();
|
||||
setRGB(COLOR_BLACK);
|
||||
}
|
||||
|
||||
void setRGB(uint32_t color) {
|
||||
rgb.setPixelColor(0, color);
|
||||
rgb.show();
|
||||
}
|
||||
|
||||
#elif defined(BOARD_LED_PIN_R) // Normal RGB LED (common anode or common cathode)
|
||||
|
||||
void initLED() {
|
||||
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, BOARD_PWM_MAX - r);
|
||||
analogWrite(BOARD_LED_PIN_G, BOARD_PWM_MAX - g);
|
||||
analogWrite(BOARD_LED_PIN_B, BOARD_PWM_MAX - b);
|
||||
#else
|
||||
analogWrite(BOARD_LED_PIN_R, r);
|
||||
analogWrite(BOARD_LED_PIN_G, g);
|
||||
analogWrite(BOARD_LED_PIN_B, 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, BOARD_PWM_MAX - color);
|
||||
#else
|
||||
analogWrite(BOARD_LED_PIN, color);
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#warning Invalid LED configuration.
|
||||
|
||||
#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_PWM_MAX : 0);
|
||||
uint32_t next = beat[m_Counter % cnt];
|
||||
m_Counter = (m_Counter+1) % cnt;
|
||||
return next;
|
||||
}
|
||||
|
||||
uint32_t waveLED(uint32_t, unsigned breathePeriod) {
|
||||
uint8_t brightness = (m_Counter < 128) ? m_Counter : 255 - m_Counter;
|
||||
|
||||
setLED(BOARD_PWM_MAX * ((float)brightness / (BOARD_PWM_MAX/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() {
|
||||
blinker.attach_ms(100, indicator_run);
|
||||
}
|
||||
|
||||
#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() {
|
||||
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() {
|
||||
Timer3.initialize(100*1000);
|
||||
Timer3.attachInterrupt(indicator_run);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#warning LED indicator needs a functional timer!
|
||||
|
||||
void indicator_run() {}
|
||||
void indicator_init() {}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************
|
||||
*
|
||||
* How to trigger an OTA update?
|
||||
* 1. In Arduino IDE menu: Sketch -> Export compiled Binary
|
||||
* 2. Open console, navigate to the sketch directory
|
||||
* 3.a Trigger update using HTTPS API on local server for specific hardware:
|
||||
* curl -v -F file=@Template_ESP8266.ino.nodemcu.bin --insecure -u admin@blynk.cc:admin https://localhost:9443/admin/ota/start?token=123
|
||||
* 3.b Trigger update using HTTPS API on local server for all hardware:
|
||||
* curl -v -F file=@Template_ESP8266.ino.nodemcu.bin --insecure -u admin@blynk.cc:admin https://localhost:9443/admin/ota/start
|
||||
* 3.c Trigger update using HTTPS API on local server for single user:
|
||||
* curl -v -F file=@Template_ESP8266.ino.nodemcu.bin --insecure -u admin@blynk.cc:admin https://localhost:9443/admin/ota/start?user=pupkin@gmail.com
|
||||
* 3.d Trigger update using HTTPS API on local server for single user and specific project:
|
||||
* curl -v -F file=@Template_ESP8266.ino.nodemcu.bin --insecure -u admin@blynk.cc:admin https://localhost:9443/admin/ota/start?user=pupkin@gmail.com&project=123
|
||||
* More about ESP8266 OTA updates:
|
||||
* https://github.com/esp8266/Arduino/blob/master/doc/ota_updates/readme.md
|
||||
*/
|
||||
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266httpUpdate.h>
|
||||
|
||||
String overTheAirURL;
|
||||
|
||||
BLYNK_WRITE(InternalPinOTA) {
|
||||
overTheAirURL = param.asString();
|
||||
|
||||
// Disconnect, not to interfere with OTA process
|
||||
Blynk.disconnect();
|
||||
|
||||
// Start OTA
|
||||
BlynkState::set(MODE_OTA_UPGRADE);
|
||||
delay(500);
|
||||
}
|
||||
|
||||
void enterOTA() {
|
||||
BlynkState::set(MODE_OTA_UPGRADE);
|
||||
|
||||
DEBUG_PRINT(String("Firmware update URL: ") + overTheAirURL);
|
||||
switch (ESPhttpUpdate.update(overTheAirURL, BOARD_FIRMWARE_VERSION)) {
|
||||
case HTTP_UPDATE_FAILED:
|
||||
DEBUG_PRINT(String("Firmware update failed (error ") + ESPhttpUpdate.getLastError() + "): " + ESPhttpUpdate.getLastErrorString());
|
||||
BlynkState::set(MODE_ERROR);
|
||||
break;
|
||||
case HTTP_UPDATE_NO_UPDATES:
|
||||
DEBUG_PRINT("No firmware updates available.");
|
||||
BlynkState::set(MODE_CONNECTING_CLOUD);
|
||||
break;
|
||||
case HTTP_UPDATE_OK:
|
||||
DEBUG_PRINT("Firmware update: OK.");
|
||||
delay(10);
|
||||
restartMCU();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
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 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();
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* General options
|
||||
*/
|
||||
|
||||
#define BOARD_FIRMWARE_VERSION "1.0.1"
|
||||
#define BOARD_HARDWARE_VERSION "1.0.0"
|
||||
|
||||
#define BOARD_NAME "Product Name" // Name of your product. Should match App Export request info.
|
||||
#define BOARD_VENDOR "Company Name" // Name of your company. Should match App Export request info.
|
||||
#define BOARD_TEMPLATE_ID "TMPL0000" // ID of the Tile Template. Can be found in Tile Template Settings
|
||||
|
||||
#define PRODUCT_WIFI_SSID "Our Product" // Name of the device, to be displayed during configuration. Should match export request info.
|
||||
#define BOARD_CONFIG_AP_URL "our-product.cc" // Config page will be available in a browser at 'http://our-product.cc/'
|
||||
|
||||
/*
|
||||
* Board configuration (see examples below).
|
||||
*/
|
||||
|
||||
#if defined(USE_CUSTOM_BOARD)
|
||||
|
||||
// Custom board configuration
|
||||
#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 2 // 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
|
||||
|
||||
#elif defined(USE_NODE_MCU_BOARD)
|
||||
|
||||
#warning "NodeMCU board selected"
|
||||
|
||||
// Example configuration for NodeMCU v1.0 Board
|
||||
#define BOARD_BUTTON_PIN 0
|
||||
#define BOARD_BUTTON_ACTIVE_LOW true
|
||||
|
||||
#define BOARD_LED_PIN_R D8
|
||||
#define BOARD_LED_PIN_G D7
|
||||
#define BOARD_LED_PIN_B D6
|
||||
#define BOARD_LED_INVERSE false
|
||||
#define BOARD_LED_BRIGHTNESS 64
|
||||
|
||||
#elif defined(USE_SPARKFUN_BLYNK_BOARD)
|
||||
|
||||
#warning "Sparkfun Blynk board selected"
|
||||
|
||||
// Example configuration for 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)
|
||||
|
||||
#warning "Witty Cloud board selected"
|
||||
|
||||
// Example configuration for 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
|
||||
#error "No board selected"
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Advanced options
|
||||
*/
|
||||
|
||||
#define BUTTON_HOLD_TIME_INDICATION 3000
|
||||
#define BUTTON_HOLD_TIME_ACTION 10000
|
||||
|
||||
#define BOARD_PWM_MAX 1023
|
||||
|
||||
#define WIFI_NET_CONNECT_TIMEOUT 30000
|
||||
#define WIFI_CLOUD_CONNECT_TIMEOUT 15000
|
||||
#define WIFI_AP_CONFIG_PORT 80
|
||||
#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
|
||||
|
||||
#if defined(APP_DEBUG)
|
||||
#define DEBUG_PRINT(...) BLYNK_LOG1(__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_PRINT(...)
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*************************************************************
|
||||
This is a DEMO. You can use it only for development and testing.
|
||||
You should open Setting.h and modify General options.
|
||||
|
||||
If you would like to add these features to your product,
|
||||
please contact Blynk for Businesses:
|
||||
|
||||
http://www.blynk.io/
|
||||
|
||||
*************************************************************/
|
||||
|
||||
#define USE_SPARKFUN_BLYNK_BOARD // Uncomment the board you are using
|
||||
//#define USE_NODE_MCU_BOARD // Comment out the boards you are not using
|
||||
//#define USE_WITTY_CLOUD_BOARD
|
||||
//#define USE_CUSTOM_BOARD // For all other ESP8266-based boards -
|
||||
// see "Custom board configuration" in Settings.h
|
||||
|
||||
#define APP_DEBUG // Comment this out to disable debug prints
|
||||
|
||||
#define BLYNK_PRINT Serial
|
||||
|
||||
#include "BlynkProvisioning.h"
|
||||
|
||||
void setup() {
|
||||
delay(500);
|
||||
Serial.begin(115200);
|
||||
|
||||
BlynkProvisioning.begin();
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// This handles the network and cloud connection
|
||||
BlynkProvisioning.run();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
#include "Settings.h"
|
||||
#include <SPI.h>
|
||||
#include <WiFi101.h>
|
||||
#include <BlynkSimpleMKR1000.h>
|
||||
#include "BlynkState.h"
|
||||
#include "ConfigStore.h"
|
||||
#include "ResetButton.h"
|
||||
#include "ConfigMode.h"
|
||||
#include "Indicator.h"
|
||||
#include "OTA.h"
|
||||
|
||||
inline
|
||||
void BlynkState::set(State m) {
|
||||
if (state != m) {
|
||||
DEBUG_PRINT(String(StateStr[state]) + " => " + StateStr[m]);
|
||||
state = m;
|
||||
}
|
||||
}
|
||||
|
||||
class Provisioning {
|
||||
|
||||
public:
|
||||
void begin()
|
||||
{
|
||||
DEBUG_PRINT("");
|
||||
DEBUG_PRINT("Hardware v" + String(BOARD_HARDWARE_VERSION));
|
||||
DEBUG_PRINT("Firmware v" + String(BOARD_FIRMWARE_VERSION));
|
||||
|
||||
indicator_init();
|
||||
button_init();
|
||||
config_init();
|
||||
|
||||
if (configStore.flagConfig) {
|
||||
BlynkState::set(MODE_CONNECTING_NET);
|
||||
} else {
|
||||
BlynkState::set(MODE_WAIT_CONFIG);
|
||||
}
|
||||
}
|
||||
|
||||
void run() {
|
||||
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: Blynk.run(); break;
|
||||
case MODE_OTA_UPGRADE: enterOTA(); break;
|
||||
case MODE_SWITCH_TO_STA: enterSwitchToSTA(); break;
|
||||
case MODE_RESET_CONFIG: enterResetConfig(); break;
|
||||
default: enterError(); break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Provisioning BlynkProvisioning;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
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] = {
|
||||
"WAIT_CONFIG",
|
||||
"CONFIGURING",
|
||||
"CONNECTING_NET",
|
||||
"CONNECTING_CLOUD",
|
||||
"RUNNING",
|
||||
"OTA_UPGRADE",
|
||||
"SWITCH_TO_STA",
|
||||
"RESET_CONFIG",
|
||||
"ERROR"
|
||||
};
|
||||
#endif
|
||||
|
||||
namespace BlynkState
|
||||
{
|
||||
volatile State state;
|
||||
|
||||
State get() { return state; }
|
||||
bool is (State m) { return (state == m); }
|
||||
void set(State m);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiMDNSResponder.h>
|
||||
|
||||
WiFiServer server(WIFI_AP_CONFIG_PORT);
|
||||
WiFiMDNSResponder mdnsResponder;
|
||||
|
||||
String urlDecode(const String& text);
|
||||
String urlFindArg(const String& url, const String& arg);
|
||||
|
||||
enum Request {
|
||||
REQ_BOARD_INFO,
|
||||
REQ_ROOT,
|
||||
REQ_CONFIG,
|
||||
REQ_RESET,
|
||||
REQ_REBOOT
|
||||
};
|
||||
|
||||
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" length=64></td></tr>
|
||||
<tr><td><label for="port">Port:</label></td> <td><input type="number" name="port" value="80" min="1" max="65535"></td></tr>
|
||||
</table><br/>
|
||||
<input type="submit" value="Apply">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)html";
|
||||
|
||||
void restartMCU() {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
void enterConfigMode()
|
||||
{
|
||||
byte mac[6];
|
||||
memset(mac, 0, sizeof(mac));
|
||||
WiFi.macAddress(mac);
|
||||
uint32_t chipId = *(uint32_t*)(mac+2) & 0xFFFFFF;
|
||||
|
||||
randomSeed(chipId);
|
||||
const uint32_t unique = random(0xFFFFF);
|
||||
char ssidBuff[64];
|
||||
snprintf(ssidBuff, sizeof(ssidBuff), "%s-%05X", PRODUCT_WIFI_SSID, unique);
|
||||
|
||||
WiFi.beginAP(ssidBuff);
|
||||
mdnsResponder.begin(BOARD_CONFIG_AP_URL);
|
||||
|
||||
delay(500);
|
||||
IPAddress myIP = WiFi.localIP();
|
||||
DEBUG_PRINT(String("AP SSID: ") + ssidBuff);
|
||||
DEBUG_PRINT(String("AP IP: ") + myIP[0] + "." + myIP[1] + "." + myIP[2] + "." + myIP[3]);
|
||||
DEBUG_PRINT(String("AP URL: ") + BOARD_CONFIG_AP_URL + ".local");
|
||||
|
||||
server.begin();
|
||||
|
||||
while(BlynkState::is(MODE_WAIT_CONFIG)) {
|
||||
mdnsResponder.poll();
|
||||
WiFiClient client = server.available(); // listen for incoming clients
|
||||
|
||||
if (client) { // if you get a client,
|
||||
String currentLine = ""; // make a String to hold incoming data from the client
|
||||
String config_line = "";
|
||||
Request req = REQ_ROOT;
|
||||
while (client.connected()) { // loop while the client's connected
|
||||
if (client.available()) { // if there's bytes to read from the client,
|
||||
char c = client.read(); // read a byte, then
|
||||
Serial.write(c); // print it out the serial monitor
|
||||
if (c == '\n') { // if the byte is a newline character
|
||||
|
||||
// if the current line is blank, you got two newline characters in a row.
|
||||
// that's the end of the client HTTP request, so send a response:
|
||||
if (currentLine.length() == 0) {
|
||||
String responce = "200 OK";
|
||||
String content = "";
|
||||
String content_type = "text/html";
|
||||
|
||||
switch(req) {
|
||||
case REQ_ROOT: {
|
||||
content = config_form;
|
||||
} break;
|
||||
case REQ_CONFIG: {
|
||||
String ssid = urlFindArg(config_line, "ssid");
|
||||
String ssidManual = urlFindArg(config_line, "ssidManual");
|
||||
String pass = urlFindArg(config_line, "pass");
|
||||
if (ssidManual != "") {
|
||||
ssid = ssidManual;
|
||||
}
|
||||
String token = urlFindArg(config_line, "blynk");
|
||||
String host = urlFindArg(config_line, "host");
|
||||
String port = urlFindArg(config_line, "port");
|
||||
|
||||
DEBUG_PRINT(String("WiFi SSID: ") + ssid + " Pass: " + pass);
|
||||
DEBUG_PRINT(String("Blynk cloud: ") + token + " @ " + host + ":" + port);
|
||||
|
||||
if (token.length() == 32 && ssid.length() > 0) {
|
||||
configStore.flagConfig = false;
|
||||
CopyString(ssid, configStore.wifiSSID);
|
||||
CopyString(pass, configStore.wifiPass);
|
||||
CopyString(token, configStore.cloudToken);
|
||||
if (host.length()) {
|
||||
CopyString(host, configStore.cloudHost);
|
||||
}
|
||||
if (port.length()) {
|
||||
configStore.cloudPort = port.toInt();
|
||||
}
|
||||
|
||||
content = R"json({"status":"ok","msg":"Configuration saved"})json";
|
||||
|
||||
BlynkState::set(MODE_SWITCH_TO_STA);
|
||||
} else {
|
||||
DEBUG_PRINT("Configuration invalid");
|
||||
content = R"json({"status":"error","msg":"Configuration invalid"})json";
|
||||
}
|
||||
content_type = "application/json";
|
||||
} break;
|
||||
case REQ_BOARD_INFO: {
|
||||
char buff[256];
|
||||
snprintf(buff, sizeof(buff),
|
||||
R"json({"board":"%s","vendor":"%s","tmpl_id":"%s","fw_ver":"%s","hw_ver":"%s"})json",
|
||||
BOARD_NAME,
|
||||
BOARD_VENDOR,
|
||||
BOARD_TEMPLATE_ID,
|
||||
BOARD_FIRMWARE_VERSION,
|
||||
BOARD_HARDWARE_VERSION
|
||||
);
|
||||
content = buff;
|
||||
content_type = "application/json";
|
||||
} break;
|
||||
case REQ_RESET: {
|
||||
BlynkState::set(MODE_RESET_CONFIG);
|
||||
content = R"json({"status":"ok","msg":"Configuration reset"})json";
|
||||
content_type = "application/json";
|
||||
} break;
|
||||
case REQ_REBOOT: {
|
||||
restartMCU();
|
||||
} break;
|
||||
}
|
||||
|
||||
client.println("HTTP/1.1 " + responce);
|
||||
client.println("Content-type:" + content_type);
|
||||
client.println();
|
||||
client.println(content);
|
||||
// break out of the while loop:
|
||||
break;
|
||||
} else { // if you got a newline, then clear currentLine:
|
||||
currentLine = "";
|
||||
}
|
||||
} else if (c != '\r') { // if you got anything else but a carriage return character,
|
||||
currentLine += c; // add it to the end of the currentLine
|
||||
}
|
||||
|
||||
if (currentLine.indexOf("GET /board_info.json") >= 0) {
|
||||
req = REQ_BOARD_INFO;
|
||||
} else if (currentLine.indexOf(" /config") >= 0) {
|
||||
req = REQ_CONFIG;
|
||||
int idx = currentLine.indexOf("?");
|
||||
config_line = "&" + currentLine.substring(idx+1, currentLine.lastIndexOf(' ')) + "&";
|
||||
} else if (currentLine.indexOf(" /reset") >= 0) {
|
||||
req = REQ_RESET;
|
||||
} else if (currentLine.indexOf(" /reboot") >= 0) {
|
||||
req = REQ_REBOOT;
|
||||
}
|
||||
}
|
||||
}
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String urlDecode(const String& text)
|
||||
{
|
||||
String decoded = "";
|
||||
char temp[] = "0x00";
|
||||
unsigned int len = text.length();
|
||||
unsigned int i = 0;
|
||||
while (i < len) {
|
||||
char decodedChar;
|
||||
char encodedChar = text.charAt(i++);
|
||||
if ((encodedChar == '%') && (i + 1 < len)) {
|
||||
temp[2] = text.charAt(i++);
|
||||
temp[3] = text.charAt(i++);
|
||||
|
||||
decodedChar = strtol(temp, NULL, 16);
|
||||
} else {
|
||||
if (encodedChar == '+') {
|
||||
decodedChar = ' ';
|
||||
} else {
|
||||
decodedChar = encodedChar;
|
||||
}
|
||||
}
|
||||
decoded += decodedChar;
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
String urlFindArg(const String& url, const String& arg)
|
||||
{
|
||||
int s = url.indexOf("&" + arg + "=");
|
||||
if (s < 0)
|
||||
return "";
|
||||
int s_len = arg.length() + 2;
|
||||
int e = url.indexOf('&', s + s_len);
|
||||
return urlDecode(url.substring(s + s_len, e));
|
||||
}
|
||||
|
||||
void enterConnectNet() {
|
||||
BlynkState::set(MODE_CONNECTING_NET);
|
||||
DEBUG_PRINT(String("Connecting to WiFi: ") + configStore.wifiSSID);
|
||||
|
||||
WiFi.end();
|
||||
|
||||
unsigned long timeoutMs = millis() + WIFI_NET_CONNECT_TIMEOUT;
|
||||
while ((timeoutMs > millis()) && (WiFi.status() != WL_CONNECTED))
|
||||
{
|
||||
WiFi.begin(configStore.wifiSSID, configStore.wifiPass);
|
||||
delay(100);
|
||||
if (!BlynkState::is(MODE_CONNECTING_NET)) {
|
||||
WiFi.disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
BlynkState::set(MODE_CONNECTING_CLOUD);
|
||||
} else {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void enterConnectCloud() {
|
||||
BlynkState::set(MODE_CONNECTING_CLOUD);
|
||||
|
||||
Blynk.disconnect();
|
||||
Blynk.config(configStore.cloudToken, configStore.cloudHost, configStore.cloudPort);
|
||||
Blynk.connect(0);
|
||||
|
||||
unsigned long timeoutMs = millis() + WIFI_CLOUD_CONNECT_TIMEOUT;
|
||||
while ((timeoutMs > millis()) &&
|
||||
(Blynk.connected() == false))
|
||||
{
|
||||
Blynk.run();
|
||||
if (!BlynkState::is(MODE_CONNECTING_CLOUD)) {
|
||||
Blynk.disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Blynk.connected()) {
|
||||
BlynkState::set(MODE_RUNNING);
|
||||
|
||||
if (!configStore.flagConfig) {
|
||||
configStore.flagConfig = true;
|
||||
config_save();
|
||||
DEBUG_PRINT("Configuration stored to flash");
|
||||
}
|
||||
} else {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void enterSwitchToSTA() {
|
||||
BlynkState::set(MODE_SWITCH_TO_STA);
|
||||
|
||||
DEBUG_PRINT("Switching to STA...");
|
||||
|
||||
WiFi.end();
|
||||
delay(1000);
|
||||
|
||||
BlynkState::set(MODE_CONNECTING_NET);
|
||||
}
|
||||
|
||||
void enterError() {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
|
||||
unsigned long timeoutMs = millis() + 10000;
|
||||
while (timeoutMs > millis() || g_buttonPressed)
|
||||
{
|
||||
delay(10);
|
||||
if (!BlynkState::is(MODE_ERROR)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
DEBUG_PRINT("Restarting after error.");
|
||||
delay(10);
|
||||
|
||||
restartMCU();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
struct ConfigStore {
|
||||
uint32_t magic;
|
||||
char version[9];
|
||||
uint8_t flagConfig:1;
|
||||
uint8_t flagApFail:1;
|
||||
uint8_t flagSelfTest:1;
|
||||
|
||||
char wifiSSID[34];
|
||||
char wifiPass[64];
|
||||
|
||||
char cloudToken[34];
|
||||
char cloudHost[34];
|
||||
uint16_t cloudPort;
|
||||
|
||||
uint16_t checksum;
|
||||
} __attribute__((packed));
|
||||
|
||||
ConfigStore configStore;
|
||||
|
||||
const ConfigStore configDefault = {
|
||||
0x626C6E6B,
|
||||
BOARD_FIRMWARE_VERSION,
|
||||
0, 0, 0,
|
||||
|
||||
"",
|
||||
"",
|
||||
|
||||
"invalid token",
|
||||
"blynk-cloud.com", 80,
|
||||
0
|
||||
};
|
||||
|
||||
#include <FlashStorage.h>
|
||||
FlashStorage(flash, ConfigStore);
|
||||
|
||||
void config_load()
|
||||
{
|
||||
memset(&configStore, 0, sizeof(configStore));
|
||||
configStore = flash.read();
|
||||
if (configStore.magic != configDefault.magic) {
|
||||
DEBUG_PRINT("Using default config.");
|
||||
configStore = configDefault;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool config_save()
|
||||
{
|
||||
flash.write(configStore);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool config_init()
|
||||
{
|
||||
config_load();
|
||||
return true;
|
||||
}
|
||||
|
||||
void enterResetConfig()
|
||||
{
|
||||
DEBUG_PRINT("Resetting configuration!");
|
||||
configStore = configDefault;
|
||||
config_save();
|
||||
BlynkState::set(MODE_WAIT_CONFIG);
|
||||
}
|
||||
|
||||
template<typename T, int size>
|
||||
void CopyString(const String& s, T(&arr)[size]) {
|
||||
s.toCharArray(arr, size);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
#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) ((x)*(BOARD_LED_BRIGHTNESS)/255)
|
||||
#define RGB(r,g,b) (DIMM(r) << 16 | DIMM(g) << 8 | DIMM(b) << 0)
|
||||
|
||||
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() {
|
||||
m_Counter = 0;
|
||||
initLED();
|
||||
}
|
||||
|
||||
uint32_t run() {
|
||||
State currState = BlynkState::get();
|
||||
|
||||
// Reset counter if indicator state changes
|
||||
if (m_PrevState != currState) {
|
||||
m_PrevState = currState;
|
||||
m_Counter = 0;
|
||||
}
|
||||
|
||||
if (g_buttonPressed) {
|
||||
if (millis() - g_buttonPressTime > BUTTON_HOLD_TIME_ACTION) { return beatLED(COLOR_WHITE, (int[]){ 100, 100 }); }
|
||||
if (millis() - g_buttonPressTime > BUTTON_HOLD_TIME_INDICATION) { return waveLED(COLOR_WHITE, 1000); }
|
||||
}
|
||||
switch (currState) {
|
||||
case MODE_RESET_CONFIG:
|
||||
case MODE_WAIT_CONFIG: return beatLED(COLOR_BLUE, (int[]){ 50, 500 });
|
||||
case MODE_CONFIGURING: return beatLED(COLOR_BLUE, (int[]){ 200, 200 });
|
||||
case MODE_CONNECTING_NET: return beatLED(COLOR_BLYNK, (int[]){ 50, 500 });
|
||||
case MODE_CONNECTING_CLOUD: return beatLED(COLOR_BLYNK, (int[]){ 100, 100 });
|
||||
case MODE_RUNNING: return waveLED(COLOR_BLYNK, 5000);
|
||||
case MODE_OTA_UPGRADE: return beatLED(COLOR_MAGENTA, (int[]){ 50, 50 });
|
||||
default: return beatLED(COLOR_RED, (int[]){ 80, 100, 80, 1000 } );
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/*
|
||||
* LED drivers
|
||||
*/
|
||||
|
||||
#if defined(BOARD_LED_PIN_WS2812) // Addressable, NeoPixel RGB LED
|
||||
|
||||
void initLED() {
|
||||
rgb.begin();
|
||||
setRGB(COLOR_BLACK);
|
||||
}
|
||||
|
||||
void setRGB(uint32_t color) {
|
||||
rgb.setPixelColor(0, color);
|
||||
rgb.show();
|
||||
}
|
||||
|
||||
#elif defined(BOARD_LED_PIN_R) // Normal RGB LED (common anode or common cathode)
|
||||
|
||||
void initLED() {
|
||||
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, BOARD_PWM_MAX - r);
|
||||
analogWrite(BOARD_LED_PIN_G, BOARD_PWM_MAX - g);
|
||||
analogWrite(BOARD_LED_PIN_B, BOARD_PWM_MAX - b);
|
||||
#else
|
||||
analogWrite(BOARD_LED_PIN_R, r);
|
||||
analogWrite(BOARD_LED_PIN_G, g);
|
||||
analogWrite(BOARD_LED_PIN_B, 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, BOARD_PWM_MAX - color);
|
||||
#else
|
||||
analogWrite(BOARD_LED_PIN, color);
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#warning Invalid LED configuration.
|
||||
|
||||
#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_PWM_MAX : 0);
|
||||
uint32_t next = beat[m_Counter % cnt];
|
||||
m_Counter = (m_Counter+1) % cnt;
|
||||
return next;
|
||||
}
|
||||
|
||||
uint32_t waveLED(uint32_t, unsigned breathePeriod) {
|
||||
uint8_t brightness = (m_Counter < 128) ? m_Counter : 255 - m_Counter;
|
||||
|
||||
setLED(BOARD_PWM_MAX * ((float)brightness / (BOARD_PWM_MAX/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() {
|
||||
blinker.attach_ms(100, indicator_run);
|
||||
}
|
||||
|
||||
#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() {
|
||||
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() {
|
||||
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() {
|
||||
MyTimer5.begin(1000/10);
|
||||
MyTimer5.attachInterrupt(indicator_run);
|
||||
MyTimer5.start();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#warning LED indicator needs a functional timer!
|
||||
|
||||
void indicator_run() {}
|
||||
void indicator_init() {}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* OTA is not ready for this board yet.
|
||||
* It could be derived from:
|
||||
* https://www.hackster.io/flower-platform/program-mkr-over-the-air-goodies-voice-control-etc-562e9a
|
||||
*/
|
||||
|
||||
String overTheAirURL;
|
||||
|
||||
BLYNK_WRITE(InternalPinOTA) {
|
||||
overTheAirURL = param.asString();
|
||||
|
||||
// Disconnect, not to interfere with OTA process
|
||||
Blynk.disconnect();
|
||||
|
||||
// Start OTA
|
||||
BlynkState::set(MODE_OTA_UPGRADE);
|
||||
delay(500);
|
||||
}
|
||||
|
||||
void enterOTA() {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
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 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();
|
||||
}
|
||||
g_buttonPressTime = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void button_init()
|
||||
{
|
||||
#if BOARD_BUTTON_ACTIVE_LOW
|
||||
pinMode(BOARD_BUTTON_PIN, INPUT_PULLUP);
|
||||
#else
|
||||
pinMode(BOARD_BUTTON_PIN, INPUT_PULLDOWN);
|
||||
#endif
|
||||
attachInterrupt(BOARD_BUTTON_PIN, button_change, CHANGE);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* General options
|
||||
*/
|
||||
|
||||
#define BOARD_FIRMWARE_VERSION "1.0.1"
|
||||
#define BOARD_HARDWARE_VERSION "1.0.0"
|
||||
|
||||
#define BOARD_NAME "Product Name" // Name of your product. Should match App Export request info.
|
||||
#define BOARD_VENDOR "Company Name" // Name of your company. Should match App Export request info.
|
||||
#define BOARD_TEMPLATE_ID "TMPL0000" // ID of the Tile Template. Can be found in Tile Template Settings
|
||||
|
||||
#define PRODUCT_WIFI_SSID "Our Product" // Name of the device, to be displayed during configuration. Should match export request info.
|
||||
#define BOARD_CONFIG_AP_URL "our-product" // Config page will be available in a browser at 'http://our-product.local/'
|
||||
|
||||
/*
|
||||
* Board configuration (see examples below).
|
||||
*/
|
||||
|
||||
#if defined(USE_CUSTOM_BOARD)
|
||||
|
||||
// Custom board configuration
|
||||
#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 6 // Set LED pin - if you have a simple 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 you need to inverse LED signal
|
||||
#define BOARD_LED_BRIGHTNESS 64 // 0..255 brightness control
|
||||
|
||||
#elif defined(USE_MKR1000_BOARD)
|
||||
|
||||
// Example configuration for MKR1000 Board
|
||||
#define BOARD_BUTTON_PIN 0
|
||||
#define BOARD_BUTTON_ACTIVE_LOW true
|
||||
|
||||
#define BOARD_LED_PIN 6
|
||||
#define BOARD_LED_INVERSE false
|
||||
|
||||
#else
|
||||
#error "No board selected"
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Advanced options
|
||||
*/
|
||||
|
||||
#define BUTTON_HOLD_TIME_INDICATION 3000
|
||||
#define BUTTON_HOLD_TIME_ACTION 10000
|
||||
|
||||
#define BOARD_PWM_MAX 255
|
||||
|
||||
#define WIFI_NET_CONNECT_TIMEOUT 30000
|
||||
#define WIFI_CLOUD_CONNECT_TIMEOUT 15000
|
||||
#define WIFI_AP_CONFIG_PORT 80
|
||||
//#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
|
||||
|
||||
#if defined(APP_DEBUG)
|
||||
#define DEBUG_PRINT(...) BLYNK_LOG1(__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_PRINT(...)
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*************************************************************
|
||||
This is a DEMO. You can use it only for development and testing.
|
||||
You should open Setting.h and modify General options.
|
||||
|
||||
If you would like to add these features to your product,
|
||||
please contact Blynk for Businesses:
|
||||
|
||||
http://www.blynk.io/
|
||||
|
||||
This example requires some additional libraries:
|
||||
https://github.com/cmaglie/FlashStorage
|
||||
https://github.com/michael71/Timer5
|
||||
|
||||
*************************************************************/
|
||||
|
||||
#define USE_MKR1000_BOARD
|
||||
|
||||
#define APP_DEBUG // Comment this out to disable debug prints
|
||||
|
||||
#define BLYNK_PRINT Serial
|
||||
|
||||
#include "BlynkProvisioning.h"
|
||||
|
||||
void setup() {
|
||||
delay(500);
|
||||
Serial.begin(115200);
|
||||
|
||||
BlynkProvisioning.begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// This handles the network and cloud connection
|
||||
BlynkProvisioning.run();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
#include "Settings.h"
|
||||
#include <SPI.h>
|
||||
#include <WiFiNINA.h>
|
||||
#include <BlynkSimpleWiFiNINA.h>
|
||||
#include "BlynkState.h"
|
||||
#include "ConfigStore.h"
|
||||
#include "ResetButton.h"
|
||||
#include "ConfigMode.h"
|
||||
#include "Indicator.h"
|
||||
#include "OTA.h"
|
||||
|
||||
inline
|
||||
void BlynkState::set(State m) {
|
||||
if (state != m) {
|
||||
DEBUG_PRINT(String(StateStr[state]) + " => " + StateStr[m]);
|
||||
state = m;
|
||||
}
|
||||
}
|
||||
|
||||
class Provisioning {
|
||||
|
||||
public:
|
||||
void begin()
|
||||
{
|
||||
DEBUG_PRINT("");
|
||||
DEBUG_PRINT("Hardware v" + String(BOARD_HARDWARE_VERSION));
|
||||
DEBUG_PRINT("Firmware v" + String(BOARD_FIRMWARE_VERSION));
|
||||
|
||||
indicator_init();
|
||||
button_init();
|
||||
config_init();
|
||||
|
||||
if (configStore.flagConfig) {
|
||||
BlynkState::set(MODE_CONNECTING_NET);
|
||||
} else {
|
||||
BlynkState::set(MODE_WAIT_CONFIG);
|
||||
}
|
||||
}
|
||||
|
||||
void run() {
|
||||
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: Blynk.run(); break;
|
||||
case MODE_OTA_UPGRADE: enterOTA(); break;
|
||||
case MODE_SWITCH_TO_STA: enterSwitchToSTA(); break;
|
||||
case MODE_RESET_CONFIG: enterResetConfig(); break;
|
||||
default: enterError(); break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Provisioning BlynkProvisioning;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
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] = {
|
||||
"WAIT_CONFIG",
|
||||
"CONFIGURING",
|
||||
"CONNECTING_NET",
|
||||
"CONNECTING_CLOUD",
|
||||
"RUNNING",
|
||||
"OTA_UPGRADE",
|
||||
"SWITCH_TO_STA",
|
||||
"RESET_CONFIG",
|
||||
"ERROR"
|
||||
};
|
||||
#endif
|
||||
|
||||
namespace BlynkState
|
||||
{
|
||||
volatile State state;
|
||||
|
||||
State get() { return state; }
|
||||
bool is (State m) { return (state == m); }
|
||||
void set(State m);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
#include <WiFiClient.h>
|
||||
|
||||
WiFiServer server(WIFI_AP_CONFIG_PORT);
|
||||
|
||||
String urlDecode(const String& text);
|
||||
String urlFindArg(const String& url, const String& arg);
|
||||
|
||||
enum Request {
|
||||
REQ_BOARD_INFO,
|
||||
REQ_ROOT,
|
||||
REQ_CONFIG,
|
||||
REQ_RESET,
|
||||
REQ_REBOOT
|
||||
};
|
||||
|
||||
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" length=64></td></tr>
|
||||
<tr><td><label for="port">Port:</label></td> <td><input type="number" name="port" value="80" min="1" max="65535"></td></tr>
|
||||
</table><br/>
|
||||
<input type="submit" value="Apply">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)html";
|
||||
|
||||
void restartMCU() {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
void enterConfigMode()
|
||||
{
|
||||
byte mac[6];
|
||||
memset(mac, 0, sizeof(mac));
|
||||
WiFi.macAddress(mac);
|
||||
uint32_t chipId = *(uint32_t*)(mac+2) & 0xFFFFFF;
|
||||
|
||||
randomSeed(chipId);
|
||||
const uint32_t unique = random(0xFFFFF);
|
||||
char ssidBuff[64];
|
||||
snprintf(ssidBuff, sizeof(ssidBuff), "%s-%05X", PRODUCT_WIFI_SSID, unique);
|
||||
|
||||
WiFi.beginAP(ssidBuff);
|
||||
|
||||
delay(500);
|
||||
IPAddress myIP = WiFi.localIP();
|
||||
DEBUG_PRINT(String("AP SSID: ") + ssidBuff);
|
||||
DEBUG_PRINT(String("AP IP: ") + myIP[0] + "." + myIP[1] + "." + myIP[2] + "." + myIP[3]);
|
||||
|
||||
server.begin();
|
||||
|
||||
while(BlynkState::is(MODE_WAIT_CONFIG)) {
|
||||
WiFiClient client = server.available(); // listen for incoming clients
|
||||
|
||||
if (client) { // if you get a client,
|
||||
String currentLine = ""; // make a String to hold incoming data from the client
|
||||
String config_line = "";
|
||||
Request req = REQ_ROOT;
|
||||
while (client.connected()) { // loop while the client's connected
|
||||
if (client.available()) { // if there's bytes to read from the client,
|
||||
char c = client.read(); // read a byte, then
|
||||
Serial.write(c); // print it out the serial monitor
|
||||
if (c == '\n') { // if the byte is a newline character
|
||||
|
||||
// if the current line is blank, you got two newline characters in a row.
|
||||
// that's the end of the client HTTP request, so send a response:
|
||||
if (currentLine.length() == 0) {
|
||||
String responce = "200 OK";
|
||||
String content = "";
|
||||
String content_type = "text/html";
|
||||
|
||||
switch(req) {
|
||||
case REQ_ROOT: {
|
||||
content = config_form;
|
||||
} break;
|
||||
case REQ_CONFIG: {
|
||||
String ssid = urlFindArg(config_line, "ssid");
|
||||
String ssidManual = urlFindArg(config_line, "ssidManual");
|
||||
String pass = urlFindArg(config_line, "pass");
|
||||
if (ssidManual != "") {
|
||||
ssid = ssidManual;
|
||||
}
|
||||
String token = urlFindArg(config_line, "blynk");
|
||||
String host = urlFindArg(config_line, "host");
|
||||
String port = urlFindArg(config_line, "port");
|
||||
|
||||
DEBUG_PRINT(String("WiFi SSID: ") + ssid + " Pass: " + pass);
|
||||
DEBUG_PRINT(String("Blynk cloud: ") + token + " @ " + host + ":" + port);
|
||||
|
||||
if (token.length() == 32 && ssid.length() > 0) {
|
||||
configStore.flagConfig = false;
|
||||
CopyString(ssid, configStore.wifiSSID);
|
||||
CopyString(pass, configStore.wifiPass);
|
||||
CopyString(token, configStore.cloudToken);
|
||||
if (host.length()) {
|
||||
CopyString(host, configStore.cloudHost);
|
||||
}
|
||||
if (port.length()) {
|
||||
configStore.cloudPort = port.toInt();
|
||||
}
|
||||
|
||||
content = R"json({"status":"ok","msg":"Configuration saved"})json";
|
||||
|
||||
BlynkState::set(MODE_SWITCH_TO_STA);
|
||||
} else {
|
||||
DEBUG_PRINT("Configuration invalid");
|
||||
content = R"json({"status":"error","msg":"Configuration invalid"})json";
|
||||
}
|
||||
content_type = "application/json";
|
||||
} break;
|
||||
case REQ_BOARD_INFO: {
|
||||
char buff[256];
|
||||
snprintf(buff, sizeof(buff),
|
||||
R"json({"board":"%s","vendor":"%s","tmpl_id":"%s","fw_ver":"%s","hw_ver":"%s"})json",
|
||||
BOARD_NAME,
|
||||
BOARD_VENDOR,
|
||||
BOARD_TEMPLATE_ID,
|
||||
BOARD_FIRMWARE_VERSION,
|
||||
BOARD_HARDWARE_VERSION
|
||||
);
|
||||
content = buff;
|
||||
content_type = "application/json";
|
||||
} break;
|
||||
case REQ_RESET: {
|
||||
BlynkState::set(MODE_RESET_CONFIG);
|
||||
content = R"json({"status":"ok","msg":"Configuration reset"})json";
|
||||
content_type = "application/json";
|
||||
} break;
|
||||
case REQ_REBOOT: {
|
||||
restartMCU();
|
||||
} break;
|
||||
}
|
||||
|
||||
client.println("HTTP/1.1 " + responce);
|
||||
client.println("Content-type:" + content_type);
|
||||
client.println();
|
||||
client.println(content);
|
||||
// break out of the while loop:
|
||||
break;
|
||||
} else { // if you got a newline, then clear currentLine:
|
||||
currentLine = "";
|
||||
}
|
||||
} else if (c != '\r') { // if you got anything else but a carriage return character,
|
||||
currentLine += c; // add it to the end of the currentLine
|
||||
}
|
||||
|
||||
if (currentLine.indexOf("GET /board_info.json") >= 0) {
|
||||
req = REQ_BOARD_INFO;
|
||||
} else if (currentLine.indexOf(" /config") >= 0) {
|
||||
req = REQ_CONFIG;
|
||||
int idx = currentLine.indexOf("?");
|
||||
config_line = "&" + currentLine.substring(idx+1, currentLine.lastIndexOf(' ')) + "&";
|
||||
} else if (currentLine.indexOf(" /reset") >= 0) {
|
||||
req = REQ_RESET;
|
||||
} else if (currentLine.indexOf(" /reboot") >= 0) {
|
||||
req = REQ_REBOOT;
|
||||
}
|
||||
}
|
||||
}
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String urlDecode(const String& text)
|
||||
{
|
||||
String decoded = "";
|
||||
char temp[] = "0x00";
|
||||
unsigned int len = text.length();
|
||||
unsigned int i = 0;
|
||||
while (i < len) {
|
||||
char decodedChar;
|
||||
char encodedChar = text.charAt(i++);
|
||||
if ((encodedChar == '%') && (i + 1 < len)) {
|
||||
temp[2] = text.charAt(i++);
|
||||
temp[3] = text.charAt(i++);
|
||||
|
||||
decodedChar = strtol(temp, NULL, 16);
|
||||
} else {
|
||||
if (encodedChar == '+') {
|
||||
decodedChar = ' ';
|
||||
} else {
|
||||
decodedChar = encodedChar;
|
||||
}
|
||||
}
|
||||
decoded += decodedChar;
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
String urlFindArg(const String& url, const String& arg)
|
||||
{
|
||||
int s = url.indexOf("&" + arg + "=");
|
||||
if (s < 0)
|
||||
return "";
|
||||
int s_len = arg.length() + 2;
|
||||
int e = url.indexOf('&', s + s_len);
|
||||
return urlDecode(url.substring(s + s_len, e));
|
||||
}
|
||||
|
||||
void enterConnectNet() {
|
||||
BlynkState::set(MODE_CONNECTING_NET);
|
||||
DEBUG_PRINT(String("Connecting to WiFi: ") + configStore.wifiSSID);
|
||||
|
||||
WiFi.end();
|
||||
|
||||
unsigned long timeoutMs = millis() + WIFI_NET_CONNECT_TIMEOUT;
|
||||
while ((timeoutMs > millis()) && (WiFi.status() != WL_CONNECTED))
|
||||
{
|
||||
WiFi.begin(configStore.wifiSSID, configStore.wifiPass);
|
||||
delay(100);
|
||||
if (!BlynkState::is(MODE_CONNECTING_NET)) {
|
||||
WiFi.disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
BlynkState::set(MODE_CONNECTING_CLOUD);
|
||||
} else {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void enterConnectCloud() {
|
||||
BlynkState::set(MODE_CONNECTING_CLOUD);
|
||||
|
||||
Blynk.disconnect();
|
||||
Blynk.config(configStore.cloudToken, configStore.cloudHost, configStore.cloudPort);
|
||||
Blynk.connect(0);
|
||||
|
||||
unsigned long timeoutMs = millis() + WIFI_CLOUD_CONNECT_TIMEOUT;
|
||||
while ((timeoutMs > millis()) &&
|
||||
(Blynk.connected() == false))
|
||||
{
|
||||
Blynk.run();
|
||||
if (!BlynkState::is(MODE_CONNECTING_CLOUD)) {
|
||||
Blynk.disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Blynk.connected()) {
|
||||
BlynkState::set(MODE_RUNNING);
|
||||
|
||||
if (!configStore.flagConfig) {
|
||||
configStore.flagConfig = true;
|
||||
config_save();
|
||||
DEBUG_PRINT("Configuration stored to flash");
|
||||
}
|
||||
} else {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void enterSwitchToSTA() {
|
||||
BlynkState::set(MODE_SWITCH_TO_STA);
|
||||
|
||||
DEBUG_PRINT("Switching to STA...");
|
||||
|
||||
WiFi.end();
|
||||
delay(1000);
|
||||
|
||||
BlynkState::set(MODE_CONNECTING_NET);
|
||||
}
|
||||
|
||||
void enterError() {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
|
||||
unsigned long timeoutMs = millis() + 10000;
|
||||
while (timeoutMs > millis() || g_buttonPressed)
|
||||
{
|
||||
delay(10);
|
||||
if (!BlynkState::is(MODE_ERROR)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
DEBUG_PRINT("Restarting after error.");
|
||||
delay(10);
|
||||
|
||||
restartMCU();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
struct ConfigStore {
|
||||
uint32_t magic;
|
||||
char version[9];
|
||||
uint8_t flagConfig:1;
|
||||
uint8_t flagApFail:1;
|
||||
uint8_t flagSelfTest:1;
|
||||
|
||||
char wifiSSID[34];
|
||||
char wifiPass[64];
|
||||
|
||||
char cloudToken[34];
|
||||
char cloudHost[34];
|
||||
uint16_t cloudPort;
|
||||
|
||||
uint16_t checksum;
|
||||
} __attribute__((packed));
|
||||
|
||||
ConfigStore configStore;
|
||||
|
||||
const ConfigStore configDefault = {
|
||||
0x626C6E6B,
|
||||
BOARD_FIRMWARE_VERSION,
|
||||
0, 0, 0,
|
||||
|
||||
"",
|
||||
"",
|
||||
|
||||
"invalid token",
|
||||
"blynk-cloud.com", 80,
|
||||
0
|
||||
};
|
||||
|
||||
#include <FlashStorage.h>
|
||||
FlashStorage(flash, ConfigStore);
|
||||
|
||||
void config_load()
|
||||
{
|
||||
memset(&configStore, 0, sizeof(configStore));
|
||||
configStore = flash.read();
|
||||
if (configStore.magic != configDefault.magic) {
|
||||
DEBUG_PRINT("Using default config.");
|
||||
configStore = configDefault;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool config_save()
|
||||
{
|
||||
flash.write(configStore);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool config_init()
|
||||
{
|
||||
config_load();
|
||||
return true;
|
||||
}
|
||||
|
||||
void enterResetConfig()
|
||||
{
|
||||
DEBUG_PRINT("Resetting configuration!");
|
||||
configStore = configDefault;
|
||||
config_save();
|
||||
BlynkState::set(MODE_WAIT_CONFIG);
|
||||
}
|
||||
|
||||
template<typename T, int size>
|
||||
void CopyString(const String& s, T(&arr)[size]) {
|
||||
s.toCharArray(arr, size);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
#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) ((x)*(BOARD_LED_BRIGHTNESS)/255)
|
||||
#define RGB(r,g,b) (DIMM(r) << 16 | DIMM(g) << 8 | DIMM(b) << 0)
|
||||
|
||||
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() {
|
||||
m_Counter = 0;
|
||||
initLED();
|
||||
}
|
||||
|
||||
uint32_t run() {
|
||||
State currState = BlynkState::get();
|
||||
|
||||
// Reset counter if indicator state changes
|
||||
if (m_PrevState != currState) {
|
||||
m_PrevState = currState;
|
||||
m_Counter = 0;
|
||||
}
|
||||
|
||||
if (g_buttonPressed) {
|
||||
if (millis() - g_buttonPressTime > BUTTON_HOLD_TIME_ACTION) { return beatLED(COLOR_WHITE, (int[]){ 100, 100 }); }
|
||||
if (millis() - g_buttonPressTime > BUTTON_HOLD_TIME_INDICATION) { return waveLED(COLOR_WHITE, 1000); }
|
||||
}
|
||||
switch (currState) {
|
||||
case MODE_RESET_CONFIG:
|
||||
case MODE_WAIT_CONFIG: return beatLED(COLOR_BLUE, (int[]){ 50, 500 });
|
||||
case MODE_CONFIGURING: return beatLED(COLOR_BLUE, (int[]){ 200, 200 });
|
||||
case MODE_CONNECTING_NET: return beatLED(COLOR_BLYNK, (int[]){ 50, 500 });
|
||||
case MODE_CONNECTING_CLOUD: return beatLED(COLOR_BLYNK, (int[]){ 100, 100 });
|
||||
case MODE_RUNNING: return waveLED(COLOR_BLYNK, 5000);
|
||||
case MODE_OTA_UPGRADE: return beatLED(COLOR_MAGENTA, (int[]){ 50, 50 });
|
||||
default: return beatLED(COLOR_RED, (int[]){ 80, 100, 80, 1000 } );
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/*
|
||||
* LED drivers
|
||||
*/
|
||||
|
||||
#if defined(BOARD_LED_PIN_WS2812) // Addressable, NeoPixel RGB LED
|
||||
|
||||
void initLED() {
|
||||
rgb.begin();
|
||||
setRGB(COLOR_BLACK);
|
||||
}
|
||||
|
||||
void setRGB(uint32_t color) {
|
||||
rgb.setPixelColor(0, color);
|
||||
rgb.show();
|
||||
}
|
||||
|
||||
#elif defined(BOARD_LED_PIN_R) // Normal RGB LED (common anode or common cathode)
|
||||
|
||||
void initLED() {
|
||||
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, BOARD_PWM_MAX - r);
|
||||
analogWrite(BOARD_LED_PIN_G, BOARD_PWM_MAX - g);
|
||||
analogWrite(BOARD_LED_PIN_B, BOARD_PWM_MAX - b);
|
||||
#else
|
||||
analogWrite(BOARD_LED_PIN_R, r);
|
||||
analogWrite(BOARD_LED_PIN_G, g);
|
||||
analogWrite(BOARD_LED_PIN_B, 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, BOARD_PWM_MAX - color);
|
||||
#else
|
||||
analogWrite(BOARD_LED_PIN, color);
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#warning Invalid LED configuration.
|
||||
|
||||
#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_PWM_MAX : 0);
|
||||
uint32_t next = beat[m_Counter % cnt];
|
||||
m_Counter = (m_Counter+1) % cnt;
|
||||
return next;
|
||||
}
|
||||
|
||||
uint32_t waveLED(uint32_t, unsigned breathePeriod) {
|
||||
uint8_t brightness = (m_Counter < 128) ? m_Counter : 255 - m_Counter;
|
||||
|
||||
setLED(BOARD_PWM_MAX * ((float)brightness / (BOARD_PWM_MAX/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() {
|
||||
blinker.attach_ms(100, indicator_run);
|
||||
}
|
||||
|
||||
#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() {
|
||||
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() {
|
||||
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() {
|
||||
MyTimer5.begin(1000/10);
|
||||
MyTimer5.attachInterrupt(indicator_run);
|
||||
MyTimer5.start();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#warning LED indicator needs a functional timer!
|
||||
|
||||
void indicator_run() {}
|
||||
void indicator_init() {}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* OTA is not ready for this board yet.
|
||||
* It could be derived from:
|
||||
* https://www.hackster.io/flower-platform/program-mkr-over-the-air-goodies-voice-control-etc-562e9a
|
||||
*/
|
||||
|
||||
String overTheAirURL;
|
||||
|
||||
BLYNK_WRITE(InternalPinOTA) {
|
||||
overTheAirURL = param.asString();
|
||||
|
||||
// Disconnect, not to interfere with OTA process
|
||||
Blynk.disconnect();
|
||||
|
||||
// Start OTA
|
||||
BlynkState::set(MODE_OTA_UPGRADE);
|
||||
delay(500);
|
||||
}
|
||||
|
||||
void enterOTA() {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
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 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();
|
||||
}
|
||||
g_buttonPressTime = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void button_init()
|
||||
{
|
||||
#if BOARD_BUTTON_ACTIVE_LOW
|
||||
pinMode(BOARD_BUTTON_PIN, INPUT_PULLUP);
|
||||
#else
|
||||
pinMode(BOARD_BUTTON_PIN, INPUT_PULLDOWN);
|
||||
#endif
|
||||
attachInterrupt(BOARD_BUTTON_PIN, button_change, CHANGE);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* General options
|
||||
*/
|
||||
|
||||
#define BOARD_FIRMWARE_VERSION "1.0.1"
|
||||
#define BOARD_HARDWARE_VERSION "1.0.0"
|
||||
|
||||
#define BOARD_NAME "Product Name" // Name of your product. Should match App Export request info.
|
||||
#define BOARD_VENDOR "Company Name" // Name of your company. Should match App Export request info.
|
||||
#define BOARD_TEMPLATE_ID "TMPL0000" // ID of the Tile Template. Can be found in Tile Template Settings
|
||||
|
||||
#define PRODUCT_WIFI_SSID "Our Product" // Name of the device, to be displayed during configuration. Should match export request info.
|
||||
#define BOARD_CONFIG_AP_URL "our-product" // Config page will be available in a browser at 'http://our-product.local/'
|
||||
|
||||
/*
|
||||
* Board configuration (see examples below).
|
||||
*/
|
||||
|
||||
#if defined(USE_CUSTOM_BOARD)
|
||||
|
||||
// Custom board configuration
|
||||
#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 6 // Set LED pin - if you have a simple 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 you need to inverse LED signal
|
||||
#define BOARD_LED_BRIGHTNESS 64 // 0..255 brightness control
|
||||
|
||||
#elif defined(USE_MKR1010_BOARD)
|
||||
|
||||
// Example configuration for MKR1000 Board
|
||||
#define BOARD_BUTTON_PIN 0
|
||||
#define BOARD_BUTTON_ACTIVE_LOW true
|
||||
|
||||
#define BOARD_LED_PIN 6
|
||||
#define BOARD_LED_INVERSE false
|
||||
|
||||
#else
|
||||
#error "No board selected"
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Advanced options
|
||||
*/
|
||||
|
||||
#define BUTTON_HOLD_TIME_INDICATION 3000
|
||||
#define BUTTON_HOLD_TIME_ACTION 10000
|
||||
|
||||
#define BOARD_PWM_MAX 255
|
||||
|
||||
#define WIFI_NET_CONNECT_TIMEOUT 30000
|
||||
#define WIFI_CLOUD_CONNECT_TIMEOUT 15000
|
||||
#define WIFI_AP_CONFIG_PORT 80
|
||||
//#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
|
||||
|
||||
#if defined(APP_DEBUG)
|
||||
#define DEBUG_PRINT(...) BLYNK_LOG1(__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_PRINT(...)
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*************************************************************
|
||||
This is a DEMO. You can use it only for development and testing.
|
||||
You should open Setting.h and modify General options.
|
||||
|
||||
If you would like to add these features to your product,
|
||||
please contact Blynk for Businesses:
|
||||
|
||||
http://www.blynk.io/
|
||||
|
||||
This example requires some additional libraries:
|
||||
https://github.com/cmaglie/FlashStorage
|
||||
https://github.com/michael71/Timer5
|
||||
|
||||
*************************************************************/
|
||||
|
||||
#define USE_MKR1010_BOARD
|
||||
|
||||
#define APP_DEBUG // Comment this out to disable debug prints
|
||||
|
||||
#define BLYNK_PRINT Serial
|
||||
|
||||
#include "BlynkProvisioning.h"
|
||||
|
||||
void setup() {
|
||||
delay(500);
|
||||
Serial.begin(115200);
|
||||
|
||||
BlynkProvisioning.begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// This handles the network and cloud connection
|
||||
BlynkProvisioning.run();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
extern "C" {
|
||||
#include "user_interface.h"
|
||||
}
|
||||
|
||||
#include "Settings.h"
|
||||
#include <BlynkSimpleEsp8266.h>
|
||||
#include "BlynkState.h"
|
||||
#include "ConfigStore.h"
|
||||
#include "ResetButton.h"
|
||||
#include "ConfigMode.h"
|
||||
#include "Indicator.h"
|
||||
#include "OTA.h"
|
||||
|
||||
inline
|
||||
void BlynkState::set(State m) {
|
||||
if (state != m) {
|
||||
DEBUG_PRINT(String(StateStr[state]) + " => " + StateStr[m]);
|
||||
state = m;
|
||||
}
|
||||
}
|
||||
|
||||
class Provisioning {
|
||||
|
||||
public:
|
||||
void begin()
|
||||
{
|
||||
DEBUG_PRINT("");
|
||||
DEBUG_PRINT("Hardware v" + String(BOARD_HARDWARE_VERSION));
|
||||
DEBUG_PRINT("Firmware v" + String(BOARD_FIRMWARE_VERSION));
|
||||
|
||||
indicator_init();
|
||||
button_init();
|
||||
config_init();
|
||||
|
||||
if (configStore.flagConfig) {
|
||||
BlynkState::set(MODE_CONNECTING_NET);
|
||||
} else {
|
||||
BlynkState::set(MODE_WAIT_CONFIG);
|
||||
}
|
||||
}
|
||||
|
||||
void run() {
|
||||
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: Blynk.run(); break;
|
||||
case MODE_OTA_UPGRADE: enterOTA(); break;
|
||||
case MODE_SWITCH_TO_STA: enterSwitchToSTA(); break;
|
||||
case MODE_RESET_CONFIG: enterResetConfig(); break;
|
||||
default: enterError(); break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Provisioning BlynkProvisioning;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
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] = {
|
||||
"WAIT_CONFIG",
|
||||
"CONFIGURING",
|
||||
"CONNECTING_NET",
|
||||
"CONNECTING_CLOUD",
|
||||
"RUNNING",
|
||||
"OTA_UPGRADE",
|
||||
"SWITCH_TO_STA",
|
||||
"RESET_CONFIG",
|
||||
"ERROR"
|
||||
};
|
||||
#endif
|
||||
|
||||
namespace BlynkState
|
||||
{
|
||||
volatile State state;
|
||||
|
||||
State get() { return state; }
|
||||
bool is (State m) { return (state == m); }
|
||||
void set(State m);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <ESP8266HTTPUpdateServer.h>
|
||||
#include <DNSServer.h>
|
||||
|
||||
ESP8266WebServer server(WIFI_AP_CONFIG_PORT);
|
||||
ESP8266HTTPUpdateServer httpUpdater;
|
||||
DNSServer dnsServer;
|
||||
const byte DNS_PORT = 53;
|
||||
|
||||
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" length=64></td></tr>
|
||||
<tr><td><label for="port">Port:</label></td> <td><input type="number" name="port" value="80" min="1" max="65535"></td></tr>
|
||||
</table><br/>
|
||||
<input type="submit" value="Apply">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)html";
|
||||
|
||||
void restartMCU() {
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
void enterConfigMode()
|
||||
{
|
||||
randomSeed(ESP.getChipId());
|
||||
const uint32_t unique = random(0xFFFFF);
|
||||
char ssidBuff[64];
|
||||
snprintf(ssidBuff, sizeof(ssidBuff), "%s-%05X", PRODUCT_WIFI_SSID, unique);
|
||||
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(100);
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAPConfig(WIFI_AP_IP, WIFI_AP_IP, WIFI_AP_Subnet);
|
||||
WiFi.softAP(ssidBuff);
|
||||
delay(500);
|
||||
IPAddress myIP = WiFi.softAPIP();
|
||||
DEBUG_PRINT(String("AP SSID: ") + ssidBuff);
|
||||
DEBUG_PRINT(String("AP IP: ") + myIP[0] + "." + myIP[1] + "." + myIP[2] + "." + myIP[3]);
|
||||
|
||||
if (myIP == (uint32_t)0)
|
||||
{
|
||||
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, BOARD_CONFIG_AP_URL, WiFi.softAPIP());
|
||||
DEBUG_PRINT(String("AP URL: ") + BOARD_CONFIG_AP_URL);
|
||||
#endif
|
||||
|
||||
httpUpdater.setup(&server);
|
||||
|
||||
server.on("/", []() {
|
||||
server.send(200, "text/html", config_form);
|
||||
});
|
||||
server.on("/config", []() {
|
||||
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");
|
||||
|
||||
String content;
|
||||
unsigned statusCode;
|
||||
|
||||
DEBUG_PRINT(String("WiFi SSID: ") + ssid + " Pass: " + pass);
|
||||
DEBUG_PRINT(String("Blynk cloud: ") + token + " @ " + host + ":" + port);
|
||||
|
||||
if (token.length() == 32 && ssid.length() > 0) {
|
||||
configStore.flagConfig = false;
|
||||
CopyString(ssid, configStore.wifiSSID);
|
||||
CopyString(pass, configStore.wifiPass);
|
||||
CopyString(token, configStore.cloudToken);
|
||||
if (host.length()) {
|
||||
CopyString(host, configStore.cloudHost);
|
||||
}
|
||||
if (port.length()) {
|
||||
configStore.cloudPort = port.toInt();
|
||||
}
|
||||
|
||||
content = R"json({"status":"ok","msg":"Configuration saved"})json";
|
||||
statusCode = 200;
|
||||
server.send(statusCode, "application/json", content);
|
||||
|
||||
BlynkState::set(MODE_SWITCH_TO_STA);
|
||||
} else {
|
||||
DEBUG_PRINT("Configuration invalid");
|
||||
content = R"json({"status":"error","msg":"Configuration invalid"})json";
|
||||
statusCode = 404;
|
||||
server.send(statusCode, "application/json", content);
|
||||
}
|
||||
});
|
||||
server.on("/board_info.json", []() {
|
||||
char buff[256];
|
||||
snprintf(buff, sizeof(buff),
|
||||
R"json({"board":"%s","vendor":"%s","tmpl_id":"%s","fw_ver":"%s","hw_ver":"%s"})json",
|
||||
BOARD_NAME,
|
||||
BOARD_VENDOR,
|
||||
BOARD_TEMPLATE_ID,
|
||||
BOARD_FIRMWARE_VERSION,
|
||||
BOARD_HARDWARE_VERSION
|
||||
);
|
||||
server.send(200, "application/json", buff);
|
||||
});
|
||||
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)) {
|
||||
dnsServer.processNextRequest();
|
||||
server.handleClient();
|
||||
if (BlynkState::is(MODE_WAIT_CONFIG) && WiFi.softAPgetStationNum() > 0) {
|
||||
BlynkState::set(MODE_CONFIGURING);
|
||||
} else if (BlynkState::is(MODE_CONFIGURING) && WiFi.softAPgetStationNum() == 0) {
|
||||
BlynkState::set(MODE_WAIT_CONFIG);
|
||||
}
|
||||
}
|
||||
|
||||
server.stop();
|
||||
}
|
||||
|
||||
void enterConnectNet() {
|
||||
BlynkState::set(MODE_CONNECTING_NET);
|
||||
DEBUG_PRINT(String("Connecting to WiFi: ") + configStore.wifiSSID);
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
if (!WiFi.begin(configStore.wifiSSID, configStore.wifiPass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long timeoutMs = millis() + WIFI_NET_CONNECT_TIMEOUT;
|
||||
while ((timeoutMs > millis()) && (WiFi.status() != WL_CONNECTED))
|
||||
{
|
||||
delay(100);
|
||||
if (!BlynkState::is(MODE_CONNECTING_NET)) {
|
||||
WiFi.disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
BlynkState::set(MODE_CONNECTING_CLOUD);
|
||||
} else {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void enterConnectCloud() {
|
||||
BlynkState::set(MODE_CONNECTING_CLOUD);
|
||||
|
||||
Blynk.disconnect();
|
||||
Blynk.config(configStore.cloudToken, configStore.cloudHost, configStore.cloudPort);
|
||||
Blynk.connect(0);
|
||||
|
||||
unsigned long timeoutMs = millis() + WIFI_CLOUD_CONNECT_TIMEOUT;
|
||||
while ((timeoutMs > millis()) &&
|
||||
(Blynk.connected() == false))
|
||||
{
|
||||
Blynk.run();
|
||||
if (!BlynkState::is(MODE_CONNECTING_CLOUD)) {
|
||||
Blynk.disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Blynk.connected()) {
|
||||
BlynkState::set(MODE_RUNNING);
|
||||
|
||||
if (!configStore.flagConfig) {
|
||||
configStore.flagConfig = true;
|
||||
config_save();
|
||||
DEBUG_PRINT("Configuration stored to flash");
|
||||
}
|
||||
} else {
|
||||
BlynkState::set(MODE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void enterSwitchToSTA() {
|
||||
BlynkState::set(MODE_SWITCH_TO_STA);
|
||||
|
||||
DEBUG_PRINT("Switching to STA...");
|
||||
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(1000);
|
||||
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);
|
||||
if (!BlynkState::is(MODE_ERROR)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
DEBUG_PRINT("Restarting after error.");
|
||||
delay(10);
|
||||
|
||||
restartMCU();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
struct ConfigStore {
|
||||
uint32_t magic;
|
||||
char version[9];
|
||||
uint8_t flagConfig:1;
|
||||
uint8_t flagApFail:1;
|
||||
uint8_t flagSelfTest:1;
|
||||
|
||||
char wifiSSID[34];
|
||||
char wifiPass[64];
|
||||
|
||||
char cloudToken[34];
|
||||
char cloudHost[34];
|
||||
uint16_t cloudPort;
|
||||
|
||||
uint16_t checksum;
|
||||
} __attribute__((packed));
|
||||
|
||||
ConfigStore configStore;
|
||||
|
||||
const ConfigStore configDefault = {
|
||||
0x626C6E6B,
|
||||
BOARD_FIRMWARE_VERSION,
|
||||
0, 0, 0,
|
||||
|
||||
"",
|
||||
"",
|
||||
|
||||
"invalid token",
|
||||
"blynk-cloud.com", 80,
|
||||
0
|
||||
};
|
||||
|
||||
#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();
|
||||
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);
|
||||
}
|
||||
|
||||
template<typename T, int size>
|
||||
void CopyString(const String& s, T(&arr)[size]) {
|
||||
s.toCharArray(arr, size);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
#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) ((x)*(BOARD_LED_BRIGHTNESS)/255)
|
||||
#define RGB(r,g,b) (DIMM(r) << 16 | DIMM(g) << 8 | DIMM(b) << 0)
|
||||
|
||||
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() {
|
||||
m_Counter = 0;
|
||||
initLED();
|
||||
}
|
||||
|
||||
uint32_t run() {
|
||||
State currState = BlynkState::get();
|
||||
|
||||
// Reset counter if indicator state changes
|
||||
if (m_PrevState != currState) {
|
||||
m_PrevState = currState;
|
||||
m_Counter = 0;
|
||||
}
|
||||
|
||||
if (g_buttonPressed) {
|
||||
if (millis() - g_buttonPressTime > BUTTON_HOLD_TIME_ACTION) { return beatLED(COLOR_WHITE, (int[]){ 100, 100 }); }
|
||||
if (millis() - g_buttonPressTime > BUTTON_HOLD_TIME_INDICATION) { return waveLED(COLOR_WHITE, 1000); }
|
||||
}
|
||||
switch (currState) {
|
||||
case MODE_RESET_CONFIG:
|
||||
case MODE_WAIT_CONFIG: return beatLED(COLOR_BLUE, (int[]){ 50, 500 });
|
||||
case MODE_CONFIGURING: return beatLED(COLOR_BLUE, (int[]){ 200, 200 });
|
||||
case MODE_CONNECTING_NET: return beatLED(COLOR_BLYNK, (int[]){ 50, 500 });
|
||||
case MODE_CONNECTING_CLOUD: return beatLED(COLOR_BLYNK, (int[]){ 100, 100 });
|
||||
case MODE_RUNNING: return waveLED(COLOR_BLYNK, 5000);
|
||||
case MODE_OTA_UPGRADE: return beatLED(COLOR_MAGENTA, (int[]){ 50, 50 });
|
||||
default: return beatLED(COLOR_RED, (int[]){ 80, 100, 80, 1000 } );
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/*
|
||||
* LED drivers
|
||||
*/
|
||||
|
||||
#if defined(BOARD_LED_PIN_WS2812) // Addressable, NeoPixel RGB LED
|
||||
|
||||
void initLED() {
|
||||
rgb.begin();
|
||||
setRGB(COLOR_BLACK);
|
||||
}
|
||||
|
||||
void setRGB(uint32_t color) {
|
||||
rgb.setPixelColor(0, color);
|
||||
rgb.show();
|
||||
}
|
||||
|
||||
#elif defined(BOARD_LED_PIN_R) // Normal RGB LED (common anode or common cathode)
|
||||
|
||||
void initLED() {
|
||||
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, BOARD_PWM_MAX - r);
|
||||
analogWrite(BOARD_LED_PIN_G, BOARD_PWM_MAX - g);
|
||||
analogWrite(BOARD_LED_PIN_B, BOARD_PWM_MAX - b);
|
||||
#else
|
||||
analogWrite(BOARD_LED_PIN_R, r);
|
||||
analogWrite(BOARD_LED_PIN_G, g);
|
||||
analogWrite(BOARD_LED_PIN_B, 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, BOARD_PWM_MAX - color);
|
||||
#else
|
||||
analogWrite(BOARD_LED_PIN, color);
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#warning Invalid LED configuration.
|
||||
|
||||
#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_PWM_MAX : 0);
|
||||
uint32_t next = beat[m_Counter % cnt];
|
||||
m_Counter = (m_Counter+1) % cnt;
|
||||
return next;
|
||||
}
|
||||
|
||||
uint32_t waveLED(uint32_t, unsigned breathePeriod) {
|
||||
uint8_t brightness = (m_Counter < 128) ? m_Counter : 255 - m_Counter;
|
||||
|
||||
setLED(BOARD_PWM_MAX * ((float)brightness / (BOARD_PWM_MAX/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() {
|
||||
blinker.attach_ms(100, indicator_run);
|
||||
}
|
||||
|
||||
#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() {
|
||||
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() {
|
||||
Timer3.initialize(100*1000);
|
||||
Timer3.attachInterrupt(indicator_run);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#warning LED indicator needs a functional timer!
|
||||
|
||||
void indicator_run() {}
|
||||
void indicator_init() {}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************
|
||||
*
|
||||
* How to trigger an OTA update?
|
||||
* 1. In Arduino IDE menu: Sketch -> Export compiled Binary
|
||||
* 2. Open console, navigate to the sketch directory
|
||||
* 3.a Trigger update using HTTPS API on local server for specific hardware:
|
||||
* curl -v -F file=@Template_ESP8266.ino.nodemcu.bin --insecure -u admin@blynk.cc:admin https://localhost:9443/admin/ota/start?token=123
|
||||
* 3.b Trigger update using HTTPS API on local server for all hardware:
|
||||
* curl -v -F file=@Template_ESP8266.ino.nodemcu.bin --insecure -u admin@blynk.cc:admin https://localhost:9443/admin/ota/start
|
||||
* 3.c Trigger update using HTTPS API on local server for single user:
|
||||
* curl -v -F file=@Template_ESP8266.ino.nodemcu.bin --insecure -u admin@blynk.cc:admin https://localhost:9443/admin/ota/start?user=pupkin@gmail.com
|
||||
* 3.d Trigger update using HTTPS API on local server for single user and specific project:
|
||||
* curl -v -F file=@Template_ESP8266.ino.nodemcu.bin --insecure -u admin@blynk.cc:admin https://localhost:9443/admin/ota/start?user=pupkin@gmail.com&project=123
|
||||
* More about ESP8266 OTA updates:
|
||||
* https://github.com/esp8266/Arduino/blob/master/doc/ota_updates/readme.md
|
||||
*/
|
||||
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266httpUpdate.h>
|
||||
|
||||
String overTheAirURL;
|
||||
|
||||
BLYNK_WRITE(InternalPinOTA) {
|
||||
overTheAirURL = param.asString();
|
||||
|
||||
// Disconnect, not to interfere with OTA process
|
||||
Blynk.disconnect();
|
||||
|
||||
// Start OTA
|
||||
BlynkState::set(MODE_OTA_UPGRADE);
|
||||
delay(500);
|
||||
}
|
||||
|
||||
void enterOTA() {
|
||||
BlynkState::set(MODE_OTA_UPGRADE);
|
||||
|
||||
DEBUG_PRINT(String("Firmware update URL: ") + overTheAirURL);
|
||||
switch (ESPhttpUpdate.update(overTheAirURL, BOARD_FIRMWARE_VERSION)) {
|
||||
case HTTP_UPDATE_FAILED:
|
||||
DEBUG_PRINT(String("Firmware update failed (error ") + ESPhttpUpdate.getLastError() + "): " + ESPhttpUpdate.getLastErrorString());
|
||||
BlynkState::set(MODE_ERROR);
|
||||
break;
|
||||
case HTTP_UPDATE_NO_UPDATES:
|
||||
DEBUG_PRINT("No firmware updates available.");
|
||||
BlynkState::set(MODE_CONNECTING_CLOUD);
|
||||
break;
|
||||
case HTTP_UPDATE_OK:
|
||||
DEBUG_PRINT("Firmware update: OK.");
|
||||
delay(10);
|
||||
restartMCU();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Instructions for myPlant (Blynk Demo App):
|
||||
|
||||
**->** http://help.blynk.cc/hardware-and-libraries/esp8266/myplant-demo **<-**
|
||||
@@ -0,0 +1,50 @@
|
||||
/**************************************************************
|
||||
* This is a DEMO. You can use it only for development and testing.
|
||||
*
|
||||
* If you would like to add these features to your product,
|
||||
* please contact Blynk for Business:
|
||||
*
|
||||
* http://www.blynk.io/
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
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 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();
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* General options
|
||||
*/
|
||||
|
||||
#define BOARD_FIRMWARE_VERSION "1.0.1"
|
||||
#define BOARD_HARDWARE_VERSION "1.0.0"
|
||||
|
||||
#define BOARD_NAME "My Plant" // Name of your product. Should match App Export request info.
|
||||
#define BOARD_VENDOR "Blynk" // Name of your company. Should match App Export request info.
|
||||
#define BOARD_TEMPLATE_ID "TMPL0000"
|
||||
|
||||
#define PRODUCT_WIFI_SSID "Blynk myPlant" // Name of the device, to be displayed during configuration. Should match export request info.
|
||||
#define BOARD_CONFIG_AP_URL "my-plant.cc" // Config page will be available in a browser at 'http://my-plant.cc/'
|
||||
|
||||
/*
|
||||
* Board configuration (see examples below).
|
||||
*/
|
||||
|
||||
#if defined(USE_CUSTOM_BOARD)
|
||||
|
||||
// Custom board configuration
|
||||
#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 2 // 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
|
||||
|
||||
#elif defined(USE_NODE_MCU_BOARD)
|
||||
|
||||
#warning "NodeMCU board selected"
|
||||
|
||||
// Example configuration for NodeMCU v1.0 Board
|
||||
#define BOARD_BUTTON_PIN 0
|
||||
#define BOARD_BUTTON_ACTIVE_LOW true
|
||||
|
||||
#define BOARD_LED_PIN_R D8
|
||||
#define BOARD_LED_PIN_G D7
|
||||
#define BOARD_LED_PIN_B D6
|
||||
#define BOARD_LED_INVERSE false
|
||||
#define BOARD_LED_BRIGHTNESS 64
|
||||
|
||||
#elif defined(USE_SPARKFUN_BLYNK_BOARD)
|
||||
|
||||
#warning "Sparkfun Blynk board selected"
|
||||
|
||||
// Example configuration for 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)
|
||||
|
||||
#warning "Witty Cloud board selected"
|
||||
|
||||
// Example configuration for 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
|
||||
#error "No board selected"
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Advanced options
|
||||
*/
|
||||
|
||||
#define BUTTON_HOLD_TIME_INDICATION 3000
|
||||
#define BUTTON_HOLD_TIME_ACTION 10000
|
||||
|
||||
#define BOARD_PWM_MAX 1023
|
||||
|
||||
#define WIFI_NET_CONNECT_TIMEOUT 30000
|
||||
#define WIFI_CLOUD_CONNECT_TIMEOUT 15000
|
||||
#define WIFI_AP_CONFIG_PORT 80
|
||||
#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
|
||||
|
||||
#if defined(APP_DEBUG)
|
||||
#define DEBUG_PRINT(...) BLYNK_LOG1(__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_PRINT(...)
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
/*************************************************************
|
||||
This is a DEMO sketch which works with Blynk myPlant app and
|
||||
showcases how your app made with Blynk can work
|
||||
|
||||
You can download free app here:
|
||||
iOS: https://itunes.apple.com/us/app/blynk-myplant/id1163620518?mt=8
|
||||
Android: https://play.google.com/store/apps/details?id=cc.blynk.appexport.demo
|
||||
|
||||
If you would like to add these features to your product,
|
||||
please contact Blynk for Businesses:
|
||||
|
||||
http://www.blynk.io/
|
||||
|
||||
*************************************************************/
|
||||
|
||||
#define USE_SPARKFUN_BLYNK_BOARD // Uncomment the board you are using
|
||||
//#define USE_NODE_MCU_BOARD // Comment out the boards you are not using
|
||||
//#define USE_WITTY_CLOUD_BOARD
|
||||
//#define USE_CUSTOM_BOARD // For all other ESP8266-based boards -
|
||||
// see "Custom board configuration" in Settings.h
|
||||
|
||||
#define APP_DEBUG // Comment this out to disable debug prints
|
||||
|
||||
#define BLYNK_PRINT Serial
|
||||
|
||||
#include "BlynkProvisioning.h"
|
||||
|
||||
void setup() {
|
||||
delay(500);
|
||||
Serial.begin(115200);
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
* Workflow to connect the device to WiFi network.
|
||||
* Here is how it works:
|
||||
* 1. At the first start hardware acts as an Access Point and
|
||||
* broadcasts it's own WiFi.
|
||||
* 2. myPlant smartphone app connects to this Access Point
|
||||
* 3. myPlant smartphone app request new Auth Token and passes
|
||||
* it together with user's WiFi SSID and password
|
||||
* 4. Hardware saves this information to EEPROM
|
||||
* 5. Hardware reboots and now connects to user's WiFi Network
|
||||
* 6. Hardware connects to Blynk Cloud and is ready to work with app
|
||||
*
|
||||
* Next time the hardware reboots, it will use the same configuration
|
||||
* to connect. User can RESET the board and re-initiate provisioning
|
||||
*
|
||||
* Explore the Settings.h for parameters
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
BlynkProvisioning.begin();
|
||||
|
||||
example_init(); // Initialize this example
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// This handles the network and cloud connection
|
||||
BlynkProvisioning.run();
|
||||
|
||||
// Run this example periodic actions
|
||||
example_run();
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
* myPlant example App code
|
||||
*
|
||||
* The following code simulates plant watering system
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
BlynkTimer timer; // Initiating timer to perform repeating event
|
||||
|
||||
static int sensorSoilMoisture = 60;
|
||||
static int sensorAirHumidity = 50;
|
||||
static int wateringAmount = 5;
|
||||
static int wateringTimer = -1;
|
||||
static bool isNotificationSent = false;
|
||||
|
||||
// Getting data from "Set watering amount" slider
|
||||
BLYNK_WRITE(V5) {
|
||||
wateringAmount = param.asInt();
|
||||
DEBUG_PRINT(String("Watering amount: ") + wateringAmount);
|
||||
}
|
||||
|
||||
// Getting data from "Start Watering" button
|
||||
BLYNK_WRITE(V6) {
|
||||
if (param.asInt() == 1) {
|
||||
// If watering started -> start simulating watering
|
||||
timer.enable(wateringTimer);
|
||||
DEBUG_PRINT("Watering started by user");
|
||||
} else {
|
||||
// If watering stopped -> stop simulating watering
|
||||
timer.disable(wateringTimer);
|
||||
DEBUG_PRINT("Watering stopped by user");
|
||||
}
|
||||
}
|
||||
|
||||
// When device starts ->
|
||||
// sync watering switch button status
|
||||
// and watering amount level from the cloud (last App value)
|
||||
BLYNK_CONNECTED() {
|
||||
Blynk.syncVirtual(V5, V6);
|
||||
}
|
||||
|
||||
// This is a sinusoidal function used for simulations
|
||||
float sinusoidal(float minv, float maxv, float period) {
|
||||
float amp = (maxv - minv) / 2.0;
|
||||
float med = minv + amp;
|
||||
return med + amp * sin((M_PI * 2 * millis()) / period);
|
||||
}
|
||||
|
||||
// Simulating values jittering
|
||||
float randomize(float minv, float maxv) {
|
||||
return float(random(minv * 1000, maxv * 1000)) / 1000;
|
||||
}
|
||||
|
||||
|
||||
void example_init() {
|
||||
|
||||
// Update sensors each 3 seconds
|
||||
timer.setInterval(3000L, []() {
|
||||
// Soil moisture
|
||||
if (sensorSoilMoisture < 33) {
|
||||
Blynk.virtualWrite(V1, "DRY");
|
||||
} else if (sensorSoilMoisture > 33) {
|
||||
Blynk.virtualWrite(V1, "MOIST");
|
||||
} else {
|
||||
Blynk.virtualWrite(V1, "WET");
|
||||
}
|
||||
|
||||
float dayPeriod = 3.0 * 60 * 1000;
|
||||
|
||||
// Light level
|
||||
int light = sinusoidal(5, 95, dayPeriod);
|
||||
if (light < 33) {
|
||||
Blynk.virtualWrite(V2, "LOW");
|
||||
} else if (light > 33) {
|
||||
Blynk.virtualWrite(V2, "GOOD");
|
||||
} else {
|
||||
Blynk.virtualWrite(V2, "MED");
|
||||
}
|
||||
|
||||
// Temperature
|
||||
Blynk.virtualWrite(V3, sinusoidal(18, 23, dayPeriod) + randomize(-1.0, 1.0));
|
||||
});
|
||||
|
||||
// Humidity updates at a different rate (5s)
|
||||
timer.setInterval(5000L, []() {
|
||||
sensorAirHumidity += random (-5, +5);
|
||||
sensorAirHumidity = constrain(sensorAirHumidity, 30, 90);
|
||||
Blynk.virtualWrite(V4, sensorAirHumidity);
|
||||
});
|
||||
|
||||
// Soil Moisture decreases 3% every second
|
||||
timer.setInterval(1000L, []() {
|
||||
sensorSoilMoisture -= 3;
|
||||
sensorSoilMoisture = constrain(sensorSoilMoisture, 7, 85);
|
||||
|
||||
if (sensorSoilMoisture < 20) {
|
||||
if (isNotificationSent == false) {
|
||||
Blynk.email("myPlant notification", "Your plant is thirsty!");
|
||||
isNotificationSent = true;
|
||||
DEBUG_PRINT("Email notification sent");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Simulate watering process
|
||||
wateringTimer = timer.setInterval(1000L, []() {
|
||||
sensorSoilMoisture += wateringAmount;
|
||||
sensorSoilMoisture = constrain(sensorSoilMoisture, 7, 85);
|
||||
|
||||
if (sensorSoilMoisture > 30) {
|
||||
isNotificationSent = false;
|
||||
}
|
||||
if (sensorSoilMoisture >= 85) {
|
||||
// Stop watering
|
||||
timer.disable(wateringTimer);
|
||||
// Update "Start Watering" button widget state
|
||||
Blynk.virtualWrite(V6, 0);
|
||||
|
||||
DEBUG_PRINT("Watering stopped automatically");
|
||||
}
|
||||
});
|
||||
timer.disable(wateringTimer);
|
||||
|
||||
}
|
||||
|
||||
void example_run() {
|
||||
timer.run();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user