顯示具有 Blynk 標籤的文章。 顯示所有文章
顯示具有 Blynk 標籤的文章。 顯示所有文章

2016年9月21日 星期三

★ 用 Blynk 同時控制多個設備

昨天有網友問我是否可以用 App Inventor 2 設計一個 App 來同時控制多個設備, 以我的菜園灑水控制器來說, 若菜園有多處裝有 Arduino 灑水控制器, 希望在 App 上按一個按鈕就可以控制它們同時灑水或停止. 這讓我馬上想到 Blynk 應該可以輕易完成這個任務, 不需要花時間自行設計一個 App 才對.

以下使用我自行焊接的兩個 Arduino+ESP8266 物聯網模組來測試 Blynk 群控的功能, 利用 App 上的一個 Slider 滑桿元件同時控制 Arduino NANO 板上的 D13 LED 閃爍速度. 此 IOT 模組的製作方式參考 :

# 製作 Arduino Nano + ESP8266 物聯網模組 (四) : 完結篇

也可以根據下列電路圖用洞洞板連線 (AMS1117 可用 3.3V 穩壓模組取代) :


關於 Blynk 的用法, 參考之前的一系列實驗記錄 :

# Blynk 應用 (一) : 用手機控制 Arduino
# Blynk 應用 (二) : 在手機顯示即時溫溼度與光度
# Blynk 的控制元件 (Controllers)
# Blynk 的顯示元件 (Displays)
# Blynk 的通知元件 (Notifications)
# Blynk 的其他元件 (Others)
# Blynk 的虛擬腳位用法整理

在 Blynk App 上則是先新增一個專案, 在此專案中新增一個 Slider M 元件, 然後綁定一個虛擬腳位 V0, 修改值域為 100 ~ 1000, 代表 LED 閃爍的頻率 (其實是延遲時間), 100 表示快閃, 1000 表示慢閃.



此專案分享如下 :


測試程式如下 :

測試 1 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object

char ssid[]="H30-L02-webbot";
char pass[]="1234567890";
char auth[]="f4d4aa2dc86849adb0971d0xxxxxx371";
volatile int blinkRate=100;  //default blink rate

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  }

void loop() {
  Blynk.run();
  blinkD13Led(blinkRate);
  }

BLYNK_WRITE(V0) { //called when V0 updated by App slider
  blinkRate=param.asInt(); //store value from App slider
  Serial.println(blinkRate);  //show current blink rate
  }

void blinkD13Led(int t) {
  digitalWrite(13, HIGH);
  delay(t);          
  digitalWrite(13, LOW);
  delay(t);          
  }

此處我們須準備一個特定的寫入事件處理函數 BLYNK_WRITE(V0) 讓 V0 腳位值更新時被呼叫, 因為手機 App 的 Slider 是一個控制元件, 當滑動滑桿時會將滑桿的值寫入伺服器中此專案之虛擬腳位 V0, 產生一個寫入事件, 會觸發設備中的寫入事件處理函數被呼叫一次. 在此函數中, 我們用特定的變數 param 取得最新的 V0 腳位之值, 用來更新全域變數 blinkRate, 此變數會被 blinkD13LED() 用來更改 LED 的閃爍頻率. 跟中斷事件類似, 由於 blinkRate 隨時會被更新, 因此要宣告為 volatile 以避免被編譯器優化.

這兩塊 IOT 模組都上傳相同的程式, 上電後, 由於 blinkRate 初始值設為 100, 因此會先快閃, 等我們滑動滑桿之後就會改變閃爍頻率 :


從上面影片可知, 滑動滑桿時, 兩個 NANO 板上的 D13 LED 都同時改變其閃爍頻率, 可見用 Blynk 可輕易達成群控之目的.

但上面測試 1 有個問題, 即每次重開機預設都是快閃 (blinkRate 預設 100), 必須在 App 上操作滑桿才會變更頻率. 要如何讓設備一開機或重新連線時能以最後一次滑桿之值閃爍呢? 這可以用特定之 BLYNK_CONNECTED() 函數來實現, 此函數會在設備重新與 Blynk Cloud 伺服器連線成功時被呼叫, 我們只要在此事件處理程序中呼叫 Blynk.syncVirtual(vPin) 即可取得伺服器上該虛擬腳位之值, 從而觸發虛擬腳位之寫入動作, 使 BLYNK_WRITE() 函數被呼叫, 這樣就會以上次的設定值閃爍了.

測試 2 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
int blinkRate;

char ssid[]="H30-L02-webbot";
char pass[]="1234567890";
char auth[]="f4d4aa2dc86849adb0971d0xxxxxx371";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  }

void loop() {
  Blynk.run();
  blinkD13Led(blinkRate);
  }

BLYNK_WRITE(V0) { //called when V0 updated by App slider
  blinkRate=param.asInt(); //store value from App slider
  Serial.println(blinkRate);
  }

BLYNK_CONNECTED() { //get data stored in virtual pin V0 from server
  Blynk.syncVirtual(V0);
  }

void blinkD13Led(int t) {
  digitalWrite(13, HIGH);
  delay(t);        
  digitalWrite(13, LOW);
  delay(t);        
  }

此程式中的 BLYNK_CONNECTED() 會在設備連上 Blynk 伺服器時被呼叫 (全部大寫的函數都是事件處理函數), 在裡面我們呼叫了指定虛擬腳位的同步函數 Blynk.syncVirtual(V0) 向伺服器取得 V0 的值, 這會導致一個 V0 刷新動作, 使得 BLYNK_WRITE(V0) 被呼叫, 從而使 LED 以上次的設定閃爍. 以上測試 2 也同時回應了網友關於程式重新起始時, 滑桿之值不是停留在上一次設定值的問題.


2016年9月2日 星期五

★ Blynk 的虛擬腳位用法整理

整理完 Blynk 的基本用法, 覺得虛擬腳位在整個架構中具有非常重要的地位, 因此我又重新咀嚼了官網教學文件關於 Virtual Pin 的敘述, 把散佈在之前測試文章中的用法做個摘要整理. 過去兩周對 Blynk 元件的使用作了研究, 心得記錄在下面六篇文章 :

# Blynk 應用 (一) : 用手機控制 Arduino
# Blynk 應用 (二) : 在手機顯示即時溫溼度與光度
# Blynk 的控制元件 (Controllers)
# Blynk 的顯示元件 (Displays)
# Blynk 的通知元件 (Notifications)
# Blynk 的其他元件 (Others)

Blynk 所設計的 Virtual Pin (vPin) 事實上是伺服器上的一個資料欄位, 乃設備 (微控器) 與手機 Blynk App 之間的一個資料傳遞通道或軟體 I/O 介面, 並非物理上實質的 I/O 腳位. 在 Blynk App 中我們可以為元件的資料輸出 (控制元件) 或輸入 (顯示元件) 綁定一個 Virtual Pin, 它會對應到伺服器中的特定資料欄位. 此虛擬腳位與設備端的繫結是透過 Blynk 函式庫中的兩個事件處理函數 : BLYNK.READ(vPin) 與 BLYNK.WRITE(vPin), 如下圖所示 :



上圖中 Blynk.virtualWrite(vPin, value) 函數是虛擬腳位的核心函數, 而 BLYNK_READ(vPin) 與 BLYNK_WRITE(vPin) 函數則是事件處理函數, 分別處理來自 Blynk 伺服器的讀取要求事件 (App 向伺服器發出要求, 伺服器又向設備發出要求), 以及 Blynk 伺服器的 vPin 寫入事件 (App 寫入伺服器, 伺服器又寫入設備).

Blynk.virtualWrite(vPin, value) 函數 : 

手機 App 可透過操作控制元件來更改虛擬腳位之值; 反方向來說, 設備端也可以呼叫 Blynk.virtualWrite(vPin, value) 函數將新值 value 寫入虛擬腳位, 此新值會立刻被傳送到 Blynk 伺服器, 再轉送至手機 App 中綁定此虛擬腳位之元件, 通常是用於在手機 Blynk App 的顯示元件上呈現設備端的狀態或數據.

虛擬腳位的值可以為數值 (整數, 浮點數), 也可以為字串, 例如 :

Blynk.virtualWrite(V0, "abc"); //傳遞字串
Blynk.virtualWrite(V0, 123); //傳遞整數
Blynk.virtualWrite(V0, 12.34); //傳遞浮點數

除了將數據傳送給顯示元件外, Blynk.virtualWrite() 函數還可以一次傳送最多四個參數 (除第一參數 vPin 之外) :

