Convert from gzip (in ASCII) to html (in ASCII)

Not necessarily a programming question, but there a lot of folks on this forum who are far more knowledgeable in html, web servers, TCP, etc than I am. So I wouldn't be surprised if someone knows the answer.

I'm in the process of dissecting the ESP32-CAM "CameraWebServer" example to understand how it works. It looks like the main web page is served by the ESP32 in compressed (gzip) format. Here's what the beginning of the big array in 'camera_index.h' looks like:

const uint8_t index_ov2640_html_gz[] = {
 0x1F, 0x8B, 0x08, 0x08, 0x23, 0xFC, 0x69, 0x5E, 0x00, 0x03, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x5F,
 0x6F, 0x76, 0x32, 0x36, 0x34, 0x30, 0x2E, 0x68, 0x74, 0x6D, 0x6C, 0x00, 0xED, 0x3D, 0x6B, 0x73,
 0xDB, 0x46, 0x92, 0xDF, 0xFD, 0x2B, 0x60, 0x24, 0x6B, 0x92, 0x25, 0x92, 0x22, 0x29, 0x4A, 0x96,
 0x15, 0x89, 0x3E, 0x5B, 0x96, 0x1F, 0xB5, 0x76, 0xE2, 0xB5, 0x12, 0xC7, 0x5B, 0xA9, 0x2D, 0x07,
 0x04, 0x86, 0x24, 0x62, 0x10, 0xE0, 0x02, 0xA0, 0x48, 0x26, 0xA5, 0xDF, 0x71, 0x3F, 0xE8, 0xFE,
 0xD8, 0x75, 0xCF, 0x03, 0x18, 0x00, 0x83, 0x07, 0x49, 0x89, 0xF4, 0xFA, 0x8E, 0x4E, 0x45, 0x78,
 0x4C, 0xF7, 0xF4, 0xBB, 0x7B, 0x66, 0x30, 0xC0, 0xF9, 0x43, 0xCB, 0x33, 0xC3, 0xD5, 0x8C, 0x68,

and it goes on for more than 6000 bytes.

So, my question … is there a simple way to convert this back to a human-readable, ASCII (.html) file? I think the task is a little more complicated by the fact that the data is now ASCII representation of hex numbers, not a binary file.

Thanks.

Here is the Gzip file format specification.
Good luck.

As common as the gzip format is, I am surprised that no one has written a library for Arduino. But here's one written in C++ that you may be able to repurpose.

I just searched in Github and found 11 libraries specifically in C++

If you succeed, please share.

Thanks @SteveMann. Will them it a look.

What I would try first would be to take that array initialization statement, and use an Arduino with an SD card module to write it out as a binary file, with a .gz suffix. Or maybe use Code::Blocks and a short C or C++ console program on Windows for the same task.

If it is a valid .gz format, any of a number of utilities (such as 7Zip on Windows or gunzip on linux) should be able to unpack it.

Good idea, thanks.

You can simply load that webpage with your browser which will inflate the gzip on the fly in order to show the page, and then using the browser developer tool (F12 keyboard button, usually) you can analize/save all the sources involved (HTML, JavaScript, CSS etc etc).

Thanks @cotestatnt . I just thought of that myself.

With Chrome, right click on web page and select "view page source". If anyone is interested, here's what the html looks like:


<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>ESP32 OV2460</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, .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
            }

            .save {
                position: absolute;
                right: 25px;
                top: 0px;
                height: 16px;
                line-height: 16px;
                padding: 0 4px;
                text-decoration: none;
                cursor: pointer
            }

            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;
            }

            label.toggle-section-label {
                cursor: pointer;
                display: block
            }

            input.toggle-section-button {
                outline: 0;
                opacity: 0;
                width: 0;
                height: 0
            }

            input.toggle-section-button:checked+section.toggle-section {
                display: none
            }

        </style>
    </head>
    <body>
        <section class="main">
            <div id="logo">
                <label for="nav-toggle-cb" id="nav-toggle">&#9776;&nbsp;&nbsp;Toggle OV2640 settings</label>
            </div>
            <div id="content">
                <div id="sidebar">
                    <input type="checkbox" id="nav-toggle-cb" checked="checked">
                    <nav id="menu">

                        <section id="xclk-section" class="nothidden">
                            <div class="input-group" id="set-xclk-group">
                                <label for="set-xclk">XCLK MHz</label>
                                <div class="text">
                                    <input id="xclk" type="text" minlength="1" maxlength="2" size="2" value="20">
                                </div>
                                <button class="inline-button" id="set-xclk">Set</button>
                            </div>
                        </section>

                        <div class="input-group" id="framesize-group">
                            <label for="framesize">Resolution</label>
                            <select id="framesize" class="default-action">
                                <!-- 2MP -->
                                <option value="13">UXGA(1600x1200)</option>
                                <option value="12">SXGA(1280x1024)</option>
                                <option value="11">HD(1280x720)</option>
                                <option value="10">XGA(1024x768)</option>
                                <option value="9">SVGA(800x600)</option>
                                <option value="8">VGA(640x480)</option>
                                <option value="7">HVGA(480x320)</option>
                                <option value="6">CIF(400x296)</option>
                                <option value="5">QVGA(320x240)</option>
                                <option value="4">240x240</option>
                                <option value="3">HQVGA(240x176)</option>
                                <option value="2">QCIF(176x144)</option>
                                <option value="1">QQVGA(160x120)</option>
                                <option value="0">96x96</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">-2</div>
                            <input type="range" id="brightness" min="-2" max="2" value="0" class="default-action">
                            <div class="range-max">2</div>
                        </div>
                        <div class="input-group" id="contrast-group">
                            <label for="contrast">Contrast</label>
                            <div class="range-min">-2</div>
                            <input type="range" id="contrast" min="-2" max="2" value="0" class="default-action">
                            <div class="range-max">2</div>
                        </div>
                        <div class="input-group" id="saturation-group">
                            <label for="saturation">Saturation</label>
                            <div class="range-min">-2</div>
                            <input type="range" id="saturation" min="-2" max="2" value="0" class="default-action">
                            <div class="range-max">2</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</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="awb_gain-group">
                            <label for="awb_gain">AWB Gain</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" id="wb_mode-group">
                            <label for="wb_mode">WB 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 SENSOR</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" id="aec2-group">
                            <label for="aec2">AEC DSP</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="ae_level-group">
                            <label for="ae_level">AE Level</label>
                            <div class="range-min">-2</div>
                            <input type="range" id="ae_level" min="-2" max="2" value="0" class="default-action">
                            <div class="range-max">2</div>
                        </div>
                        <div class="input-group" id="aec_value-group">
                            <label for="aec_value">Exposure</label>
                            <div class="range-min">0</div>
                            <input type="range" id="aec_value" min="0" max="1200" value="204" class="default-action">
                            <div class="range-max">1200</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="30" value="5" class="default-action">
                            <div class="range-max">31x</div>
                        </div>
                        <div class="input-group" id="gainceiling-group">
                            <label for="gainceiling">Gain Ceiling</label>
                            <div class="range-min">2x</div>
                            <input type="range" id="gainceiling" min="0" max="6" value="0" class="default-action">
                            <div class="range-max">128x</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="raw_gma-group">
                            <label for="raw_gma">Raw GMA</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="dcw-group">
                            <label for="dcw">DCW (Downsize EN)</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="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="led-group">
                          <label for="led_intensity">LED Intensity</label>
                          <div class="range-min">0</div>
                          <input type="range" id="led_intensity" min="0" max="255" value="0" class="default-action">
                          <div class="range-max">255</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="disabled">Enroll Face</button>
                        </section>

                        <div style="margin-top: 8px;"><center><span style="font-weight: bold;">Advanced Settings</span></center></div>
                        <hr style="width:100%">
                        <label for="nav-toggle-reg" class="toggle-section-label">&#9776;&nbsp;&nbsp;Register Get/Set</label><input type="checkbox" id="nav-toggle-reg" class="hidden toggle-section-button" checked="checked">
                        <section class="toggle-section">
                            <!--h4>Set Register</h4-->
                            <div class="input-group" id="set-reg-group">
                                <label for="set-reg">Reg, Mask, Value</label>
                                <div class="text">
                                    <input id="reg-addr" type="text" minlength="4" maxlength="6" size="6" value="0x111">
                                </div>
                                <div class="text">
                                    <input id="reg-mask" type="text" minlength="4" maxlength="4" size="4" value="0x80">
                                </div>
                                <div class="text">
                                    <input id="reg-value" type="text" minlength="4" maxlength="4" size="4" value="0x80">
                                </div>
                                <button class="inline-button" id="set-reg">Set</button>
                            </div>
                            <hr style="width:50%">
                            <!--h4>Get Register</h4-->
                            <div class="input-group" id="get-reg-group">
                                <label for="get-reg">Reg, Mask</label>
                                <div class="text">
                                    <input id="get-reg-addr" type="text" minlength="4" maxlength="6" size="6" value="0x111">
                                </div>
                                <div class="text">
                                    <input id="get-reg-mask" type="text" minlength="4" maxlength="6" size="6" value="0x80">
                                </div>
                                <button class="inline-button" id="get-reg">Get</button>
                            </div>
                            <div class="input-group">
                                <label for="get-reg-value">Value</label>
                                <div class="text">
                                    <span id="get-reg-value">0x1234</span>
                                </div>
                            </div>
                        </section>
                        <hr style="width:100%">
                        <label for="nav-toggle-2640pll" class="toggle-section-label">&#9776;&nbsp;&nbsp;CLK</label><input type="checkbox" id="nav-toggle-2640pll" class="hidden toggle-section-button" checked="checked">
                        <section class="toggle-section">

                            <div class="input-group"><label for="2640pll1">CLK 2X</label><div class="switch"><input id="2640pll1" type="checkbox" class="reg-action" reg="0x111" offset="7" mask="0x01"><label class="slider" for="2640pll1"></label></div></div>

                            <div class="input-group"><label for="2640pll3">CLK DIV</label><div class="text">0<input id="2640pll3" type="text" minlength="1" maxlength="2" size="2" value="1" class="reg-action" reg="0x111" offset="0" mask="0x3f">63</div></div>
                            <div class="input-group"><label for="2640pll5">Auto PCLK</label><div class="switch"><input id="2640pll5" type="checkbox" class="reg-action" reg="0xd3" offset="7" mask="0x01"><label class="slider" for="2640pll5"></label></div></div>
                            <div class="input-group"><label for="2640pll4">PCLK DIV</label><div class="text">0<input id="2640pll4" type="text" minlength="1" maxlength="3" size="3" value="4" class="reg-action" reg="0xd3" offset="0" mask="0x7f">127</div></div>

                        </section>
                        <hr style="width:100%">
                        <label for="nav-toggle-win" class="toggle-section-label">&#9776;&nbsp;&nbsp;Window</label><input type="checkbox" id="nav-toggle-win" class="hidden toggle-section-button" checked="checked">
                        <section class="toggle-section">

                            <div class="input-group">
                                <label for="start-x">Sensor Resolution</label><select id="start-x">
                                    <option value="2">CIF (400x296)</option>
                                    <option value="1">SVGA (800x600)</option>
                                    <option value="0" selected="selected">UXGA (1600x1200)</option>
                                </select>
                            </div>

                            <div class="input-group" id="set-offset-res-group">
                                <label for="offset-x">Offset</label>
                                <div class="text">
                                    X:<input id="offset-x" type="text" minlength="1" maxlength="3" size="6" value="400">
                                </div>
                                <div class="text">
                                    Y:<input id="offset-y" type="text" minlength="1" maxlength="3" size="6" value="300">
                                </div>
                            </div>
                            <div class="input-group" id="set-total-res-group">
                                <label for="total-x">Window Size</label>
                                <div class="text">
                                    X:<input id="total-x" type="text" minlength="1" maxlength="4" size="6" value="800">
                                </div>
                                <div class="text">
                                    Y:<input id="total-y" type="text" minlength="1" maxlength="4" size="6" value="600">
                                </div>
                            </div>
                            <div class="input-group" id="set-output-res-group">
                                <label for="output-x">Output Size</label>
                                <div class="text">
                                    X:<input id="output-x" type="text" minlength="1" maxlength="4" size="6" value="320">
                                </div>
                                <div class="text">
                                    Y:<input id="output-y" type="text" minlength="1" maxlength="4" size="6" value="240">
                                </div>
                            </div>
                            <button id="set-resolution">Set Resolution</button>
                        </section>



                    </nav>
                </div>
                <figure>
                    <div id="stream-container" class="image-container hidden">
                        <a id="save-still" href="#" class="button save" download="capture.jpg">Save</a>
                        <div class="close" id="close-stream">×</div>
                        <img id="stream" src="" crossorigin>
                    </div>
                </figure>
            </div>
        </section>
        <script>
