2021愛智先行者—人體存在感傳感器


【本文正在參與"2021愛智先行者-征文大賽"活動】https://mp.weixin.qq.com/s/I2s99dZpbP0QpMiKFneYJA


上次說了在 智能燈光開關 的我說了在憋大招,那肯定不會只有這點東西,那么今天我們就來繼續!之前 智能燈光開關和光照傳感器 已經簡單的完成了一個小場景的設備搭建,但是這么好玩的點子怎么就只做這么簡單的東西呢?那當然不行了啊,得搞事情!搞大事情!

之前的場景僅僅具備檢查有沒有關燈,並且遠程關燈的功能,家里有沒有其他人在家,是沒辦法感知到的,還是不夠智能。攝像頭又太復雜太貴,於是我准備尋找一個好使的傳感器來感知,人的存在。

硬件選擇

首先,就是翼輝的 邊緣計算機 Spirit 1 邊緣計算機,這套環境都是建立在這個玩意的基礎上。

還有祖傳的安信可 ESP32S

人體存在傳感器

人體存在傳感器方面困擾了我好一陣子,所以准備專門講一下這個傳感器,我嘗試了市面上很多人體傳感器,常見的大都是CW多普勒體制+類似bis0001芯片 把信號放大進行檢測,要么是青蛙眼,只能檢測到運動,而且是大幅度運動(具體點:2米距離我甩手沒用,得晃動身體。淘寶買到賊貴的那種 );要么是誤報率極高,判斷難度很高。都沒辦法簡單的滿足我的需求,我在打游戲的時候傳感器檢測不到我,把我燈關了豈不是很尷尬?

不過最后我還是找到了一個好東西:階躍時進的 HS2BC3A 這是一個毫米波傳感器。這玩意可有意思了,采用類似雷達的原理,向檢測區域發射 24GHz 的 FMCW 無線電波,並接收區域內的所有運動、微動、極弱微動的目標反射的無線電波,經傳感器系統中的毫米波 MMIC 電路轉換為電信號,並由數字信號算法處理單元進行信號處理(呼吸信號 提取算法),解算出目標信息(存在、微動、運動、靜止等狀態)。

這玩意實際上也是一個青蛙眼,也是通過檢測到運動來判斷,但是他精度能檢測到人呼吸產生的運動,眾所周知,人不呼吸就會死,所以這個問題也就不復存在了,而且不只是呼吸,包括人的很多大小動作也能被他准確的捕捉到(呼吸都能捕捉到,別說動動手指什么的了),而且還可以通過串口配置很多參數。我實際用起來效果非常棒。

HS2BC3A 可以說是老少皆宜, 有簡單的IO數字量輸出滿足基本使用需求,還有串口可以進行復雜的配置和詳細數據輸出,串口詳細輸出甚至能追蹤最多8個目標,報告目標的數量距離與信噪比(與數據可靠性相關)。還可以修改模塊探測距離,靈敏度,輸出模式,輸出延時時間(確定目標延時與目標丟失延時),主動獲取數據等功能,需要注意的一點是,串口的配置不影響IO口輸出,關閉了串口上的主動上報並不會影響IO口輸出。

因為這個模塊太過敏感(HS2BC3A 探測范圍是100°×100°,近距離探測范圍還會更大一些),在測試和調試的時候,建議將靈敏度和探測距離調整到最低,然后將輸出延時時間設置為最短,將模塊放置到高於頭頂的位置,方便調試代碼,要不然會一直檢測到調試人員的存在,加上默認持續15S檢測不到信號才會判斷沒人,導致我測試的時候一直沒辦法切換到無人狀態,一度讓我懷疑,是不是設備壞了 。

在這里插入圖片描述

實際使用的時候官方默認配置就挺好用(我安裝在天花板上,大概2-3米高),一個傳感器完全可以覆蓋主卧和客廳這些位置。手冊上寫最遠7米,設置最多可以到9米,但是遠距離探測角度會變窄。

不過官方手冊上說因為是檢測人體活動和呼吸,如果環境中有干擾源可以通過減少靈敏度規避,或者通過詳細串口獲取詳細數據:點雲目標輸出 $JYRPO ,這個信息包含了目標序號,目標距離,目標信噪比等信息其中信噪比和可靠性有關,信噪比越大,代表當前檢測到的目標可靠性越高,方便手動對數據進行篩選。

在這里插入圖片描述

代碼解析

獲取代碼