Blynk.virtualWrite(V0, "hello", 123, 12.34, 0.1234);  //一次最多紀錄 4 個參數

這種多參數的用法是用在需要將資料儲存於伺服器資料庫的場合, 如上所言, 虛擬腳位只是伺服器資料庫中的資料欄位, 設備端程式呼叫 Blynk.virtualWrite() 其實就是將這些參數寫入這個欄位中. 我們可以利用此功能來儲存系統狀態變數, 如同使用 ATmega328P 內部的 EEPROM 記憶體一樣, 當系統因為失去電力或網路斷線時, 最後狀態可以儲存在虛擬腳位中, 等設備重新與伺服器連線時再將這些狀態取出回到斷線前狀態繼續執行.

在官網教學中有一個利用 Blynk.virtualWrite() 將設備端變數儲存到 Blynk 伺服器的範例 :

# ServerAsDataStorage_MultiValue.ino

此範例程式不太實際, 因為其中 V0 儲存之 "SomeStaticData" 讓人摸不著頭緒. 我將此範例改編為如下程式. 在手機 Blynk App 中, 此專案包含了一個 Value Display 元件以及一個 Button 元件, 前者用來顯示程式已經執行的秒數; 後者用來產生一個虛擬腳位寫入事件, 我們要將此按鈕已被按下的次數記錄下來, 連同已執行秒數憶起儲存在伺服器的虛擬腳位中, 系統重開機時這兩個狀態會被重新載入, 程式就可以從系統關機時的狀態回復. App 的設定如下 :

按鈕綁定到虛擬腳位 V2 :


數值顯示器綁定 V1 :


而虛擬腳位不綁定任何 App 元件, 純粹是用來儲存資料用, 程式如下 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <SimpleTimer.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
SimpleTimer timer; //create a Timer object

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="3a5a3d8c4e0545669ffb847xxxxxx033";

int upTime; //seconds
int pressCount; //counter for pressing button

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  timer.setInterval(1000L, pushUptime); //Don't send more that 10 values per second
  }

void loop() {
  Blynk.run();
  timer.run();
  }

BLYNK_CONNECTED() {  //called when connected to the server
  Blynk.syncVirtual(V0);  //get data stored in virtual pin V0 from server
  }

BLYNK_WRITE(V0) { //called when V0 updated (sync)
  upTime=param[0].asInt();  //restore from server
  pressCount=param[1].asInt();  //restore from server
  }

BLYNK_WRITE(V2) { //called when V2 updated
  ++pressCount; //increment counter
  }

void pushUptime() { //push Arduino uptime to virtual pin V0
  ++upTime; //seconds
  Blynk.virtualWrite(V0, upTime, pressCount); //store multi values to server
  Blynk.virtualWrite(V1, upTime); //update App display
  BLYNK_LOG("Uptime=%d",upTime);
  BLYNK_LOG("Press Count=%d",pressCount);
  }

此程式裡有兩個全域變數,  pressCount 用來記錄按鈕已經按下多少次, upTime 則記錄程式已經執行的秒數, 在 timer 的每秒中斷處理函數 pushUptime() 中我們利用 Blynk.virtualWrite() 將這兩個狀態變數儲存到伺服器裡此專案的虛擬腳位 V0. 而為了顯示程式已執行秒數, 則使用 V1 虛擬腳位來儲存, 並綁定到 App 裡的 Value 顯示器中.


序列埠監控視窗擷取訊息如下, 板子原先開機過一次, 按鈕 35 之後拔掉電源再重新插上去, 此為第二次開機時的 log, 可知當板子重新通電後, 在尚未與 Blynk 伺服器連線時, upTime 與 pressCount 為初始值, 直到連線成功後與伺服器同步, 取回斷電前狀態後就更新這兩個全域變數, 系統狀態就從斷電前開始執行了 :

[0] Blynk v0.3.8 on Arduino Nano
[499] Connecting to EDIMAX-tony
[1517] Failed to disable Echo
[2518] Uptime=1
[2518] Press Count=0
[3517] Uptime=2
[3517] Press Count=0
[4517] Uptime=3
[4517] Press Count=0
[15218] Uptime=4
[15218] Press Count=0
[16323] Uptime=5
[16323] Press Count=0
[16334] Uptime=6
[16334] Press Count=0
[16350] Uptime=7
[16360] Press Count=0
[16394] Uptime=8
[16403] Press Count=0
[16435] Uptime=9
[16446] Press Count=0
[16478] Uptime=10
[16489] Press Count=0
[16551] Ready (ping: 228ms).
[18176] Uptime=11
[18176] Press Count=0
[18589] Uptime=12
[18589] Press Count=0
[19098] Uptime=1485
[19098] Press Count=35
[19508] Uptime=1486
[19508] Press Count=35
[19917] Uptime=1487
[19918] Press Count=35
[20328] Uptime=1488
[20328] Press Count=35
[20738] Uptime=1489
[20738] Press Count=35

可見在 [19098] 時, 重新與伺服器連線後便從 V0 取回斷電時的數值繼續執行. 注意, 綁定顯示器元件的虛擬腳位只能儲存單一數值, 不可以存放多重數值, 例如上例中 App 的數值顯示器若綁定 V0 將不會顯示任何數值, 因為它不知道要顯示多重數值中的哪一個, 必須另外綁定只儲存單一數值的 V1.

此專案設計分享如下 :


要特別注意的是, 要避免在 loop() 函數中呼叫 Blynk.virtualWrite(vPin, value), 因為那樣設備很容易以過高的頻率更新 vPin 之值 (超過每秒 10 次), 使得伺服器發生 Flood Error (資料氾濫錯誤). 參考 :

# http://docs.blynk.cc/#troubleshooting-flood-error

在 loop() 中就算搭配 delay() 也無法解決 Flood Error 問題, 反而會產生其他問題, 例如因延遲太久導致連線中斷以及效能低落等等. 正確做法是使用 Blynk 函式庫的 SimpleTimer 計時器函數來控制數據傳輸頻率. Blynk 伺服器的上限是每秒 10 筆, 當資料傳送頻率超過每秒 10 筆時伺服器會自動切斷連線, 手機 Blynk App 會顯示 "Your hardware is offline". 如果更新 vPin 的頻率需要超過每秒 10 次, 則必須自建 Blynk 伺服器, 修改 server.properties 檔案中的 user.message.quota.limit 屬性, 同時也要修改 Blynk 函式庫 BlynkConfig.h 檔案中的 BLYNK_MSG_LIMIT 常數, 參考 :

# Troubleshooting


BLYNK.WRITE(vPin) 函數 :

BLYNK.WRITE(vPin) 是設備端虛擬腳位的 setter 函數 (寫入事件處理函數), 當寫入設備端 vPin 的事件發生時, 此函數就會被呼叫. 具體來說, 當操作手機 App 的控制元件時, 其新值將透過所綁定之虛擬腳位寫入伺服器之資料欄位, 伺服器同時將此新值放在全域變數 param 中傳遞給設備端, 這將觸發設備端寫入事件, 使事件處理函數 BLYNK.WRITE(vPin) 被呼叫. 設備端韌體可在此函數中利用 param 變數取得 App 端所傳遞的資料, 並呼叫 asInt(), asString() 等函數將 param 轉成適當資料型態 :

BLYNK_WRITE(V0) {  //虛擬腳位 V0 的寫入事件處理函數
  int v1=param.asInt(); //以整數型態取得參數
  Double v2=param.asDouble();  //以浮點數型態取得參數
  Float v3=param.asFloat();  //以浮點數型態取得參數
  String value=param.asStr();  //以字串型態取得參數
  //有些元件如搖桿會傳遞多的變數, 則 param 為一參數陣列
  int x=param[0].asInt();  //以整數型態取得參數
  int y=param[1].asInt();  //以整數型態取得參數
  //... 處理寫入值
  }

除了由 App 端的控制元件觸發 BLYNK.WRITE(vPin) 被執行外, 設備端也可以自己呼叫 Blynk.syncVirtual(vPin) 函數來觸發 BLYNK.WRITE(vPin) 被執行.

BLYNK.READ(vPin) 函數 :

BLYNK_READ(vPin) 函數是設備端虛擬腳位的 getter 函數 (讀取事件處理函數), 當伺服器向設備發出 vPin 讀取要求時, 此函數就會被呼叫, 此種要求通常來自 App 中設定為 Reading 模式的顯示元件, 在此模式中 App 的顯示元件會週期性透過伺服器向設備發出 vPin 資料讀取要求. 在此 BLYNK_READ(vPin) 函數中, 設備必須利用 Blynk.virtualWrite(vPin, value) 函數將 vPin 的新值 value 傳送到伺服器.  例如 :

