diff --git a/README.md b/README.md index 4659d70..c808335 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,9 @@ The Library runs on any kind of **ESP8266** and **ESP32** (NodeMCU, AI Thinker, - Time control by @iangray001 - Vertical controls by @iangray001 - Time/date/password/color input types by @pcbbc +- Delayed response support @MartinMueller2003 +- Fragmented control transfer @ MartinMueller2003 +- Extended Callback @MartinMueller2003 ## Roadmap diff --git a/data/js/controls.js b/data/js/controls.js index 36fabe3..0395071 100644 --- a/data/js/controls.js +++ b/data/js/controls.js @@ -60,6 +60,8 @@ const UPDATE_SEPARATOR = 119; const UI_TIME = 20; const UPDATE_TIME = 120; +const UI_FRAGMENT = 21; + const UP = 0; const DOWN = 1; const LEFT = 2; @@ -77,6 +79,8 @@ const C_ALIZARIN = 6; const C_DARK = 7; const C_NONE = 255; +var controlAssemblyArray = new Object(); +var FragmentAssemblyTimer = new Object(); var graphData = new Array(); var hasAccel = false; var sliderContinuous = false; @@ -190,6 +194,12 @@ function restart() { } function conStatusError() { + FragmentAssemblyTimer.forEach(element => { + clearInterval(element); + }); + FragmentAssemblyTimer = new Object(); + controlAssemblyArray = new Object(); + if (true === websockConnected) { websockConnected = false; websock.close(); @@ -210,17 +220,20 @@ function handleVisibilityChange() { } function start() { + let location = window.location.hostname; + let port = window.location.port; +// let location = "192.168.10.229"; +// let port = ""; + document.addEventListener("visibilitychange", handleVisibilityChange, false); if ( - window.location.port != "" || - window.location.port != 80 || - window.location.port != 443 + port != "" || + port != 80 || + port != 443 ) { - websock = new WebSocket( - "ws://" + window.location.hostname + ":" + window.location.port + "/ws" - ); + websock = new WebSocket( "ws://" + location + "/ws" ); } else { - websock = new WebSocket("ws://" + window.location.hostname + "/ws"); + websock = new WebSocket("ws://" + location + "/ws"); } // is the timer running? @@ -241,33 +254,54 @@ function start() { $("#conStatus").addClass("color-green"); $("#conStatus").text("Connected"); websockConnected = true; + FragmentAssemblyTimer.forEach(element => { + clearInterval(element); + }); + FragmentAssemblyTimer = new Object(); + controlAssemblyArray = new Object(); }; websock.onclose = function (evt) { + // console.log("Close evt: '" + evt + "'"); + // console.log("Close reason: '" + evt.reason + "'"); + // console.log("Close code: '" + evt.code + "'"); console.log("websock close"); conStatusError(); + FragmentAssemblyTimer.forEach(element => { + clearInterval(element); + }); + FragmentAssemblyTimer = new Object(); + controlAssemblyArray = new Object(); }; websock.onerror = function (evt) { console.log("websock Error"); - console.log(evt); + // console.log("Error evt: '" + evt + "'"); + // console.log("Error data: '" + evt.data + "'"); restart(); + FragmentAssemblyTimer.forEach(element => { + clearInterval(element); + }); + FragmentAssemblyTimer = new Object(); + controlAssemblyArray = new Object(); }; var handleEvent = function (evt) { - console.log(evt); + // console.log("handleEvent:Data evt: '" + evt + "'"); + // console.log("handleEvent:Data data: '" + evt.data + "'"); try { var data = JSON.parse(evt.data); } catch (Event) { console.error(Event); - // start the update over again + // console.info("start the update over again"); websock.send("uiok:" + 0); return; } var e = document.body; var center = ""; + // console.info("data.type: '" + data.type + "'"); switch (data.type) { case UI_INITIAL_GUI: @@ -279,7 +313,9 @@ function start() { if (data.sliderContinuous) { sliderContinuous = data.sliderContinuous; } + // console.info("UI_INITIAL_GUI:data record: '" + data + "'"); data.controls.forEach(element => { + // console.info("element: '" + JSON.stringify(element) + "'"); var fauxEvent = { data: JSON.stringify(element), }; @@ -295,7 +331,9 @@ function start() { break; case UI_EXTEND_GUI: + // console.info("UI_EXTEND_GUI data record: '" + data + "'"); data.controls.forEach(element => { + // console.info("UI_EXTEND_GUI:element: '" + JSON.stringify(element) + "'"); var fauxEvent = { data: JSON.stringify(element), }; @@ -601,6 +639,88 @@ function start() { websock.send("time:" + rv + ":" + data.id); break; + case UI_FRAGMENT: + let FragmentLen = data.length; + let FragementOffset = data.offset; + let NextFragmentOffset = FragementOffset + FragmentLen; + let Total = data.total; + let Arrived = (FragmentLen + FragementOffset); + let FragmentFinal = Total === Arrived; + // console.info("UI_FRAGMENT:FragmentLen '" + FragmentLen + "'"); + // console.info("UI_FRAGMENT:FragementOffset '" + FragementOffset + "'"); + // console.info("UI_FRAGMENT:NextFragmentOffset '" + NextFragmentOffset + "'"); + // console.info("UI_FRAGMENT:Total '" + Total + "'"); + // console.info("UI_FRAGMENT:Arrived '" + Arrived + "'"); + // console.info("UI_FRAGMENT:FragmentFinal '" + FragmentFinal + "'"); + + if (!data.hasOwnProperty('control')) + { + console.error("UI_FRAGMENT:Missing control record, skipping control"); + break; + } + let control = data.control; + StopFragmentAssemblyTimer(data.control.id); + + // is this the first fragment? + if(0 === FragementOffset) + { + // console.info("Found first fragment"); + controlAssemblyArray[control.id] = data; + // console.info("Value: " + controlAssemblyArray[control.id].control.value); + controlAssemblyArray[control.id].offset = NextFragmentOffset; + StartFragmentAssemblyTimer(control.id); + let TotalRequest = JSON.stringify({ 'id' : control.id, 'offset' : NextFragmentOffset }); + websock.send("uifragmentok:" + 0 + ": " + TotalRequest + ":"); + // console.info("asked for fragment 2"); + break; + } + + // not first fragment. are we assembling this control? + if("undefined" === typeof controlAssemblyArray[control.id]) + { + // it looks like we missed the first fragment. Start the control over + console.error("Missing first fragment for control: " + control.id); + StartFragmentAssemblyTimer(control.id); + let TotalRequest = JSON.stringify({ 'id' : control.id, 'offset' : 0 }); + websock.send("uifragmentok:" + 0 + ": " + TotalRequest + ":"); + // console.info("asked for fragment 1"); + break; + } + + // is this the expected next fragment + if(FragementOffset !== controlAssemblyArray[control.id].offset) + { + console.error("Wrong next fragment. Expected: " + controlAssemblyArray[control.id].offset + " Got: " + FragementOffset); + StartFragmentAssemblyTimer(control.id); + let TotalRequest = JSON.stringify({ 'id' : control.id, 'offset' : controlAssemblyArray[control.id].length + controlAssemblyArray[control.id].offset }); + websock.send("uifragmentok:" + 0 + ": " + TotalRequest + ":"); + // console.info("asked for the expected fragment"); + break; + } + + // console.info("Add to existing fragment"); + controlAssemblyArray[control.id].control.value += control.value; + controlAssemblyArray[control.id].offset = NextFragmentOffset; + // console.info("Value: " + controlAssemblyArray[control.id].control.value); + + if(true === FragmentFinal) + { + var fauxEvent = { + data: JSON.stringify(controlAssemblyArray[control.id].control), + }; + handleEvent(fauxEvent); + controlAssemblyArray[control.id] = null; + // console.info("Found last fragment"); + } + else + { + // console.info("Ask for next fragment."); + StartFragmentAssemblyTimer(control.id); + let TotalRequest = JSON.stringify({ 'id' : control.id, 'offset' : NextFragmentOffset}); + websock.send("uifragmentok:" + 0 + ": " + TotalRequest + ":"); + } + break; + default: console.error("Unknown type or event"); break; @@ -650,6 +770,36 @@ function start() { websock.onmessage = handleEvent; } +function StartFragmentAssemblyTimer(Id) +{ + StopFragmentAssemblyTimer(Id); + FragmentAssemblyTimer[Id] = setInterval(function(_Id) + { + // does the fragment assembly still exist? + if("undefined" !== typeof controlAssemblyArray[_Id]) + { + if(null !== controlAssemblyArray[_Id]) + { + // we have a valid control that is being assembled + // ask for the next part + let TotalRequest = JSON.stringify({ 'id' : controlAssemblyArray[_Id].control.id, 'offset' : controlAssemblyArray[_Id].offset}); + websock.send("uifragmentok:" + 0 + ": " + TotalRequest + ":"); + } + } + }, 1000, Id); +} + +function StopFragmentAssemblyTimer(Id) +{ + if("undefined" !== typeof FragmentAssemblyTimer[Id]) + { + if(FragmentAssemblyTimer[Id]) + { + clearInterval(FragmentAssemblyTimer[Id]); + } + } +} + function sliderchange(number) { var val = $("#sl" + number).val(); websock.send("slvalue:" + val + ":" + number); diff --git a/data/js/controls.min.js b/data/js/controls.min.js index 48be64b..9fa8cef 100644 --- a/data/js/controls.min.js +++ b/data/js/controls.min.js @@ -1,107 +1,128 @@ -const UI_INITIAL_GUI=200;const UI_RELOAD=201;const UPDATE_OFFSET=100;const UI_EXTEND_GUI=210;const UI_TITEL=0;const UI_PAD=1;const UPDATE_PAD=101;const UI_CPAD=2;const UPDATE_CPAD=102;const UI_BUTTON=3;const UPDATE_BUTTON=103;const UI_LABEL=4;const UPDATE_LABEL=104;const UI_SWITCHER=5;const UPDATE_SWITCHER=105;const UI_SLIDER=6;const UPDATE_SLIDER=106;const UI_NUMBER=7;const UPDATE_NUMBER=107;const UI_TEXT_INPUT=8;const UPDATE_TEXT_INPUT=108;const UI_GRAPH=9;const ADD_GRAPH_POINT=10;const CLEAR_GRAPH=109;const UI_TAB=11;const UPDATE_TAB=111;const UI_SELECT=12;const UPDATE_SELECT=112;const UI_OPTION=13;const UPDATE_OPTION=113;const UI_MIN=14;const UPDATE_MIN=114;const UI_MAX=15;const UPDATE_MAX=115;const UI_STEP=16;const UPDATE_STEP=116;const UI_GAUGE=17;const UPDATE_GAUGE=117;const UI_ACCEL=18;const UPDATE_ACCEL=118;const UI_SEPARATOR=19;const UPDATE_SEPARATOR=119;const UI_TIME=20;const UPDATE_TIME=120;const UP=0;const DOWN=1;const LEFT=2;const RIGHT=3;const CENTER=4;const C_TURQUOISE=0;const C_EMERALD=1;const C_PETERRIVER=2;const C_WETASPHALT=3;const C_SUNFLOWER=4;const C_CARROT=5;const C_ALIZARIN=6;const C_DARK=7;const C_NONE=255;var graphData=new Array();var hasAccel=false;var sliderContinuous=false;function colorClass(colorId){colorId=Number(colorId);switch(colorId){case C_TURQUOISE:return"turquoise";case C_EMERALD:return"emerald";case C_PETERRIVER:return"peterriver";case C_WETASPHALT:return"wetasphalt";case C_SUNFLOWER:return"sunflower";case C_CARROT:return"carrot";case C_ALIZARIN:return"alizarin";case C_DARK:case C_NONE:return"dark";default:return"";}} -var websock;var websockConnected=false;var WebSocketTimer=null;function requestOrientationPermission(){} -function saveGraphData(){localStorage.setItem("espuigraphs",JSON.stringify(graphData));} -function restoreGraphData(id){var savedData=localStorage.getItem("espuigraphs",graphData);if(savedData!=null){savedData=JSON.parse(savedData);let idData=savedData[id];return Array.isArray(idData)?idData:[];} -return[];} -function restart(){$(document).add("*").off();$("#row").html("");conStatusError();start();} -function conStatusError(){if(true===websockConnected){websockConnected=false;websock.close();$("#conStatus").removeClass("color-green");$("#conStatus").addClass("color-red");$("#conStatus").html("Error / No Connection ↻");$("#conStatus").off();$("#conStatus").on({click:restart,});}} -function handleVisibilityChange(){if(!websockConnected&&!document.hidden){restart();}} -function start(){document.addEventListener("visibilitychange",handleVisibilityChange,false);if(window.location.port!=""||window.location.port!=80||window.location.port!=443){websock=new WebSocket("ws://"+window.location.hostname+":"+window.location.port+"/ws");}else{websock=new WebSocket("ws://"+window.location.hostname+"/ws");} -if(null===WebSocketTimer){WebSocketTimer=setInterval(function(){if(websock.readyState===3){restart();}},5000);} -websock.onopen=function(evt){console.log("websock open");$("#conStatus").addClass("color-green");$("#conStatus").text("Connected");websockConnected=true;};websock.onclose=function(evt){console.log("websock close");conStatusError();};websock.onerror=function(evt){console.log("websock Error");console.log(evt);restart();};var handleEvent=function(evt){console.log(evt);try{var data=JSON.parse(evt.data);} -catch(Event){console.error(Event);websock.send("uiok:"+0);return;} -var e=document.body;var center="";switch(data.type){case UI_INITIAL_GUI:$("#row").html("");$("#tabsnav").html("");$("#tabscontent").html("");if(data.sliderContinuous){sliderContinuous=data.sliderContinuous;} -data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element),};handleEvent(fauxEvent);});if(data.totalcontrols>(data.controls.length-1)){websock.send("uiok:"+(data.controls.length-1));} -break;case UI_EXTEND_GUI:data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element),};handleEvent(fauxEvent);});if(data.totalcontrols>data.startindex+(data.controls.length-1)){websock.send("uiok:"+(data.startindex+(data.controls.length-1)));} -break;case UI_RELOAD:window.location.reload();break;case UI_TITEL:document.title=data.label;$("#mainHeader").html(data.label);break;case UI_LABEL:case UI_NUMBER:case UI_TEXT_INPUT:case UI_SELECT:case UI_GAUGE:case UI_SEPARATOR:if(data.visible)addToHTML(data);break;case UI_BUTTON:if(data.visible){addToHTML(data);$("#btn"+data.id).on({touchstart:function(e){e.preventDefault();buttonclick(data.id,true);},touchend:function(e){e.preventDefault();buttonclick(data.id,false);},});} -break;case UI_SWITCHER:if(data.visible){addToHTML(data);switcher(data.id,data.value);} -break;case UI_CPAD:case UI_PAD:if(data.visible){addToHTML(data);$("#pf"+data.id).on({touchstart:function(e){e.preventDefault();padclick(UP,data.id,true);},touchend:function(e){e.preventDefault();padclick(UP,data.id,false);},});$("#pl"+data.id).on({touchstart:function(e){e.preventDefault();padclick(LEFT,data.id,true);},touchend:function(e){e.preventDefault();padclick(LEFT,data.id,false);},});$("#pr"+data.id).on({touchstart:function(e){e.preventDefault();padclick(RIGHT,data.id,true);},touchend:function(e){e.preventDefault();padclick(RIGHT,data.id,false);},});$("#pb"+data.id).on({touchstart:function(e){e.preventDefault();padclick(DOWN,data.id,true);},touchend:function(e){e.preventDefault();padclick(DOWN,data.id,false);},});$("#pc"+data.id).on({touchstart:function(e){e.preventDefault();padclick(CENTER,data.id,true);},touchend:function(e){e.preventDefault();padclick(CENTER,data.id,false);},});} -break;case UI_SLIDER:if(data.visible){addToHTML(data);rangeSlider(!sliderContinuous);} -break;case UI_TAB:if(data.visible){$("#tabsnav").append("