2021年9月22日 星期三

ESP32 CAMRA 二維碼(QR Code)辨識之門鎖控制 05-狀態控制工作流程

網頁最後修改時間: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_WIFIUNLOCK 或 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 的三種結果(MATCHUNMATCHINVALID),切換到相對應的狀態。

在 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 的號碼,以及為 wifiReconnectTaskdeviceStateControlTask 這兩個工作任務提供合適的 usStackDepth 堆疊大小後才行。

完整版本的動作輸出,看 "新產品上市-ESP32-CAM 燒錄與接腳擴充二合一底板介紹、使用方法和實作展示" 網頁最下方的展示影片。


【(04)結論】

到這篇,已經完成 ESP32-CAM 二維碼辨識之門鎖控制一系列的全部網頁撰寫,若完成硬體配線後在測試過程中有任何問題,歡迎在下面留言詢問。


.

.


<<部落格相關文章>>


    .

    .

    沒有留言:

    張貼留言

    留言屬名為"Unknown"或"不明"的用戶,大多這樣的留言都會直接被刪除掉,不會得到任何回覆!

    發問問題,請描述清楚你(妳)的問題,別人回答前不會想去 "猜" 問題是什麼?

    不知道怎麼發問,請看 [公告] 部落格提問須知 - 如何問問題 !