BLYNK_READ(V0) {
  Serial.println(millis()/1000);
  Blynk.virtualWrite(V0, millis()/1000);
  }

當收到伺服器的 V0 讀取要求後, 此事件處理函數會被呼叫, 設備就將資料用 Blynk.virtualWrite() 函數傳送給伺服器. 當然 App 端的顯示元件必須設定在週期性的 Reading 模式, 不可以設在 Push 模式. 若設在 Push 模式就不需要 BLYNK_READ(vPin) 函數, 而是需要一個計時器來觸發一個自訂的資料推送函數, 參考 :

# Blynk 的顯示元件 (Displays)

其他參考 :

# How to use Virtual Pins in Blynk


2016年8月27日 星期六

★ Blynk 的其他元件 (Others)

 本周大部分閒暇時間都花在鑽研 Blynk 四大類元件的用法, 結果記錄在下面五篇文章 :

# Blynk 應用 (一) : 用手機控制 Arduino
# Blynk 應用 (二) : 在手機顯示即時溫溼度與光度
# Blynk 的控制元件 (Controllers)
# Blynk 的顯示元件 (Displays)
# Blynk 的通知元件 (Notifications)

現在剩下其他類元件了, 補上這一塊後, 對於 Blynk 就會有整體的認識了.

以下測試程式是在我自己用洞洞板焊接的 Arduino Nano+ESP8266 IOT 模組上進行實驗, 電路圖參考 :

# 製作 Arduino Nano + ESP8266 物聯網模組 (四) : 完結篇



Blynk 的其他 (Others) 類元件就是無法歸類在 Controls, Displays, Notifications 這三類的元件, 目前有如下五種元件 :


一. 橋接器元件 (Bridge) :  

先來測試 Blynk 最強的 Bridge 元件, 它是最能彰顯物聯網能力的元件, 因為它可以通過 Blynk Cloud 伺服器達成 Machine to machine 的控制. 官網說明文件參考 :

# http://docs.blynk.cc/#widgets-other-bridge

簡言之, Bridge 元件是用來在設備與設備之間搭起互相通訊的橋樑, 通訊管道建立之後, 即使手機的 Blynk App 離線也不會影響到兩個設備之間的通訊, 這就是 M2M (Machine to machine) 通訊的一種典型例子. 設備A 只要擁有設備 B 的金鑰, 就能透過 Blynk Cloud 控制設備 B 的任何腳位狀態 (Digital/Analog/Virtual), App 的 Bridge 元件僅僅是在 Blynk Cloud 註冊此功能, 幫忙搭起溝通的橋樑而已, 讓設備 A 可以控制設備 B. 我們可以多個專案中使用 Bridge 元件來進行多重 M2M 控制.

首先在專案中新增 Bridge 元件 :


點一下元件進入設定畫面 :


M2M 的控制事實上是做在設備端的韌體程式裡, 我們須指定一個虛擬腳位做為與另一個設備端溝通的管道, 而 Blynk App 專案中建立 Bridge 元件只是告訴 Blynk Cloud 此專案有用到 Bridge 功能而已, 所以 App 裡此元件沒有需要設定的選項.

只要在專案中添加此 Bridge 元件後, 與此專案相連結的設備 (即擁有此專案金鑰之設備) 即擁有控制其他 Blynk 設備之能力. 接下來便是撰寫設備端韌體程式, 參考官網範例 :

# https://github.com/blynkkk/blynk-library/blob/master/examples/Widgets/Bridge/Bridge.ino#L33

此範例有 A, B 兩設備, 設備 A 在 App 中註冊了 Bridge 元件, 用來控制另一個專案所連結的設備 B. 設備 A 首先利用虛擬腳位 V1 建立一個 WidgetBridge 物件 :

WidgetBridge bridge1(V1);

要控制另一個設備必須擁有該設備之金鑰, 並且在連線 Blynk Cloud 成功後呼叫 setAuthToken() 函數來獲得對方設備認可, 這是由 BLYNK_CONNECTED() 函數負責的 :

BLYNK_CONNECTED() {
  bridge1.setAuthToken("AuthTokenOfDeviceB");
  }

接著利用計時器每秒去 (High/Low) 交替更改設備 B 的 D9 與 V5 腳位狀態 (toggle).

在下面的測試中我使用兩塊 Arduino Nano+ESP8266 物聯網模組來當設備 A 與設備 B 以便測試 Bridge 功能. 我使用設備 A 的 V0 腳位當作 Bridge 的管道, 用來透過 Blynk Cloud 遠端控制另一塊模組設備 B. 為了方便起見, 我將 D9 改為 D13, 因為 D13 腳位在 UNO/Nano 板上有一個內建 LED 與其相接, 可以直接看到腳位狀態變化. 另外我把推送給設備 B 的 V5 腳位的數據改成設備 A 程式執行後所經過的秒數, 而不是 0/1 交替, 這樣在設備 B 的 Blynk App 上使用一個數值顯示器就能看到設備 A 程式已執行多少秒了.

為了在設備 B 上顯示設備 A 程式已執行的時間, 我先在 Blynk App 上建立了一個新專案叫做 Device B, 這樣會得到一個專案認證金鑰來代表設備 B :


按 E-mail 鈕將金鑰寄到信箱以便複製到程式裡面. 然後在 Device B 專案裡新增一個 Value Display M 元件來顯示設備 A 的程式執行時間, 此顯示元件綁定虛擬腳位 V4 :


這裡我們使用 Push 方式從設備 B 的虛擬腳位 V4 取得資料來顯示. 注意, 不能直接綁定到 Bridge 所使用的 V5. 原因後述. 設定好後按左上方的返回鍵回到專案面板如下 :


按最右邊的 Play 鍵開始執行設備 B 的 App.

接下來要撰寫設備 B 的韌體程式, 參考下面這篇關於數值顯示 Push 模式的範例 :

# Blynk 的顯示元件 (Displays)

不過這裡不需要使用 SimpleTimer 物件去週期性呼叫自訂函數將資料推送給 App, 而是必須準備一個 BLYNK_WRITE(vPin) 函數, 此函數會在虛擬腳位的值更新時被呼叫, 在此函數中我們須使用 param 的 asInt(), asFloat(), 或 asString() 等函數將字串轉成所要的資料型態, 然後呼叫 Blynk.virtualWrite(vPin) 將資料推送到 App 上顯示元件所綁定的另一個虛擬腳位.

在官網文件中關於 Bridge 的說明有這一段重要的話 :

"Keep in mind that bridge.virtualWrite doesn’t send any value to mobile app. You need to call Blynk.virtualWrite for that."

這就是為什麼我們在上面設定數值顯示器時, 不能直接將其綁定到 Bridge 的 V5 的原因了. 因為 App 上的 WidgetBridge 物件的 virtualWrite() 並不能更新遠端設備所連結 App 上的顯示元件, 必須用 Blynk 物件的 virtualWrite() 才行.

設備 B 的程式如下 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object

char ssid[]="H30-L02-webbot";
char pass[]="1234567890";
char auth[]="0155b16357f1432d876cfb3xxxxxxf33";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  }

void loop() {
  Blynk.run();
  }

BLYNK_WRITE(V5) { //called when V5 updated by Device A via Bridge
  int deviceA_uptime=param.asInt(); //store value that came via Bridge
  Blynk.virtualWrite(V4, deviceA_uptime); //display in App
  }

注意, 上面程式中我故意讓設備 B 透過我的手機行動網路基地台連上 Internet, 下面的設備 A 則仍然是透過家中社區網路上網,

我們會在設備 A 呼叫 WidgetBridge 物件的 virtualWrite() 將程式已執行時間推送到設備 B 的虛擬腳位 V5, 這會觸發設備 B 韌體程式的 BLYNK_WRITE() 函數, 我們再從 V5 取出推送過來的值, 用 Blynk.virtualWrite() 推送到 App.

上面搞定設備 B 後, 接下來撰寫設備 A 的程式如下 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <SimpleTimer.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
SimpleTimer timer; //create a Timer object

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="e80fe6aae0d442518820f61xxxxxx8f5";  //Device A's auth token

WidgetBridge deviceB(V0);  //use Device A's V0 to create a Bridge object for Device B

static bool value=true;

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  while (Blynk.connect() == false) {} //wait until connected
  timer.setInterval(1000L, blynkDeviceB); //Don't send more that 10 values per second
  }

void loop() {
  Blynk.run();
  timer.run();
  }

