четвер, 19 вересня 2019 р.

Blynk: шаблон-конструктор для створення прошивок з WiFi Manager, WebOTA та автоматичним перепідключенням до вашого WiFi і серверу Blynk

Передмова

Для зручності створив початковий шаблон-конструктор прошивки для системи Blynk з початковими налаштуваннями по WiFi зі смартфону (SSID, PASS, TOKEN, Name Device). З можливістю оновлювати прошивку по повітрю WebOTA, та перепідключенням до мережі WiFi і серверу Blynk у разі пропадання зв'язку. Плюс, якщо зв'язок буде втрачено, функціонал самого пристрою не буде "гальмувати", а буде працювати в режимі OFFLINE як слід, до наступного підключення до серверу. Є обробка натискання системної кнопки "FLASH" яка під'єднана до GPIO_0 і зазвичай присутня на всіх платах на базі "ESP8266" і пристроїв "IoT", наприклад, дуже популярних "Sonoff". Кнопка розпізнає довжину натискання. Коротке натискання - для свого коду, який буде щось вмикати/вимикати. Довге натискання від 5 до 10 секунд - перезавантаження пристрою. Довге натискання понад 10 секунд - скидання всіх налаштувань.

Код шаблону

Код шаблону можна завантажити на GitHub, Dropbox,  або скопіювати прямо звідси:

/*
 Name:  Blynk_Table_Menu.ino
 Created: 9/19/2019 9:04:46 AM
 Author: Andriy Honcharenko
*/

/* CODE BEGIN Includes */
#include <BlynkSimpleEsp8266.h>
#include <Ticker.h>
#include <EEPROM.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h>
#include <WiFiManager.h>
/* CODE END Includes */

/* CODE BEGIN UD */
/* User defines ---------------------------------------------------------*/
#define BLYNK_PRINT Serial

#define NAME_DEVICE      "MyHomeIoT-ESP8266"

#define BUTTON_SYS0_PIN     0
#define LED_SYS_PIN      13

#define BUTTON_SYS_B0_VPIN    V20
#define WIFI_SIGNAL_VPIN    V80

#define INTERVAL_PRESSED_RESET_ESP  3000L
#define INTERVAL_PRESSED_RESET_SETTINGS 5000L
#define INTERVAL_PRESSED_SHORT   50
#define INTERVAL_SEND_DATA    30033L
#define INTERVAL_RECONNECT    60407L
#define INTERVAL_REFRESH_DATA   4065L
#define WIFI_MANAGER_TIMEOUT   180

#define EEPROM_SETTINGS_SIZE   512
#define EEPROM_START_SETTING_WM   0
#define EEPROM_SALT_WM     12661

#define LED_SYS_TOGGLE()    digitalWrite(LED_SYS_PIN, !digitalRead(LED_SYS_PIN))
#define LED_SYS_ON()     digitalWrite(LED_SYS_PIN, LOW)
#define LED_SYS_OFF()     digitalWrite(LED_SYS_PIN, HIGH)
/* CODE END UD */

/* CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
bool shouldSaveConfigWM  = false; //flag for saving data
bool btnSystemState0  = false;
bool triggerBlynkConnect = false;
bool isFirstConnect   = true; // Keep this flag not to re-sync on every reconnection

int startPressBtn = 0;

//structure for initial settings. It now takes 116 bytes
typedef struct {
 char  host[33] = NAME_DEVICE;    // 33 + '\0' = 34 bytes
 char  blynkToken[33] = "";     // 33 + '\0' = 34 bytes
 char  blynkServer[33] = "blynk-cloud.com"; // 33 + '\0' = 34 bytes
 char  blynkPort[6] = "8442";    // 04 + '\0' = 05 bytes
 int   salt = EEPROM_SALT_WM;    // 04   = 04 bytes
} WMSettings;         // 111 + 1  = 112 bytes (112 this is a score of 0)
//-----------------------------------------------------------------------------------------

WMSettings wmSettings;

BlynkTimer timer;

Ticker tickerESP8266;

//Declaration OTA WebUpdater
ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;
/* CODE END PV */

/* CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
static void configModeCallback(WiFiManager* myWiFiManager);
static void saveConfigCallback(void);
static void tick(void);
static void untick(void);
static void readSystemKey(void);
static void timerRefreshData(void);
static void timerSendServer(void);
static void timerReconnect(void);
/* CODE END PFP */

// the setup function runs once when you press reset or power the board

void setup() 
{
 Serial.begin(115200);

 pinMode(BUTTON_SYS0_PIN, INPUT_PULLUP);
 pinMode(LED_SYS_PIN, OUTPUT);

 // Read the WM settings data from EEPROM to RAM
 EEPROM.begin(EEPROM_SETTINGS_SIZE);
 EEPROM.get(EEPROM_START_SETTING_WM, wmSettings);
 EEPROM.end();

 if (wmSettings.salt != EEPROM_SALT_WM) 
 {
  Serial.println(F("Invalid wmSettings in EEPROM, trying with defaults"));
  WMSettings defaults;
  wmSettings = defaults;
 }

 // Print old values to the terminal
 Serial.println(wmSettings.host);
 Serial.println(wmSettings.blynkToken);
 Serial.println(wmSettings.blynkServer);
 Serial.println(wmSettings.blynkPort);

 tickerESP8266.attach(0.5, tick);   // start ticker with 0.5 because we start in AP mode and try to connect

 //Local intialization. Once its business is done, there is no need to keep it around
 WiFiManager wifiManager;

 //reset saved wmSettings
 //wifiManager.resetSettings();

 //set minimu quality of signal so it ignores AP's under that quality
 //defaults to 8%
 //wifiManager.setMinimumSignalQuality();

 //sets timeout before webserver loop ends and exits even if there has been no setup.
 //useful for devices that failed to connect at some point and got stuck in a webserver loop
 //in seconds setConfigPortalTimeout is a new name for setTimeout
 wifiManager.setConfigPortalTimeout(WIFI_MANAGER_TIMEOUT);

 // The extra parameters to be configured (can be either global or just in the setup)
 // After connecting, parameter.getValue() will get you the configured value
 // id/name placeholder/prompt default length
 WiFiManagerParameter custom_device_name_text("<br/>Enter name of the device<br/>or leave it as it is<br/>");
 wifiManager.addParameter(&custom_device_name_text);

 WiFiManagerParameter custom_device_name("device-name", "device name", wmSettings.host, 33);
 wifiManager.addParameter(&custom_device_name);

 WiFiManagerParameter custom_blynk_text("<br/>Blynk config.<br/>");
 wifiManager.addParameter(&custom_blynk_text);

 WiFiManagerParameter custom_blynk_token("blynk-token", "blynk token", wmSettings.blynkToken, 33);
 wifiManager.addParameter(&custom_blynk_token);

 WiFiManagerParameter custom_blynk_server("blynk-server", "blynk server", wmSettings.blynkServer, 33);
 wifiManager.addParameter(&custom_blynk_server);

 WiFiManagerParameter custom_blynk_port("blynk-port", "port", wmSettings.blynkPort, 6);
 wifiManager.addParameter(&custom_blynk_port);

 //set config save notify callback
 wifiManager.setSaveConfigCallback(saveConfigCallback);

 //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode
 wifiManager.setAPCallback(configModeCallback);

 //set custom ip for portal
 //wifiManager.setAPStaticIPConfig(IPAddress(10,0,1,1), IPAddress(10,0,1,1), IPAddress(255,255,255,0));

 //fetches ssid and pass from eeprom and tries to connect
 //if it does not connect it starts an access point with the specified name
 //here  "AutoConnectAP"
 //and goes into a blocking loop awaiting configuration
 //wifiManager.autoConnect();
 //or use this for auto generated name ESP + ChipID

 if (wifiManager.autoConnect(wmSettings.host))
 {
  //if you get here you have connected to the WiFi
  Serial.println(F("Connected WiFi!"));
 }
 else
 {
  Serial.println(F("failed to connect and hit timeout"));
 }

 untick(); // cancel the flashing LED

 // Copy the entered values to the structure
 strcpy(wmSettings.host, custom_device_name.getValue());
 strcpy(wmSettings.blynkToken, custom_blynk_token.getValue());
 strcpy(wmSettings.blynkServer, custom_blynk_server.getValue());
 strcpy(wmSettings.blynkPort, custom_blynk_port.getValue());

 // Print new values to the terminal
 Serial.println(wmSettings.host);
 Serial.println(wmSettings.blynkToken);
 Serial.println(wmSettings.blynkServer);
 Serial.println(wmSettings.blynkPort);

 if (shouldSaveConfigWM)
 {
  LED_SYS_ON();
  // Write the input to the EEPROM
  EEPROM.begin(EEPROM_SETTINGS_SIZE);
  EEPROM.put(EEPROM_START_SETTING_WM, wmSettings);
  EEPROM.end();
  //---------------------------------
  LED_SYS_OFF();
 }

 // Run OTA WebUpdater
 MDNS.begin(wmSettings.host);
 httpUpdater.setup(&httpServer);
 httpServer.begin();
 MDNS.addService("http", "tcp", 80);
 Serial.printf("HTTPUpdateServer ready! Open http://%s.local/update in your browser\n", wmSettings.host);

 // Configure connection to blynk server
 Blynk.config(wmSettings.blynkToken, wmSettings.blynkServer, atoi(wmSettings.blynkPort));

 if (Blynk.connect())
 {
  //TODO: something to do if connected
 }
 else
 {
  //TODO: something to do if you failed to connect
 }

 timer.setInterval(INTERVAL_REFRESH_DATA, timerRefreshData);
 timer.setInterval(INTERVAL_SEND_DATA, timerSendServer);
 timer.setInterval(INTERVAL_RECONNECT, timerReconnect); 
}

