初始化提交

This commit is contained in:
王立帮
2024-07-20 22:09:06 +08:00
commit c247dd07a6
6876 changed files with 2743096 additions and 0 deletions

View File

@@ -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;

View File

@@ -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);
};

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);
};

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);
};

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);
};

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);
};

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -0,0 +1,3 @@
# Instructions for myPlant (Blynk Demo App):
**->** http://help.blynk.cc/hardware-and-libraries/esp8266/myplant-demo **<-**

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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();
}