void blynkDeviceB() { //Here we will send HIGH or LOW once per second
  long uptime=millis()/1000L; //get seconds after running
  deviceB.virtualWrite(V5, uptime); //update deviceB's V5 with uptime  
  if (value) {deviceB.digitalWrite(13, HIGH);} //update deviceB's D13
  else {deviceB.digitalWrite(13, LOW);}  //update deviceB's D13
  value=!value; //toggle
  }

BLYNK_CONNECTED() { //called when deviceA connect to Blynk Server
  deviceB.setAuthToken("0155b16357f1432d876cfb3yyyyyyf33");
  }


我將 Device A (上), B (下) 兩塊 IOT 模組分別上傳程式後, 用兩個行動電源供電. 上電後約 6 秒兩塊都連上 Internet, 這時圖中下方的設備 B 之 Nano 板上 D13 LED 開始間隔一秒閃爍, 影片如下 :


打開手機執行 Device B 專案, 可見數值顯示器上的數字每一秒往上跳一下, 顯示設備 A 透過 Bridge 推送過來的程式已執行秒數 :


即使將設備 A 的專案停止執行也不會影響設備 B 上的 LED 閃爍, 也不會影響手機上 Device B 專案的設備 A 已執行時間顯示, 甚至關閉手機網路讓 Blynk App 與 Blynk 伺服器離線,  設備 B 的 LED 照常閃爍, 僅 Device B 專案上的秒數顯示暫停不動, 手機再連網時數字就會繼續跟上去. 可見 Bridge 的運作與手機 Blynk App 無關, 它是由 Blynk Cloud 伺服器在操控的.

上面這個簡單範例只是單向的 M2M 控制, 前面提過可以用多個 Bridge 元件達成多重 M2M 控制. 接下來要將上面範例擴展為雙向 M2M 控制, 亦即除了讓設備 A 透過 Bridge 控制設備 B 的 D13 LED 閃爍外, 也同時讓設備 B 控制設備 A 的 D13 LED 閃爍; 而且手機上的 Blynk App 都能看到對方設備的程式已執行時間.

為了讓設備 B 可以控制設備 A 的 LED 閃爍與推送設備 B 程式已執行時間, Device B 專案必須新增一個 Bridge 元件 :


這樣便在 Blynk Cloud 註冊設備 B 有 Bridge 功能了. 當然設備 B 的韌體程式也仿照上面範例的設備 A 程式進行修改, 讓它能控制設備 A 的 D13 LED 閃爍, 並將設備 B 程式已執行時間推送到設備 A 的 App. 設備 B 同樣利用自己的虛擬腳位 V0 與 Blynk Cloud 個 Bridge 功能溝通, 控制設備 A 的 D13 LED.

下面是修改後的設備 B 韌體程式 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <SimpleTimer.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
SimpleTimer timer; //create a Timer object

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="0155b16357f1432d876cfb3xxxxxxf33"; //Device B's auth token

WidgetBridge deviceA(V0);  //use Device B's V0 to create a Bridge object to control Device A

static bool value=true;

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  while (Blynk.connect() == false) {} //wait until connected
  timer.setInterval(1000L, blynkDeviceA); //Don't send more that 10 values per second
  }

void loop() {
  Blynk.run();
  timer.run();
  }

void blynkDeviceA() { //Here we will send HIGH or LOW once per second
  long uptime=millis()/1000L; //get seconds after running
  deviceA.virtualWrite(V5, uptime); //update deviceA's V5 with uptime
  if (value) {deviceA.digitalWrite(13, HIGH);} //update deviceA's D13
  else {deviceA.digitalWrite(13, LOW);}  //update deviceA's D13
  value=!value; //toggle
  }

BLYNK_CONNECTED() { //called when deviceB connect to Blynk Server
  deviceA.setAuthToken("e80fe6aae0d442518820f61xxxxxx8f5"); //Device A's auth token
  }

BLYNK_WRITE(V5) { //called when V5 updated by Device A via Bridge
  int deviceA_uptime=param.asInt(); //store value that came via Bridge
  Blynk.virtualWrite(V4, deviceA_uptime); //display in App
  }

與上一個範例相比, 主要是多了 SimpleTimer 與 BridgetWidget, 因為它也要透過 Bridge 功能去控制設備 A 的 D13 LED, 所以多加了 BLYNK_CONNECTED() 函數, 利用 setAuthToken() 向 Blynk Cloud 送出想要控制的設備之金鑰; 同時它也要把自己的 uptime 推送給設備 A, 所以添加了自訂的 blynkDeviceA(), 讓計時器每秒去呼叫它, 將設備 B 自己的 uptime 寫入設備 A 的虛擬腳位 V5 上, 以及讓設備 A 的 D13 LED 閃爍.

回過頭來看設備 A, 因為我們也要在其 App 上顯示設備 B 推送過來的 uptime, 所以 Device A 專案裡除了原先的 Bridge 元件外還要新增一個 Value Display M 元件, 綁定設備 A 的虛擬腳位 V4 :



這樣設備 A 的 App 就設定好了. 此外, 設備 A 的韌體程式也需要修改, 因為要顯示來自設備 B 的 uptime 資料 (設備 B 已指定寫入設備 A 的 V5), 必須加上特定的 BLYNK_WRITE(V5) 函數, 每當設備 A 的 V5 被設備 B 透過 Bridge 寫入更新時, 這個 BLYNK_WRITE(V5) 函數就會被呼叫, 我們必須在此函數中把從 V5 收到資料推送到數值顯示器所綁定的 V4 腳位, 這樣 Device A 專案上的顯示器才會呈現設備 B 的 uptime.

設備 A 的韌體程式修改如下, 與上一範例相比, 僅加入 BLYNK_WRITE(V5) 函數來處理顯示設備 A 推送過來的 uptime 而已 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <SimpleTimer.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
SimpleTimer timer; //create a Timer object

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="e80fe6aae0d442518820f61xxxxxx8f5"; //Device A's auth token

WidgetBridge deviceB(V0);  //use Device A's V0 to create a Bridge object to control Device B

static bool value=true;

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  while (Blynk.connect() == false) {} //wait until connected
  timer.setInterval(1000L, blynkDeviceB); //Don't send more that 10 values per second
  }

void loop() {
  Blynk.run();
  timer.run();
  }

void blynkDeviceB() { //Here we will send HIGH or LOW once per second
  long uptime=millis()/1000L; //get seconds after running
  deviceB.virtualWrite(V5, uptime); //update deviceB's V5 with uptime
  if (value) {deviceB.digitalWrite(13, HIGH);} //update deviceB's D13
  else {deviceB.digitalWrite(13, LOW);}  //update deviceB's D13
  value=!value; //toggle
  }

BLYNK_CONNECTED() { //called when deviceA connect to Blynk Server
  deviceB.setAuthToken("0155b16357f1432d876cfb3xxxxxxf33"); //Device B's auth token
  }

BLYNK_WRITE(V5) { //called when V5 updated by Device B via Bridge
  int deviceB_uptime=param.asInt(); //store value that came via Bridge
  Blynk.virtualWrite(V4, deviceB_uptime); //display in App
  }  

程式上傳後兩塊 IOT 模組同時上電後測試結果如預期, 現在兩塊板子的 D13 LED 都會閃爍  (上 : 設備 A, 下 : 設備 B) :


而且兩塊板子的 App 也都會顯示對方的 uptime :



上面範例的專案設計分享如下 :

Device A

Device B

從上面兩個簡單範例可知, 這個 Bridge 元件實在太好用了, 透過 Blynk Cloud 伺服器, 散佈在全球任何角落的兩個設備只要連上互聯網, 他們不僅可以一對一互相監控, 也可以一對多, 多對一, 或者多對多進行多重監控 (multiple control and monitor), 實際應用範例就要靠想像力去發揮了.例如水塔抽水自動控制, 馬達在樓下, 水塔在樓頂, 若使用一個 Arduino 控制的話, 感測器的電線要拉很遠, 要不然就要用藍芽或無線射頻模組. 不如使用兩塊 Arduino Nano+ESP8266 模組, 樓下的那塊負責控制抽水馬達, 樓上的負責偵測水位, 透過 wifi 與 Blynk 的 Bridge 做一對一互相監控, 還可以在全球任何地方作遠端監控. 也可以應用在車庫自動照明, 例如大門偵測到車子回家後, 便透過 Bridge 控制車庫燈打開, 而車庫控制器偵測到車子已停妥, 也透過 Bridge 控制客廳燈開啟等等.

在 Blynk 討論群組發現這篇文章 :

# Why nobody uses bridge?

