Components and supplies
Arduino UNO R3 - Made in italy
esp32cam
Robot Chasis with wheel
Tools and machines
Arduino IDE
Jumper wires (Male to Female)
Apps and platforms
Arduino IDE
Project description
Code
Arduino_Code
cpp
Arduino Side Code
1#include <SoftwareSerial.h> 2SoftwareSerial SUART(11, 10); //SRX, STX 3 4//STATUS: DETECTION IS WORKING 5 6void setup() 7{ 8 Serial.begin(9600); 9 SUART.begin(9600); 10 pinMode(2, OUTPUT); 11 pinMode(5, OUTPUT); 12 pinMode(4, OUTPUT); 13 pinMode(6, OUTPUT); 14 pinMode(1, INPUT); 15} 16 17void Stop() { 18 digitalWrite(2,LOW); 19 analogWrite(5,0); 20 digitalWrite(4,HIGH); 21 analogWrite(6,0); 22} 23 24void Move_Backward(int speed) { 25 digitalWrite(2,LOW); 26 analogWrite(5,speed); 27 digitalWrite(4,HIGH); 28 analogWrite(6,speed); 29} 30 31void Rotate_Left(int speed) { 32 digitalWrite(2,LOW); 33 analogWrite(5,speed); 34 digitalWrite(4,LOW); 35 analogWrite(6,speed); 36} 37 38void Rotate_Right(int speed) { 39 digitalWrite(2,HIGH); 40 analogWrite(5,speed); 41 digitalWrite(4,HIGH); 42 analogWrite(6,speed); 43} 44 45void Move_Forward(int speed) { 46 digitalWrite(2,HIGH); 47 analogWrite(5,speed); 48 digitalWrite(4,LOW); 49 analogWrite(6,speed); 50} 51 52void loop() 53{ 54 char y; 55 if (SUART.available()!=0) 56 { 57 char y = SUART.read(); 58 Serial.println(y); 59 switch (y) { 60 case 'P': // Pedestrian Sign 61 Move_Forward(45); 62 break; 63 case 'G': // GO 64 Move_Forward(85); 65 break; 66 case 'S': // STOP SIGN 67 Stop(); 68 break; 69 case 'U': // U-TURN SIGN 70 Rotate_Left(80); 71 break; 72 case 'N': //NULL 73 Stop(); 74 break; 75 case 'R': //RIGHT TURN SIGN 76 Rotate_Right(60); 77 break; 78 case 'T': 79 Stop(); 80 break; 81 } 82 } 83}
ESP32Cam Code
cpp
1/* 2Author : ChungYi Fu (Kaohsiung, Taiwan) 2021-7-3 22:00 3https://www.facebook.com/francefu 4 5http://192.168.xxx.xxx //網頁首頁管理介面 6http://192.168.xxx.xxx:81/stream //取得串流影像 <img src="http://192.168.xxx.xxx:81/stream"> 7http://192.168.xxx.xxx/capture //取得影像 <img src="http://192.168.xxx.xxx/capture"> 8http://192.168.xxx.xxx/status //取得視訊參數值 9 10自訂指令格式 : 11http://APIP/control?cmd=P1;P2;P3;P4;P5;P6;P7;P8;P9 12http://STAIP/control?cmd=P1;P2;P3;P4;P5;P6;P7;P8;P9 13 14預設AP端IP: 192.168.4.1 15 16自訂指令格式 http://192.168.xxx.xxx/control?cmd=P1;P2;P3;P4;P5;P6;P7;P8;P9 17http://192.168.xxx.xxx/control?ip //取得APIP, STAIP 18http://192.168.xxx.xxx/control?mac //取得MAC位址 19http://192.168.xxx.xxx/control?restart //重啟ESP32-CAM 20http://192.168.xxx.xxx/control?digitalwrite=pin;value //數位輸出 21http://192.168.xxx.xxx/control?analogwrite=pin;value //類比輸出 22http://192.168.xxx.xxx/control?digitalread=pin //數位讀取 23http://192.168.xxx.xxx/control?analogread=pin //類比讀取 24http://192.168.xxx.xxx/control?touchread=pin //觸碰讀取 25http://192.168.xxx.xxx/control?resetwifi=ssid;password //重設Wi-Fi網路 26http://192.168.xxx.xxx/control?flash=value //內建閃光燈 value= 0~255 27 28官方指令格式 http://192.168.xxx.xxx/control?var=***&val=*** 29http://192.168.xxx.xxx/control?var=framesize&val=value // value = 10->UXGA(1600x1200), 9->SXGA(1280x1024), 8->XGA(1024x768) ,7->SVGA(800x600), 6->VGA(640x480), 5 selected=selected->CIF(400x296), 4->QVGA(320x240), 3->HQVGA(240x176), 0->QQVGA(160x120) 30http://192.168.xxx.xxx/control?var=quality&val=value // value = 10 ~ 63 31http://192.168.xxx.xxx/control?var=brightness&val=value // value = -2 ~ 2 32http://192.168.xxx.xxx/control?var=contrast&val=value // value = -2 ~ 2 33http://192.168.xxx.xxx/control?var=hmirror&val=value // value = 0 or 1 34http://192.168.xxx.xxx/control?var=vflip&val=value // value = 0 or 1 35http://192.168.xxx.xxx/control?var=flash&val=value // value = 0 ~ 255 36 37查詢Client端IP 38查詢IP:http://192.168.4.1/?ip 39重設網路:http://192.168.4.1/?resetwifi=ssid;password 40*/ 41 42//輸入WIFI連線帳號密碼 43const char* ssid = "AndroidF"; //your network SSID 44const char* password = "abc12345"; //your network password 45 46//輸入AP端連線帳號密碼 47const char* apssid = "ESP32-CAM"; 48const char* appassword = "12345678"; //AP密碼至少要8個字元以上 49 50#include <WiFi.h> 51#include <esp32-hal-ledc.h> //用於控制伺服馬達 52#include "soc/soc.h" //用於電源不穩不重開機 53#include "soc/rtc_cntl_reg.h" //用於電源不穩不重開機 54 55//官方函式庫 56#include "esp_camera.h" //視訊函式庫 57#include "esp_http_server.h" //HTTP Server函式庫 58#include "img_converters.h" //影像格式轉換函式庫 59 60String Feedback=""; //自訂指令回傳客戶端訊息 61 62//自訂指令參數值 63String Command=""; 64String cmd=""; 65String P1=""; 66String P2=""; 67String P3=""; 68String P4=""; 69String P5=""; 70String P6=""; 71String P7=""; 72String P8=""; 73String P9=""; 74 75//自訂指令拆解狀態值 76byte ReceiveState=0; 77byte cmdState=1; 78byte strState=1; 79byte questionstate=0; 80byte equalstate=0; 81byte semicolonstate=0; 82 83typedef struct { 84 httpd_req_t *req; 85 size_t len; 86} jpg_chunking_t; 87 88#define PART_BOUNDARY "123456789000000000000987654321" 89static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; 90static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; 91static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; 92 93httpd_handle_t stream_httpd = NULL; 94httpd_handle_t camera_httpd = NULL; 95 96//ESP32-CAM模組腳位設定 97#define PWDN_GPIO_NUM 32 98#define RESET_GPIO_NUM -1 99#define XCLK_GPIO_NUM 0 100#define SIOD_GPIO_NUM 26 101#define SIOC_GPIO_NUM 27 102 103#define Y9_GPIO_NUM 35 104#define Y8_GPIO_NUM 34 105#define Y7_GPIO_NUM 39 106#define Y6_GPIO_NUM 36 107#define Y5_GPIO_NUM 21 108#define Y4_GPIO_NUM 19 109#define Y3_GPIO_NUM 18 110#define Y2_GPIO_NUM 5 111#define VSYNC_GPIO_NUM 25 112#define HREF_GPIO_NUM 23 113#define PCLK_GPIO_NUM 22 114 115void setup() { 116 WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //關閉電源不穩就重開機的設定 117 118 Serial.begin(9600); 119 Serial.setDebugOutput(true); //開啟診斷輸出 120 Serial.println(); 121 String serial; 122 123 //視訊組態設定 https://github.com/espressif/esp32-camera/blob/master/driver/include/esp_camera.h 124 camera_config_t config; 125 config.ledc_channel = LEDC_CHANNEL_0; 126 config.ledc_timer = LEDC_TIMER_0; 127 config.pin_d0 = Y2_GPIO_NUM; 128 config.pin_d1 = Y3_GPIO_NUM; 129 config.pin_d2 = Y4_GPIO_NUM; 130 config.pin_d3 = Y5_GPIO_NUM; 131 config.pin_d4 = Y6_GPIO_NUM; 132 config.pin_d5 = Y7_GPIO_NUM; 133 config.pin_d6 = Y8_GPIO_NUM; 134 config.pin_d7 = Y9_GPIO_NUM; 135 config.pin_xclk = XCLK_GPIO_NUM; 136 config.pin_pclk = PCLK_GPIO_NUM; 137 config.pin_vsync = VSYNC_GPIO_NUM; 138 config.pin_href = HREF_GPIO_NUM; 139 config.pin_sscb_sda = SIOD_GPIO_NUM; 140 config.pin_sscb_scl = SIOC_GPIO_NUM; 141 config.pin_pwdn = PWDN_GPIO_NUM; 142 config.pin_reset = RESET_GPIO_NUM; 143 config.xclk_freq_hz = 20000000; 144 config.pixel_format = PIXFORMAT_JPEG; 145 146 // 147 // WARNING!!! PSRAM IC required for UXGA resolution and high JPEG quality 148 // Ensure ESP32 Wrover Module or other board with PSRAM is selected 149 // Partial images will be transmitted if image exceeds buffer size 150 // 151 // if PSRAM IC present, init with UXGA resolution and higher JPEG quality 152 // for larger pre-allocated frame buffer. 153 if(psramFound()){ //是否有PSRAM(Psuedo SRAM)記憶體IC 154 config.frame_size = FRAMESIZE_UXGA; 155 config.jpeg_quality = 10; 156 config.fb_count = 2; 157 } else { 158 config.frame_size = FRAMESIZE_SVGA; 159 config.jpeg_quality = 12; 160 config.fb_count = 1; 161 } 162 163 //視訊初始化 164 esp_err_t err = esp_camera_init(&config); 165 if (err != ESP_OK) { 166 Serial.printf("Camera init failed with error 0x%x", err); 167 ESP.restart(); 168 } 169 170 //可自訂視訊框架預設大小(解析度大小) 171 sensor_t * s = esp_camera_sensor_get(); 172 // initial sensors are flipped vertically and colors are a bit saturated 173 if (s->id.PID == OV3660_PID) { 174 s->set_vflip(s, 1); // flip it back 175 s->set_brightness(s, 1); // up the brightness just a bit 176 s->set_saturation(s, -2); // lower the saturation 177 } 178 // drop down frame size for higher initial frame rate 179 s->set_framesize(s, FRAMESIZE_QVGA); //解析度 UXGA(1600x1200), SXGA(1280x1024), XGA(1024x768), SVGA(800x600), VGA(640x480), CIF(400x296), QVGA(320x240), HQVGA(240x176), QQVGA(160x120), QXGA(2048x1564 for OV3660) 180 181 //s->set_vflip(s, 1); //垂直翻轉 182 //s->set_hmirror(s, 1); //水平鏡像 183 184 //閃光燈(GPIO4) 185 ledcAttachPin(4, 4); 186 ledcSetup(4, 5000, 8); 187 188 WiFi.mode(WIFI_AP_STA); //其他模式 WiFi.mode(WIFI_AP); WiFi.mode(WIFI_STA); 189 190 //指定Client端靜態IP 191 //WiFi.config(IPAddress(192, 168, 201, 100), IPAddress(192, 168, 201, 2), IPAddress(255, 255, 255, 0)); 192 193 for (int i=0;i<2;i++) { 194 WiFi.begin(ssid, password); //執行網路連線 195 196 delay(1000); 197 Serial.println(""); 198 Serial.print("Connecting to "); 199 Serial.println(ssid); 200 201 long int StartTime=millis(); 202 while (WiFi.status() != WL_CONNECTED) { 203 delay(500); 204 if ((StartTime+5000) < millis()) break; //等待10秒連線 205 } 206 207 if (WiFi.status() == WL_CONNECTED) { //若連線成功 208 WiFi.softAP((WiFi.localIP().toString()+"_"+(String)apssid).c_str(), appassword); //設定SSID顯示客戶端IP 209 Serial.println(""); 210 Serial.println("STAIP address: "); 211 Serial.println(WiFi.localIP()); 212 Serial.println(""); 213 214 for (int i=0;i<5;i++) { //若連上WIFI設定閃光燈快速閃爍 215 ledcWrite(4,10); 216 delay(200); 217 ledcWrite(4,0); 218 delay(200); 219 } 220 break; 221 } 222 } 223 224 if (WiFi.status() != WL_CONNECTED) { //若連線失敗 225 WiFi.softAP((WiFi.softAPIP().toString()+"_"+(String)apssid).c_str(), appassword); 226 227 for (int i=0;i<2;i++) { //若連不上WIFI設定閃光燈慢速閃爍 228 ledcWrite(4,10); 229 delay(1000); 230 ledcWrite(4,0); 231 delay(1000); 232 } 233 } 234 235 //指定AP端IP 236 //WiFi.softAPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0)); 237 Serial.println(""); 238 Serial.println("APIP address: "); 239 Serial.println(WiFi.softAPIP()); 240 Serial.println(""); 241 242 startCameraServer(); 243 244 //設定閃光燈為低電位 245 pinMode(4, OUTPUT); 246 digitalWrite(4, LOW); 247} 248 249void loop() { 250 String serial = P1; 251 252 int commandIndex = 0; // Create an integer variable to store the command index 253 // Map the strings to integer indices 254 if (serial == "PEDESTRIAN") { 255 commandIndex = 1; 256 } else if (serial == "GO") { 257 commandIndex = 2; 258 } else if (serial == "STOP") { 259 commandIndex = 3; 260 } else if (serial == "U_TURN") { 261 commandIndex = 4; 262 } else if (serial == "NULL") { 263 commandIndex = 5; 264 } else if (serial == "RIGHT_TURN") { 265 commandIndex = 6; 266 } 267 268 switch (commandIndex) { 269 case 1: 270 Serial.println("P"); 271 break; 272 case 2: 273 Serial.println("G"); 274 break; 275 case 3: 276 Serial.println("S"); 277 break; 278 case 4: 279 Serial.println("U"); 280 break; 281 case 5: 282 Serial.println("N"); 283 break; 284 case 6: 285 Serial.println("R"); 286 break; 287 default: 288 Serial.println("T"); 289 } 290} 291 292static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size_t len){ 293 jpg_chunking_t *j = (jpg_chunking_t *)arg; 294 if(!index){ 295 j->len = 0; 296 } 297 if(httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK){ 298 return 0; 299 } 300 j->len += len; 301 return len; 302} 303 304//影像截圖 305static esp_err_t capture_handler(httpd_req_t *req){ 306 camera_fb_t * fb = NULL; 307 esp_err_t res = ESP_OK; 308 309 fb = esp_camera_fb_get(); 310 if (!fb) { 311 Serial.println("Camera capture failed"); 312 httpd_resp_send_500(req); 313 return ESP_FAIL; 314 } 315 316 httpd_resp_set_type(req, "image/jpeg"); 317 httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); 318 httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 319 320 size_t fb_len = 0; 321 if(fb->format == PIXFORMAT_JPEG){ 322 fb_len = fb->len; 323 res = httpd_resp_send(req, (const char *)fb->buf, fb->len); 324 } else { 325 jpg_chunking_t jchunk = {req, 0}; 326 res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL; 327 httpd_resp_send_chunk(req, NULL, 0); 328 fb_len = jchunk.len; 329 } 330 esp_camera_fb_return(fb); 331 return res; 332} 333 334//影像串流 335static esp_err_t stream_handler(httpd_req_t *req){ 336 camera_fb_t * fb = NULL; 337 esp_err_t res = ESP_OK; 338 size_t _jpg_buf_len = 0; 339 uint8_t * _jpg_buf = NULL; 340 char * part_buf[64]; 341 342 res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); 343 if(res != ESP_OK){ 344 return res; 345 } 346 347 httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 348 349 while(true){ 350 fb = esp_camera_fb_get(); 351 if (!fb) { 352 Serial.println("Camera capture failed"); 353 res = ESP_FAIL; 354 } else { 355 if(fb->format != PIXFORMAT_JPEG){ 356 bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); 357 esp_camera_fb_return(fb); 358 fb = NULL; 359 if(!jpeg_converted){ 360 Serial.println("JPEG compression failed"); 361 res = ESP_FAIL; 362 } 363 } else { 364 _jpg_buf_len = fb->len; 365 _jpg_buf = fb->buf; 366 } 367 } 368 369 if(res == ESP_OK){ 370 res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); 371 } 372 if(res == ESP_OK){ 373 res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); 374 } 375 if(res == ESP_OK){ 376 size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len); 377 res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); 378 } 379 if(fb){ 380 esp_camera_fb_return(fb); 381 fb = NULL; 382 _jpg_buf = NULL; 383 } else if(_jpg_buf){ 384 free(_jpg_buf); 385 _jpg_buf = NULL; 386 } 387 if(res != ESP_OK){ 388 break; 389 } 390 } 391 392 return res; 393} 394 395//指令參數控制 396static esp_err_t cmd_handler(httpd_req_t *req){ 397 char* buf; //存取網址後帶的參數字串 398 size_t buf_len; 399 char variable[128] = {0,}; //存取參數var值 400 char value[128] = {0,}; //存取參數val值 401 String myCmd = ""; 402 403 buf_len = httpd_req_get_url_query_len(req) + 1; 404 if (buf_len > 1) { 405 buf = (char*)malloc(buf_len); 406 if(!buf){ 407 httpd_resp_send_500(req); 408 return ESP_FAIL; 409 } 410 if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) { 411 if (httpd_query_key_value(buf, "var", variable, sizeof(variable)) == ESP_OK && 412 httpd_query_key_value(buf, "val", value, sizeof(value)) == ESP_OK) { 413 } 414 else { 415 myCmd = String(buf); //如果非官方格式不含var, val,則為自訂指令格式 416 } 417 } 418 free(buf); 419 } else { 420 httpd_resp_send_404(req); 421 return ESP_FAIL; 422 } 423 424 Feedback="";Command="";cmd="";P1="";P2="";P3="";P4="";P5="";P6="";P7="";P8="";P9=""; 425 ReceiveState=0,cmdState=1,strState=1,questionstate=0,equalstate=0,semicolonstate=0; 426 if (myCmd.length()>0) { 427 myCmd = "?"+myCmd; //網址後帶的參數字串轉換成自訂指令格式 428 for (int i=0;i<myCmd.length();i++) { 429 getCommand(char(myCmd.charAt(i))); //拆解自訂指令參數字串 430 } 431 } 432 433 if (cmd.length()>0) { 434 //Serial.println(""); 435 //Serial.println("Command: "+Command); 436 //Serial.println("cmd= "+cmd+" ,P1= "+P1+" ,P2= "+P2+" ,P3= "+P3+" ,P4= "+P4+" ,P5= "+P5+" ,P6= "+P6+" ,P7= "+P7+" ,P8= "+P8+" ,P9= "+P9); 437 //Serial.println(""); 438 439 //自訂指令區塊 http://192.168.xxx.xxx/control?cmd=P1;P2;P3;P4;P5;P6;P7;P8;P9 440 if (cmd=="your cmd") { 441 // You can do anything 442 // Feedback="<font color=\"red\">Hello World</font>"; //可為一般文字或HTML語法 443 } 444 else if (cmd=="ip") { //查詢APIP, STAIP 445 Feedback="AP IP: "+WiFi.softAPIP().toString(); 446 Feedback+="<br>"; 447 Feedback+="STA IP: "+WiFi.localIP().toString(); 448 } 449 else if (cmd=="mac") { //查詢MAC位址 450 Feedback="STA MAC: "+WiFi.macAddress(); 451 } 452 else if (cmd=="restart") { 453 ESP.restart(); 454 } 455 else if (cmd=="digitalwrite") { 456 ledcDetachPin(P1.toInt()); 457 pinMode(P1.toInt(), OUTPUT); 458 digitalWrite(P1.toInt(), P2.toInt()); 459 } 460 else if (cmd=="digitalread") { 461 Feedback=String(digitalRead(P1.toInt())); 462 } 463 else if (cmd=="analogwrite") { 464 if (P1=="4") { 465 ledcAttachPin(4, 4); 466 ledcSetup(4, 5000, 8); 467 ledcWrite(4,P2.toInt()); 468 } 469 else { 470 ledcAttachPin(P1.toInt(), 9); 471 ledcSetup(9, 5000, 8); 472 ledcWrite(9,P2.toInt()); 473 } 474 } 475 else if (cmd=="analogread") { 476 Feedback=String(analogRead(P1.toInt())); 477 } 478 else if (cmd=="touchread") { 479 Feedback=String(touchRead(P1.toInt())); 480 } 481 else if (cmd=="resetwifi") { //重設網路連線 482 for (int i=0;i<2;i++) { 483 WiFi.begin(P1.c_str(), P2.c_str()); 484 Serial.print("Connecting to "); 485 Serial.println(P1); 486 long int StartTime=millis(); 487 while (WiFi.status() != WL_CONNECTED) { 488 delay(500); 489 if ((StartTime+5000) < millis()) break; 490 } 491 Serial.println(""); 492 Serial.println("STAIP: "+WiFi.localIP().toString()); 493 Feedback="STAIP: "+WiFi.localIP().toString(); 494 495 if (WiFi.status() == WL_CONNECTED) { 496 WiFi.softAP((WiFi.localIP().toString()+"_"+P1).c_str(), P2.c_str()); 497 for (int i=0;i<2;i++) { //若連不上WIFI設定閃光燈慢速閃爍 498 ledcWrite(4,10); 499 delay(300); 500 ledcWrite(4,0); 501 delay(300); 502 } 503 break; 504 } 505 } 506 } 507 else if (cmd=="flash") { //控制內建閃光燈 508 ledcAttachPin(4, 4); 509 ledcSetup(4, 5000, 8); 510 int val = P1.toInt(); 511 ledcWrite(4,val); 512 } 513 else if (cmd=="serial") { 514 //if (P1!=""&P1!="stop") Serial.println(P1); 515 //if (P2!=""&P2!="stop") Serial.println(P2); 516 //Serial.println(); 517 } 518 else { 519 Feedback="Command is not defined"; 520 } 521 522 if (Feedback=="") Feedback=Command; //若沒有設定回傳資料就回傳Command值 523 524 const char *resp = Feedback.c_str(); 525 httpd_resp_set_type(req, "text/html"); //設定回傳資料格式 526 httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); //允許跨網域讀取 527 return httpd_resp_send(req, resp, strlen(resp)); 528 } 529 else { 530 //官方指令區塊,也可在此自訂指令 http://192.168.xxx.xxx/control?var=xxx&val=xxx 531 int val = atoi(value); 532 sensor_t * s = esp_camera_sensor_get(); 533 int res = 0; 534 535 if(!strcmp(variable, "framesize")) { 536 if(s->pixformat == PIXFORMAT_JPEG) 537 res = s->set_framesize(s, (framesize_t)val); 538 } 539 else if(!strcmp(variable, "quality")) res = s->set_quality(s, val); 540 else if(!strcmp(variable, "contrast")) res = s->set_contrast(s, val); 541 else if(!strcmp(variable, "brightness")) res = s->set_brightness(s, val); 542 else if(!strcmp(variable, "hmirror")) res = s->set_hmirror(s, val); 543 else if(!strcmp(variable, "vflip")) res = s->set_vflip(s, val); 544 else if(!strcmp(variable, "flash")) { 545 ledcAttachPin(4, 4); 546 ledcSetup(4, 5000, 8); 547 ledcWrite(4,val); 548 } 549 else { 550 res = -1; 551 } 552 553 if(res){ 554 return httpd_resp_send_500(req); 555 } 556 557 if (buf) { 558 Feedback = String(buf); 559 const char *resp = Feedback.c_str(); 560 httpd_resp_set_type(req, "text/html"); 561 httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 562 return httpd_resp_send(req, resp, strlen(resp)); //回傳參數字串 563 } 564 else { 565 httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 566 return httpd_resp_send(req, NULL, 0); 567 } 568 } 569} 570 571//顯示視訊參數狀態(須回傳json格式載入初始設定) 572static esp_err_t status_handler(httpd_req_t *req){ 573 static char json_response[1024]; 574 575 sensor_t * s = esp_camera_sensor_get(); 576 char * p = json_response; 577 *p++ = '{'; 578 p+=sprintf(p, "\"flash\":%d,", 0); 579 p+=sprintf(p, "\"framesize\":%u,", s->status.framesize); 580 p+=sprintf(p, "\"quality\":%u,", s->status.quality); 581 p+=sprintf(p, "\"brightness\":%d,", s->status.brightness); 582 p+=sprintf(p, "\"contrast\":%d,", s->status.contrast); 583 p+=sprintf(p, "\"hmirror\":%u,", s->status.hmirror); 584 p+=sprintf(p, "\"vflip\":%u", s->status.vflip); 585 *p++ = '}'; 586 *p++ = 0; 587 httpd_resp_set_type(req, "application/json"); 588 httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 589 return httpd_resp_send(req, json_response, strlen(json_response)); 590} 591 592//自訂網頁首頁 593static const char PROGMEM INDEX_HTML[] = R"rawliteral(<!doctype html> 594<html> 595 <head> 596 <meta charset="utf-8"> 597 <meta name="viewport" content="width=device-width,initial-scale=1"> 598 <meta http-equiv="Access-Control-Allow-Headers" content="Origin, X-Requested-With, Content-Type, Accept"> 599 <meta http-equiv="Access-Control-Allow-Methods" content="GET,POST,PUT,DELETE,OPTIONS"> 600 <meta http-equiv="Access-Control-Allow-Origin" content="*"> 601 <title>Teachable Machine</title> 602 <style> 603 body{font-family:Arial,Helvetica,sans-serif;background:#181818;color:#EFEFEF;font-size:16px}h2{font-size:18px}section.main{display:flex}#menu,section.main{flex-direction:column}#menu{display:flex;flex-wrap:nowrap;min-width:340px;background:#363636;padding:8px;border-radius:4px;margin-top:-10px;margin-right:10px}#content{display:flex;flex-wrap:wrap;align-items:stretch}figure{padding:0;margin:0;-webkit-margin-before:0;margin-block-start:0;-webkit-margin-after:0;margin-block-end:0;-webkit-margin-start:0;margin-inline-start:0;-webkit-margin-end:0;margin-inline-end:0}figure img{display:block;width:100%;height:auto;border-radius:4px;margin-top:8px}@media (min-width: 800px) and (orientation:landscape){#content{display:flex;flex-wrap:nowrap;align-items:stretch}figure img{display:block;max-width:100%;max-height:calc(100vh - 40px);width:auto;height:auto}figure{padding:0;margin:0;-webkit-margin-before:0;margin-block-start:0;-webkit-margin-after:0;margin-block-end:0;-webkit-margin-start:0;margin-inline-start:0;-webkit-margin-end:0;margin-inline-end:0}}section#buttons{display:flex;flex-wrap:nowrap;justify-content:space-between}#nav-toggle{cursor:pointer;display:block}#nav-toggle-cb{outline:0;opacity:0;width:0;height:0}#nav-toggle-cb:checked+#menu{display:none}.input-group{display:flex;flex-wrap:nowrap;line-height:22px;margin:5px 0}.input-group>label{display:inline-block;padding-right:10px;min-width:47%}.input-group input,.input-group select{flex-grow:1}.range-max,.range-min{display:inline-block;padding:0 5px}button{display:block;margin:5px;padding:0 12px;border:0;line-height:28px;cursor:pointer;color:#fff;background:#ff3034;border-radius:5px;font-size:16px;outline:0}button:hover{background:#ff494d}button:active{background:#f21c21}button.disabled{cursor:default;background:#a0a0a0}input[type=range]{-webkit-appearance:none;width:100%;height:22px;background:#363636;cursor:pointer;margin:0}input[type=range]:focus{outline:0}input[type=range]::-webkit-slider-runnable-track{width:100%;height:2px;cursor:pointer;background:#EFEFEF;border-radius:0;border:0 solid #EFEFEF}input[type=range]::-webkit-slider-thumb{border:1px solid rgba(0,0,30,0);height:22px;width:22px;border-radius:50px;background:#ff3034;cursor:pointer;-webkit-appearance:none;margin-top:-11.5px}input[type=range]:focus::-webkit-slider-runnable-track{background:#EFEFEF}input[type=range]::-moz-range-track{width:100%;height:2px;cursor:pointer;background:#EFEFEF;border-radius:0;border:0 solid #EFEFEF}input[type=range]::-moz-range-thumb{border:1px solid rgba(0,0,30,0);height:22px;width:22px;border-radius:50px;background:#ff3034;cursor:pointer}input[type=range]::-ms-track{width:100%;height:2px;cursor:pointer;background:0 0;border-color:transparent;color:transparent}input[type=range]::-ms-fill-lower{background:#EFEFEF;border:0 solid #EFEFEF;border-radius:0}input[type=range]::-ms-fill-upper{background:#EFEFEF;border:0 solid #EFEFEF;border-radius:0}input[type=range]::-ms-thumb{border:1px solid rgba(0,0,30,0);height:22px;width:22px;border-radius:50px;background:#ff3034;cursor:pointer;height:2px}input[type=range]:focus::-ms-fill-lower{background:#EFEFEF}input[type=range]:focus::-ms-fill-upper{background:#363636}.switch{display:block;position:relative;line-height:22px;font-size:16px;height:22px}.switch input{outline:0;opacity:0;width:0;height:0}.slider{width:50px;height:22px;border-radius:22px;cursor:pointer;background-color:grey}.slider,.slider:before{display:inline-block;transition:.4s}.slider:before{position:relative;content:"";border-radius:50%;height:16px;width:16px;left:4px;top:3px;background-color:#fff}input:checked+.slider{background-color:#ff3034}input:checked+.slider:before{-webkit-transform:translateX(26px);transform:translateX(26px)}select{border:1px solid #363636;font-size:14px;height:22px;outline:0;border-radius:5px}.image-container{position:relative;min-width:160px}.close{position:absolute;right:5px;top:5px;background:#ff3034;width:16px;height:16px;border-radius:100px;color:#fff;text-align:center;line-height:18px;cursor:pointer}.hidden{display:none} 604 </style> 605 <script src="https:\/\/ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script> 606 <script src="https:\/\/cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.3.1/dist/tf.min.js"></script> 607 <script src="https:\/\/cdn.jsdelivr.net/npm/@teachablemachine/image@0.8/dist/teachablemachine-image.min.js"></script> 608 <script src="https:\/\/cdn.jsdelivr.net/npm/@teachablemachine/pose@0.8/dist/teachablemachine-pose.min.js"></script> 609 </head> 610 <body> 611 <section class="main"> 612 <figure> 613 <div id="stream-container" class="image-container hidden"> 614 <div class="close" id="close-stream" style="display:none">×</div> 615 <img id="stream" src="" style="display:none" crossorigin="anonymous"> 616 <canvas id="canvas" width="0" height="0"></canvas> 617 </div> 618 </figure> 619 <section id="buttons"> 620 <table> 621 <tr><td><button id="restart" onclick="try{fetch(document.location.origin+'/control?restart');}catch(e){}">Restart</button></td><td><button id="get-still" style="display:none">Get Still</button></td><td><button id="toggle-stream" style="display:none"></td></tr> 622 </table> 623 </section> 624 <div id="logo"> 625 <label for="nav-toggle-cb" id="nav-toggle">☰ Toggle settings</label> 626 </div> 627 <div id="content"> 628 <div id="sidebar"> 629 <input type="checkbox" id="nav-toggle-cb"> 630 <nav id="menu"> 631 <div class="input-group"> 632 <label for="kind">Kind</label> 633 <select id="kind"> 634 <option value="image">image</option> 635 <option value="pose">pose</option> 636 </select> 637 </div> 638 <div class="input-group"> 639 <label for="modelPath">Model Path</label> 640 <input type="text" id="modelPath" value=""> 641 </div> 642 <div class="input-group"> 643 <label for="btnModel"></label> 644 <button type="button" id="btnModel" onclick="LoadModel();">Start Recognition</button> 645 </div> 646 <div class="input-group" id="flash-group"> 647 <label for="flash">Flash</label> 648 <div class="range-min">0</div> 649 <input type="range" id="flash" min="0" max="255" value="0" class="default-action"> 650 <div class="range-max">255</div> 651 </div> 652 <div class="input-group" id="framesize-group"> 653 <label for="framesize">Resolution</label> 654 <select id="framesize" class="default-action"> 655 <option value="10">UXGA(1600x1200)</option> 656 <option value="9">SXGA(1280x1024)</option> 657 <option value="8">XGA(1024x768)</option> 658 <option value="7">SVGA(800x600)</option> 659 <option value="6">VGA(640x480)</option> 660 <option value="5" selected="selected">CIF(400x296)</option> 661 <option value="4">QVGA(320x240)</option> 662 <option value="3">HQVGA(240x176)</option> 663 <option value="0">QQVGA(160x120)</option> 664 </select> 665 </div> 666 <div class="input-group" id="quality-group"> 667 <label for="quality">Quality</label> 668 <div class="range-min">10</div> 669 <input type="range" id="quality" min="10" max="63" value="10" class="default-action"> 670 <div class="range-max">63</div> 671 </div> 672 <div class="input-group" id="brightness-group"> 673 <label for="brightness">Brightness</label> 674 <div class="range-min">-2</div> 675 <input type="range" id="brightness" min="-2" max="2" value="0" class="default-action"> 676 <div class="range-max">2</div> 677 </div> 678 <div class="input-group" id="contrast-group"> 679 <label for="contrast">Contrast</label> 680 <div class="range-min">-2</div> 681 <input type="range" id="contrast" min="-2" max="2" value="0" class="default-action"> 682 <div class="range-max">2</div> 683 </div> 684 <div class="input-group" id="hmirror-group"> 685 <label for="hmirror">H-Mirror</label> 686 <div class="switch"> 687 <input id="hmirror" type="checkbox" class="default-action" checked="checked"> 688 <label class="slider" for="hmirror"></label> 689 </div> 690 </div> 691 <div class="input-group" id="vflip-group"> 692 <label for="vflip">V-Flip</label> 693 <div class="switch"> 694 <input id="vflip" type="checkbox" class="default-action" checked="checked"> 695 <label class="slider" for="vflip"></label> 696 </div> 697 </div> 698 </nav> 699 </div> 700 </div> 701 </section> 702 <br> 703 <div id="result" style="color:red"><div> 704 705 <script> 706 document.addEventListener('DOMContentLoaded', function (event) { 707 var baseHost = document.location.origin 708 var streamUrl = baseHost + ':81' 709 const hide = el => { 710 el.classList.add('hidden') 711 } 712 const show = el => { 713 el.classList.remove('hidden') 714 } 715 const disable = el => { 716 el.classList.add('disabled') 717 el.disabled = true 718 } 719 const enable = el => { 720 el.classList.remove('disabled') 721 el.disabled = false 722 } 723 const updateValue = (el, value, updateRemote) => { 724 updateRemote = updateRemote == null ? true : updateRemote 725 let initialValue 726 if (el.type === 'checkbox') { 727 initialValue = el.checked 728 value = !!value 729 el.checked = value 730 } else { 731 initialValue = el.value 732 el.value = value 733 } 734 if (updateRemote && initialValue !== value) { 735 updateConfig(el); 736 } 737 } 738 function updateConfig (el) { 739 let value 740 switch (el.type) { 741 case 'checkbox': 742 value = el.checked ? 1 : 0 743 break 744 case 'range': 745 case 'select-one': 746 value = el.value 747 break 748 case 'button': 749 case 'submit': 750 value = '1' 751 break 752 default: 753 return 754 } 755 const query = `${baseHost}/control?var=${el.id}&val=${value}` 756 fetch(query) 757 .then(response => { 758 console.log(`request to ${query} finished, status: ${response.status}`) 759 }) 760 } 761 document 762 .querySelectorAll('.close') 763 .forEach(el => { 764 el.onclick = () => { 765 hide(el.parentNode) 766 } 767 }) 768 // read initial values 769 fetch(`${baseHost}/status`) 770 .then(function (response) { 771 return response.json() 772 }) 773 .then(function (state) { 774 document 775 .querySelectorAll('.default-action') 776 .forEach(el => { 777 updateValue(el, state[el.id], false) 778 }) 779 }) 780 const view = document.getElementById('stream') 781 const viewContainer = document.getElementById('stream-container') 782 const stillButton = document.getElementById('get-still') 783 const streamButton = document.getElementById('toggle-stream') 784 const closeButton = document.getElementById('close-stream') 785 const stopStream = () => { 786 //window.stop(); 787 view.src=""; 788 streamButton.innerHTML = 'Start Stream' 789 } 790 const startStream = () => { 791 view.src = `${streamUrl}/stream` 792 show(viewContainer) 793 streamButton.innerHTML = 'Stop Stream' 794 } 795 // Attach actions to buttons 796 stillButton.onclick = () => { 797 stopStream() 798 try{ 799 view.src = `${baseHost}/capture?_cb=${Date.now()}` 800 } 801 catch(e) { 802 view.src = `${baseHost}/capture?_cb=${Date.now()}` 803 } 804 show(viewContainer) 805 } 806 closeButton.onclick = () => { 807 stopStream() 808 hide(viewContainer) 809 } 810 streamButton.onclick = () => { 811 const streamEnabled = streamButton.innerHTML === 'Stop Stream' 812 if (streamEnabled) { 813 stopStream() 814 } else { 815 startStream() 816 } 817 } 818 // Attach default on change action 819 document 820 .querySelectorAll('.default-action') 821 .forEach(el => { 822 el.onchange = () => updateConfig(el) 823 }) 824 }) 825 </script> 826 827 <script> 828 var getStill = document.getElementById('get-still'); 829 var ShowImage = document.getElementById('stream'); 830 var canvas = document.getElementById("canvas"); 831 var context = canvas.getContext("2d"); 832 var modelPath = document.getElementById('modelPath'); 833 var result = document.getElementById('result'); 834 var kind = document.getElementById('kind'); 835 let Model; 836 837 async function LoadModel() { 838 if (modelPath.value=="") { 839 result.innerHTML = "Please input model path."; 840 return; 841 } 842 843 result.innerHTML = "Please wait for loading model."; 844 845 const URL = modelPath.value; 846 const modelURL = URL + "model.json"; 847 const metadataURL = URL + "metadata.json"; 848 if (kind.value=="image") { 849 Model = await tmImage.load(modelURL, metadataURL); 850 } 851 else if (kind.value=="pose") { 852 Model = await tmPose.load(modelURL, metadataURL); 853 } 854 maxPredictions = Model.getTotalClasses(); 855 result.innerHTML = ""; 856 857 getStill.style.display = "block"; 858 getStill.click(); 859 } 860 861 async function predict() { 862 var data = ""; 863 var maxClassName = ""; 864 var maxProbability = ""; 865 866 canvas.setAttribute("width", ShowImage.width); 867 canvas.setAttribute("height", ShowImage.height); 868 context.drawImage(ShowImage, 0, 0, ShowImage.width, ShowImage.height); 869 870 if (kind.value=="image") 871 var prediction = await Model.predict(canvas); 872 else if (kind.value=="pose") { 873 var { pose, posenetOutput } = await Model.estimatePose(canvas); 874 var prediction = await Model.predict(posenetOutput); 875 } 876 877 if (maxPredictions>0) { 878 for (let i = 0; i < maxPredictions; i++) { 879 if (i==0) { 880 maxClassName = prediction[i].className; 881 maxProbability = prediction[i].probability; 882 } 883 else { 884 if (prediction[i].probability>maxProbability) { 885 maxClassName = prediction[i].className; 886 maxProbability = prediction[i].probability; 887 } 888 } 889 data += prediction[i].className + "," + prediction[i].probability.toFixed(2) + "<br>"; 890 } 891 result.innerHTML = data; 892 result.innerHTML += "<br>Result: " + maxClassName + "," + maxProbability; 893 894 $.ajax({url: document.location.origin+'/control?serial='+maxClassName+";"+maxProbability+';stop', async: false}); 895 } 896 else 897 result.innerHTML = "Unrecognizable"; 898 899 getStill.click(); 900 } 901 902 ShowImage.onload = function (event) { 903 if (Model) { 904 try { 905 document.createEvent("TouchEvent"); 906 setTimeout(function(){predict();},250); 907 } 908 catch(e) { 909 predict(); 910 } 911 } 912 } 913 </script> 914 </body> 915</html>)rawliteral"; 916 917//網頁首頁 http://192.168.xxx.xxx 918static esp_err_t index_handler(httpd_req_t *req){ 919 httpd_resp_set_type(req, "text/html"); 920 return httpd_resp_send(req, (const char *)INDEX_HTML, strlen(INDEX_HTML)); 921} 922 923//自訂網址路徑要執行的函式 924void startCameraServer(){ 925 httpd_config_t config = HTTPD_DEFAULT_CONFIG(); //可在HTTPD_DEFAULT_CONFIG()中設定Server Port 926 927 //http://192.168.xxx.xxx/ 928 httpd_uri_t index_uri = { 929 .uri = "/", 930 .method = HTTP_GET, 931 .handler = index_handler, 932 .user_ctx = NULL 933 }; 934 935 //http://192.168.xxx.xxx/status 936 httpd_uri_t status_uri = { 937 .uri = "/status", 938 .method = HTTP_GET, 939 .handler = status_handler, 940 .user_ctx = NULL 941 }; 942 943 //http://192.168.xxx.xxx/control 944 httpd_uri_t cmd_uri = { 945 .uri = "/control", 946 .method = HTTP_GET, 947 .handler = cmd_handler, 948 .user_ctx = NULL 949 }; 950 951 //http://192.168.xxx.xxx/capture 952 httpd_uri_t capture_uri = { 953 .uri = "/capture", 954 .method = HTTP_GET, 955 .handler = capture_handler, 956 .user_ctx = NULL 957 }; 958 959 //http://192.168.xxx.xxx:81/stream 960 httpd_uri_t stream_uri = { 961 .uri = "/stream", 962 .method = HTTP_GET, 963 .handler = stream_handler, 964 .user_ctx = NULL 965 }; 966 967 Serial.printf("Starting web server on port: '%d'\n", config.server_port); //Server Port 968 if (httpd_start(&camera_httpd, &config) == ESP_OK) { 969 //註冊自訂網址路徑對應執行的函式 970 httpd_register_uri_handler(camera_httpd, &index_uri); 971 httpd_register_uri_handler(camera_httpd, &cmd_uri); 972 httpd_register_uri_handler(camera_httpd, &status_uri); 973 httpd_register_uri_handler(camera_httpd, &capture_uri); 974 } 975 976 config.server_port += 1; //Stream Port 977 config.ctrl_port += 1; //UDP Port 978 Serial.printf("Starting stream server on port: '%d'\n", config.server_port); 979 if (httpd_start(&stream_httpd, &config) == ESP_OK) { 980 httpd_register_uri_handler(stream_httpd, &stream_uri); 981 } 982} 983 984//自訂指令拆解參數字串置入變數 985void getCommand(char c) 986{ 987 if (c=='?') ReceiveState=1; 988 if ((c==' ')||(c=='\r')||(c=='\n')) ReceiveState=0; 989 990 if (ReceiveState==1) 991 { 992 Command=Command+String(c); 993 994 if (c=='=') cmdState=0; 995 if (c==';') strState++; 996 997 if ((cmdState==1)&&((c!='?')||(questionstate==1))) cmd=cmd+String(c); 998 if ((cmdState==0)&&(strState==1)&&((c!='=')||(equalstate==1))) P1=P1+String(c); 999 if ((cmdState==0)&&(strState==2)&&(c!=';')) P2=P2+String(c); 1000 if ((cmdState==0)&&(strState==3)&&(c!=';')) P3=P3+String(c); 1001 if ((cmdState==0)&&(strState==4)&&(c!=';')) P4=P4+String(c); 1002 if ((cmdState==0)&&(strState==5)&&(c!=';')) P5=P5+String(c); 1003 if ((cmdState==0)&&(strState==6)&&(c!=';')) P6=P6+String(c); 1004 if ((cmdState==0)&&(strState==7)&&(c!=';')) P7=P7+String(c); 1005 if ((cmdState==0)&&(strState==8)&&(c!=';')) P8=P8+String(c); 1006 if ((cmdState==0)&&(strState>=9)&&((c!=';')||(semicolonstate==1))) P9=P9+String(c); 1007 1008 if (c=='?') questionstate=1; 1009 if (c=='=') equalstate=1; 1010 if ((strState>=9)&&(c==';')) semicolonstate=1; 1011 } 1012}
Comments
Only logged in users can leave comments