網頁最後修改時間: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 中,就完成整個程式的撰寫。
編譯、上傳成功後,就會如下圖輸出類似的開機訊息。
二維碼辨識成功後,狀態切換工作流程所輸出的訊息。
此篇所提供的 ESP32QRDoorLock 參考程式碼,基本上已能與展示影片提供相同的操作(二維碼辨識和狀態切換),不過需要先自行補上 GPIO 的號碼,以及為 wifiReconnectTask 和 deviceStateControlTask 這兩個工作任務提供合適的 usStackDepth 堆疊大小後才行。
完整版本的動作輸出,看 "新產品上市-ESP32-CAM 燒錄與接腳擴充二合一底板介紹、使用方法和實作展示" 網頁最下方的展示影片。
【(04)結論】
到這篇,已經完成 ESP32-CAM 二維碼辨識之門鎖控制一系列的全部網頁撰寫,若完成硬體配線後在測試過程中有任何問題,歡迎在下面留言詢問。
.
.
<<部落格相關文章>>
.
.
沒有留言:
張貼留言
留言屬名為"Unknown"或"不明"的用戶,大多這樣的留言都會直接被刪除掉,不會得到任何回覆!
發問問題,請描述清楚你(妳)的問題,別人回答前不會想去 "猜" 問題是什麼?
不知道怎麼發問,請看 [公告] 部落格提問須知 - 如何問問題 !