這是去年 2015 Bridge 功能剛出來時的討論文章, 在最底下發現一個跟我的 IOT 模組很像的模組, 原來早就有人這麼做了. 更多範例參考 :

# Blynk Board Bridge Widget Demo



二. 時鐘元件 (RTC) :

此元件可讓我們取得 Blynk Cloud 伺服器的時間, 透過時區選擇, 伺服器傳回來的就是我們本地時間, 可用來同步硬體設備如 Arduino 的內部時鐘, 亦即不需要大費周章與 NTP 同步了, 透過 Blynk 就能輕而易舉辦到.

在專案中新增一個 RTC 元件如下 :


點一下元件進入設定畫面 :


RTC 元件只能綁定虛擬腳位, 這裡我選定 V2. 底下有一個 Time Zone 選項, 已經自動偵測是台灣時區, 預設即為 +8, 不須更改. 另外我們需要一個 Value Display M 元件以便在 App 上顯示時間, 綁定到虛擬腳位 V3, 使用 PUSH 模式 :


這樣 App 專案的設定就完成了, 裡面有兩個元件 :


韌體程式部分, 使用 RTC 元件需先匯入下列兩個函式庫 :

#include <TimeLib.h>
#include <WidgetRTC.h>

然後建立 WidgetRTC 物件 :

WidgetRTC rtc;

再呼叫特定之 BLYNK_ATTACH_WIDGET() 函數綁定虛擬腳位即可 :

BLYNK_ATTACH_WIDGET(rtc, vPin);

函式庫 TimeLib 位於 Blynk 函式庫安裝目錄 Time 子目錄下, 開啟 Time.cpp 可以找到它所提供的函式, 例如 hour(), minute() 等, 事實上跟以前測試 NTP 時所下載的 Time 函式庫是一樣的, 參考這篇 :

# 利用 NTP 伺服器來同步 Arduino 系統時鐘 (三)

其函式列表如下 :



 函式 說明
 hour() 傳回現在的時 (24 小時制)
 hourFormat12() 傳回現在的時 (12小時制)
 minute() 傳回現在的分
 second() 傳回現在的秒
 year() 傳回現在的年
 month() 傳回現在的月
 day() 傳回現在的日
 weekday() 傳回現在的星期 (星期日為 1)


傳回值均為整數. 實際範例參考官網教學文件:

# https://github.com/blynkkk/blynk-library/blob/master/examples/Widgets/RTC/RTC.ino

我參考之前測 NTP 的程式將其改編如下 : 


#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <SimpleTimer.h>
#include <TimeLib.h>
#include <WidgetRTC.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
SimpleTimer timer; //create a Timer object

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="f4d4aa2dc86849adb0971d0zzzzzz371"; //Device A's auth token

WidgetRTC rtc;  //create an RTC object
BLYNK_ATTACH_WIDGET(rtc, V2); //binding RTC to virtual pin

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  while (Blynk.connect() == false) {} //wait until connected
  rtc.begin();
  timer.setInterval(1000L, showTime); //Don't send more that 10 values per second
  }

void loop() {
  Blynk.run();
  timer.run();
  }

void showTime() {
  String d=getDate();
  String t=getTime();
  String w=getWeek();
  Serial.println(d + F(" ") + t + F(" ") + w);
  Blynk.virtualWrite(V3, d + F(" ") + t + F(" ") + w); //Send time to App vPin
  }

String getDate() {
  String d=(String)year() + "-";
  byte M=month();
  if (M < 10) {d.concat('0');}
  d.concat(M);
  d.concat('-');
  byte D=day();
  if (D < 10) {d.concat('0');}
  d.concat(D);
  return d;
  }

String getTime() {
  String t="";
  byte h=hour();
  if (h < 10) {t.concat('0');}
  t.concat(h);
  t.concat(':');
  byte m=minute();
  if (m < 10) {t.concat('0');}
  t.concat(m);
  t.concat(':');
  byte s=second();
  if (s < 10) {t.concat('0');}
  t.concat(s);
  return t;
  }

String getWeek() {
  String w[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
  return w[weekday()-1];
  }

此程式中我們利用計時器每秒觸發執行自訂的 showTime() 函數, 於其內呼叫 Blynk.virtualWrite() 函數將日期時間字串推送到虛擬腳位 V3 所綁定的顯示器上. 而取得日期, 時間, 以及星期的功能分別由 getDate(), getTime(), 以及 getWeek() 負責, 這樣比較有彈性. 由於 Time 函式庫的 getWeek() 傳回值星期天是 1, 星期六是 7, 故利用陣列轉成英文縮寫字串傳回.


比起以前用 NTP 伺服器, 親自操控緩衝器累得半死, 用 Blynk 的 RTC 元件實在是太輕鬆啦! 當然我們也可以將這個日期時間資訊輸出到實體的 1602 LCD 去, 參考下面這篇的測試 6-1 :


# 利用 NTP 伺服器來同步 Arduino 系統時鐘 (三)

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <SimpleTimer.h>
#include <TimeLib.h>
#include <WidgetRTC.h>
#include <LiquidCrystal.h>
#define RS 2
#define E 3
#define D4 10
#define D5 11
#define D6 12
#define D7 13

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
SimpleTimer timer; //create a Timer object
LiquidCrystal lcd(RS,E,D4,D5,D6,D7);  //create LCD object

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="f4d4aa2dc86849adb0971d0zzzzzz371"; //Device A's auth token

WidgetRTC rtc;  //create an RTC object
BLYNK_ATTACH_WIDGET(rtc, V2); //binding RTC to virtual pin

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  while (Blynk.connect() == false) {} //wait until connected
  rtc.begin();
  timer.setInterval(1000L, showTime); //Don't send more that 10 values per second
  lcd.begin(16,2); //define 2*16 LCD
  lcd.clear(); //clear screen    
  }

void loop() {
  Blynk.run();
  timer.run();
  }

void showTime() {
  String d=getDate();
  String t=getTime();
  String w=getWeek();
  Serial.println(d + F(" ") + t + F(" ") + w);
  lcd.setCursor(0,0); //move to (x,y)
  lcd.print(d); //print date
  lcd.setCursor(11,0); //move to (x,y)
  lcd.print(w); //print date 
  lcd.setCursor(0,1); //move to (x,y)
  lcd.print(t); //print date
  Blynk.virtualWrite(V3, d + F(" ") + t + F(" ") + w); //Send time to the App
  }

String getDate() {
  String d=(String)year() + "-";
  byte M=month();
  if (M < 10) {d.concat('0');}
  d.concat(M);
  d.concat('-');
  byte D=day();
  if (D < 10) {d.concat('0');}
  d.concat(D);
  return d;
  }

String getTime() {
  String t="";
  byte h=hour();
  if (h < 10) {t.concat('0');}
  t.concat(h);
  t.concat(':');
  byte m=minute();
  if (m < 10) {t.concat('0');}
  t.concat(m);
  t.concat(':');
  byte s=second();
  if (s < 10) {t.concat('0');}
  t.concat(s);
  return t;
  }

String getWeek() {
  String w[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
  return w[weekday()-1];

  }

藍色部分就是加進去跟 LCD 液晶模組有關者. 執行結果如下 :



Blynk 的出現實在是幫了開發者一個大忙, 當然也不能說以前的努力是白忙了, 像 NTP 伺服器對時就搞了好久才成功, 但至少經過這些過程總算知道底層的細節實作要怎樣做啦! 此專案分享如下 :



三. 選單元件 (Menu) : 

選單元件就是下拉式選單, 依寬度不同分成兩個 :
  1. Menu M : 寬度為手機螢幕一半
  2. Menu L : 寬度為手機螢幕寬
新增一個 Menu L 元件如下 :


點一下元件進入設定畫面, 不管是 M 還是 L, 其設定畫面都一樣 :


選單元件只能綁定虛擬腳位, 因為其選項值必須經過程式處理. 預設有兩個選項, 每按底下的 +Add item 一次會增加一個, 選項數目可以任意新增, 選項的標題預設是 Item 1, 2, ... 可加以編輯修改, 但其前面中括號內的數字 [1], [2], ... 是固定的選項值無法編輯, 亦即第一項固定傳回 1, 第二項固定傳回 2, ... 例如下面的閃燈選擇選單裡, 我們綁定 V0 腳位, 新增 item3 :


當我們選擇 Menu 元件上的選項時, 其值便透過所綁定的虛擬腳位 V0 傳送給 Blynk 伺服器, 在設備端要接收此選項必須於韌體程式中定義此虛擬腳位的特定 BLYNK_WRITE(vPin) 函數, 這樣的話每當此虛擬腳位被更新時, BLYNK_WRITE() 函數就會被呼叫, 我們就可以在此函數內利用特定之 param 物件處理選項值, 參考官網教學文件範例 :

# https://github.com/blynkkk/blynk-library/blob/master/examples/Widgets/Menu/Menu.ino

我將其改編為 ESP8266 版本如下 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object

char ssid[]="EDIMAX-meinung";
char pass[]="1234567890";
char auth[]="f4d4aa2dc86849adb0971d0zzzzzz371";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  }

