一、前言
1.1 功能介紹
隨著物聯(lián)網(wǎng)技術(shù)的飛速發(fā)展,無線視頻傳輸在各類智能設(shè)備中逐漸成為標(biāo)準(zhǔn)功能。傳統(tǒng)的視頻監(jiān)控系統(tǒng)通常依賴有線連接,限制了設(shè)備的靈活性和安裝位置。在智能家居、遙控小車、掃地機(jī)器人等應(yīng)用場(chǎng)景中,視頻流的無線傳輸顯得尤為重要。為了實(shí)現(xiàn)這一目標(biāo),ESP32-Camera模塊作為一種低成本、功能強(qiáng)大的硬件平臺(tái),成為了一個(gè)理想選擇。它不僅具備強(qiáng)大的無線通信能力(Wi-Fi和藍(lán)牙),而且內(nèi)置攝像頭能夠進(jìn)行實(shí)時(shí)圖像采集,廣泛應(yīng)用于遙控小車視頻傳輸、居家監(jiān)控以及掃地機(jī)器人的視頻傳輸?shù)葓?chǎng)景。
ESP32-Camera模塊的設(shè)計(jì)非常適合嵌入式視頻流傳輸應(yīng)用。它的易用性、低功耗和集成化特點(diǎn),使得用戶可以輕松地將其集成到各種設(shè)備中,免去復(fù)雜的硬件設(shè)計(jì)和開發(fā)。購買該模塊后,用戶無需編寫固件,模塊便會(huì)自動(dòng)創(chuàng)建一個(gè)Wi-Fi熱點(diǎn),設(shè)備連接后通過瀏覽器即可訪問圖像捕捉和視頻流傳輸功能,這為開發(fā)者提供了極大的便利。在這種預(yù)設(shè)的固件功能下,用戶可以直接使用模塊進(jìn)行簡單的圖像和視頻流顯示,無需過多的配置或編程。
為了滿足更多靈活的需求,提升用戶體驗(yàn),本項(xiàng)目通過Qt開發(fā)設(shè)計(jì)一個(gè)適用于Windows電腦端和Android手機(jī)端的上位機(jī)應(yīng)用程序。該應(yīng)用能夠?qū)崟r(shí)獲取ESP32-Camera模塊的視頻流,并顯示在設(shè)備的界面上。這不僅簡化了圖像數(shù)據(jù)的管理和展示,還為用戶提供了更友好的操作界面,使得視頻流傳輸過程更加流暢、直觀,方便集成到各種實(shí)際應(yīng)用中。
本項(xiàng)目通過無線局域網(wǎng)傳輸技術(shù),解決傳統(tǒng)視頻監(jiān)控系統(tǒng)中有線連接的局限性,并通過智能設(shè)備實(shí)時(shí)顯示視頻流,進(jìn)一步提升遙控小車、掃地機(jī)器人等智能設(shè)備的功能性和互動(dòng)性。在居家環(huán)境中,基于ESP32-Camera的無線視頻傳輸系統(tǒng)能夠帶來更加便捷的監(jiān)控解決方案,滿足現(xiàn)代家庭對(duì)安全、便利和高效的需求。
硬件模塊如下:
瀏覽器訪問效果:
手機(jī)APP訪問效果:
1.2 ESP32-Camera模塊
ESP32-Camera模塊是一款集成了ESP32微控制器和攝像頭的開發(fā)板,適用于需要圖像捕捉和處理的項(xiàng)目。該模塊通常采用OV2640或V3660的CMOS圖像傳感器,支持分辨率從VGA到較高分辨率的圖像捕捉。它能夠通過內(nèi)置的Wi-Fi和藍(lán)牙功能與其他設(shè)備進(jìn)行無線通信,因此特別適用于物聯(lián)網(wǎng)(IoT)項(xiàng)目中的視頻監(jiān)控、遠(yuǎn)程監(jiān)控、圖像處理和智能安防等應(yīng)用。
ESP32-Camera模塊提供了多個(gè)I/O接口,支持GPIO、PWM、I2C、SPI等通信協(xié)議,這使得它在嵌入式系統(tǒng)中具有很高的靈活性。此外,模塊還集成了一個(gè)小型的SD卡插槽,用于存儲(chǔ)拍攝的圖像或視頻。它的低功耗特性也使其適用于需要長時(shí)間工作的應(yīng)用場(chǎng)景。
由于ESP32本身具備較強(qiáng)的計(jì)算能力和可編程性,ESP32-Camera模塊能夠進(jìn)行簡單的圖像處理任務(wù),如邊緣檢測(cè)、人臉識(shí)別等。開發(fā)者可以利用ESP32的處理能力直接在設(shè)備上執(zhí)行一些計(jì)算任務(wù),減少了對(duì)云端計(jì)算資源的依賴。
ESP32-Camera模塊非常適合于DIY項(xiàng)目和原型開發(fā),它擁有良好的社區(qū)支持和豐富的開發(fā)資源。開發(fā)者可以利用Arduino IDE或ESP-IDF進(jìn)行開發(fā),并可以使用攝像頭模塊進(jìn)行圖像捕獲、視頻流傳輸?shù)炔僮?。由于其低成本和廣泛的兼容性,ESP32-Camera在市場(chǎng)上非常受歡迎,并廣泛應(yīng)用于各種智能家居和物聯(lián)網(wǎng)設(shè)備中。
1.3 功能總結(jié)
利用ESP32-Camera模塊做一個(gè)局域網(wǎng)無線圖傳功能,用于遙控小車視頻傳輸,掃地機(jī)器人視頻傳輸、居家監(jiān)控等等場(chǎng)景。 ESP32-Camera模塊 買回來以后,本身自帶固件,不需要編程;上電之后,ESP32-Camera模塊就會(huì)創(chuàng)建一個(gè)名字為ESP32
開頭的熱點(diǎn),同時(shí)在ESP32-CAM內(nèi)部創(chuàng)建了一個(gè)HTTP服務(wù)器。手機(jī)或者電腦連接上熱點(diǎn)以后,打開瀏覽器,輸入IP地址192.168.4.1
?就可以看到一個(gè)網(wǎng)頁。 在網(wǎng)頁上可以完成拍照、視頻瀏覽、圖像尺寸設(shè)置等等。 為了更加靈活使用模塊,本項(xiàng)目利用采用Qt開發(fā)設(shè)計(jì),設(shè)計(jì)了Windows電腦端、Android手機(jī)APP端的上位機(jī),獲取顯示視頻流,實(shí)時(shí)顯示視頻,方便集成到項(xiàng)目中。
熱點(diǎn)名稱:
網(wǎng)頁瀏覽器效果:
1.4 講解視頻
【基于ESP32-CAM實(shí)現(xiàn)局域網(wǎng)WIFI圖傳】
https://www.bilibili.com/video/BV161cDeMED8/?share_source=copy_web&vd_source=347136f3e32fe297fc17177194ce0a8b
1.5 APP下載
我用夸克網(wǎng)盤分享了「基于ESP32-CAM設(shè)計(jì)的遠(yuǎn)程攝像頭_圖傳APP.zip」,點(diǎn)擊鏈接即可保存。
鏈接:https://pan.quark.cn/s/b66c9c876fb5
二、攝像頭模塊
當(dāng)前項(xiàng)目設(shè)計(jì)的時(shí)候,購買的模塊是這一款【默認(rèn)有固件,買回來插電就可以使用,不需要燒錄代碼,不需要編程】。
我當(dāng)前使用的這款的攝像頭是OV3660像素清晰一些,帶OV2640攝像頭模塊是20塊錢。
三、代碼設(shè)計(jì)思路
3.1 前提條件
本項(xiàng)目采用Qt5開發(fā),需要安裝好QT5開發(fā)環(huán)境即可。項(xiàng)目當(dāng)前使用的Qt版本是5.12.6
?。
3.2 打開工程
資源包解壓之后,將工程工程放在純英文路徑下。 雙擊打開工程
3.3 選擇編譯器
第一次打開工程,會(huì)讓你選擇編譯器。 看下面:我選擇了2個(gè)。 一個(gè)是Windows電腦的,一個(gè)是Android手機(jī)的。 如果你的QT環(huán)境沒有搭建Android環(huán)境,那就選擇Windows的編譯器。?MinGW
?即可。
3.4 編譯Windows版本
選擇左下角的編譯器。
再點(diǎn)擊綠色三角形。
運(yùn)行之后,電腦上也要連接ESP32的WIFI熱點(diǎn),連接之后,點(diǎn)擊開始播放即可。
然后點(diǎn)擊開始播放之后,看到的視頻畫面。
3.5 編譯Android的版本
左下角選擇編譯器,再進(jìn)行構(gòu)建。
構(gòu)建成功就看到生成的APK文件??梢灾苯涌截惖紸ndroid手機(jī)上安裝使用。
將APK安裝包拷貝到Android手機(jī)安裝之后。連接ESP32攝像頭板子的熱點(diǎn),連接之后,打開手機(jī)APP,點(diǎn)擊開始播放,就可以在手機(jī)上看到圖像了。
四、網(wǎng)頁代碼
這是ESP32-CAM的顯示視頻流的網(wǎng)頁代碼,從里面可以了解到如何與服務(wù)器交互的流程:?在ESP32-CAM內(nèi)部是創(chuàng)建了一個(gè)HTTP服務(wù)器。
<!DOCTYPE?html>
<!--?saved?from?url=(0019)http://192.168.4.1/?-->
<html><head><meta?http-equiv="Content-Type"?content="text/html;?charset=UTF-8">
????????
????????<meta?name="viewport"?content="width=device-width,initial-scale=1">
????????<title>ESP32?OV3660</title>
????????<style>
????????????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:?none;
????????????????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:?0px;
????????????????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?0?0?0px;
????????????????????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:?flex
????????????}
????????????.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
????????????}
????????????input[type=text]?{
????????????????border:?1px?solid?#363636;
????????????????font-size:?14px;
????????????????height:?20px;
????????????????margin:?1px;
????????????????outline:?0;
????????????????border-radius:?5px
????????????}
????????????.inline-button?{
????????????????line-height:?20px;
????????????????margin:?2px;
????????????????padding:?1px?4px?2px?4px;
????????????}
????????</style>
????</head>
????<body>
????????<section?class="main">
????????????<div?id="logo">
????????????????<label?for="nav-toggle-cb"?id="nav-toggle">? Toggle?OV3660?settings</label>
????????????</div>
????????????<div?id="content">
????????????????<div?id="sidebar">
????????????????????<input?type="checkbox"?id="nav-toggle-cb"?checked="checked">
????????????????????<nav?id="menu">
????????????????????????<div?class="input-group"?id="framesize-group">
????????????????????????????<label?for="framesize">Resolution</label>
????????????????????????????<select?id="framesize"?class="default-action">
????????????????????????????????<option?value="11">QXGA(2048x1564)</option>
????????????????????????????????<option?value="10">UXGA(1600x1200)</option>
????????????????????????????????<option?value="9">SXGA(1280x1024)</option>
????????????????????????????????<option?value="8">XGA(1024x768)</option>
????????????????????????????????<option?value="7">SVGA(800x600)</option>
????????????????????????????????<option?value="6">VGA(640x480)</option>
????????????????????????????????<option?value="5"?selected="selected">CIF(400x296)</option>
????????????????????????????????<option?value="4">QVGA(320x240)</option>
????????????????????????????????<option?value="3">HQVGA(240x176)</option>
????????????????????????????????<option?value="0">QQVGA(160x120)</option>
????????????????????????????</select>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="quality-group">
????????????????????????????<label?for="quality">Quality</label>
????????????????????????????<div?class="range-min">4</div>
????????????????????????????<input?type="range"?id="quality"?min="4"?max="63"?value="10"?class="default-action">
????????????????????????????<div?class="range-max">63</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="brightness-group">
????????????????????????????<label?for="brightness">Brightness</label>
????????????????????????????<div?class="range-min">-3</div>
????????????????????????????<input?type="range"?id="brightness"?min="-3"?max="3"?value="0"?class="default-action">
????????????????????????????<div?class="range-max">3</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="contrast-group">
????????????????????????????<label?for="contrast">Contrast</label>
????????????????????????????<div?class="range-min">-3</div>
????????????????????????????<input?type="range"?id="contrast"?min="-3"?max="3"?value="0"?class="default-action">
????????????????????????????<div?class="range-max">3</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="saturation-group">
????????????????????????????<label?for="saturation">Saturation</label>
????????????????????????????<div?class="range-min">-4</div>
????????????????????????????<input?type="range"?id="saturation"?min="-4"?max="4"?value="0"?class="default-action">
????????????????????????????<div?class="range-max">4</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="sharpness-group">
????????????????????????????<label?for="sharpness">Sharpness</label>
????????????????????????????<div?class="range-min">-3</div>
????????????????????????????<input?type="range"?id="sharpness"?min="-3"?max="3"?value="0"?class="default-action">
????????????????????????????<div?class="range-max">3</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="denoise-group">
????????????????????????????<label?for="denoise">De-Noise</label>
????????????????????????????<div?class="range-min">Auto</div>
????????????????????????????<input?type="range"?id="denoise"?min="0"?max="8"?value="0"?class="default-action">
????????????????????????????<div?class="range-max">8</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="ae_level-group">
????????????????????????????<label?for="ae_level">Exposure?Level</label>
????????????????????????????<div?class="range-min">-5</div>
????????????????????????????<input?type="range"?id="ae_level"?min="-5"?max="5"?value="0"?class="default-action">
????????????????????????????<div?class="range-max">5</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="gainceiling-group">
????????????????????????????<label?for="gainceiling">Gainceiling</label>
????????????????????????????<div?class="range-min">0</div>
????????????????????????????<input?type="range"?id="gainceiling"?min="0"?max="511"?value="0"?class="default-action">
????????????????????????????<div?class="range-max">511</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="special_effect-group">
????????????????????????????<label?for="special_effect">Special?Effect</label>
????????????????????????????<select?id="special_effect"?class="default-action">
????????????????????????????????<option?value="0"?selected="selected">No?Effect</option>
????????????????????????????????<option?value="1">Negative</option>
????????????????????????????????<option?value="2">Grayscale</option>
????????????????????????????????<option?value="3">Red?Tint</option>
????????????????????????????????<option?value="4">Green?Tint</option>
????????????????????????????????<option?value="5">Blue?Tint</option>
????????????????????????????????<option?value="6">Sepia</option>
????????????????????????????</select>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="awb-group">
????????????????????????????<label?for="awb">AWB?Enable</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="awb"?type="checkbox"?class="default-action"?checked="checked">
????????????????????????????????<label?class="slider"?for="awb"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="dcw-group">
????????????????????????????<label?for="dcw">Advanced?AWB</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="dcw"?type="checkbox"?class="default-action"?checked="checked">
????????????????????????????????<label?class="slider"?for="dcw"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="awb_gain-group">
????????????????????????????<label?for="awb_gain">Manual?AWB</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="awb_gain"?type="checkbox"?class="default-action"?checked="checked">
????????????????????????????????<label?class="slider"?for="awb_gain"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group?hidden"?id="wb_mode-group">
????????????????????????????<label?for="wb_mode">AWB?Mode</label>
????????????????????????????<select?id="wb_mode"?class="default-action">
????????????????????????????????<option?value="0"?selected="selected">Auto</option>
????????????????????????????????<option?value="1">Sunny</option>
????????????????????????????????<option?value="2">Cloudy</option>
????????????????????????????????<option?value="3">Office</option>
????????????????????????????????<option?value="4">Home</option>
????????????????????????????</select>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="aec-group">
????????????????????????????<label?for="aec">AEC?Enable</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="aec"?type="checkbox"?class="default-action"?checked="checked">
????????????????????????????????<label?class="slider"?for="aec"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group?hidden"?id="aec_value-group">
????????????????????????????<label?for="aec_value">Manual?Exposure</label>
????????????????????????????<div?class="range-min">0</div>
????????????????????????????<input?type="range"?id="aec_value"?min="0"?max="1536"?value="320"?class="default-action">
????????????????????????????<div?class="range-max">1536</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="aec2-group">
????????????????????????????<label?for="aec2">Night?Mode</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="aec2"?type="checkbox"?class="default-action"?checked="checked">
????????????????????????????????<label?class="slider"?for="aec2"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="agc-group">
????????????????????????????<label?for="agc">AGC</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="agc"?type="checkbox"?class="default-action"?checked="checked">
????????????????????????????????<label?class="slider"?for="agc"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group?hidden"?id="agc_gain-group">
????????????????????????????<label?for="agc_gain">Gain</label>
????????????????????????????<div?class="range-min">1x</div>
????????????????????????????<input?type="range"?id="agc_gain"?min="0"?max="64"?value="5"?class="default-action">
????????????????????????????<div?class="range-max">64x</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="raw_gma-group">
????????????????????????????<label?for="raw_gma">GMA?Enable</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="raw_gma"?type="checkbox"?class="default-action"?checked="checked">
????????????????????????????????<label?class="slider"?for="raw_gma"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="lenc-group">
????????????????????????????<label?for="lenc">Lens?Correction</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="lenc"?type="checkbox"?class="default-action"?checked="checked">
????????????????????????????????<label?class="slider"?for="lenc"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="hmirror-group">
????????????????????????????<label?for="hmirror">H-Mirror</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="hmirror"?type="checkbox"?class="default-action"?checked="checked">
????????????????????????????????<label?class="slider"?for="hmirror"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="vflip-group">
????????????????????????????<label?for="vflip">V-Flip</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="vflip"?type="checkbox"?class="default-action"?checked="checked">
????????????????????????????????<label?class="slider"?for="vflip"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="bpc-group">
????????????????????????????<label?for="bpc">BPC</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="bpc"?type="checkbox"?class="default-action">
????????????????????????????????<label?class="slider"?for="bpc"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="wpc-group">
????????????????????????????<label?for="wpc">WPC</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="wpc"?type="checkbox"?class="default-action"?checked="checked">
????????????????????????????????<label?class="slider"?for="wpc"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="colorbar-group">
????????????????????????????<label?for="colorbar">Color?Bar</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="colorbar"?type="checkbox"?class="default-action">
????????????????????????????????<label?class="slider"?for="colorbar"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="face_detect-group">
????????????????????????????<label?for="face_detect">Face?Detection</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="face_detect"?type="checkbox"?class="default-action">
????????????????????????????????<label?class="slider"?for="face_detect"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<div?class="input-group"?id="face_recognize-group">
????????????????????????????<label?for="face_recognize">Face?Recognition</label>
????????????????????????????<div?class="switch">
????????????????????????????????<input?id="face_recognize"?type="checkbox"?class="default-action">
????????????????????????????????<label?class="slider"?for="face_recognize"></label>
????????????????????????????</div>
????????????????????????</div>
????????????????????????<section?id="buttons">
????????????????????????????<button?id="get-still">Get?Still</button>
????????????????????????????<button?id="toggle-stream">Start?Stream</button>
????????????????????????????<button?id="face_enroll"?class="disabled"?disabled="">Enroll?Face</button>
????????????????????????</section>
????????????????????</nav>
????????????????</div>
????????????????<figure>
????????????????????<div?id="stream-container"?class="image-container?hidden">
????????????????????????<div?class="close"?id="close-stream">×</div>
????????????????????????<img?id="stream"?src="http://192.168.4.1/">
????????????????????</div>
????????????????</figure>
????????????</div>
????????</section>
????????<script>
document.addEventListener('DOMContentLoaded',?function?(event)?
{
??var?baseHost?=?document.location.origin
??var?streamUrl?=?baseHost?+?':81'
??const?hide?=?el?=>?{
????el.classList.add('hidden')
??}
??const?show?=?el?=>?{
????el.classList.remove('hidden')
??}
??const?disable?=?el?=>?{
????el.classList.add('disabled')
????el.disabled?=?true
??}
??const?enable?=?el?=>?{
????el.classList.remove('disabled')
????el.disabled?=?false
??}
??const?updateValue?=?(el,?value,?updateRemote)?=>?{
????updateRemote?=?updateRemote?==?null???true?:?updateRemote
????let?initialValue
????if?(el.type?===?'checkbox')?{
??????initialValue?=?el.checked
??????value?=?!!value
??????el.checked?=?value
????}?else?{
??????initialValue?=?el.value
??????el.value?=?value
????}
????if?(updateRemote?&&?initialValue?!==?value)?{
??????updateConfig(el);
????}?else?if(!updateRemote){
??????if(el.id?===?"aec"){
????????value???hide(exposure)?:?show(exposure)
??????}?else?if(el.id?===?"agc"){
????????if?(value)?{
??????????hide(agcGain)
????????}?else?{
??????????show(agcGain)
????????}
??????}?else?if(el.id?===?"awb_gain"){
????????value???show(wb)?:?hide(wb)
??????}?else?if(el.id?===?"face_recognize"){
????????value???enable(enrollButton)?:?disable(enrollButton)
??????}
????}
??}
??function?updateConfig?(el)?{
????let?value
????switch?(el.type)?{
??????case?'checkbox':
????????value?=?el.checked???1?:?0
????????break
??????case?'range':
??????case?'select-one':
????????value?=?el.value
????????break
??????case?'button':
??????case?'submit':
????????value?=?'1'
????????break
??????default:
????????return
????}
????const?query?=?`${baseHost}/control?var=${el.id}&val=${value}`
????console.log(`request?to?${query}`)
????fetch(query)
??????.then(response?=>?{
????????console.log(`request?to?${query}?finished,?status:?${response.status}`)
??????})
??}
??document
????.querySelectorAll('.close')
????.forEach(el?=>?{
??????el.onclick?=?()?=>?{
????????hide(el.parentNode)
??????}
????})
??//?read?initial?values
??fetch(`${baseHost}/status`)
????.then(function?(response)?{
??????return?response.json()
????})
????.then(function?(state)?{
??????document
????????.querySelectorAll('.default-action')
????????.forEach(el?=>?{
????????????updateValue(el,?state[el.id],?false)
????????})
????})
??const?view?=?document.getElementById('stream')
??const?viewContainer?=?document.getElementById('stream-container')
??const?stillButton?=?document.getElementById('get-still')
??const?streamButton?=?document.getElementById('toggle-stream')
??const?enrollButton?=?document.getElementById('face_enroll')
??const?closeButton?=?document.getElementById('close-stream')
??const?stopStream?=?()?=>?{
????window.stop();
????streamButton.innerHTML?=?'Start?Stream'
??}
??const?startStream?=?()?=>?{
????view.src?=?`${streamUrl}/stream`
????console.log(`${streamUrl}/stream`)
????show(viewContainer)
????streamButton.innerHTML?=?'Stop?Stream'
??}
??//?Attach?actions?to?buttons
??stillButton.onclick?=?()?=>?{
????stopStream()
????view.src?=?`${baseHost}/capture?_cb=${Date.now()}`
????
????console.log(`${baseHost}/capture?_cb=${Date.now()}`)
????show(viewContainer)
??}
??closeButton.onclick?=?()?=>?{
????stopStream()
????hide(viewContainer)
??}
??streamButton.onclick?=?()?=>?{
????const?streamEnabled?=?streamButton.innerHTML?===?'Stop?Stream'
????if?(streamEnabled)?{
??????stopStream()
????}?else?{
??????startStream()
????}
??}
??enrollButton.onclick?=?()?=>?{
????updateConfig(enrollButton)
??}
??//?Attach?default?on?change?action
??document
????.querySelectorAll('.default-action')
????.forEach(el?=>?{
??????el.onchange?=?()?=>?updateConfig(el)
????})
??//?Custom?actions
??//?Gain
??const?agc?=?document.getElementById('agc')
??const?agcGain?=?document.getElementById('agc_gain-group')
??agc.onchange?=?()?=>?{
????updateConfig(agc)
????if?(agc.checked)?{
??????hide(agcGain)
????}?else?{
??????show(agcGain)
????}
??}
??//?Exposure
??const?aec?=?document.getElementById('aec')
??const?exposure?=?document.getElementById('aec_value-group')
??aec.onchange?=?()?=>?{
????updateConfig(aec)
????aec.checked???hide(exposure)?:?show(exposure)
??}
??//?AWB
??const?awb?=?document.getElementById('awb_gain')
??const?wb?=?document.getElementById('wb_mode-group')
??awb.onchange?=?()?=>?{
????updateConfig(awb)
????awb.checked???show(wb)?:?hide(wb)
??}
??//?Detection?and?framesize
??const?detect?=?document.getElementById('face_detect')
??const?recognize?=?document.getElementById('face_recognize')
??const?framesize?=?document.getElementById('framesize')
??framesize.onchange?=?()?=>?{
????updateConfig(framesize)
????if?(framesize.value?>?5)?{
??????updateValue(detect,?false)
??????updateValue(recognize,?false)
????}
??}
??detect.onchange?=?()?=>?{
????if?(framesize.value?>?5)?{
??????alert("Please?select?CIF?or?lower?resolution?before?enabling?this?feature!");
??????updateValue(detect,?false)
??????return;
????}
????updateConfig(detect)
????if?(!detect.checked)?{
??????disable(enrollButton)
??????updateValue(recognize,?false)
????}
??}?
??recognize.onchange?=?()?=>?{
????if?(framesize.value?>?5)?{
??????alert("Please?select?CIF?or?lower?resolution?before?enabling?this?feature!");
??????updateValue(recognize,?false)
??????return;
????}
????updateConfig(recognize)
????if?(recognize.checked)?{
??????enable(enrollButton)
??????updateValue(detect,?true)
????}?else?{
??????disable(enrollButton)
????}
??}
})
????????</script>
????
</body></html>