// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // % COMMUNICATION var timeoutID; // We need this ID to reset the timer every 30 seconds // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // Server Sent Event function buildUpdateConnection() { // Establishes server-sent-event-connection, which is used for all sorts of // updates and exists as long as the client is running. // Executed at programstart (see also SEAWebClientMain.js). var path = "http://" + hostPort + "/update"; if (debugCommunication) { console.log("%cto server (SSE): " + path, "color:white;background:lightblue"); } try { var src = new EventSource(path); } catch (e) { console.log(e) alertify.prompt( "NETWORK ERROR", "Failed to establish connection to data-server at the given address!" + "Try to enter HOST and PORT of the data-server manually!", hostPort, function(evt, value) { hostPort = value; buildUpdateConnection(); }, function() { }) } src.onmessage = function(e) { var message = JSON.parse(e.data); if (message) { handleUpdateMessage(src, message); } }; src.onerror = function(e) { console.log(e); console.log('EVTSRC error') alertify .prompt( "NETWORK ERROR", "Failed to establish connection to data-server at the given address!" + "Try to enter HOST and PORT of the data-server manually!", hostPort, function(evt, value) { hostPort = value; buildUpdateConnection(); }, function() { }) src.close(); }; } function handleUpdateMessage(src, message) { // Handles incoming SSE-messages depending on type of message. if (debugCommunication > 1) { console.log("%cfrom server (SSE): " + message.type, "color:white;background:lightgray", message); } // console.log(message.type, "at", new Date()) // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // <---------------------------------------------------------------------------------BUG!!! // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! resetTimer(src); switch (message.type) { // id-message: Confirms establishment of SSE-connection and determines // specific ID of the client case "id": for (var i = 0; i < swiper.length; i++) { swiper[i].removeAllSlides(); } clientID = message.id; if ("device" in message) { if (message.device == "_inst_select") { clientTitle = "select instrument"; console.log('IDselect') pushInitCommand("getblock?path=_inst_select&", "instrument selection"); menuMode = true; sizeChange(); } else { clientTitle = message.instrument + " " + message.device; console.log('loadBlocks', message); loadFirstBlocks(); } document.title = "SEA "+clientTitle; } else { document.title = "SEA "+clientTitle + " " + message.title; } var header = document.getElementById("header"); header.style.width = 'auto'; let instrument = document.getElementById("instrument"); let device = document.getElementById("device"); instrument.style.width = 'auto' device.style.width = 'auto' instrument.innerHTML = message.instrument device.innerHTML = message.device console.log('ID', initCommands); nextInitCommand(); break; // console-update-message: Confirms a command. case "command": var histories = document.getElementsByClassName("history"); for (var i = 0; i < histories.length; i++) { var line = document.createElement('div'); line.innerHTML = htmlEscape(message.line); line.style.color = "cornflowerblue"; if (message.origin == "self") { line.style.fontWeight = "bold"; } histories[i].appendChild(document.createElement('br')); histories[i].appendChild(line); histories[i].scrollTop = histories[i].scrollHeight; } var cmd = htmlEscape(message.line); if (commandHistory.indexOf(cmd) === -1) { commandHistory.unshift(cmd); } break; // console-update-message: Confirms execution of command. case "reply": var histories = document.getElementsByClassName("history"); for (var i = 0; i < histories.length; i++) { var line = document.createElement('div'); line.innerHTML = htmlEscape(message.line); line.style.color = "black"; if (message.origin == "self") { line.style.fontWeight = "bold"; } else if (message.origin == "async") { line.style.color = "green"; if (!showAsync) continue; } histories[i].appendChild(line); histories[i].scrollTop = histories[i].scrollHeight; } break; // graph-message: Evokes redraw of graphics. case "graph": alert('obsolete code "graph" called') console.log("graph"); createCharts2(message.graph); break; // redraw.message: Communicates changes requiring reset of a whole group case "redraw": // SHOULD BE TESTED // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // SPECIAL CASE: is message.title = "main possible? It should evoke // redraw of client // special meaning: s < 0: replace slides in all swiper instances reqJSON(-1, "http://" + hostPort + "/getblock?path=" + message.path + "&id=" + clientID, successHandler, errorHandler); break; // graph-update-message: case "graph-update": //if (getUpdatesGraphics) { //timeServer = message.time; updateCharts2(message.graph); //} break; // update-message: Communicates change of values. case "update": if (debugCommunication > 1) { console.log(message); } updateValues(message, src); break; } } function htmlEscape(str) { str = "" + str; if (!str) return ""; return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(//g, '>'); } function resetTimer(src) { // Executed every time a heartbeat-message is obtained. // If no heartbeat-messages are obtained for a certain amount of time, // an error-message is thrown. clearTimeout(timeoutID); timeoutID = setTimeout(function(src) { console.log("timeout"); alertify.error("connection lost"); if (src) { src.close(); } }, 60000); } function updateValues(message, src) { // Handles changes of parameter-values for (var i = 0; i < message.updates.length; i++) { var component = message.updates[i]; var value = component.value; var matches = document.getElementsByName(component.name); for (var j = 0; j < matches.length; j++) { var type = matches[j].__ctype__; if (type == "rdonly") { var text = htmlEscape(value); if (text) { matches[j].innerHTML = text; } } else if (type == "input") { var row = matches[j].parentNode.parentNode.parentNode; row.style.backgroundColor = "white"; var mval = matches[j].value; var oldValue = ('oldValue' in matches[j]) ? matches[j].oldValue : mval; if (value != mval && parseFloat(value) != parseFloat(mval) && value != oldValue) { if (matches[j] == document.activeElement || oldValue != mval) { row.style.backgroundColor = "orange"; } else { matches[j].value = value; } } matches[j].actualValue = value; resizeTextfield(matches[j]); } else if (type == "checkbox") { var row = matches[j].parentNode.parentNode; row.style.backgroundColor = "white"; // console.log('CBX', matches[j].name, message, Boolean(value && value != 'false')); matches[j].checked = Boolean(value && value != 'false'); } else if (type == "enum") { matches[j].style.display = "block"; var row = matches[j].parentNode.parentNode; row.style.backgroundColor = "white"; matches[j].value = value; } } } } // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // XMLHttpRequest function reqJSON(s, url, successHandler, errorHandler) { var xhr = typeof XMLHttpRequest != 'undefined' ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); if (debugCommunication) { console.log("%cto server (reqJSON): " + url, "color:white;background:lightgreen"); } xhr.open('get', url, true); xhr.onreadystatechange = function() { // console.log(xhr) var status; var data; // https://xhr.spec.whatwg.org/#dom-xmlhttprequest-readystate if (xhr.readyState == 4) { // `DONE` status = xhr.status; if (status == 200) { data = JSON.parse(xhr.responseText); successHandler && successHandler(s, data); } else { errorHandler && errorHandler(status); } } }; xhr.send(); } function reqJSONPOST(s, url, parameters, successHandler, errorHandler) { var xhr = typeof XMLHttpRequest != 'undefined' ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); if (debugCommunication) { console.log("%cto server (reqJSON): " + url, "color:white;background:lightgreen"); } xhr.open('post', url, true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function() { // console.log(xhr) var status; var data; // https://xhr.spec.whatwg.org/#dom-xmlhttprequest-readystate if (xhr.readyState == 4) { // `DONE` status = xhr.status; if (status == 200) { data = JSON.parse(xhr.responseText); successHandler && successHandler(s, data); } else { errorHandler && errorHandler(status); } } }; xhr.send(parameters); } function successHandler(s, message) { // Handles incoming XMLHttp-messages depending on type of message. // s: slide number or -1 for replacing slide in all slider instances if (debugCommunication) { console.log("%cfrom server (reqJSON): " + message.type, "color:white;background:dimgray", message); } switch (message.type) { // Response to a "getblock"-server-request. case "draw": if (debugCommunication) { console.log(message); } if (message.path == "main") { // Happens only initially or at device change. for (var sLocal = 0; sLocal < 2; sLocal++) { // was up to MAXBLOCK insertSlide(sLocal, message.title, "main", createContent( sLocal, message)); } insertSlide(2, "", "parameters", createContent(2, {components:[]})); } else { if (s < 0) { // redraw: check for slides in all swiper instances // not used any more? for (var isw = 0; isw < MAXBLOCK; isw ++) { var isl = findSlide(isw, message.path); if (isl !== null) { var slide = swiper[isw].slides[isl]; if (slide) { console.log("redraw", isw, isl); replaceSlideContent(slide, message.title, createContent(isw, message)); } } } } else if (message.path == '_overview') { // remove comment of next line when you want overview _instead_ of Graphics // isl = insertSlide(s, message.title, "_overview", createContent(sLocal, message)); // swiper[sLocal].slideTo(isl); /* go to found slide */ } else { // insertSlide(s, message.title, message.path, createContent(s, message)); let sLocal = paramSlider[s]; isl = insertSlide(sLocal, message.title, 'parameters', createContent(sLocal, message)); swiper[sLocal].slideTo(isl); /* go to found slide */ } } nextInitCommand(); // Request for updates. if (getUpdates) { reqJSON(s, "http://" + hostPort + "/updateblock?path=" + message.path + "&id=" + clientID, successHandler, errorHandler); } break; // Response to a "update-block"-server-request. case "accept-block": break; // Response to a "console"-server-request. case "accept-console": // draw console, only on the first and the last swiper insertSlide(0, "console", "console", createContentConsole(sLocal)); insertSlide(3, "console", "console", createContentConsole(sLocal)); nextInitCommand(); // send empty command in order to trigger getting history reqJSON(0, "http://" + hostPort + "/sendcommand?command=&id=" + clientID, successHandler, errorHandler); break; // Response to a "gettime"-server-request. case "time": timeRange = message.time; /*createGraphics(); // By default mostleft swiper-instance shows graphics. swiper[0].slideTo(0); // Update time-selection. (see also SEAWebClientGraphics.js) var select = document.getElementsByClassName("select-time")[0]; begin = timeRange[0] - timeRange[1]; select.value = begin; // Server-request for variable-list.*/ reqJSONPOST(0, "http://" + hostPort + "/getvars", "time=" + timeRange[1] + "&userconfiguration=" + JSON.stringify(getFormattedUserConfigurationFromLocalStorage()) + "&id=" + clientID, successHandler, errorHandler); break; // Response to a "getvars"-server-request. case "var_list": //blocks = message.blocks; /*var varlist = []; for (var i = 0; i < blocks.length; i++) { for (var j = 0; j < blocks[i].curves.length; j++) { varlist.push(blocks[i].curves[j].name); } } // Update graphics if (varlist.length > 0) { reqJSON(0, "http://" + hostPort + "/graph?time=" + timeRange + "&variables=" + varlist + "&id=" + clientID, successHandler, errorHandler); } else { nextInitCommand(); }*/ // graphs.receivedVars(message.blocks); graphs.initGraphs(message.blocks); nextInitCommand(); break; // Response to a "graph"-server-request. case "graph-draw": // obsolete? if (debugCommunication) { console.log("graph-draw", message); } alert('obsolete code graph-draw called') createCharts2(message.graph); nextInitCommand(); // Request for updates. reqJSON(s, "http://" + hostPort + "/updategraph?id=" + clientID, successHandler, errorHandler); break; // Response to a "updategraph"-server-request. case "accept-graph": break; case "error": console.log("%cError-Message received!", "color:white;background:red"); console.log(message); break; default: break; } } function errorHandler(status) { if (debugCommunication) { console.log("error", status); } }