void loop() {
  Blynk.run();
  }

BLYNK_WRITE(V0) { //called when vPin updated by server
  switch (param.asInt()) {
    case 1: { //Item 1
      Serial.println("Item 1 selected");
      blinkD13(1);
      break;
      }
    case 2: { //Item 2
      Serial.println("Item 2 selected");
      blinkD13(2);
      break;
      }
    case 3: { //Item 3
      Serial.println("Item 3 selected");
      blinkD13(3);
      break;
      }
    default: {
      Serial.println("Unknown item selected");
      }
    }
  }

void blinkD13(int count) { //blink D13 LED
  for (int i=0; i<count; i++) {
    digitalWrite(13, HIGH);
    delay(500);
    digitalWrite(13, LOW);
    delay(500);  
    }
  }

上面程式中我新增了一個自訂函數 blinkD13(), 傳入欲閃爍的次數, 則 Arduino 板子上的 D13 LED 便會閃爍指定之次數. 當設備的的虛擬腳位 V0 被伺服器寫入時 (即選項被改變時), BLYNK_WRITE() 函數便會被呼叫, 寫入的值 (即選項值) 會放在 param 物件中傳給 Arduino, 利用 asInt() 將字串轉成整數即可得到選項值, 關於 BLYNK_READ() 與 BLYNK_WRITE() 參考 :

# Blynk 的控制元件 (Controllers)

程式上傳後執行結果如預期, D13 LED 會依選項值閃爍指定次數. 從元件功能來看, 我覺得其實此元件應該放在控制元件類才對. 另外, 使用按鈕元件一個要耗掉 200 單位能量, Menu 元件才耗費 400 單位能量, 所以如果要用到很多 Button 元件的話 (超過兩個), 不如改用 Menu, 因為裡面的選項似乎無限制, 每一個選項相當於一個按鈕. 此專案分享如下 :



四. 頁籤元件 (Tabs) : 

此元件是一個頁面排版容器, 只是用來擴增專案的版面空間而已, 亦即若整個專案用了很多元件, 一個螢幕版面可能擺不下, 必須上下滑動很麻煩, 可以依據功能或操作順序分類, 將這些元件分別擺放到不同的頁籤裡, 切換頁籤即可看到不同的元件, 只要每一個頁籤內的全部元件不會超過螢幕高度, 就不用上下滑動螢幕了.

新增一個 Tabs 元件如下圖, 預設已有兩個頁籤 :


點一下右邊兩個圈圈的鏍母按鈕進入設定畫面 :


可以修改預設的頁籤標題 Tab1, Tab2 為自訂標題, 按 +Add Tab 可以新增頁籤, 此元件最多可容納 4 個頁籤. 回到專案面板點選要加入元件的頁籤, 則所新增的元件就會放在該頁籤內 :


由於 Tabs 只是個容器而已, 所以耗用能量為 0 單位, 不用白不用 (有需要才用啦!).

其他類元件還有一個藍芽元件 (BLE), 目前還在 Beta 階段尚未完備, 官網教學文件也沒有資料, 等正式推出後再來補齊.


2016年8月20日 星期六

★ Blynk 的顯示元件 (Displays)

整理完 Blynk 控制器元件後, 繼續來看看顯示元件 (Displays). 顯示元件主要用來呈現從硬體設備端取得的資料並加以圖形化, 對 App 而言是從遠端設備讀取數據 (Read), 這可以用兩種方式達成, 一個方式是由 Blink App 的顯示元件主動向硬體設備發出週期性的要求 (Request); 另一個方式是由硬體設備向 Blink App 顯示元件定期推送資料 (Push).


目前有十種顯示元件如下 :


其中 Graph (曲線圖或長條圖) 與 Gauge (儀表) 我已經在上週利用 Blynk 顯示 Arduino 溫溼度與照度對 Blynk 初步測試時使用過了, 參考 :

# Blynk 應用 (二) : 在手機顯示即時溫溼度與光度

以下測試程式是在我自己用洞洞板焊接的 Arduino Nano+ESP8266 IOT 模組上進行實驗, 電路圖參考 :

# 製作 Arduino Nano + ESP8266 物聯網模組 (四) : 完結篇



若懶得焊接板子, 可以照上面電路圖自行用麵包板接線, 只不過 AMS1117 不是 DIP 封裝, 沒辦法插入麵包板, 比較麻煩而已.

一. 數值顯示器 (Value Display) :

此種顯示元件有四個, 可以綁定硬體設備的 Digital/Analog/Virtual 輸出腳, 主要差別是元件外觀的大小與是否具有將輸出資料格式化的功能 :
  1. Value Display S : 數值顯示器 (短)
  2. Value Display M : 數值顯示器 (中)
  3. Labeled Value M : 格式化的數值顯示器 (中)
  4. Labeled Value L : 格式化的數值顯示器 (長)
前兩種的設定畫面如下 :


"VALUE" 框填入此顯示器標題, 例如溫度, 溼度等. PIN 可以選 Digital/Analog/Virtual, 右邊是其數值上下限值. 而 Reading Frequncy 為讀取頻率, 預設 1 秒, 這表示 Blynk App 會週期性向硬體設備發出讀取要求, 如果此顯示器綁定的是實體腳位 (Digital/Analog), 則設備端不需要寫傳送資料的程式碼, Blynk App 會自動取得資料.

但是, 如果綁定虛擬腳位 (Virtual Pin), 則設備端韌體程式必須將資料寫入該虛擬腳位, App 才會收到所傳遞的數據. 以 Arduino 來說, 必須定義一個 BLYNK_READ(vPin) 函數, 然後呼叫 Blynk.virtualWrite() 將資料傳送給 App.


例如下面這個範例是顯示程式執行以來經過之秒數 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="e80fe6aae0d442518820f61xxxxxx8f5";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  }

void loop() {
  Blynk.run();
  }

BLYNK_READ(V0) {
  Serial.println(millis()/1000);
  Blynk.virtualWrite(V0, millis()/1000);
  }


使用虛擬腳位時, Reading Frequncy 也可以選擇 PUSH 模式, 從硬體設備推送資料到 Blynk App, 而非由 App 提出要求, 如下圖所示 :


這必須配合計時器 (Timer) 週期性地呼叫 Blynk.virtualWrite() 向 Blynk App 推送資料, 才能讓顯示器元件呈現即時數據, 例如 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <SimpleTimer.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
SimpleTimer timer; //create a Timer object

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="e80fe6aae0d442518820f61xxxxxx8f5";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  timer.setInterval(1000L, pushUptime); //Don't send more that 10 values per second
  }

void loop() {
  Blynk.run();
  timer.run();
  }

void pushUptime() { //push Arduino uptime to virtual pin V0
  Serial.println(millis()/1000);
  Blynk.virtualWrite(V0, millis()/1000);
  }

在上面的程式中, 為了使用計時器, 我們匯入了 SimpleTimer 函式庫並建立了 SimpleTimer 物件, 在 setup() 函式中呼叫計時器物件的 setInterval() 方法並指定每秒觸發 pushUptime() 函式一次, 呼叫 Blynk.virtualWrite() 將程式執行以來所經過的秒數寫入虛擬腳位 V0, 這樣 App 上就會即時顯示程式執行已過了幾秒了 :


注意, 千萬不可以把 Blynk.VirtualWrite() 放在 loop() 迴圈函數中, 這樣會傳送太頻繁的資料給 Blynk Cloud 伺服器, 造成所謂 "Flood Error", 參考 :

# http://docs.blynk.cc/#troubleshooting-flood-error

務必如上例所示使用 SimpleTimer 計時器函數來控制數據傳輸頻率. 當資料傳送頻率超過每秒 20 筆時, Blynk Cloud 會自切斷連線, 手機 Blynk App 會顯示 "Your hardware is offline".