為了方便講解邏輯,我會打亂代碼的順序可能還會進行裁剪,要是想直接拿代碼跑的朋友可以直接去 靈感桌面的秘密寶庫 獲取代碼,或者直接 clone:

https://gitee.com/inspiration-desktop/DEV-lib-arduino.git

這次受限於篇幅,我就不在贅述代碼獲取了,代碼在 human_body_induction 文件夾里面,如果有不會使用的朋友,可以參考上一篇文檔:2021愛智先行者—智能燈光開關-CSDN社區

設備控制命令:

通過 Spirit 1 的應用程序或者調試工具 嗅探器 向傳感器設備發送的命令:

{
  "method": "get",
  "obj": ["rtgy"]
}

設備和協議初始化流程:

基於官方 demo 寫的不需要做什么修改,主要是設備初始化,管腳配置,和協議初始化部分。

/*
 * 初始化傳感器
 */
void sensor_init()
{
    // 初始化 GOIP 口為輸入模式,接收傳感器發送的信息 
    pinMode(sensor_in,INPUT);
    // 創建傳感器任務,周期性傳感器的數據並發送給 EdgerOS
    xTaskCreate(periodic_sensor_task, "periodic_sensor_task", ESP_TASK_STACK_SIZE, NULL, ESP_TASK_PRIO, NULL);

}

void setup() {
    byte mac[6];
    Serial.begin(115200);
    Serial.setDebugOutput(true);
    Serial.println();

    // 初始化傳感器
    sensor_init();

    // 清除一下按鍵狀態機的狀態
    button.reset();
  
    // 創建按鍵掃描線程,長按 IO0 按鍵,松開后ESP32 將會進入 SmartConfig 模式
    sddc_printf("長按按鍵進入 Smartconfig...\n");
    button.attachLongPressStop(esp_io0_key_task);
    xTaskCreate(esp_tick_task, "button_tick", ESP_TASK_STACK_SIZE, NULL, ESP_TASK_PRIO, NULL);
    
    // 啟動 WiFi 並且連接網絡
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) 
    {
        delay(500);
        Serial.print(".");
    }
  
    // 獲取並打印 IP 地址
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.print("'ip :");
    Serial.print(WiFi.localIP());
    Serial.println("' to connect"); 
  
    // sddc協議初始化
    sddc_lib_main(&sys_cfg);

    // 獲取並打印網卡 mac 地址
    WiFi.macAddress(mac);
    sddc_printf("MAC addr: %02x:%02x:%02x:%02x:%02x:%02x\n",
              mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]);
    // 使用網卡 mac 地址設置設備唯一標識 UID
    sddc_set_uid(G_sddc, mac);
}

void loop() {
    // 運行 SDDC 協議循環
    while (1) 
    {
        sddc_printf("SDDC running...\n");
        sddc_run(G_sddc);
        sddc_printf("SDDC quit!\n");
    }

    // 銷毀 SDDC 協議
    sddc_destroy(G_sddc);
}

配置設備信息

這部分代碼可以配置 WiFi 名字和 WiFi 密碼,要使用的引腳,並且配置設備在 Spirit 1 上顯示的信息:

#define SDDC_CFG_PORT             680U             // SDDC 協議使用的端口號
#define PIN_INPUT                 0                // 選擇 IO0 進行控制
#define ESP_TASK_STACK_SIZE       4096
#define ESP_TASK_PRIO             25

static const int sensor_in  = 34;                  // 數據輸入引腳

static const char* ssid     = "EOS-Tenda";         // WiFi 名
static const char* password = "1234567890";        // WiFi 密碼

static int rtgy_state       = 1;
static int xTicksToDelay    = 1000;                // 周期延時時間
  
OneButton button(PIN_INPUT, true);

/*
 *  系統對象狀態獲取注冊
 */
DEV_STATE_GET  dev_state_get_reg[] = {
        {"rtgy",   DEV_IO_TYPE,  get_sensor_state},
};

/*
 *  當前設備的信息定義
 */
DEV_INFO    dev_info = {
            .name     = "人體感應模塊",
            .type     = "device.rtgy",
            .excl     = SDDC_FALSE,
            .desc     = "ESP-32S",
            .model    = "IDRTGY01B",
            .vendor   = "inspiration-desktop",
};

/*
 *   系統注冊對象匯聚
 */
