網頁最後修改時間:2021/09/22
經過前面幾篇網頁的介紹,ESP32 CAMRA 二維碼(QR Code)辨識之門鎖控制(下面簡稱 ESP32QRDoorLock)進入到最後的一篇。本篇將整合所有的部分,利用狀態轉移的概念方式,管理整個門鎖動作的工作流程。
內容有:
【(00)簡介】
本篇會建立一個負責管理門鎖狀態的工作任務,來管理狀態之間的轉換工作。
工作任務裡各狀態之間的轉換,取決於無線網路連線情形、二維碼辨識結果和門鎖開關位置,經由這些狀態決定 LED、電磁鎖的動作。
以 x04_ESP32CamQRCodeReader.ino 為基礎,首先在 lsInterrupt 區域的上方插入狀態控制工作任務的控制代碼(handle)的全局變數。
94 95 |
/*================== TASK: DEVICESTATECONTROL =================*/
TaskHandle_t xDeviceStateControlTaskHandle;
|
FASTLED 區域的下方,宣告和定義一個列舉型別的全局變數 xDeviceState,裡面有八個預先定義的裝置狀態,初始值設定為 STARTUP。
126 127 128 129 130 131 132 133 134 135 136 137 138 |
/*================== STATE =================*/ enum device_state_t{ STARTUP = 0, // 開機模式或等同於 OFF STANDBY_WO_WIFI, // 與待機模式同,但 WIFI 沒有連線到無線分享器 // ,也就是只啟用 QRCode 功能 STANDBY, // 待機模式: 門鎖住,等待辨識二維碼 // ,也就是啟用 ORCode 和 WiFi 功能 QRCODEMATCH, // 二維碼辨識比對匹配 QRCODEUNMATCH, // 二維碼辨識比對不符 QRCODEINVALID, // 無效二維碼 UNLOCK, // 門已開啟,等待關門且取修二維碼辨識的動作 LOCK, // 門已關閉,開啟二維碼辨識功能 } xDeviceState = STARTUP; |
狀態轉換需要的全局變數宣告和定義好了之後,下面就可以開始撰寫這部分的的程式。
【(01)LED blink () 程式碼變更】
此系列前面幾篇所使用的 blink () 功能簡單,能設定閃爍次數和顏色已很足夠。但對於 ESP32QRDoorLock 來說,blink () 不只需要可設定閃爍次數和顏色的功能,還要可設定延遲的時間。如此,LED 的操作才會簡單、變化才會多樣。
修改之後的 blink () 函式,除了支援上面的所說的功能之外,還提供三種不同的閃爍方式:
- blink_count < 0:不管延遲時間,顯示指定的顏色直到下一個 blink () 被執行,通常用來關閉 LED;
- blink_count = 0:保持恆亮,直到延遲時間逾時再關閉;
- blink_count > 0:閃爍指定的次數,亮滅間隔一個延遲時間;
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
/**======================================================================== ** blink() *? 系統運行狀態的指示 *@param blink_count const int8_t *@param delay const TickType_t *@param color CRGB *========================================================================**/ void blink (const int8_t blink_count, // 閃爍次數 const TickType_t delay, // 延遲時間 (ms) const CRGB color) // WS2812 顏色 { uint8_t state = LOW; if (blink_count < 0) { // -1, OFF ws2812[0] = color; FastLED.show(); } else if (blink_count == 0 ) { // 0, 保持恆亮 delay (ms) ws2812[0] = color; FastLED.show(); vTaskDelay (pdMS_TO_TICKS(delay)); ws2812[0] = BLACK; FastLED.show(); } else { // >= 1 for (int x = 0; x < (blink_count << 1); ++x) { if (state ^= HIGH) { ws2812[0] = color; } else { ws2812[0] = BLACK; } FastLED.show(); vTaskDelay (pdMS_TO_TICKS(delay)); } } } |
在這裡修改的 blink (),主要用於門鎖狀態控制的工作任務中。
【(02)門鎖狀態控制】
下面是 ESP32QRDoorLock 狀態轉移示意圖。依列舉型別所定義的,有九個狀態。工作流程由無線網路狀態(xWifiReconnectState)、二維碼辨識結果(xOnQrCodeState)、門鎖開關狀態和中斷旗標(bIntTrigger)作為轉換的判斷依據。
下面說說這整個狀態轉移工作流程,同時這也是 deviceStateControlTask 撰寫的依據。
開機啟動後的 xOnQrCodeState 預設值是 STARTUP,以 xWifiReconnectState 為主要判斷依據,再根據門鎖開關狀態,決定下一個轉換的狀態是 STANDBY_NO_WIFI、UNLOCK 或 STANDBY 的哪一個?或還是留在當前的狀態中。
若由 STARTUP 進入到 STANDBY_NO_WIFI 狀態,表示無線網路超過 15 秒還未能正常連線。進入到此狀態後(LED 每秒閃爍一次黃色),每隔 10 秒還會再做一次連線狀態檢查。若在 Watchdog 計時器逾時重置之前連線,則會跳回到 STARTUP,否則就只能等待被重置。
若由 STARTUP 進入到 UNLOCK 狀態,表示在前一個狀態做檢查的時候,門是開啟的。這時會開啟中斷服務函式,然後一直等待,直到門被關閉(門鎖開關檢測到關閉;LED 每 800ms 閃爍一次紅色,共三次),才會轉換到下一個 LOCK 的狀態。LOCK 狀態的動作很簡單,單純切換回 STARTUP 狀態,並輸出門關閉的訊息。
如果無線網路連線以及門鎖開關狀態都正常,那麼就會從 STARTUP 直接切換到 STANDBY 的狀態(LED 每秒亮滅一次藍色)。在這個狀態中,主要會檢查兩個切換到下一個狀態的條件:門鎖開關的位置和二維碼辨識的結果。
在 STANDBY 的狀態中,首先檢查的是門鎖開關是否在關閉的位置。如果不是,則切換到 UNLOCK 狀態(LED 每 200ms 閃爍一次紅色,共兩次)等待門被關閉;若門已關閉,則根據二維碼辨識 xOnQrCodeState 的三種結果(MATCH、UNMATCH 和 INVALID),切換到相對應的狀態。
在 STANDBY 的狀態中,若二維碼辨識結果為 MATCH,則切換狀態至 QRCODEMATCH。首先執行開鎖動作,接著 LED 綠色亮兩秒關閉,最後檢查門鎖狀態。若門判斷已開啟,則切換至 UNLOCK 狀態;若門判斷未開,則表示開鎖的動作未完成或是出現錯誤,LED 每 400ms 閃爍一次紅色(共三次),再切換狀態至 STARTUP。
在 STANDBY 的狀態中,若二維碼辨識結果為 UNMATCH,則切換狀態至 QRCODEUNMATCH。LED 常亮紅色 1500ms,再切換狀態至 STARTUP。
在 STANDBY 的狀態中,若二維碼辨識結果為 INVALID,則切換狀態至 QRCODEINVALID。LED 每 500ms 閃爍一次橙色(共二次),再切換狀態至 STARTUP。
'將上面所說的狀態轉換的概念,轉換為如下的程式碼。
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 |
/**======================================================================== ** systemStateControlTask() *? 系統狀態控制的任務 *@param pvParameters void* *========================================================================**/ void deviceStateControlTask (void *pvParameters) { while (true) { switch (xDeviceState) { case STARTUP: { //* 這裡分別使用 xOnQrCodeState, xWifiReconnectState 和 // 無線網路連線狀態做判斷,最後得出 xDeviceState 的結果。 static int t = 0; if (xWifiReconnectState == CONNECTING) { vTaskDelay (pdMS_TO_TICKS(200)); if (t++ >= 75) { t = 0; //* 每 200ms 亮滅一次,連續三次,橙色 blink (3, 100, ORANGE); if (dDoorState == CLOSE) { xDeviceState = STANDBY_WO_WIFI; Serial.println (F("device state change to STANDBY_WO_WIFI")); } else { xDeviceState = UNLOCK; Serial.println (F("\nDOOR OPENED")); Serial.println (F("device state change to UNLOCK")); } } } else if (xWifiReconnectState == CONNECTED || xWifiReconnectState == RUNNING) { t = 0; //* 連線成功,每 200ms 亮滅一次,連續五次,紫色 blink (5, 100, VIOLET); if (dDoorState == CLOSE) { xDeviceState = STANDBY; Serial.println (F("device state change to STANDBY")); } else { xDeviceState = UNLOCK; Serial.println (F("\nDOOR OPENED")); Serial.println (F("device state change to UNLOCK")); } } else if (xWifiReconnectState == DISCONNECTED) { blink (-1, 0, BLACK); Serial.println (F("detect wifi disconnected")); } else {} } break; case STANDBY_WO_WIFI: { static int t = 0; //* 每秒亮滅一次, 黃色 blink (1, 500, YELLOW); //* 如果跳到這個狀態的同時無線網路連線上了,就要跳回起頭重新檢查一下無線網路狀態 if (t > 9 && (xWifiReconnectState != CONNECTED || xWifiReconnectState != RUNNING) ) { t = 0; xDeviceState = STARTUP; Serial.println (F("device state change to STARTUP")); } else { t++; } } break; case STANDBY: //* 每秒亮滅一次, 藍色 blink (1, 500, BLUE); //* 檢測門鎖狀態 if (dDoorState == OPEN) { xDeviceState = UNLOCK; Serial.println (F("Error detected: door open in STANBY")); blink (2, 200, RED); Serial.println (F("device state change to UNLOCK")); break; } //* 檢測是否符合狀態轉移的條件 if (xOnQrCodeState == MATCH) { xDeviceState = QRCODEMATCH; Serial.println (F("device state change to QRCODEMATCH")); xOnQrCodeState = WORKING; } else if (xOnQrCodeState == UNMATCH) { xDeviceState = QRCODEUNMATCH; Serial.println (F("device state change to QRCODEUNMATCH")); xOnQrCodeState = WORKING; } else if (xOnQrCodeState == INVALID) { xDeviceState = QRCODEINVALID; Serial.println (F("device state change to QRCODEINVALID")); xOnQrCodeState = WORKING; } else {} break; case QRCODEMATCH: doorControl (false); blink (0, 2000, GREEN); if (dDoorState == OPEN) { xDeviceState = UNLOCK; Serial.println (F("device state change to UNLOCK")); } else { //* 每 400ms 閃爍一次,共三次,紅色。 blink (3, 200, RED); Serial.println(F("ERROR: The door can not be opened!")); xDeviceState = STARTUP; Serial.println (F("device state change to STARTUP")); } break; case QRCODEUNMATCH: { //* 常亮兩秒, 紅色 blink (0, 1500, RED); xDeviceState = STARTUP; Serial.println (F("device state change to STARTUP")); } break; case QRCODEINVALID: { //* 每 500ms 閃爍一次,共兩次,橙色。 blink (2, 250, ORANGE); xDeviceState = STARTUP; Serial.println (F("device state change to STARTUP")); } break; case UNLOCK: // 偵測關門的中斷 attachInterrupt (digitalPinToInterrupt(LIMITSWITCH), lsInterrupt, FALLING); Serial.println (F("waiting for door close...")); while (dDoorState == OPEN || bIntTrigger == false); //* 每 800ms 閃爍一次,共三次,紅色。 blink (3, 400, RED); bIntTrigger = false; xDeviceState = LOCK; Serial.println (F("done!\ndevice state change to LOCK")); break; case LOCK: { xDeviceState = STARTUP; Serial.println (F("done!\ndevice state change to STARTUP")); } break; default: Serial.println (F("deviceStateControlTask: wrong case")); break; } vTaskDelay (pdMS_TO_TICKS(100)); } } |
最後一個動作。將上面的 deviceStateControlTask,加入到 setup () 中做為工作任務。
558 559 560 561 562 563 564 565 566 |
/*======= SYSTEMSTATECONTROLTASK =======*/ //* Core 0, Priority 1 xTaskCreatePinnedToCore ( deviceStateControlTask, "deviceStateControlTask", usStackDepth, NULL, // usStackDepth 請自行測試該值大小 1, // Priotity 1 &xDeviceStateControlTaskHandle, 0 // Core 0 ); delay (250); |
【(03)參考程式碼】
把上面的程式碼插入到 x04_ESP32CamQRCodeReader.ino 中,就完成整個程式的撰寫。
/**------------------------------------------------------------------------ | |
* x05_ESP32CamDeviceStateControl.ino | |
* ESP32-CAM 二維碼辨識之門鎖狀態轉換控制。 | |
* | |
* Adapted by Ruten.Proteus | |
* Test Date: 2021/09/20 | |
* | |
* {功能說明} | |
* + [o] 辨識二維條碼裡的資料,並根據資料作開/關門的動作; | |
* + [o] 不同的動作以 WS2812 RGB LED 做顯示; | |
* + [o] 支援 Watchdog auto-reset 功能; | |
* + [?] 韌體支援 OTA 上傳; | |
* | |
* {Result} Work !!! | |
* | |
*------------------------------------------------------------------------**/ | |
/**======================================================================== | |
* HEADER FILES | |
*========================================================================**/ | |
#include <Arduino.h> | |
#include <WiFi.h> | |
#include <esp_task_wdt.h> | |
#include <FastLED.h> | |
#include <ESP32QRCodeReader.h> | |
/*================== Arduino OTA =================*/ | |
/** 是否使用 OTA 功能? 不使用就註解下一行 */ | |
#define ENABLE_OTA | |
#ifdef ENABLE_OTA | |
#include <ArduinoOTA.h> | |
#endif | |
/**------------------------------------------------------------------------ | |
* GLOBAL VARIABLES | |
*------------------------------------------------------------------------**/ | |
/*================== WIFI =================*/ | |
//** 無線分享器名稱與密碼 */ | |
const char* SSID = "填入無線分享器名稱"; | |
const char* PASSWORD = "填入無線分享器連線密碼"; | |
// 裝置名稱 | |
const char* DEVICENAME = "ESP32QRDoorLock"; | |
/*================== WATCHDOG =================*/ | |
#define WDT_TIMEOUT 30 // 設定 Watchdog 計時器的時間 | |
/*================== QR CODE READER =================*/ | |
ESP32QRCodeReader qrcodereader (CAMERA_MODEL_AI_THINKER); | |
/*================== FASTLED =================*/ | |
const int NUM_LEDS = #1; // 填入 LED 的數量取代 #1 | |
const uint8_t DATA_PIN = #2; // 填入 WS2812 通訊接腳號碼取代 #2 | |
// Define the array of leds | |
CRGB ws2812[NUM_LEDS]; | |
/*================== GPIO =================*/ | |
/** 電磁鎖 */ | |
const uint8_t DOORLOCK = #3; // 填入電磁鎖驅動模組控制接腳號碼取代 #3 | |
/** 門磁(磁簧)開關 */ | |
const uint8_t LIMITSWITCH = #4; // 填入門磁開關訊號接接腳號碼取代 #4 | |
// 門的狀態 | |
#define OPEN HIGH | |
#define CLOSE LOW | |
#define dDoorState digitalRead(LIMITSWITCH) | |
/*================== TASK: WIFIRECONNECT =================*/ | |
enum wifi_reconnect_state { | |
UNKNOWN, | |
RUNNING, | |
CONNECTING, | |
CONNECTED, | |
DISCONNECTED, | |
} xWifiReconnectState = UNKNOWN; | |
bool OTA_status = false; | |
TaskHandle_t xWiFiReConnectTaskHandle; | |
/*================== TASK: ONQRCODE =================*/ | |
enum onqrcode_state_t { | |
OFF, | |
WORKING, | |
MATCH, | |
UNMATCH, | |
INVALID | |
} xOnQrCodeState = OFF; | |
TaskHandle_t xOnQrCodeTaskHandle; | |
/*================== TASK: DEVICESTATECONTROL =================*/ | |
TaskHandle_t xDeviceStateControlTaskHandle; | |
/*================== lsInterrupt =================*/ | |
volatile bool bIntTrigger = false; | |
unsigned long ulLastMicros; | |
// 定義特定任務的 QR Code | |
// https://qr.ioi.tw/zh/ | |
const char *pcQrCode[] { | |
"Qh@4#I_6+", // unlock | |
"PfgW~!$&U" // do nothing | |
}; | |
/*======================== END OF GLOBAL VARIABLES ========================*/ | |
/**------------------------------------------------------------------------ | |
* ENUM AND STRUCT | |
*------------------------------------------------------------------------**/ | |
/*================== FASTLED =================*/ | |
// Colors definition for WS2812 | |
#define BLACK CRGB ( 0, 0 , 0) // Black | |
#define WHITE CRGB (255, 255, 255) // White | |
#define RED CRGB (255, 0, 0) // Red | |
#define ORANGE CRGB (255, 127, 0) // Orange | |
#define YELLOW CRGB (255, 255, 0) // Yellow | |
#define GREEN CRGB ( 0, 255, 0) // Green | |
#define BLUE CRGB ( 0, 0, 255) // Blue | |
#define INDIGO CRGB ( 0, 255, 255) | |
#define VIOLET CRGB (127, 0, 255) // Violet | |
/*================== STATE =================*/ | |
enum device_state_t{ | |
STARTUP = 0, // 開機模式或等同於 OFF | |
STANDBY_WO_WIFI, // 與待機模式同,但 WIFI 沒有連線到無線分享器 | |
// ,也就是只啟用 QRCode 功能 | |
STANDBY, // 待機模式: 門鎖住,等待辨識二維碼 | |
// ,也就是啟用 ORCode 和 WiFi 功能 | |
QRCODEMATCH, // 二維碼辨識比對匹配 | |
QRCODEUNMATCH, // 二維碼辨識比對不符 | |
QRCODEINVALID, // 無效二維碼 | |
UNLOCK, // 門已開啟,等待關門且取修二維碼辨識的動作 | |
LOCK, // 門已關閉,開啟二維碼辨識功能 | |
} xDeviceState = STARTUP; | |
/*========================= END OF ENUM AND STRUCT ========================*/ | |
void blink (const int8_t blink_count, const TickType_t delay, const CRGB color); | |
/**======================================================================== | |
** ioInit() | |
*? ESP32 接腳和 FastLED 初始化 | |
*========================================================================**/ | |
void ioInit () { | |
pinMode (DOORLOCK, OUTPUT); | |
digitalWrite (DOORLOCK, LOW); | |
pinMode (LIMITSWITCH, INPUT_PULLUP); | |
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(ws2812, NUM_LEDS); | |
blink (-1, 0, BLACK); | |
} | |
/**======================================================================== | |
** lsInterrupt() | |
*? 中斷處理函式 | |
*========================================================================**/ | |
void IRAM_ATTR lsInterrupt () { | |
if((long)(micros() - ulLastMicros) >= 150000L) { | |
bIntTrigger = true; | |
detachInterrupt (digitalPinToInterrupt(LIMITSWITCH)); | |
} | |
ulLastMicros = micros(); | |
} | |
/**======================================================================== | |
** blink() | |
*? 系統運行狀態的指示 | |
*@param blink_count const int8_t | |
*@param delay const TickType_t | |
*@param color CRGB | |
*========================================================================**/ | |
void blink (const int8_t blink_count, // 閃爍次數 | |
const TickType_t delay, // 延遲時間 (ms) | |
const CRGB color) // WS2812 顏色 | |
{ | |
uint8_t state = LOW; | |
if (blink_count < 0) { // -1, OFF | |
ws2812[0] = color; | |
FastLED.show(); | |
} else if (blink_count == 0 ) { // 0, 保持恆亮 delay (ms) | |
ws2812[0] = color; | |
FastLED.show(); | |
vTaskDelay (pdMS_TO_TICKS(delay)); | |
ws2812[0] = BLACK; | |
FastLED.show(); | |
} else { // >= 1 | |
for (int x = 0; x < (blink_count << 1); ++x) { | |
if (state ^= HIGH) { | |
ws2812[0] = color; | |
} else { | |
ws2812[0] = BLACK; | |
} | |
FastLED.show(); | |
vTaskDelay (pdMS_TO_TICKS(delay)); | |
} | |
} | |
} | |
/**======================================================================== | |
** doorControl() | |
*? 門鎖開關控制 | |
*@param isMotor const bool | |
* 門鎖的形式。true: DC Motor; false: 電磁開關 | |
*========================================================================**/ | |
void doorControl (const bool isMotor) { | |
if (isMotor) { | |
// 如果使用電動鎖頭,寫程式在這裡 | |
// ... | |
} else { | |
digitalWrite (DOORLOCK, HIGH); | |
vTaskDelay (pdMS_TO_TICKS(1000)); | |
digitalWrite (DOORLOCK, LOW); | |
} | |
} | |
/**======================================================================== | |
** wifiReconnectTask() | |
*? 監測無線連線狀態的任務。若出現問題會造成 WatchDog 重置系統重新連線。 | |
*@param pvParameter void* | |
*========================================================================**/ | |
void wifiReconnectTask (void *pvParameters) { | |
int wifiStatus = 0; | |
int wifiPrev = WL_CONNECTED; | |
int n = 0; | |
unsigned long previousMillis = 0; | |
const long interval = 500; | |
esp_task_wdt_add(NULL); // 添加當前任務到看門狗計時器 | |
for(;;) { | |
wifiStatus = WiFi.status(); | |
#ifdef ENABLE_OTA | |
ArduinoOTA.handle(); | |
#endif | |
if (OTA_status) // 如果有 OTA,重置 Watchdog 計時器 | |
esp_task_wdt_reset(); | |
/** 確認 WiFi 狀態是否已經改變? */ | |
if ((wifiStatus == WL_CONNECTED)&&(wifiPrev != WL_CONNECTED)) { | |
xWifiReconnectState = CONNECTED; | |
esp_task_wdt_reset(); // 重置 Watchdog 計時器 | |
Serial.printf ("\nConnected to WiFi!\nLocal IP address: %s\n\n", | |
(WiFi.localIP ().toString ()).c_str ()); | |
} | |
else if ((wifiStatus != WL_CONNECTED)&&(wifiPrev == WL_CONNECTED)) { | |
xWifiReconnectState = DISCONNECTED; | |
esp_task_wdt_reset(); // 重置 Watchdog 計時器 | |
Serial.println(F("\nWiFi disconnected!")); | |
WiFi.disconnect(); | |
Serial.println(F("Scanning for WiFi networks")); | |
n = WiFi.scanNetworks(false, true); | |
Serial.println(F("Scan done")); | |
if (n == 0) { | |
Serial.println(F("No networks found. Resetting ESP32.")); | |
esp_restart(); | |
} else { | |
Serial.print(n); | |
Serial.println(F(" networks found")); | |
for (int i = 0; i < n; ++i) { | |
/** 列出每一個找到的網路的 SSID 和 RSSI */ | |
Serial.print(i + 1); Serial.print(F(": ")); | |
Serial.print(WiFi.SSID(i)); Serial.print(F(" (")); | |
Serial.print(WiFi.RSSI(i)); Serial.print(F(")")); | |
Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*"); | |
vTaskDelay (pdMS_TO_TICKS(10)); | |
} | |
} | |
esp_task_wdt_reset(); // 重置 Watchdog 計時器 | |
Serial.println(); | |
Serial.println(F("Please, check if your WiFi network is on the list and check if it's strong enough (greater than -90).")); | |
Serial.println("ESP32 will reset itself after "+String(WDT_TIMEOUT)+" seconds if can't connect to the network"); | |
Serial.println("Connecting to: " + String(SSID)); | |
xWifiReconnectState = CONNECTING; | |
WiFi.reconnect(); | |
} | |
else if ((wifiStatus == WL_CONNECTED)&&(wifiPrev == WL_CONNECTED)) { | |
xWifiReconnectState = RUNNING; | |
esp_task_wdt_reset(); // 重置 Watchdog 計時器 | |
vTaskDelay (pdMS_TO_TICKS(1000)); | |
} | |
else { | |
/** 不要重置 Watchdoh 計時器,如果計時器到達設定的時間就會重置 ESP32 */ | |
unsigned long currentMillis = millis(); | |
if (currentMillis - previousMillis >= interval) { | |
previousMillis = currentMillis; | |
Serial.print (F(".")); | |
} | |
} | |
wifiPrev = wifiStatus; | |
} | |
} | |
/**======================================================================== | |
** onQrCodeTask() | |
*? 監測二維碼辨識資料是否成功與否的任務 | |
*@param pvParameter void* | |
*========================================================================**/ | |
void onQrCodeTask (void *pvParameters) { | |
Serial.print("onQrCodeTask: Executing on core "); | |
Serial.println(xPortGetCoreID()); | |
struct QRCodeData qrCodeData; | |
while (true) { | |
if (xOnQrCodeState == OFF) xOnQrCodeState = WORKING; | |
if (qrcodereader.receiveQrCode(&qrCodeData, 100)) { | |
Serial.println (F("Found QRCode")); | |
if (qrCodeData.valid) { | |
Serial.print (F("Payload: ")); | |
Serial.println ((const char*)qrCodeData.payload); | |
if (strcmp((const char*)qrCodeData.payload, pcQrCode[0]) == 0) { | |
xOnQrCodeState = MATCH; | |
} else { | |
xOnQrCodeState = UNMATCH; | |
} | |
} else { | |
Serial.print (F("Invalid: ")); | |
Serial.println ((const char *)qrCodeData.payload); | |
xOnQrCodeState = INVALID; | |
} | |
} | |
vTaskDelay (pdMS_TO_TICKS(100)); | |
} | |
} | |
/**======================================================================== | |
** setupOTA | |
*? ESP32 OTA 韌體上傳功能 | |
*========================================================================**/ | |
void setupOTA () { | |
#ifdef ENABLE_OTA | |
ArduinoOTA | |
.onStart([]() { | |
String type; | |
if (ArduinoOTA.getCommand() == U_FLASH) | |
type = "sketch"; | |
else // U_SPIFFS | |
type = "filesystem"; | |
// NOTE: if updating SPIFFS this would be the place | |
// to unmount SPIFFS using SPIFFS.end() | |
Serial.println("Start updating " + type); | |
OTA_status = true; | |
}) | |
.onEnd([]() { | |
Serial.println(F("\nEnd")); | |
}) | |
.onProgress([](unsigned int progress, unsigned int total) { | |
Serial.printf("Progress: %u%%\r", (progress / (total / 100))); | |
esp_task_wdt_reset (); | |
OTA_status = true; | |
}) | |
.onError([](ota_error_t error) { | |
Serial.printf("Error[%u]: ", error); | |
if (error == OTA_AUTH_ERROR) Serial.println(F("Auth Failed")); | |
else if (error == OTA_BEGIN_ERROR) Serial.println(F("Begin Failed")); | |
else if (error == OTA_CONNECT_ERROR) Serial.println(F("Connect Failed")); | |
else if (error == OTA_RECEIVE_ERROR) Serial.println(F("Receive Failed")); | |
else if (error == OTA_END_ERROR) Serial.println(F("End Failed")); | |
OTA_status = false; | |
esp_restart (); | |
}); | |
ArduinoOTA.setHostname (DEVICENAME); | |
ArduinoOTA.begin(); | |
#endif | |
} | |
/**======================================================================== | |
** systemStateControlTask() | |
*? 系統狀態控制的任務 | |
*@param pvParameters void* | |
*========================================================================**/ | |
void deviceStateControlTask (void *pvParameters) { | |
while (true) { | |
switch (xDeviceState) { | |
case STARTUP: | |
{ | |
//* 這裡分別使用 xOnQrCodeState, xWifiReconnectState 和 | |
// 無線網路連線狀態做判斷,最後得出 xDeviceState 的結果。 | |
static int t = 0; | |
if (xWifiReconnectState == CONNECTING) { | |
vTaskDelay (pdMS_TO_TICKS(200)); | |
if (t++ >= 75) { | |
t = 0; | |
//* 每 200ms 亮滅一次,連續三次,橙色 | |
blink (3, 100, ORANGE); | |
if (dDoorState == CLOSE) { | |
xDeviceState = STANDBY_WO_WIFI; | |
Serial.println (F("device state change to STANDBY_WO_WIFI")); | |
} else { | |
xDeviceState = UNLOCK; | |
Serial.println (F("\nDOOR OPENED")); | |
Serial.println (F("device state change to UNLOCK")); | |
} | |
} | |
} else if (xWifiReconnectState == CONNECTED || | |
xWifiReconnectState == RUNNING) { | |
t = 0; | |
//* 連線成功,每 200ms 亮滅一次,連續五次,紫色 | |
blink (5, 100, VIOLET); | |
if (dDoorState == CLOSE) { | |
xDeviceState = STANDBY; | |
Serial.println (F("device state change to STANDBY")); | |
} else { | |
xDeviceState = UNLOCK; | |
Serial.println (F("\nDOOR OPENED")); | |
Serial.println (F("device state change to UNLOCK")); | |
} | |
} else if (xWifiReconnectState == DISCONNECTED) { | |
blink (-1, 0, BLACK); | |
Serial.println (F("detect wifi disconnected")); | |
} else {} | |
} | |
break; | |
case STANDBY_WO_WIFI: | |
{ | |
static int t = 0; | |
//* 每秒亮滅一次, 黃色 | |
blink (1, 500, YELLOW); | |
//* 如果跳到這個狀態的同時無線網路連線上了,就要跳回起頭重新檢查一下無線網路狀態 | |
if (t > 9 && | |
(xWifiReconnectState != CONNECTED || xWifiReconnectState != RUNNING) ) { | |
t = 0; | |
xDeviceState = STARTUP; | |
Serial.println (F("device state change to STARTUP")); | |
} else { | |
t++; | |
} | |
} | |
break; | |
case STANDBY: | |
//* 每秒亮滅一次, 藍色 | |
blink (1, 500, BLUE); | |
//* 檢測門鎖狀態 | |
if (dDoorState == OPEN) { | |
xDeviceState = UNLOCK; | |
Serial.println (F("Error detected: door open in STANBY")); | |
blink (2, 200, RED); | |
Serial.println (F("device state change to UNLOCK")); | |
break; | |
} | |
//* 檢測是否符合狀態轉移的條件 | |
if (xOnQrCodeState == MATCH) { | |
xDeviceState = QRCODEMATCH; | |
Serial.println (F("device state change to QRCODEMATCH")); | |
xOnQrCodeState = WORKING; | |
} else if (xOnQrCodeState == UNMATCH) { | |
xDeviceState = QRCODEUNMATCH; | |
Serial.println (F("device state change to QRCODEUNMATCH")); | |
xOnQrCodeState = WORKING; | |
} else if (xOnQrCodeState == INVALID) { | |
xDeviceState = QRCODEINVALID; | |
Serial.println (F("device state change to QRCODEINVALID")); | |
xOnQrCodeState = WORKING; | |
} else {} | |
break; | |
case QRCODEMATCH: | |
doorControl (false); | |
blink (0, 2000, GREEN); | |
if (dDoorState == OPEN) { | |
xDeviceState = UNLOCK; | |
Serial.println (F("device state change to UNLOCK")); | |
} | |
else { | |
//* 每 400ms 閃爍一次,共三次,紅色。 | |
blink (3, 200, RED); | |
Serial.println(F("ERROR: The door can not be opened!")); | |
xDeviceState = STARTUP; | |
Serial.println (F("device state change to STARTUP")); | |
} | |
break; | |
case QRCODEUNMATCH: | |
{ | |
//* 常亮兩秒, 紅色 | |
blink (0, 1500, RED); | |
xDeviceState = STARTUP; | |
Serial.println (F("device state change to STARTUP")); | |
} | |
break; | |
case QRCODEINVALID: | |
{ | |
//* 每 500ms 閃爍一次,共兩次,橙色。 | |
blink (2, 250, ORANGE); | |
xDeviceState = STARTUP; | |
Serial.println (F("device state change to STARTUP")); | |
} | |
break; | |
case UNLOCK: | |
// 偵測關門的中斷 | |
attachInterrupt (digitalPinToInterrupt(LIMITSWITCH), lsInterrupt, FALLING); | |
Serial.println (F("waiting for door close...")); | |
while (dDoorState == OPEN || bIntTrigger == false); | |
//* 每 800ms 閃爍一次,共三次,紅色。 | |
blink (3, 400, RED); | |
bIntTrigger = false; | |
xDeviceState = LOCK; | |
Serial.println (F("done!\ndevice state change to LOCK")); | |
break; | |
case LOCK: | |
{ | |
xDeviceState = STARTUP; | |
Serial.println (F("done!\ndevice state change to STARTUP")); | |
} | |
break; | |
default: | |
Serial.println (F("deviceStateControlTask: wrong case")); | |
break; | |
} | |
vTaskDelay (pdMS_TO_TICKS(100)); | |
} | |
} | |
/**======================================================================== | |
** setup() | |
*? Arduino setup function | |
*========================================================================**/ | |
void setup () { | |
delay (2000); | |
//* UART 初始化 | |
Serial.begin (115200); | |
Serial.println (F("\nx05_ESP32CamDeviceStateControl Booting...")); | |
//* WiFi 設定 | |
WiFi.mode (WIFI_STA); | |
WiFi.setHostname (DEVICENAME); | |
WiFi.begin (SSID, PASSWORD); | |
//* 外設初始化 | |
ioInit (); | |
//* OTA 設定 | |
OTA_status = false; | |
setupOTA (); | |
//* WatchDog 初始化 | |
esp_task_wdt_init (WDT_TIMEOUT, true); | |
//*------------------ Task 初始化設定 -----------------*/ | |
/*======= WIFI RECONNECT =======*/ | |
xTaskCreatePinnedToCore ( | |
wifiReconnectTask, "wifiReconnectTask", | |
usStackDepth, NULL, // usStackDepth 請自行測試該值大小 | |
0, // Priotity 0 | |
&xWiFiReConnectTaskHandle, 0 // Core 0 | |
); | |
delay (250); | |
/*======= SYSTEMSTATECONTROLTASK =======*/ | |
//* Core 0, Priority 1 | |
xTaskCreatePinnedToCore ( | |
deviceStateControlTask, "deviceStateControlTask", | |
usStackDepth, NULL, // usStackDepth 請自行測試該值大小 | |
1, // Priotity 1 | |
&xDeviceStateControlTaskHandle, 0 // Core 0 | |
); | |
delay (250); | |
/*======= ESP32QRCODEREADER =======*/ | |
qrcodereader.setup (); | |
Serial.print (F("Setup QRCode Reader")); | |
qrcodereader.beginOnCore (1); | |
Serial.println (F("Begin on Core 1")); | |
xTaskCreatePinnedToCore ( | |
onQrCodeTask, "onQrCodeTask", | |
4 * 1024, NULL, | |
2, // Priority 2 | |
&xOnQrCodeTaskHandle, 0 // Core 0 | |
); | |
delay (250); | |
//* 印出 ESP32 Heap 和 PSRAM 的資訊 | |
log_d("Total heap: %d", ESP.getHeapSize ()); | |
log_d("Free heap: %d", ESP.getFreeHeap ()); | |
log_d("Total PSRAM: %d", ESP.getPsramSize ()); | |
log_d("Free PSRAM: %d", ESP.getFreePsram ()); | |
} | |
/**======================================================================== | |
** loop() | |
*? Arduino loop function | |
*========================================================================**/ | |
void loop () {} |
編譯、上傳成功後,就會如下圖輸出類似的開機訊息。
二維碼辨識成功後,狀態切換工作流程所輸出的訊息。
此篇所提供的 ESP32QRDoorLock 參考程式碼,基本上已能與展示影片提供相同的操作(二維碼辨識和狀態切換),不過需要先自行補上 GPIO 的號碼,以及為 wifiReconnectTask 和 deviceStateControlTask 這兩個工作任務提供合適的 usStackDepth 堆疊大小後才行。
完整版本的動作輸出,看 "新產品上市-ESP32-CAM 燒錄與接腳擴充二合一底板介紹、使用方法和實作展示" 網頁最下方的展示影片。
【(04)結論】
到這篇,已經完成 ESP32-CAM 二維碼辨識之門鎖控制一系列的全部網頁撰寫,若完成硬體配線後在測試過程中有任何問題,歡迎在下面留言詢問。
.
.
<<部落格相關文章>>
.
.
沒有留言:
張貼留言
留言屬名為"Unknown"或"不明"的用戶,大多這樣的留言都會直接被刪除掉,不會得到任何回覆!
發問問題,請描述清楚你(妳)的問題,別人回答前不會想去 "猜" 問題是什麼?
不知道怎麼發問,請看 [公告] 部落格提問須知 - 如何問問題 !