接下來看看格式化的顯示器元件, 此元件提供四種格式化字串, 可設備端傳來的數據機以期望的格式顯示 :

 格式 作用 (原始數據例如 12.6789)
 /pin/ 不格式化, 顯示原始數據 12.6789
 /pin./ 顯示四捨五入後之整數部分 13
 /pin.#/ 顯示四捨五入後至小數後一位 12.7
 /pin.##/ 顯示四捨五入後至小數後兩位 12.68

其設定畫面與 Value Display 相同, 只是多了一個 LABEL 欄位而已, 可將上面的格式化字串插入任何字串, 例如 "溫度 /pin.##/ 度" 就會顯示至小數後兩位 :


下面範例是從 DHT11 取得溫濕度後, 利用 PUSH 方式推送至格式化數值顯示器, 但不格式化而是顯示原始數據, 溫度使用虛擬腳位 V0, 濕度使用虛擬腳位 V1, 手機 App 設定如下 :



設備端程式如下 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <SimpleTimer.h>
#include "DHT.h"
#define DHTPIN 6
#define DHTTYPE DHT11

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
SimpleTimer timer; //create a Timer object
DHT dht(DHTPIN, DHTTYPE); // Initialize DHT sensor

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="e80fe6aae0d442518820f61xxxxxx8f5";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  timer.setInterval(1000L, pushWeather); //Don't send more that 10 values per second
  }

void loop() {
  Blynk.run();
  timer.run();
  }

void pushWeather() { //push Arduino uptime to virtual pin V0
  float Temperature=dht.readTemperature(); //get temperature (C) from DHT11
  float Humidity=dht.readHumidity(); //get humidity from DHT11
  Serial.print("Temperature=" + (String)Temperature + " ");
  Serial.println("Humidity=" + (String)Humidity);
  Blynk.virtualWrite(V0, Temperature);
  Blynk.virtualWrite(V1, Humidity);
  }

結果如下 :


由於 DHT11 精確度只到整數, 因此推送給 App 的原始數據小數部分是 .000 的浮點數. 可見格式化顯示元件的好處是可以隨意控制想要呈現的輸出格式, 除了收到的原始數據可格式化外, 還能添加其他文字. 此專案分享如下 :



二. LED 顯示器 :

此元件用來在手機上以燈光明亮度顯示遠端數據, 其值介於 0~255 之間, 0 會讓 LED 燈滅, 而 255 讓 LED 最亮. LED 只能綁定到虛擬腳位, 不能綁定 Digital 或 Analog 腳. 其設定畫面如下 :


在設備端的韌體程式裡, 必須先建立對應此虛擬腳位的 WidgetLED 物件, 呼叫此物件的 setValue() 方法可以設定 LED 的值 (0~255), 呼叫 on() 可將 LED 設為最亮, 相當於呼叫 setValue(255); 而呼叫 off() 方法則熄滅 LED, 相當於呼叫 setValue(0). 呼叫 getValue() 則可以取得目前 LED 的亮度值 (0~255).

下面範例是由設備端透過虛擬腳位 V0 控制 Blynk App 的 LED 燈每秒閃爍 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <SimpleTimer.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
SimpleTimer timer; //create a Timer object
WidgetLED led0(V0);

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="e80fe6aae0d442518820f61xxxxxxx8f5";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  timer.setInterval(1000L, blinkLED); //Don't send more that 10 values per second
  }

void loop() {
  Blynk.run();
  timer.run();
  }

void blinkLED() { //push Arduino uptime to virtual pin V0
  if (led0.getValue()) { //LED not off
    led0.off();
    Serial.println("LED V0: off");
    }
  else { //LED off
    led0.on();
    Serial.println("LED V0: on");
    }
  }

執行結果 LED 燈確實會每秒閃爍一次 :




下面範例則是使用 setValue() 讓 LED 越來越亮, 到達 255 後又越來越暗, 如此周而復始 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <SimpleTimer.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
SimpleTimer timer; //create a Timer object
WidgetLED led0(V0);
int i;
boolean brighter=true;

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="e80fe6aae0d442518820f61xxxxxx8f5";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  timer.setInterval(1000L, brighter_darker_toggle); //Don't send more that 10 values per second
  }

void loop() {
  Blynk.run();
  timer.run();
  }

void brighter_darker_toggle() { //push Arduino uptime to virtual pin V0
  if (brighter) {
    if (i < 255) {i += 5;}  //brighter
    else {brighter=false;}  //darker
    }
  else { //darker
    if (i > 0) {i -= 5;}  //darker
    else {brighter=true;}  //brighter
    }
  Serial.println(i);
  led0.setValue(i);
  Blynk.virtualWrite(V1, i);  
  }

在此範例中, 我另外添加了一個 Value Display 來顯示亮度值, 並將其綁定到虛擬腳位 V1 :



三. LCD 液晶顯示器 :

此元件模擬 1602 液晶顯示器, 其設定畫面如下 :


它有 Simple 與 Advanced 兩種模式可選, 所謂的 Advanced 模式是把 LCD 顯示元件當成真正的 1602 一樣, 必須先建立一個 WidgetLCD 物件, 然後呼叫 print() 函數輸出到手機 App 上指定的虛擬腳位 (Advanced 模式只能使用 Virtual Pin), 下列範例是在 LCD 上顯示 "Hello World", 綁定到 App 的虛擬腳位 V0, 設定如下 :


Colors 欄位可設定 LCD 背景與文字顏色.

程式如下 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
WidgetLCD lcd(V0);

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="e80fe6aae0d442518820f61xxxxxx8f5";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  while (Blynk.connect() == false) {} //wait until connected
  lcd.clear(); //clear LCD screen
  lcd.print(4, 0, "Hello"); //(X: 0-15, Y: 0-1, "message")
  lcd.print(4, 1, "World"); //(X: 0-15, Y: 0-1, "message")
  }

void loop() {
  Blynk.run();
  }


上面程式中, 輸出到 LCD 的 print() 是放在 setup() 裡, 這樣設備開機後 LCD 螢幕顯示完 Hello World 便不動了. 如果要顯示變動資料, 同樣地, 為了避免要求太頻繁造成 Flood Error, 不可以把 print() 指令直接放在 loop 裡, 而是要將其放在一個函數中, 利用 SimpleTimer 物件的 setInterval() 去設定週期性觸發此函數.

例如下面的範例是將上面的程式已執行時間改在 LCD 元件顯示 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <SimpleTimer.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
SimpleTimer timer; //create a Timer object
WidgetLCD lcd(V0);

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="e80fe6aae0d442518820f61xxxxxx8f5";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  lcd.clear(); //clear LCD screen
  timer.setInterval(1000L, pushUptime); //Don't send more that 10 values per second
  }

void loop() {
  Blynk.run();
  timer.run();
  }

void pushUptime() { //push Arduino uptime to virtual pin V0
  lcd.print(0, 0, "Uptime"); //(X: 0-15, Y: 0-1, "message")    
  lcd.print(0, 1, millis()/1000); //(X: 0-15, Y: 0-1, "message")  
  }


回到 Simple mode, 此模式將 1602 的兩排文字當成是腳位, 可以綁定到 Analog 與 Virtual 兩種腳位, 這樣就與上面的 Value Display 類似, 可以選擇 Reading 或 Push 模式來取得設備端的資料, Push 模式是在設備端韌體程式中, 使用計時器自行將資料推送到 App 中, 必須用 Blynk.VirtualWrite() 將資料輸出到虛擬腳位 (因此只能綁定虛擬腳位); 而 Reading 模式則是在 Blynk App 中的 Reading Frequency 欄位中指定固定周期向設備端要求資料, 可以綁定 Analog 或 Virtual 腳位, 若綁定虛擬腳位, 則設備端韌體程式必須準備 BLYNK_READ(vPin) 函式才行, 若綁定 Analog 腳位就不用.

下面範例程式是使用 Push 模式向 App 的 V0 推送程式執行以來的秒數, 向 V1 推送毫秒數 :


程式如下 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <SimpleTimer.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
SimpleTimer timer; //create a Timer object

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="e80fe6aae0d442518820f61xxxxxx8f5";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  timer.setInterval(1000L, sendSeconds); //Don't send more that 10 values per second
  timer.setInterval(1000L, sendMillis); //Don't send more that 10 values per second
  }

void loop() {
  Blynk.run();
  timer.run();
  }

void sendSeconds() {
  Blynk.virtualWrite(V0, millis() / 1000);
  }

void sendMillis() {
  Blynk.virtualWrite(V1, millis());
  }


如果採用 Reading 模式的話, 設備端韌體程式就不需要費心使用計時器推送了 (不需要匯入 SimpleTimer 函式庫), 但若綁定虛擬腳位, 必須準備 BLYNK_READ() 函數對應, 設定如下 :