// the loop function runs over and over again until power down or reset
void loop() 
{
 if (Blynk.connected())
 {
  Blynk.run(); // Initiates Blynk Server  
 }
 else
 {
  if (!tickerESP8266.active())
  {
   tickerESP8266.attach(2, tick);
  }
 }

 timer.run(); // Initiates BlynkTimer
 
 httpServer.handleClient(); // Initiates OTA WebUpdater  

 readSystemKey();
}

/* BLYNK CODE BEGIN */
BLYNK_CONNECTED()
{
 untick(); 

 Serial.println(F("Blynk Connected!"));
 Serial.println(F("local ip"));
 Serial.println(WiFi.localIP());  

 char str[32];
 sprintf_P(str, PSTR("%s Online!"), wmSettings.host); 
 Blynk.notify(str);

 if (isFirstConnect)
 {
  Blynk.syncAll();
  isFirstConnect = false;
 }
}

BLYNK_WRITE(BUTTON_SYS_B0_VPIN) // Example
{ 
 //TODO: something to do when a button is clicked in the Blynk app
 
 Serial.println(F("System_0 button pressed is App!")); 
}
/* BLYNK CODE END */

/* CODE BEGIN USER FUNCTION */
static void timerRefreshData(void)
{
 //TODO: here are functions for updating data from sensors, ADC, etc ...
}

static void timerSendServer(void)
{
 if (Blynk.connected())
 {
  //TODO: here are the functions that send data to the Blynk server
  Blynk.virtualWrite(WIFI_SIGNAL_VPIN, map(WiFi.RSSI(), -105, -40, 0, 100)); //Example send level WiFi signal
 }
 else
 {
  //TODO:
 }
}

static void timerReconnect(void)
{
 if (WiFi.status() != WL_CONNECTED)
 {
  Serial.println(F("WiFi not connected"));

  if (WiFi.begin() == WL_CONNECTED)
  {
   Serial.println(F("WiFi reconnected"));
  }
  else
  {
   Serial.println(F("WiFi not reconnected"));
  }
 }
 else// if (WiFi.status() == WL_CONNECTED)
 {
  Serial.println(F("WiFi in connected"));

  if (!Blynk.connected())
  {
   if (Blynk.connect())
   {
    Serial.println(F("Blynk reconnected"));
   }
   else
   {
    Serial.println(F("Blynk not reconnected"));
   }
  }
  else
  {
   Serial.println(F("Blynk in connected"));
  }
 }
}

