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


6 則留言:

  1. 請問 , 在文中有提到 Blynk在最初試用時,會給2000個試用金幣,然而為何我只有1000個金幣,是版本不同嗎 ? , 還是那裏要從新設定,1000個金幣確實是不夠用,謝謝......

    回覆刪除
  2. 一個帳號有 2000 單位金幣, 可以建立多個專案, 但這些專案會共享這 2000 的金幣, 把所有專案刪除就會回復 2000 單位了.

    回覆刪除
  3. 會不會是因為,我用兩台手機,但都用同一個帳號登入Blynk,所以才會只剩下每台手機各分到1000個金幣 ?

    回覆刪除
  4. 還有另一個問題請教,就是如果我用同一個Blynk帳號登入,建立A專案,得到一個Blynk Auth Code(權限碼),然後將程示給 A user使用(A User使用自己的SSIS及PWD),又將A專案的程示碼給B user使用(所以是相同的 Blynk Auth Code),但B user 也是使用自己的SSIS及PWD, 這樣兩個使用者的 Blynk Auth Code是相同的,程式碼也一樣,唯讀不同的是各別用自己網域的SSIS及PWD,請問這樣程式會打架嗎?,會不會相衝突.

    謝謝指導 .......

    回覆刪除
  5. SSID+PWD 是設備端韌體連上基地台認證用的, 而 AUTH 是連線 BLYNK 伺服器用的, 伺服器只認 AUTH, 管不到本地的基地台. 如果有兩個設備都燒錄了相同韌體, 只差在 SSID+PWD, 他們都會與伺服器建立 TCP 連線, 伺服器本來就可以建立多重連線, 但伺服器會不會限制同時間只能一個 AUTH 連線還要進一步測試才知道. 總之伺服器只看 AUTH, 不會管 SSID+PWD 的.

    回覆刪除
  6. 我確定不相關 我好幾個proj都是異地共用auth 同步控制的~

    回覆刪除