前言
之前看了官方玩過一個智慧貓眼攝像頭,我很有興趣,但是那個 IDF 平臺屬實難整,我光安裝都整了一天,網不好下載的包可能有問題。然後命令列操作也比較麻煩,我就想到了無敵的 arduino ,ESP32-CAM 這個板子本來就是 arduino 支援的,移植上去問題不大。SDDC 聯結器的移植,按之前我移植 SDDC 的經驗,官方程式碼的程式碼規範和可移植性都很不錯,移植的難度不大。那就開幹!
硬體選擇
這我用的是和愛智官方一樣的,安信可的ESP32 - CAM 攝像頭,這裡推薦這種分成兩塊板子的這種,還有一種把串列埠整合到一塊板子上的,發熱太嚴重,燒錄程式的時候需要手動按 IO0 然後按 RST 按鍵。
然後在 arduino 上選擇對應的開發板:
程式碼獲取
從愛智官方的示例文章:ESP32 SDDC 裝置開發 中可以獲取到SDDC 聯結器的示例程式碼,和 SDDC 聯結器的庫,然後 arduino 的裝置程式碼可以直接用 arduino 提供的示例程式碼來改。
當然,你們看到篇文章的時候,就不用管這麼多啦︿( ̄︶ ̄)︿
看過我之前文章的朋友肯定都知道,我早就幫大夥打包好了~
只需要去 -- 我們的微信公眾號 我最討厭新增各種微信公眾號才能獲取資源了,所以還是老樣子, 直接去靈感桌面的祕密寶庫 獲取程式碼,或者直接 clone:
這次用到的是下圖紅圈的三個資料夾:
cjson:我移植的 cjson 庫,就是標準的 cjson 庫,放到 arduino 安裝目錄下的 libraries 資料夾裡,百度一下 cjson 的函式使用就行了。
LIBESP32_CAM:是我移植自官方的SDDC 聯結器庫,也是放入 libraries 資料夾裡就行。裡面是 SDDC 協議的處理函式,我們不用管。因為這個庫包含了 SDDC 協議相關內容,我們就不再用原來的 libsddc 庫了
sddc_demo 資料夾裡面就是我們各種感測器的 demo 程式碼了:
紅圈的 ESP32_Camera資料夾裡面就是我們程式碼,點進去就能看見 ESP32_Camera.ino 檔案,雙擊檔案會自動啟動 arduino-IDE 開啟程式碼。在工具 -> 埠 選擇對應的 COM 口然後點選上傳就可以把程式碼燒錄到板子裡:
具體 arduino 使用教程可以看我之前的文章 arduino開發指導 和 手把手帶你 arduino 開發:基於ESP32S 的第一個應用-紅外測溫槍(帶引腳圖)
程式碼解析
這個程式碼因為是移植愛智和 arduino 官方的程式碼,相關解析已經很多了,我就重點講一下我修改的部分。
我主要修改了 esp_on_message() 和 esp_send_image(),esp_connector_task()函式。
程式碼如下:
/*
* Send image to connector
*/
static void esp_send_image(sddc_connector_t *conn)
{
void *data;
size_t size;
size_t totol_len = 0;
size_t len;
int ret;
data = fb->buf;
size = fb->len;
while (totol_len < size)
{
//len = min((size - totol_len), (1460 - 16));
len = min((size - totol_len), 1420);
ret = sddc_connector_put(conn, data, len, (totol_len + len) == size);
if (ret < 0)
{
sddc_printf("Failed to put!\n");
break;
}
totol_len += len;
data += len;
}
sddc_printf("Total put %d byte\n", totol_len);
esp_camera_fb_return(fb);
}
esp_send_image() 函式這主要是刪掉了原有的呼叫拍照的函式,因為拍照的邏輯放在了 esp_on_message 函式中,而且 esp_camera_fb_get() 會返回一個 fd 作為全域性變數使用,就不需要在這重新拍一張照片了。
/*
* sddc connector task
*/
static void esp_connector_task(void *arg)
{
sddc_connector_t *conn;
BaseType_t ret;
while (1)
{
ret = xQueueReceive(conn_mqueue_handle, &conn, portMAX_DELAY);
if (ret == pdTRUE)
{
esp_send_image(conn);
sddc_connector_destroy(conn);
cam_flag = 0;
}
}
vTaskDelete(NULL);
}
/*
* Lock timer callback function
*/
static void esp_lock_timer_callback(TimerHandle_t handle)
{
sddc_printf("Close the door!\n");
}
esp_connector_task 函式加入了一個全域性變數 cam_flag 避免由於拍照太快導致傳送不及時。
/*
* Handle MESSAGE
*/
static sddc_bool_t esp_on_message(sddc_t *sddc, const uint8_t *uid, const char *message, size_t len)
{
cJSON *root = cJSON_Parse(message);
cJSON *value;
cJSON *cam;
char *str;
int ret;
sddc_return_value_if_fail(root, SDDC_TRUE);
str = cJSON_Print(root);
sddc_goto_error_if_fail(str);
sddc_printf("esp_on_message: %s\n", str);
cJSON_free(str);
cam = cJSON_GetObjectItem(root, "cam");
if (cJSON_IsString(cam))
{
if (strcmp(cam->valuestring, "image") == 0 && (cam_flag == 0))
{
char *str;
size_t size;
cam_flag = 1;
// 拍照
fb = esp_camera_fb_get();
sddc_goto_error_if_fail(fb);
// 獲得影像資料大小
size = fb->len;
// 聯結器
cJSON *connector = cJSON_GetObjectItem(root, "connector");
sddc_goto_error_if_fail(cJSON_IsObject(connector));
cJSON *port = cJSON_GetObjectItem(connector, "port");
sddc_goto_error_if_fail(cJSON_IsNumber(port));
cJSON *token = cJSON_GetObjectItem(connector, "token");
sddc_goto_error_if_fail(!token || cJSON_IsString(token));
sddc_connector_t *conn = sddc_connector_create(sddc, uid, port->valuedouble, token ? token->valuestring : NULL, SDDC_FALSE);
if (conn == NULL)
{
cam_flag = 0;
printf("error !\n");
}else
{
printf("連線建立成功!\n");
}
sddc_goto_error_if_fail(conn);
// 建立 cJSON 物件
value = cJSON_CreateObject();
sddc_goto_error_if_fail(value);
// size 為影像資料大小
cJSON_AddNumberToObject(value, "size", size);
sddc_printf("Send picture to EdgerOS, file size %d\n", size);
// cJSON 物件轉換成字串
str = cJSON_Print(value);
sddc_goto_error_if_fail(str);
// 傳送圖片大小訊息
sddc_broadcast_message(sddc, str, strlen(str), 1, SDDC_FALSE, NULL);
cJSON_free(str);
if(conn_mqueue_handle == NULL)
{
Serial.println("conn_mqueue_handle ERROR");
}
if(&conn == NULL)
{
Serial.println("&conn ERROR");
}
ret = xQueueSend(conn_mqueue_handle, &conn, 0);
if (ret != pdTRUE)
{
sddc_connector_destroy(conn);
sddc_goto_error_if_fail(ret == pdTRUE);
}
} else if (strcmp(cam->valuestring, "shoot") == 0)
{
cJSON *root = NULL;
char *str;
size_t size;
// 攝像頭捕捉一幀影像
fb = esp_camera_fb_get();
sddc_goto_error_if_fail(fb);
// 獲得影像資料大小
size = fb->len;
// 建立 cJSON 物件
root = cJSON_CreateObject();
sddc_goto_error_if_fail(root);
// cmd 為 recv
cJSON_AddStringToObject(root, "cam", "image");
// size 為影像資料大小
cJSON_AddNumberToObject(root, "size", size);
sddc_printf("Send picture to EdgerOS, file size %d\n", size);
// cJSON 物件轉換成字串
str = cJSON_Print(root);
sddc_goto_error_if_fail(str);
// 傳送訊息
sddc_broadcast_message(sddc, str, strlen(str), 1, SDDC_FALSE, NULL);
cJSON_free(str);
}
} else
{
sddc_printf("Command no specify!\n");
}
error:
cJSON_Delete(root);
esp_camera_fb_return(fb);
return SDDC_TRUE;
}
主要改動是因為這一份官方程式碼的鎖和攝像頭是一體的,而且官方的 IDF 平臺使用的 camera_run(),camera_get_data_size() 和 camera_get_fb() 這幾個函式在 arduino 上沒有,我改成了esp_camera_fb_get() ,還新增了一個在聯結器之前先傳送一個 size 用來校驗圖片完整性。
其他的裝置初始化和配置啥的我都用的 arduino 示例程式碼的配置
總結
我智慧貓眼的應用程式碼被我搞丟了,得去找官方看看那邊有沒有保留,所以暫時沒法展示。