diff --git a/hardware/esp32-cam-udp/.gitignore b/hardware/esp32-cam-udp/.gitignore new file mode 100644 index 00000000..03f4a3c1 --- /dev/null +++ b/hardware/esp32-cam-udp/.gitignore @@ -0,0 +1 @@ +.pio diff --git a/hardware/esp32-cam-udp/README.md b/hardware/esp32-cam-udp/README.md new file mode 100644 index 00000000..5629241d --- /dev/null +++ b/hardware/esp32-cam-udp/README.md @@ -0,0 +1,5 @@ +# ESP-CAM-UDP + +ESP32→(UDP)→NodeJS→(WebSocket)→browser + +800x600 30fps 200ms diff --git a/hardware/esp32-cam-udp/package-lock.json b/hardware/esp32-cam-udp/package-lock.json new file mode 100644 index 00000000..414b85d6 --- /dev/null +++ b/hardware/esp32-cam-udp/package-lock.json @@ -0,0 +1,32 @@ +{ + "name": "eye", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "ws": "^8.14.2" + } + }, + "node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/hardware/esp32-cam-udp/package.json b/hardware/esp32-cam-udp/package.json new file mode 100644 index 00000000..b082e9a7 --- /dev/null +++ b/hardware/esp32-cam-udp/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "ws": "^8.14.2" + } +} diff --git a/hardware/esp32-cam-udp/platformio.ini b/hardware/esp32-cam-udp/platformio.ini new file mode 100644 index 00000000..b076f523 --- /dev/null +++ b/hardware/esp32-cam-udp/platformio.ini @@ -0,0 +1,11 @@ +[env:main] +platform=espressif32 +framework=arduino +board=esp32cam +; board_build.filesystem=littlefs +targets=upload +lib_deps= + esp32-camera + ; https://github.com/dvarrel/AsyncTCP.git + ; https://github.com/dvarrel/ESPAsyncWebSrv.git + diff --git a/hardware/esp32-cam-udp/src/main.cpp b/hardware/esp32-cam-udp/src/main.cpp new file mode 100644 index 00000000..23b4c4be --- /dev/null +++ b/hardware/esp32-cam-udp/src/main.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include + +#define HOST "toko-minibookx" +#define PORT 3333 +#define SIZE 1024 + +WiFiUDP udp; +IPAddress host; + +static camera_config_t cam_cfg={// https://github.com/espressif/esp-who/blob/master/docs/en/Camera_connections.md + .pin_pwdn=-1,.pin_reset=-1, + .pin_xclk=4,.pin_sccb_sda=18,.pin_sccb_scl=23, + + .pin_d7=36,.pin_d6=37,.pin_d5=38,.pin_d4=39,.pin_d3=35,.pin_d2=14,.pin_d1=13,.pin_d0=34, + .pin_vsync=5,.pin_href=27,.pin_pclk=25, + + .xclk_freq_hz=20000000,//EXPERIMENTAL: Set to 16MHz on ESP32-S2 or ESP32-S3 to enable EDMA mode + .ledc_timer=LEDC_TIMER_0,.ledc_channel=LEDC_CHANNEL_0, + + .pixel_format=PIXFORMAT_JPEG,//YUV422,GRAYSCALE,RGB565,JPEG + .frame_size=FRAMESIZE_VGA,//QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates. + + .jpeg_quality=12, //0-63, for OV series camera sensors, lower number means higher quality + .fb_count=2, //When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode. + .grab_mode=CAMERA_GRAB_WHEN_EMPTY//CAMERA_GRAB_LATEST. Sets when buffers should be filled +}; + +void setup(){ + psramInit();pinMode(21,OUTPUT);pinMode(22,OUTPUT); + + WiFi.begin(); + for(uint8_t i=0;WiFi.status()!=WL_CONNECTED;i++){ + if(i>20){ + digitalWrite(21,HIGH); + WiFi.beginSmartConfig();while(!WiFi.smartConfigDone()); + } + delay(500); + } + MDNS.begin("udp-cam"); + host=MDNS.queryHost(HOST); + digitalWrite(21,LOW); + esp_camera_init(&cam_cfg); + digitalWrite(22,HIGH); +} + +void loop(){ + camera_fb_t *fb=esp_camera_fb_get(); + const uint8_t *t=(uint8_t*)(&fb->timestamp.tv_usec); + uint8_t n=(fb->len+SIZE-1)/SIZE; + + udp.beginPacket(host,PORT);udp.printf("STRT");udp.write(t,4);udp.write(n);udp.endPacket(); + for(uint8_t i=0;ibuf+SIZE*i,i+1==n?fb->len-SIZE*i:SIZE);udp.endPacket(); + } + esp_camera_fb_return(fb); +} diff --git a/hardware/esp32-cam-udp/svr.mjs b/hardware/esp32-cam-udp/svr.mjs new file mode 100644 index 00000000..ba8f8fae --- /dev/null +++ b/hardware/esp32-cam-udp/svr.mjs @@ -0,0 +1,68 @@ +import * as DGRAM from 'dgram'; +import * as HTTP from 'http'; +import * as WS from 'ws'; + +const +udp=DGRAM.createSocket('udp4'), +svr=HTTP.createServer((req,res)=>( + res.writeHead(200,{'Content-Type':'text/html'}), + res.end(` + + + + + + video + + + + + + + + `) +)), +ws=new Set(), +wss=new WS.WebSocketServer({server:svr,path:'/ws'}); + +wss.on('connection',_=>ws.add(_)); +wss.on('close',_=>ws.delete(_)); + +let fb={},t; +udp.on('message',(x,i)=>( + x={ + tag:x.subarray(0,4)+'', + t:x.readUInt32LE(4), + i:x[8], + x:x.subarray(9) + }, + ({ + STRT:_=>(fb[x.t]=[...Array(x.i)],setTimeout(_=>delete fb[x.t],500)), + DATA:_=>fb[x.t]&&( + fb[x.t][x.i]=x.x, + fb[x.t].every(_=>_)&&( + _=Buffer.concat(fb[x.t]),delete fb[x.t],ws.forEach(x=>x.send(_)), + console.log({fps:Math.round(1000/(-t+(t=Date.now()))),size:_.length,t:x.t}) + ) + ), + }[x.tag])() + // console.log(x+'',i.address,i.port) +)); +udp.on('listening',_=>console.log(`port ${udp.address().port} ...`)); +udp.bind(3333); +svr.listen(80); \ No newline at end of file