程式如下 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <BlynkSimpleShieldEsp8266.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="e80fe6aae0d442518820f61xxxxxx8f5";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  }

void loop() {
  Blynk.run();
  }

BLYNK_READ(V0) {
  Blynk.virtualWrite(V0, millis() / 1000);
  }

BLYNK_READ(V1) {
  Blynk.virtualWrite(V1, millis());
  }

所以 Reading 模式在設備端韌體部分比較簡單, 但結果是一樣的. 注意, LCD 在 Simple mode 下兩列 message 都是可以格式化的, 用法跟上面 Formated Value Display 元件完全一樣.

四. 終端機元件 (Terminal) :

此元件除了用來顯示設備端訊息外, 也可以從 App 對設備下達指令, 模擬一般 Command Line 通訊軟體如 Putty 的基本雙向通訊功能. 此元件只能綁定虛擬腳位, 作為設備與 App 間的溝通介面, 其設定畫面如下 :


其中 INPUT LINE 欄位設定是否要開啟輸入框, 若設為 ON, 則終端機視窗底下會有一個輸入框, 使用者若輸入文字再按 Enter, 就會透過所綁定的虛擬腳位 (INPUT 選項) 傳送給設備, 這樣 BLYNK_WRITE(vPin) 函數就會被呼叫, 我們可在此函數中處理回應, 利用呼叫終端機物件的 println() 函數來將回應訊息傳給 App (當然是透過所綁定之虛擬腳位). 如果只是要像 LCD 那樣顯示設備端資訊, 不需要向設備下指令, 就可以設為 OFF. AUTOSCROLL 欄位設定輸出訊息是否要自動向下捲動.

設備端韌體程式必須建立一個 WidgetTermal 物件, 它類似序列埠物件, 透過呼叫 print(), println(), write() 以及 flush() 等函數向虛擬腳位輸出訊息, 傳送到 App 的 Terminal 元件上. 終端機回應處理部分使用的是 Reading (Request) 模式, 亦即必須為虛擬腳位準備 BLYNK_WRITE() 函數來處理回應訊息.

下面範例改寫自官網說明文件 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
WidgetTerminal terminal(V0);

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="e80fe6aae0d442518820f61xxxxxx8f5";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  while (Blynk.connect() == false) {} //wait until connected
  terminal.println(F("Blynk v" BLYNK_VERSION ": Device started"));
  terminal.println(F("-------------"));
  terminal.println(F("Type 'Hello' and get a reply, or type"));
  terminal.println(F("anything else and get it printed back."));
  terminal.flush();  //Ensure everything is sent
  }

void loop() {
  Blynk.run();
  }

BLYNK_WRITE(V0) { //called when V0 updated (user send data to V0)
  if (String("Hello") == param.asStr()) { //if receives "Hello"
    terminal.println(F("You said: 'Hello'")); //show user command "Hello" in Widget
    terminal.println(F("I said: 'World'")); //show hardware response "World"
    }
  else { //otherwise send it back
    terminal.print(F("You said:"));
    terminal.write(param.getBuffer(), param.getLength());  //echo
    terminal.println();  //new line
    }
  terminal.flush(); //Ensure everything is sent
  }

此程式的 BLYNK_WRITE() 函數就是處理 App 端虛擬腳位 V0 傳來的 Reading Request, 其要求參數 param 攜帶了使用者在 Terminal 元件所下指令, 經字串比對若指令為 "Hello", 就回應 "World", 否則就將指令 echo 回去, 這時會呼叫 getBuffer() 函數取得緩衝器內的 raw data, 再用 write() 函數寫回去. 結果如下 :


這很像 Linux 的 shell 對吧! 注意, 上面的程式中使用了 Blynk 函式庫定義的版本常數 BLYNK_VERSION, 放在 F() 函數內直接與常數字串相接即可, 不可用 "+" 串接.

五. 歷史圖形元件 (History Graph) :

之前曾經使用 Graph 元件來顯示氣候資料, 可以顯示量測值隨時間變化的圖形 (折線圖或長條圖), 參考 :

# Blynk 應用 (二) : 在手機顯示即時溫溼度與光度

然而 Graph 元件的時間尺度是固定無法調整的, 沒辦法看到之前的數據變化情形. 而此 History Graph 元件提供三種格局的尺度, 可以讓我們放大縮小時間軸, 以便觀察之前傳送到 Blynk 雲端伺服器的資料. 此元件可以綁定 Digital/Analog/Virtual 三種腳位, 但一個元件只能同時觀察四個量測變數, 設定畫面如下 :


History Graph 是顯示過去曾傳送到伺服器的所有資料, 因為我之前使用 V0, V1, V2 做過別的實驗, 這裡為了演示, 避免之前的數據讓圖形很奇怪 (例如 V0 之前用來傳送程式執行秒數, 其數值高達數萬, 現在拿來顯示溫度又變成 20~30, 其歷史圖形就很突兀), 所以我改用 V4~V7 當參數. 雖然目前我手邊沒有氣壓計模組, 我還是補上去, 免得四個通道有一個是空的. 底下 "Show Legend" 欄位預設 OFF, 設為 ON 會在上方顯示四個參數的圖例 (曲線顏色說明).

注意, 上面設定項目裡面沒有 Reading Frequency 可選, 這是因為此元件是用來從雲端資料庫中取出所指定之腳位了歷史資料來顯示, 所以並不需要為此元件特別定義資料驅動方式, 這些腳位由其他程式碼驅動, History Graph 只是從資料庫取出歷史資料來顯示而已. 在下面程式碼中為了演示之故, History Graph 元件特別使用 Push 方式以計時器推送資料給 V4~V7, 正常應用情況下可能是用其他顯示元件如 Gauge 以 Reading 模式取得資料. 程式如下 :

#define BLYNK_PRINT Serial  //Comment this out to disable prints and save space
#include <SoftwareSerial.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <SimpleTimer.h>
#include "DHT.h"
#define DHTPIN 6
#define DHTTYPE DHT11

SoftwareSerial esp8266(7, 8); //(RX, TX)
ESP8266 wifi(&esp8266); //create wifi object
SimpleTimer timer; //create a Timer object
DHT dht(DHTPIN, DHTTYPE); //Initialize DHT sensor

char ssid[]="EDIMAX-tony";
char pass[]="1234567890";
char auth[]="e80fe6aae0d442518820f61xxxxxx8f5";

void setup() {
  Serial.begin(9600); //Set console baud rate
  esp8266.begin(9600); //Set ESP8266 baud rate
  Blynk.begin(auth, wifi, ssid, pass);
  while (Blynk.connect() == false) {} //wait until connected
  timer.setInterval(1000L, pushWeather); //Don't send more that 10 values per second
  }

void loop() {
  Blynk.run();
  timer.run();
  }

void pushWeather() {
  float Temperature=dht.readTemperature(); //get temperature (C) from DHT11
  float Humidity=dht.readHumidity(); //get humidity from DHT11
  int cds=analogRead(A0); //get cds voltage (0~5V to 0~1023 dark)
  int Luminance=100-map(cds,0,1023,0,100); //map 0~1023(dark) to 0(dark)~100%
  Blynk.virtualWrite(V4, Temperature);
  Blynk.virtualWrite(V5, Humidity);
  Blynk.virtualWrite(V6, Luminance);
  }

這裡利用計時器每秒將氣候資料透過虛擬腳位推送到雲端, 然後顯示在手機 App 上. 下面是一小時尺規內 (1h) 的歷史圖形 :


可見此元件提供六種時間尺規來顯示圖形 :

  1. 1h : 一小時
  2. 6h : 六小時
  3. 1d : 一天
  4. 1w : 一周
  5. 1m : 一個月
  6. 3m : 三個月

亦即, History Graph 最長也只能顯示過去 3 個月的歷史圖形. 點 6h 即切換到六小時尺規內的歷史圖形如下 :


在 Play 狀態下, 於歷史圖形上向左邊滑動, 會出現如下畫面 :


按 "Export to CSV" 會將此元件所綁定腳位的歷史資料以 CSV 壓縮檔寄送到郵件信箱 :


因上面程式實際上只有 V4~V6 三支虛擬腳位有資料, 所以只有三個壓縮檔 :


解壓縮後的 CSV 檔內容如下 (此為溫度 V4) :


按下面的 "Erase Data" 則會刪除此元件所綁定的所有腳位之歷史資料.


刪除後資料庫便無資料, 顯示 "No data" :


OK, 以上便是顯示元件的用法測試.

參考 :

# Blynk HTTP RESTful API