// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // % 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 program start (see also SEAWebClientMain.js). var path = "http://" + hostPort + "/update?" + window.clientTags; 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": clientID = message.id; if ("device" in message) { if (message.device == "_inst_select") { window.clientTitle = "select instrument"; console.log('IDselect') pushInitCommand("getblock?path=_inst_select&", "instrument selection"); sizeChange(); } else { if (message.instrument) { window.instrument = message.instrument; } if (message.device) { window.device = message.device; } window.clientTitle = window.instrument + " " + window.device; // console.log('loadBlocks', message); loadFirstBlocks(); } document.title = clientTitle; } else { document.title = 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); } handleUpdate(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 handleUpdate(message, src) { // Handles changes of parameter-values for (var i = 0; i < message.updates.length; i++) { let component = message.updates[i]; // Check for status updates if (component.name.split(":")[1] == 'status') { updateStatus(component); } // Check for target updates in the module block if (component.name.split(":")[1] == 'target') { updateTarget(component); } updateValue(component); } } function updateTarget(component) { let matches = document.getElementsByName(component.name); let elem = matches[0]; // Should be the only match // elem.value = component.value; let row = elem.closest('div'); row.classList.remove('row-waiting-for-answer'); elem.actualValue = component.value; if(elem.__ctype__ == 'input') { resizeTextfield(elem); } } function updateStatus(component) { let matches = document.getElementsByName(component.name); let status_icon = matches[0]; let row = status_icon.closest(".row"); let right = row.lastChild; let statusCode = component.statuscode; // Update status info, visible when mouse cursor is hovering over status icon let status_info = document.getElementsByName(component.name.split(":")[0] + '-info')[0]; if(status_info) { status_info.innerHTML = component.formatted; } status_icon.classList.remove('icon-status-disabled', 'icon-status-idle', 'icon-status-warn', 'icon-status-busy', 'icon-status-error'); row.classList.remove('row-disabled'); right.classList.remove = 'col-right-disabled'; switch (statusCode) { case 0: status_icon.classList.add('icon-status-disabled'); row.classList.add('row-disabled'); right.classList.add = 'col-right-disabled'; break; case 1: status_icon.classList.add('icon-status-idle'); break; case 2: status_icon.classList.add('icon-status-warn'); break; case 3: status_icon.classList.add('icon-status-busy'); break; case 4: status_icon.classList.add('icon-status-error'); break; } } function updateValue(component) { let matches = document.getElementsByName(component.name); for (var j = 0; j < matches.length; j++) { let elem = matches[j]; let type = elem.__ctype__; // -> Show Dom-Properties if (type == "rdonly") { let text = htmlEscape(component.formatted); if (text) { elem.innerHTML = text; } } else if (type == "input") { let row = elem.closest('div'); row.classList.remove('row-waiting-for-answer'); elem.actualValue = component.value; resizeTextfield(elem); } else if (type == "checkbox") { let row = elem.closest('div'); row.classList.remove('row-waiting-for-answer'); if (component.value == 'False' || component.value == 'false' || component.value == 0) { elem.checked = false; } else { elem.checked = true; } } else if (type == "enum") { let row = elem.closest('div'); row.classList.remove('row-waiting-for-answer'); // let options = elem.childNodes; // for (var j = 0; j < options.length; j++) { // if (options[j].label == component.value) { // elem.value = j + 1; // } // } } else if (type == "none") { // pushbutton (e.g. stop command) let row = elem.closest('div'); row.classList.remove('row-waiting-for-answer'); } } } // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // 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): %s", "color:white;background:darkgreen", url); } 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 (reqJSONPOST): %s", "color:white;background:lightgreen", url); } 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. 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 (message.path == "main") { // Happens only initially or at device change. appendToGridElement(1, message.title, "main", createContent(message)); // appendToGridElement(2, "", "parameters", createContent({components:[]})); } else { // In the module-block a parameter was selected showParams = true; // -> write parameter-block to grid-element2 isl = appendToGridElement(2, message.title, 'parameters', createContent(message)); adjustGrid(); if (nColumns == 1 || nColumns == 2 || nColumns == 3) { document.getElementsByClassName('icon-close-container')[0].innerHTML = ''; } } 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 to the last grid-element appendToGridElement(3, "console", "console",createContentConsole(3)); 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. // 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.*/ // console.log('TIME', timeRange) reqJSONPOST(0, "http://" + hostPort + "/getvars", "time=" + timeRange[0] + ',' + 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); document.getElementById("device").innerHTML = message.device 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 "accept-command": if (message.result) { updateValue(message.result); } 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); } }