document.addEventListener('DOMContentLoaded', function (event) {
  var baseHost = document.location.origin
  var streamUrl = baseHost + ':81'

  function fetchUrl(url, cb){
    fetch(url)
      .then(function (response) {
        if (response.status !== 200) {
          cb(response.status, response.statusText);
        } else {
          response.text().then(function(data){
            cb(200, data);
          }).catch(function(err) {
            cb(-1, err);
          });
        }
      })
      .catch(function(err) {
        cb(-1, err);
      });
  }

  function setReg(reg, offset, mask, value, cb){
    //console.log('Set Reg', '0x'+reg.toString(16), offset, '0x'+mask.toString(16), '0x'+value.toString(16), '('+value+')');
    value = (value & mask) << offset;
    mask = mask << offset;
    fetchUrl(`${baseHost}/reg?reg=${reg}&mask=${mask}&val=${value}`, cb);
  }

  function getReg(reg, offset, mask, cb){
    mask = mask << offset;
    fetchUrl(`${baseHost}/greg?reg=${reg}&mask=${mask}`, function(code, txt){
      let value = 0;
      if(code == 200){
        value = parseInt(txt);
        value = (value & mask) >> offset;
        txt = ''+value;
      }
      cb(code, txt);
    });
  }

  function setXclk(xclk, cb){
    fetchUrl(`${baseHost}/xclk?xclk=${xclk}`, cb);
  }

  function setWindow(start_x, start_y, end_x, end_y, offset_x, offset_y, total_x, total_y, output_x, output_y, scaling, binning, cb){
    fetchUrl(`${baseHost}/resolution?sx=${start_x}&sy=${start_y}&ex=${end_x}&ey=${end_y}&offx=${offset_x}&offy=${offset_y}&tx=${total_x}&ty=${total_y}&ox=${output_x}&oy=${output_y}&scale=${scaling}&binning=${binning}`, cb);
  }

  const setRegButton = document.getElementById('set-reg')
  setRegButton.onclick = () => {
    let reg = parseInt(document.getElementById('reg-addr').value);
    let mask = parseInt(document.getElementById('reg-mask').value);
    let value = parseInt(document.getElementById('reg-value').value);

    setReg(reg, 0, mask, value, function(code, txt){
      if(code != 200){
        alert('Error['+code+']: '+txt);
      }
    });
  }

  const getRegButton = document.getElementById('get-reg')
  getRegButton.onclick = () => {
    let reg = parseInt(document.getElementById('get-reg-addr').value);
    let mask = parseInt(document.getElementById('get-reg-mask').value);
    let value = document.getElementById('get-reg-value');

    getReg(reg, 0, mask, function(code, txt){
      if(code != 200){
        value.innerHTML = 'Error['+code+']: '+txt;
      } else {
        value.innerHTML = '0x'+parseInt(txt).toString(16)+' ('+txt+')';
      }
    });
  }

  const setXclkButton = document.getElementById('set-xclk')
  setXclkButton.onclick = () => {
    let xclk = parseInt(document.getElementById('xclk').value);

    setXclk(xclk, function(code, txt){
      if(code != 200){
        alert('Error['+code+']: '+txt);
      }
    });
  }

  const setResButton = document.getElementById('set-resolution')
  setResButton.onclick = () => {
    let start_x = parseInt(document.getElementById('start-x').value);
    let offset_x = parseInt(document.getElementById('offset-x').value);
    let offset_y = parseInt(document.getElementById('offset-y').value);
    let total_x = parseInt(document.getElementById('total-x').value);
    let total_y = parseInt(document.getElementById('total-y').value);
    let output_x = parseInt(document.getElementById('output-x').value);
    let output_y = parseInt(document.getElementById('output-y').value);

    setWindow(start_x, 0, 0, 0, offset_x, offset_y, total_x, total_y, output_x, output_y, false, false, function(code, txt){
      if(code != 200){
        alert('Error['+code+']: '+txt);
      }
    });
  }

  const setRegValue = (el) => {
    let reg = el.attributes.reg?parseInt(el.attributes.reg.nodeValue):0;
    let offset = el.attributes.offset?parseInt(el.attributes.offset.nodeValue):0;
    let mask = el.attributes.mask?parseInt(el.attributes.mask.nodeValue):255;
    let value = 0;
    switch (el.type) {
      case 'checkbox':
        value = el.checked ? mask : 0;
        break;
      case 'range':
      case 'text':
      case 'select-one':
        value = el.value;
        break
      default:
        return;
    }

    setReg(reg, offset, mask, value, function(code, txt){
      if(code != 200){
        alert('Error['+code+']: '+txt);
      }
    });
  }

  // Attach on change action for register elements
  document
    .querySelectorAll('.reg-action')
    .forEach(el => {
        if (el.type === 'text') {
            el.onkeyup = function(e){
                if(e.keyCode == 13){
                    setRegValue(el);
                }
            }
        } else {
            el.onchange = () => setRegValue(el)
        }
    })


  const updateRegValue = (el, value, updateRemote) => {
    let initialValue;
    let offset = el.attributes.offset?parseInt(el.attributes.offset.nodeValue):0;
    let mask = (el.attributes.mask?parseInt(el.attributes.mask.nodeValue):255) << offset;
    value = (value & mask) >> offset;
    if (el.type === 'checkbox') {
      initialValue = el.checked
      value = !!value
      el.checked = value
    } else {
      initialValue = el.value
      el.value = value
    }
  }


  const printReg = (el) => {
    let reg = el.attributes.reg?parseInt(el.attributes.reg.nodeValue):0;
    let offset = el.attributes.offset?parseInt(el.attributes.offset.nodeValue):0;
    let mask = el.attributes.mask?parseInt(el.attributes.mask.nodeValue):255;
    let value = 0;
    switch (el.type) {
      case 'checkbox':
        value = el.checked ? mask : 0;
        break;
      case 'range':
      case 'select-one':
        value = el.value;
        break
      default:
        return;
    }
    value = (value & mask) << offset;
    return '0x'+reg.toString(16)+', 0x'+value.toString(16);
  }



  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) {
          show(gainCeiling)
          hide(agcGain)
        } else {
          hide(gainCeiling)
          show(agcGain)
        }
      } else if(el.id === "awb_gain"){
        value ? show(wb) : hide(wb)
      } else if(el.id === "face_recognize"){
        value ? enable(enrollButton) : disable(enrollButton)
      } else if(el.id == "led_intensity"){
        value > -1 ? show(ledGroup) : hide(ledGroup)
      }
    }
  }

  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}`

    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)
        })
      document
        .querySelectorAll('.reg-action')
        .forEach(el => {
            let reg = el.attributes.reg?parseInt(el.attributes.reg.nodeValue):0;
            if(reg == 0){
              return;
            }
            updateRegValue(el, state['0x'+reg.toString(16)], 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 saveButton = document.getElementById('save-still')
  const ledGroup = document.getElementById('led-group')

  const stopStream = () => {
    window.stop();
    streamButton.innerHTML = 'Start Stream'
  }

  const startStream = () => {
    view.src = `${streamUrl}/stream`
    show(viewContainer)
    streamButton.innerHTML = 'Stop Stream'
  }

  // Attach actions to buttons
  stillButton.onclick = () => {
    stopStream()
    view.src = `${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)
  }

  saveButton.onclick = () => {
    var canvas = document.createElement("canvas");
    canvas.width = view.width;
    canvas.height = view.height;
    document.body.appendChild(canvas);
    var context = canvas.getContext('2d');
    context.drawImage(view,0,0);
    try {
      var dataURL = canvas.toDataURL('image/jpeg');
      saveButton.href = dataURL;
      var d = new Date();
      saveButton.download = d.getFullYear() + ("0"+(d.getMonth()+1)).slice(-2) + ("0" + d.getDate()).slice(-2) + ("0" + d.getHours()).slice(-2) + ("0" + d.getMinutes()).slice(-2) + ("0" + d.getSeconds()).slice(-2) + ".jpg";
    } catch (e) {
      console.error(e);
    }
    canvas.parentNode.removeChild(canvas);
  }

  // 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')
  const gainCeiling = document.getElementById('gainceiling-group')
  agc.onchange = () => {
    updateConfig(agc)
    if (agc.checked) {
      show(gainCeiling)
      hide(agcGain)
    } else {
      hide(gainCeiling)
      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>

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.