static void configModeCallback(WiFiManager* myWiFiManager)
{
 Serial.println(F("Entered config mode"));
 Serial.println(WiFi.softAPIP());
 //if you used auto generated SSID, print it
 Serial.println(myWiFiManager->getConfigPortalSSID());
 //entered config mode, make led toggle faster
 tickerESP8266.attach(0.2, tick);
}

//callback notifying us of the need to save config
static void saveConfigCallback()
{
 Serial.println(F("Should save config"));
 shouldSaveConfigWM = true;
}

static void tick(void)
{
 //toggle state  
 LED_SYS_TOGGLE();     // set pin to the opposite state
}

static void untick(void)
{
 tickerESP8266.detach();
 LED_SYS_OFF(); //keep LED off 
 
}

static void readSystemKey(void)
{
 if (!digitalRead(BUTTON_SYS0_PIN) && !btnSystemState0)
 {
  btnSystemState0 = true;
  startPressBtn = millis();
 }
 else if (digitalRead(BUTTON_SYS0_PIN) && btnSystemState0)
 {
  btnSystemState0 = false;
  int pressTime = millis() - startPressBtn;

  if (pressTime > INTERVAL_PRESSED_RESET_ESP && pressTime < INTERVAL_PRESSED_RESET_SETTINGS)
  {
   if (Blynk.connected())
   {    
    Blynk.notify(String(wmSettings.host) + F(" reboot!"));
   }
   
   Blynk.disconnect();  
   tickerESP8266.attach(0.1, tick);   
   delay(2000);
   ESP.restart();
  }
  else if (pressTime > INTERVAL_PRESSED_RESET_SETTINGS)
  {
   if (Blynk.connected())
   {        
    Blynk.notify(String(wmSettings.host) + F(" setting reset! Connected WiFi AP this device!"));
   }   

   WMSettings defaults;
   wmSettings = defaults;

   LED_SYS_ON();
   // We write the default data to EEPROM
   EEPROM.begin(EEPROM_SETTINGS_SIZE);
   EEPROM.put(EEPROM_START_SETTING_WM, wmSettings);
   EEPROM.end();
   //------------------------------------------
   LED_SYS_OFF();

   delay(1000);
   WiFi.disconnect();
   delay(1000);
   ESP.restart();
  }
  else if (pressTime < INTERVAL_PRESSED_RESET_ESP && pressTime > INTERVAL_PRESSED_SHORT)
  {   
   Serial.println(F("System button_0 pressed is Device!"));
   // TODO: insert here what will happen when you press the ON / OFF button   
  }
  else if (pressTime < INTERVAL_PRESSED_SHORT)
  {   
   Serial.printf("Fixed false triggering %ims", pressTime);
   Serial.println();
  }  
 }
}
/* CODE END USER FUNCTION */

Можливості

  • При першому, після прошивання, увімкнені пристроєм утворюється точка WiFi з ім'ям "MyHomeIoT-ESP8266". Підключиться до цієї точки своїм смартфоном і відкрийте браузер та перейдіть за адресою 192.168.4.1 до налаштувань;
  • Для прикладу і перевірки на віртуальну шпильку V80 відправляються дані рівня сигналу WiFi. Додайте якийсь віджет "value" і назначте йому віртуальну шпильку V80;
  • Кнопка, яка підключена до GPIO_0 обробляється шаблоном та може реагувати на коротке натискання - вставляєте свій код для увімкнення/вимкнення чогось, або інших дій. Довге натискання кнопки від 5 до 10 секунд - перезавантаження пристрою. Довге натискання понад 10 секунд - скидання всіх налаштувань;
  • Для прикладу є обробка віртуальної кнопки V20. Якщо в BlynkApp додати до проекту кнопку на віртуальну шпильку V20, то пристрій в термінал буде надсилати повідомлення про те що кнопку натиснули з додатку Blynk. Інший код-функціонал на ваш розсуд;
  • Є можливість оновити прошивку по повітрю - WebOTA. Для цього потрібно знати IP пристрою і з будь якого браузера який знаходиться в тій же ж самій мережі набрати "IP_Your_Drvice/update". Наприклад ваш пристрій має IP - 192.168.100.111, тоді в браузер вводимо "192.168.100.111/update", обираємо файл прошивки і тиснемо кнопку "update". 
  • При втраті WiFi мережі, або зв'язку з сервером Blynk, функціонал пристрою не буде "гальмувати" і зв'язок автоматично відновиться при першій можливості;
  • Присутні в шаблоні три таймери для збору даних з датчиків (вставляєте свій код), для надсилання даних на сервер (вставляєте свій код. Є приклад з рівнем сигналу WiFi), та таймер для перепідключення до WiFi і Blynk;
  • Якщо до пристрою GPIO_13 підключити світлодіод, це додасть інформативності. Часте блимання - створена точка WiFi, повільне блимання - йде підключення до Wi-Fi і серверу Blynk, блимання раз на 2 секунди - немає підключення до серверу, або мережі.