SDDC_CONFIG_INFO sys_cfg = {
        .token             = "1234567890",            // 設備密碼
        .devinfo           = &dev_info,               
        .io_dev_reg        = io_dev,
        .io_dev_reg_num    = ARRAY_SIZE(io_dev),
        .num_dev_reg       = num_dev,
        .num_dev_reg_num   = ARRAY_SIZE(num_dev),
        .state_get_reg     = dev_state_get_reg,
        .state_get_reg_num = ARRAY_SIZE(dev_state_get_reg),
        .dis_dev_reg       = dis_dev,
        .dis_dev_num       = ARRAY_SIZE(dis_dev),
};

回調函數注冊

這是收到命令后回調函數注冊的位置,在這里注冊的函數才能被 SDK 正確的調用,執行正確的動作。

具體 SDK 的解析可以參考 同人逼死官方系列!基於sddc 協議的SDK框架 sddc_sdk_lib 解析同人逼死官方系列!從 DDC 嗅探器到 sddc_sdk_lib 的數據解析

/* 
 *  數字量設備對象函數與處理方法注冊
 */
NUM_DEV_REGINFO num_dev[] = {
//        {"set_num_demo", demo},                          // 字符串為輸入命令,demo為命令處理函數

};

/*
 *  顯示設備對象函數與處理方法注冊
 */
DIS_DEV_REGINFO dis_dev[] = {
//        {"set_dis_demo", demo},                          // 字符串為輸入命令,demo為命令處理函數

};

/*
 * IO設備對象設置函數與處理方法注冊
 */
IO_DEV_REGINFO io_dev[] = {
//        {"set_io_demo", demo},                           // 字符串為輸入命令,demo為命令處理函數
          {"SW_ctrl", SW_ctrl},
};

/*
 *  系統對象狀態獲取注冊
 */
DEV_STATE_GET  dev_state_get_reg[] = {
//        {"demo", DEV_NUM_TYPE, num_get_demo},           // demo為輸入命令,字符串為命令處理函數
//        {"demo", DEV_IO_TYPE, io_get_demo},
//        {"demo", DEV_DISPLAY_TYPE, dis_get_demo},
        {"rtgy",   DEV_IO_TYPE,  get_sensor_state},
};

數據獲取與上報流程

這里是我們自己編寫的處理流程 ,可以根據你的需求自己更改,收到 set 或者 get 后根據前面的注冊的函數,進入對應的處理函數。

 /*
  * 周期上報函數
  */
static void periodic_sensor_task(void *arg)
{
    int newval = 0;
    int oldval = 0;
    int i = 0;
    // 監控鎖開啟和關閉狀態
    while(1)
    {
        newval = digitalRead(sensor_in);
        if (newval == 0) {
            i++;
        } else {
            i = 0;  
            rtgy_state = 1;
        }
        if( i > 15)
        {
            if (rtgy_state != 0){
                rtgy_state = 0;
                report_sensor_state();
            }
            i = 0; 
        } 
        
        // 任務創建之后,設定延時周期
        delay(xTicksToDelay);
    }
}

/* 
 *  主動數據上報函數
 */
static void report_sensor_state()
{  
    int sensorValue = 0;
    cJSON *value;
    cJSON *root;
    char  *msg;
     
    value = cJSON_CreateArray();
    root = cJSON_CreateObject();
    sddc_return_if_fail(value);
    sddc_return_if_fail(root);
      
    sddc_return_if_fail(value);
      
    // 獲取傳感器數據
    cJSON_AddItemToArray(value, cJSON_CreateString("rtgy"));   // 這里的字符串要和系統對象狀態獲取注冊結構體里的對應
    cJSON_AddItemToObject(root, "obj", value);
      
    // 發送數據給 EdgerOS
    msg = cJSON_Print(root);
    printf("觸發上報: %s\n",msg);
    object_report(root);
      
    cJSON_Delete(value);
    cJSON_free(msg);
}

/* 
 *  單次獲取數據
 */
sddc_bool_t get_sensor_state(char *objvalue, int value_len)
{
    if(rtgy_state)
    {
        strncpy(objvalue, "ON", value_len);
    }else
    {
        strncpy(objvalue, "OFF", value_len);
    }
    
    return SDDC_TRUE;
}

總結

這只是最簡單的通過讀的應用,本來考慮使用串口進行配置與獲取詳細數據的,但是在具體實現的時候遇到一點BUG,就先用IO湊合一下,之后有時間再把復雜功能完善。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM