【IOT環控系統開發】監控您的家(上) — 開發準備

作者/圖片提供:曹永忠/王景穗

在智慧家庭中,最重要的是家庭的安全與防護,而目前能夠提供安全與防護的方式大部分都選擇安裝視訊監控鏡頭,所以本文要介紹一個物廉價美的一個整合晶片:ESP32 CAM視訊整合模組,這是安可信(AI Thinker)為 ESP32 推出了ESP32 CAM視訊整合模組,本模組目前網路賣場市價約為新台幣二百元到三百元之間,本身附帶一個結合了 OmniVision 的 OV2640 影像感測器模組,基本像數為640480,加上MicroSD 記憶卡卡槽,大小約為2740mm,只要MicroUsb 5V供電,就可以運作,使其適合做為網路攝影機應用。

ESP32 CAM視訊整合模組

簡易主機監控平台架構

筆者為了簡化視訊簡易主機監控平台複雜性,如下圖所示,提出一個架構,筆者使用一個到多個ESP32 CAM視訊整合模組,並且使用NodeMCU-32S Lua WiFi 物聯網開發板,撰寫對應的韌體,使NodeMCU-32S Lua WiFi 成為一個無線基地台,與視訊簡易主機監控網站(網頁伺服器)相連,使用者可以透過無線網路連上其熱點,就可以觀看視訊簡易主機監控網站,就可以看到一個到多個ESP32 CAM視訊整合模組捕抓到的動態視訊。

視訊簡易主機監控平台程式結果畫面

ESP32 CAM視訊整合模組

對於ESP32 CAM視訊整合模組,筆者參閱陸向陽老師的【好物開箱】為低階網路攝影而生的ESP32-CAM一文,可以看到ESP32-CAM硬體功能配置圖,該模組配備OmniVision 的 OV2640 影像感測器模組,基本像數為640*480,為了可以儲存攝影、拍照等資料,又加上MicroSD 記憶卡卡槽,可以插入達4G MicroSD 記憶卡,為了視訊,也加入了PSRAM(Pseudo SRAM)加快整個處理的速度,並且在下方加入內置的高亮度的Flash Lamp,宛如一個功能完整的監控視訊攝影機(陸向陽, 2019)。

ESP32-CAM硬體功能配置圖(圖片來源

ESP32 CAM視訊整合模組主要功能模組為下:

  • 最小的 802.11b / g / n Wi-Fi BT SoC 模塊
  • 低功耗 32 位 CPU,也可以為應用處理器服務
  • 高達 160MHz 的時鐘速度,總結計算能力高達 600 DMIPS
  • 內置 520 KB SRAM,外部 4MPSRAM
  • 支持 UART / SPI / I2C / PWM / ADC / DAC
  • 支持 OV2640 和 OV7670 攝像頭,內置閃光燈。
  • 支持圖像 Wi-Fi 上傳
  • 支持 TF 卡
  • 支持多種睡眠模式。
  • 嵌入式 Lwip 和 FreeRTOS
  • 支持 STA / AP / STA + AP 操作模式
  • 支持 Smart Config / AirKiss 技術
  • 支持串列埠本地和遠程韌體升級(FOTA)

(資料來源:SeeedStudio官網

如下表所示,為ESP32 CAM視訊整合模組整體規格:

(資料來源: SeeedStudio官網

ESP32 CAM視訊整合模組開發準備

筆者為了簡化,如下圖所示,購買了ESP32 CAM視訊整合模組與燒錄底座,方便開發整體的系統。

ESP32 CAM與燒錄模組

筆者將ESP32 CAM視訊整合模組至於燒錄底座之上,如下圖所示就完成下圖之視訊攝影機。

整合燒錄模組之ESP32 CAM

ESP32 CAM視訊系統開發

我們遵照前幾章所述,將ESP32 CAM視訊整合模組的驅動程式安裝好之後,我們打開ESP 32開發板的開發工具:Sketch IDE整合開發軟體(安裝Arduino開發環境,請參考『ESP32程式設計(基礎篇):ESP32 IOT Programming (Basic Concept & Tricks)』之『Arduino開發IDE安裝』(曹永忠, 2020a, 2020b, 2020f),安裝ESP 32開發板 SDK請參考『ESP32程式設計(基礎篇):ESP32 IOT Programming (Basic Concept & Tricks)』之『安裝ESP32 Arduino 整合開發環境』(曹永忠, 2020a, 2020b, 2020c, 2020d, 2020e),編寫一段程式,編寫一段程式,並透過開發工具將整個程式編譯後,燒錄後上傳到ESP32 CAM視訊整合模組,進行程式測試。

ESP32CAM視訊鏡頭程式(ESPCAMV20210530.ino)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
#include "initPins.h"
#include "esp_camera.h"

#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h" //disable brownout problems
#include "soc/rtc_cntl_reg.h"  //disable brownout problems
#include "esp_http_server.h"

//Replace with your network credentials


#define PART_BOUNDARY "123456789000000000000987654321"

// This project was tested with the AI Thinker Model, M5STACK PSRAM Model and M5STACK WITHOUT PSRAM
#define CAMERA_MODEL_AI_THINKER
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WITHOUT_PSRAM

// Not tested with this model
//#define CAMERA_MODEL_WROVER_KIT

#if defined(CAMERA_MODEL_WROVER_KIT)
  #define PWDN_GPIO_NUM    -1
  #define RESET_GPIO_NUM   -1
  #define XCLK_GPIO_NUM    21
  #define SIOD_GPIO_NUM    26
  #define SIOC_GPIO_NUM    27
 
  #define Y9_GPIO_NUM      35
  #define Y8_GPIO_NUM      34
  #define Y7_GPIO_NUM      39
  #define Y6_GPIO_NUM      36
  #define Y5_GPIO_NUM      19
  #define Y4_GPIO_NUM      18
  #define Y3_GPIO_NUM       5
  #define Y2_GPIO_NUM       4
  #define VSYNC_GPIO_NUM   25
  #define HREF_GPIO_NUM    23
  #define PCLK_GPIO_NUM    22

#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
  #define PWDN_GPIO_NUM     -1
  #define RESET_GPIO_NUM    15
  #define XCLK_GPIO_NUM     27
  #define SIOD_GPIO_NUM     25
  #define SIOC_GPIO_NUM     23
 
  #define Y9_GPIO_NUM       19
  #define Y8_GPIO_NUM       36
  #define Y7_GPIO_NUM       18
  #define Y6_GPIO_NUM       39
  #define Y5_GPIO_NUM        5
  #define Y4_GPIO_NUM       34
  #define Y3_GPIO_NUM       35
  #define Y2_GPIO_NUM       32
  #define VSYNC_GPIO_NUM    22
  #define HREF_GPIO_NUM     26
  #define PCLK_GPIO_NUM     21

#elif defined(CAMERA_MODEL_M5STACK_WITHOUT_PSRAM)
  #define PWDN_GPIO_NUM     -1
  #define RESET_GPIO_NUM    15
  #define XCLK_GPIO_NUM     27
  #define SIOD_GPIO_NUM     25
  #define SIOC_GPIO_NUM     23
 
  #define Y9_GPIO_NUM       19
  #define Y8_GPIO_NUM       36
  #define Y7_GPIO_NUM       18
  #define Y6_GPIO_NUM       39
  #define Y5_GPIO_NUM        5
  #define Y4_GPIO_NUM       34
  #define Y3_GPIO_NUM       35
  #define Y2_GPIO_NUM       17
  #define VSYNC_GPIO_NUM    22
  #define HREF_GPIO_NUM     26
  #define PCLK_GPIO_NUM     21

#elif defined(CAMERA_MODEL_AI_THINKER)
  #define PWDN_GPIO_NUM     32
  #define RESET_GPIO_NUM    -1
  #define XCLK_GPIO_NUM      0
  #define SIOD_GPIO_NUM     26
  #define SIOC_GPIO_NUM     27
 
  #define Y9_GPIO_NUM       35
  #define Y8_GPIO_NUM       34
  #define Y7_GPIO_NUM       39
  #define Y6_GPIO_NUM       36
  #define Y5_GPIO_NUM       21
  #define Y4_GPIO_NUM       19
  #define Y3_GPIO_NUM       18
  #define Y2_GPIO_NUM        5
  #define VSYNC_GPIO_NUM    25
  #define HREF_GPIO_NUM     23
  #define PCLK_GPIO_NUM     22
#else
  #error "Camera model not selected"
#endif

static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";

httpd_handle_t stream_httpd = NULL;

//-----------------
const char* ssid     = "CAMAP";
const char* password = "12345678";
// 設定ESP32固定IP的位置
IPAddress local_IP(192, 168, 4, 100);
// Set your Gateway IP address
IPAddress gateway(192, 168, 4, 1);
IPAddress subnet(255, 255, 0, 0);
IPAddress primaryDNS(8, 8, 8, 8);   //optional
IPAddress secondaryDNS(8, 8, 4, 4); //optional
//--------------


static esp_err_t stream_handler(httpd_req_t *req){
  camera_fb_t * fb = NULL;
  esp_err_t res = ESP_OK;
  size_t _jpg_buf_len = 0;
  uint8_t * _jpg_buf = NULL;
  char * part_buf[64];

  res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
  if(res != ESP_OK){
    return res;
  }

  while(true){
    fb = esp_camera_fb_get();
    if (!fb) {
      Serial.println("Camera capture failed");
      res = ESP_FAIL;
    } else {
      if(fb->width > 400){
        if(fb->format != PIXFORMAT_JPEG){
          bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
          esp_camera_fb_return(fb);
          fb = NULL;
          if(!jpeg_converted){
            Serial.println("JPEG compression failed");
            res = ESP_FAIL;
          }
        } else {
          _jpg_buf_len = fb->len;
          _jpg_buf = fb->buf;
        }
      }
    }
    if(res == ESP_OK){
      size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
      res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
    }
    if(res == ESP_OK){
      res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
    }
    if(res == ESP_OK){
      res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
    }
    if(fb){
      esp_camera_fb_return(fb);
      fb = NULL;
      _jpg_buf = NULL;
    } else if(_jpg_buf){
      free(_jpg_buf);
      _jpg_buf = NULL;
    }
    if(res != ESP_OK){
      break;
    }
    Serial.printf("MJPG: %uB\n",(uint32_t)(_jpg_buf_len));
  }
  return res;
}

void startCameraServer(){
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 10000;
//  config.server_port = 10002
  ;
    // map port to Web server
  httpd_uri_t index_uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = stream_handler,
    .user_ctx  = NULL
  };
 
  Serial.printf("Starting web server on port: '%d'\n", config.server_port);
  if (httpd_start(&stream_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(stream_httpd, &index_uri);
  }
}

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
 
  Serial.begin(115200);
  Serial.setDebugOutput(false);
 
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
 
  if(psramFound()){
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }
 
  // Camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
  // Wi-Fi connection
 
 // Configures static IP address
  if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
    Serial.println("STA Failed to configure");
  }
 
  // Connect to Wi-Fi network with SSID and password
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());  
  // Start streaming web server
  startCameraServer();
}

void loop() {
  delay(1);
}

ESP32CAM視訊鏡頭程式(initPins.h)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
ESP32CAM視訊鏡頭程式(initPins.h)
#include
#include
WiFiMulti wifiMulti;
  IPAddress ip ;
  String ipdata ;
  String apname ;
String MacData ;



//--------------
long POW(long num, int expo)
{
  long tmp =1 ;
  if (expo > 0)
  {
        for(int i = 0 ; i< expo ; i++)
          tmp = tmp * num ;
         return tmp ;
  }
  else
  {
   return tmp ;
  }
}

String SPACE(int sp)
{
    String tmp = "" ;
    for (int i = 0 ; i < sp; i++)
      {
          tmp.concat(' ')  ;
      }
    return tmp ;
}


String strzero(long num, int len, int base)
{
  String retstring = String("");
  int ln = 1 ;
    int i = 0 ;
    char tmp[10] ;
    long tmpnum = num ;
    int tmpchr = 0 ;
    char hexcode[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'} ;
    while (ln <= len) { tmpchr = (int)(tmpnum % base) ; tmp[ln-1] = hexcode[tmpchr] ; ln++ ; tmpnum = (long)(tmpnum/base) ; } for (i = len-1; i >= 0 ; i --)
      {
          retstring.concat(tmp[i]);
      }
   
  return retstring;
}

unsigned long unstrzero(String hexstr, int base)
{
  String chkstring  ;
  int len = hexstr.length() ;
 
    unsigned int i = 0 ;
    unsigned int tmp = 0 ;
    unsigned int tmp1 = 0 ;
    unsigned long tmpnum = 0 ;
    String hexcode = String("0123456789ABCDEF") ;
    for (i = 0 ; i < (len ) ; i++) { // chkstring= hexstr.substring(i,i) ; hexstr.toUpperCase() ; tmp = hexstr.charAt(i) ; // give i th char and return this char tmp1 = hexcode.indexOf(tmp) ; tmpnum = tmpnum + tmp1* POW(base,(len -i -1) ) ; } return tmpnum; } String print2HEX(int number) { String ttt ; if (number >= 0 && number < 16)
  {
    ttt = String("0") + String(number,HEX);
  }
  else
  {
      ttt = String(number,HEX);
  }
  return ttt ;
}
String GetMacAddress() {
  // the MAC address of your WiFi shield
  String Tmp = "" ;
  byte mac[6];
 
  // print your MAC address:
  WiFi.macAddress(mac);
  for (int i=0; i<6; i++)
    {
        Tmp.concat(print2HEX(mac[i])) ;
    }
    Tmp.toUpperCase() ;
  return Tmp ;
}

String IpAddress2String(const IPAddress& ipAddress)
{
  return String(ipAddress[0]) + String(".") +\
  String(ipAddress[1]) + String(".") +\
  String(ipAddress[2]) + String(".") +\
  String(ipAddress[3])  ;
}




String chrtoString(char *p)
{
    String tmp ;
    char c ;
    int count = 0 ;
    while (count <100)
    {
        c= *p ;
        if (c != 0x00)
          {
            tmp.concat(String(c)) ;
          }
          else
          {
              return tmp ;
          }
       count++ ;
       p++;
       
    }
}


void CopyString2Char(String ss, char *p)
{
         //  sprintf(p,"%s",ss) ;

  if (ss.length() <=0)
      {
           *p =  0x00 ;
          return ;
      }
    ss.toCharArray(p, ss.length()+1) ;
   // *(p+ss.length()+1) = 0x00 ;
}

boolean CharCompare(char *p, char *q)
  {
      boolean flag = false ;
      int count = 0 ;
      int nomatch = 0 ;
      while (flag <100) { if (*(p+count) == 0x00 or *(q+count) == 0x00) break ; if (*(p+count) != *(q+count) ) { nomatch ++ ; } count++ ; } if (nomatch >0)
      {
        return false ;
      }
      else
      {
        return true ;
      }
     
       
  }

void ShowAP()
{
  Serial.print("Access Point:") ;
  Serial.print(apname) ;
  Serial.print("\n") ;

}


void ShowMAC()
{
  Serial.print("MAC:") ;
  Serial.print(MacData) ;
  Serial.print("\n") ;

}
void ShowIP()
{
  Serial.print("IP:") ;
  Serial.print(ipdata) ;
  Serial.print("\n") ;

}


void ShowInternet()
{
    ShowMAC() ;
    ShowAP() ;
    ShowIP()  ;
}

共同作者介紹

王景穗(Wang-Jing Suei)

國立高雄大學(NUK)電機工程學系碩士,碩士論文主要專研物聯網技術與微處理器應用整合,並協助學校針對實作性課程的遠距教學系統開發,結合自動控制系統與物聯網技術,設計開發教育性遠距操作系統。

(責任編輯:謝涵如)

Author: 曹永忠

國立中央大學資訊管理學系博士,目前在國立暨南國際大學電機工程學系兼任助理教授、國立高雄科技大學商務資訊應用系兼任助理教授自由作家,專注於軟體工程、軟體開發與設計、物件導向程式設計、物聯網系統開發、Arduino開發、嵌入式系統開發。長期投入資訊系統設計與開發、企業應用系統開發、軟體工程、物聯網系統開發、軟硬體技術整合等領域,並持續發表作品及相關專業著作,並通過台灣圖霸的專家認證。

Share This Post On

發表

跳至工具列