пʼятниця, 7 червня 2019 р.

Blynk: Авто-кватирка на ESP8266 та Stepper Motor

Передмова

Розробив на замовлення "Авто-кватирку", яка може міряти температуру і вологість в приміщення сенсором DHT, та відкривати чи закривати кватирку, або вікно за допомоги крокового двигуна (Stepper Motor 28BYJ-48 With Driver Module ULN2003). Схему і код публікую з дозволу замовника. 

Зачиняти і відчиняти вікно можна як з кнопки на пристрою, так і зі смартфону з додатку Blynk. А також задати залежності відкриття і закриття від температури і вологості в приміщенні. Плюс є два тижневих планувальника подій, яким можна задати час відкриття, та час закриття вікна. Чим повністю автоматизувати процес провітрювання приміщення.

Залізяччя

Схема

Схема авто-кватирка
Макет авто-кватирки

Скетч

Текст програми завеликий для публікації в статті. Завантажити скетч можна на GitHub.
На початку програми є деякі налаштування, за допомоги яких можна встановити інші піни для сенсора, мотора, кнопки, геркону, світлодіоду та номера віртуальних шпильок в Blynk:

#define BUTTON_SYSTEM   0
#define LED_BLUE    2
#define INITIAL_POSITION_SWITCH 16
#define ULN2003_IN1    5
#define ULN2003_IN2    4
#define ULN2003_IN3    14
#define ULN2003_IN4    12

#define DHTTYPE DHT11  // DHT11, DHT 22  (AM2302), AM2321
#define DHTPIN 13   // DHT PIN

// Сенсор DHT
#define TMP_DHT V5
#define HUM_DHT V6

#define MOTOR_MOVE_SLIDER   V20 // Віджет слайдера
#define MOTOR_SPEED_STEP_CONTROL V21 // Віджет STEP CONTROL
#define MOTOR_MAXIMUM_STEPS   V22 // Максимальна кількість кроків для слайдеру і кнопки
#define MOTOR_DIRECTION_MENU  V60 // Меню направлення руху мотору

#define TERMINAL   V41
#define TIME_INPUT_0  V50
#define TIME_INPUT_1  V51
#define WIFI_SIGNAL V80

