<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>EM Camera</title><style>body {font-family: Arial,Helvetica,sans-serif;background: #FFFFFF;color: #FFFFFF;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: #0066FF;padding: 8px;border-radius: 4px;margin-top: -10px;margin-right: 10px;}#logo{background: #0066FF;display: flex;flex-wrap: wrap;}#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: none}.input-group {display: flex;flex-wrap: nowrap;line-height: 22px;margin: 5px 0}.lng {background: #0066FF;line-height: 20px;margin: 2px;padding: 0px 10px 10px 20px;}.lng:hover {background: #0066FF}.lng:active {background: #0066FF}.input-group>label {display: inline-block;padding-right: 10px;min-width: 47%}.input-group input,.input-group select {flex-grow: 1}.xclk-input {min-height: 24px}.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: #FFFFFF;background: #1A4D80;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: #0048C0}button:active {background: #0048C0}button.disabled {cursor: default;background: #a0a0a0}input[type=range] {-webkit-appearance: none;width: 100%;height: 22px;background: #0066FF;cursor: pointer;margin: 0}input[type=range]:focus {outline: 0}input[type=range]::-webkit-slider-runnable-track {width: 100%;height: 2px;cursor: pointer;background: #FFFFFF;border-radius: 0;border: 0 solid #FFFFFF}input[type=range]::-webkit-slider-thumb {border: 1px solid rgba(0,0,30,0);height: 22px;width: 22px;border-radius: 50px;background: #1A4D80;cursor: pointer;-webkit-appearance: none;margin-top: -11.5px}input[type=range]:focus::-webkit-slider-runnable-track {background: #FFFFFF}input[type=range]::-moz-range-track {width: 100%;height: 2px;cursor: pointer;background: #FFFFFF;border-radius: 0;border: 0 solid #FFFFFF}input[type=range]::-moz-range-thumb {border: 1px solid rgba(0,0,30,0);height: 22px;width: 22px;border-radius: 50px;background: #1A4D80;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: #FFFFFF;border: 0 solid #FFFFFF;border-radius: 0}input[type=range]::-ms-fill-upper {background: #FFFFFF;border: 0 solid #FFFFFF;border-radius: 0}input[type=range]::-ms-thumb {border: 1px solid rgba(0,0,30,0);height: 22px;width: 22px;border-radius: 50px;background: #1A4D80;cursor: pointer;height: 2px}input[type=range]:focus::-ms-fill-lower {background: #FFFFFF}input[type=range]:focus::-ms-fill-upper {background: #000000}.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: #FFFFFF}input:checked+.slider {background-color: #1A4D80}input:checked+.slider:before {-webkit-transform: translateX(26px);transform: translateX(26px)}select {border: 1px solid #000000;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: #0066FF;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 #000000;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;</label><button class="lng" value="ja" onclick="setLang(this.value)">ja</button><button class="lng" value="en" onclick="setLang(this.value)">en</button></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" id="set_xclk_label">クロック(MHz)</label><div class="text"><input class="xclk-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" id="framesize_label">解像度</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"  id="quality_label">画質</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" id="brightness_label">明度</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" id="contrast_label">コントラスト</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" id="saturation_label">彩度</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" hidden>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" id="awb_label">自動ホワイトバランス</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" id="awb_gain_label">自動WBゲイン</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" id="wb_mode_label">WBモード</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" id="aec_label">自動露光</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">AE 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" id="ae_level_label">自動露光レベル</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" id="aec_value_label">露光時間</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" id="agc_label">自動ゲイン</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" id="agc_gain_label">ゲイン</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" id="gainceiling_label">ゲインシーリング</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" id="raw_gma_label">ガンマ補正</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" id="lenc_label">レンズ補正</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" id="hmirror_label">左右反転</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" id="vflip_label">上下反転</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="LED-group"><label for="LED" id="LED_label">LED</label><div class="switch"><input id="LED" type="checkbox" class="default-action" checked="checked"><label class="slider" for="LED"></label></div></div><div class="input-group" id="retry-group"><label for="retry" id="retry_label">リトライ回数</label><div class="text"><input id="retry" type="number" min="1" max="10" value="5"></div></div><section id="buttons"><button id="toggle-stream">Start</button><button id="toggle-save">保存</button></section></nav></div><figure><div id="stream-container" class="image-container hidden"><img id="stream" src="" crossorigin></div></figure></div></section><script>const lng = {ja:{set_xclk_label: "クロック(MHz)",framesize_label: "解像度",quality_label: "画質",brightness_label: "明度",contrast_label: "コントラスト",saturation_label: "彩度",awb_label: "自動ホワイトバランス",awb_gain_label: "自動WBゲイン",wb_mode_label: "WBモード",aec_label: "自動露光",ae_level_label: "自動露光レベル",aec_value_label: "露光時間",agc_label: "自動ゲイン",agc_gain_label: "ゲイン",gainceiling_label: "ゲインシーリング",raw_gma_label: "ガンマ補正",lenc_label: "レンズ補正",hmirror_label: "左右反転",vflip_label: "上下反転",toggle_save: "保存",retry_label: "リトライ回数",},en:{set_xclk_label: "XCLK MHz",framesize_label: "Resolution",quality_label: "Quality",brightness_label: "Brightness",contrast_label: "Contrast",saturation_label: "Saturation",awb_label: "Auto WB",awb_gain_label: "Auto WB Gain",wb_mode_label: "WB Mode",aec_label: "Auto Exposure",ae_level_label: "AE Level",aec_value_label: "Exposure",agc_label: "Auto Gain",agc_gain_label: "Gain",gainceiling_label: "Gain Ceiling",raw_gma_label: "Raw GMA",lenc_label: "Lens Correction",hmirror_label: "H-Mirror",vflip_label: "V-Flip",toggle_save: "Save",retry_label: "Retry",}}function setLang(e){document.getElementById('set_xclk_label').textContent = lng[e].set_xclk_label;document.getElementById('framesize_label').textContent = lng[e].framesize_label;document.getElementById('quality_label').textContent = lng[e].quality_label;document.getElementById('brightness_label').textContent = lng[e].brightness_label;document.getElementById('contrast_label').textContent = lng[e].contrast_label;document.getElementById('saturation_label').textContent = lng[e].saturation_label;document.getElementById('awb_label').textContent = lng[e].awb_label;document.getElementById('awb_gain_label').textContent = lng[e].awb_gain_label;document.getElementById('wb_mode_label').textContent = lng[e].wb_mode_label;document.getElementById('aec_label').textContent = lng[e].aec_label;document.getElementById('ae_level_label').textContent = lng[e].ae_level_label;document.getElementById('aec_value_label').textContent = lng[e].aec_value_label;document.getElementById('agc_label').textContent = lng[e].agc_label;document.getElementById('agc_gain_label').textContent = lng[e].agc_gain_label;document.getElementById('gainceiling_label').textContent = lng[e].gainceiling_label;document.getElementById('raw_gma_label').textContent = lng[e].raw_gma_label;document.getElementById('lenc_label').textContent = lng[e].lenc_label;document.getElementById('hmirror_label').textContent = lng[e].hmirror_label;document.getElementById('vflip_label').textContent = lng[e].vflip_label;document.getElementById('toggle-save').textContent = lng[e].toggle_save;document.getElementById('retry_label').textContent = lng[e].retry_label;}document.addEventListener('DOMContentLoaded', function (event) {var baseHost = document.location.originfunction 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 setXclk(xclk, cb){fetchUrl(`${baseHost}/xclk?xclk=${xclk}`, cb);}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);}});}function paramsave(param, cb){fetchUrl(`${baseHost}/save${param}`, cb);}const saveButton = document.getElementById('toggle-save')saveButton.onclick = () => {LED = document.getElementById('LED').checked ? 1 : 0retry = parseInt(document.getElementById('retry').value);param = `?LED=${LED}&cap_retry=${retry}`paramsave(param, function(code, txt){if(code != 200){alert('Error['+code+']: '+txt);}else{alert("Save!");}});}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 : updateRemotelet initialValueif (el.type === 'checkbox') {initialValue = el.checkedvalue = !!valueel.checked = value} else {initialValue = el.valueel.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)}}}function updateConfig (el) {let valueswitch (el.type) {case 'checkbox':value = el.checked ? 1 : 0breakcase 'range':case 'select-one':value = el.valuebreakcase 'button':case 'submit':value = '1'breakdefault:return}const query = `${baseHost}/control?var=${el.id}&val=${value}`fetch(query).then(response => {console.log(`request to ${query} finished, status: ${response.status}`)})}const view = document.getElementById('stream')const viewContainer = document.getElementById('stream-container')const streamButton = document.getElementById('toggle-stream')const stopStream = () => {window.stop();streamButton.innerHTML = 'Start'}const startStream = () => {view.src = `${baseHost}/stream`show(viewContainer)streamButton.innerHTML = 'Stop'}streamButton.onclick = () => {const streamEnabled = streamButton.innerHTML === 'Stop'if (streamEnabled) {stopStream()} else {startStream()}}document.querySelectorAll('.close').forEach(el => {el.onclick = () => {hide(el.parentNode)}})startStream()// read initial valuesfetch(`${baseHost}/status`).then(function (response) {return response.json()}).then(function (state) {document.querySelectorAll('.default-action').forEach(el => {updateValue(el, state[el.id], false)})})// Attach default on change actiondocument.querySelectorAll('.default-action').forEach(el => {el.onchange = () => updateConfig(el)})// Custom actions// Gainconst 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)}}// Exposureconst aec = document.getElementById('aec')const exposure = document.getElementById('aec_value-group')aec.onchange = () => {updateConfig(aec)aec.checked ? hide(exposure) : show(exposure)}// AWBconst awb = document.getElementById('awb_gain')const wb = document.getElementById('wb_mode-group')awb.onchange = () => {updateConfig(awb)awb.checked ? show(wb) : hide(wb)}// Detection and framesizeconst framesize = document.getElementById('framesize')framesize.onchange = () => {updateConfig(framesize)}})</script></body></html>