Рекомендації по збиранню

  1. Зібрати макет чи завершений пристрій відповідно до наведеної схеми. Обов'язково живіть окремими джерелами струму двигун і сам пристрій. Геркон розташуйте на рамі вікна, а магніт розташуйте на кватирці або фрамузі так, щоб геркон замикався при закритті кватирки чи фрамуги. Розташування крокового двигуна і яка саме буде конструкція відкриття/закриття залежить від вашої фантазії і можливостей і виходить за рамки цієї статті;
  2. Скомпілювати скетч та залити до ESP8266 за допомоги Arduino IDE, або MS Visual Studio з плагіном visualMicro. Обов'язково перед компіляцією зробіть зміни до коду в бібліотеку "stepper". Відкрийте файл "stepper.cpp" та додайте рядок "delay(0);" до коду в це місце:
    // decrement the number of steps, moving one step each time:
      while (steps_left > 0)
      {
        delay(0);
        unsigned long now = micros();
    
    Або взяти готовий бінарник, як вас влаштовує типова схема наведена вище, і прошити цей бінарник будь яким прошивачем, який може прошити ESP8266. Наприклад "flash download tools", "ESP8266Flasher", тощо. Після прошивання, обов'язково зняти живлення з ESP8266. Вимкнути взагалі весь пристрій що прошили.
  3. Завантажити на свій смартфон додаток Blynk для андроїд, або Blynk для iOS;
  4. Запустити додаток і зареєструватись в системі Blynk:
  5. Увімкнути сканування QRcode: 
  6. Сканувати цей QRcode:
  7. Після сканування у вас з'явиться готовий проект. Відкрийте налаштування проекту:
  8. Зайдіть у розділ "Device":
  9. Зайдіть у властивості "My Devices", як немає жодного пристрою, додайте "New Device":
  10. Отримайте "Auth Token" та натисніть де позначено червоним прямокутником - "AUTH TOKEN" скопіюється до буферу. За потреби натисніть на "Email" і отримаєте "AUTH TOKEN" на свою поштову скриньку:
  11. З головного екрану проекту запустіть свій проект:
  12. Подайте живлення на свій пристрій. Двигун почне обертання, поки не замкнеться "геркон" магнітом (калібрування положення "вікно закрите"), або не пройде 10 секунд (timeout). На стадії тестування і ознайомлення можна самому рукою піднести магніт до геркону, або якимсь іншим чином замкнути контакти геркону. Далі почне блимати синій світлодіод на платі ESP8266, який під'єднано до GPIO2. На своєму смартфоні скануємо мережі Wi-Fi і підключаємось до мережі з назвою "Stepper-Motor-Wemos". Після підключення вам запропонується увійти через браузер на сторінку налаштувань пристрою, або самі запустіть браузер і зайдіть на сторінку налаштувань пристрою за адресою 192.168.4.1;
  13.  Зайти в меню "Configure WiFi":
  14. Ввести "SSID", "password" вашої WiFi мережі де буде працювати пристрій, та вставити з буферу обміну "Auth Token". Зберегти налаштування кнопкою "Save":
  15. Пристрій перезавантажиться, підключиться до вашої WiFi мережі і під'єднається до серверу Blynk. Смартфон від'єднається від мережі пристрою і під'єднається до вашої WiFi мережі. Запускаємо додаток Blynk з цим проектом і спостерігаємо на екрані данні про температуру і вологість, а також можете спробувати керувати слайдером щоб порухати кватирку чи фрамугу вікна.

Опис можливостей

В проекті є налаштування максимальної кількості кроків двигуна "MAX STEPS" і швидкість руху двигуна "SPEED MOTOR". Направлення руху двигуна (revers/direction). Рівень сигналу WiFi. Та вікно терміналу де друкуються деякі події, що відбуваються. Два планувальника подій "TIME INPUT". Та датчики температури і вологості. 

Термінал приймає деякі команди:
  • version - друкує в термінал поточну версію прошивки;
  • name - друкує в термінал поточну назву пристрою;
  • ip - друкує в термінал IP пристрою в мережі;
  • mac - друкує в термінал MAC адресу пристрою;
  • reboot - перезавантажує пристрій;
  • reset - скидає налаштування до "заводських";
  • pins - пам'ятка на яких віртуальних шпилька що "сидить".
Також в проекті присутній віджет "Eventor" за допомоги якого можна встановити залежності від температури і вологості і назначити для цього дії. 

Пристрій підтримує оновлення прошивки по WEB OTA. Для цього потрібно в браузері ПК, який знаходиться в тій же ж мережі що і пристрій, набрати IP адресу пристрою і додати "/update", наприклад пристрій має IP адресу "192.168.0.102" тоді в браузері набрати таку адресу "192.168.0.102/update" і ви потрапите на сторінку оновлення прошивки. Обирайте файл прошивки і тисніть кнопку "UPDATE". Після оновлення пристрій перезавантажиться і почне працювати знову.

Приклад роботи