558 lines
17 KiB
HTML
558 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
<meta name="viewport" content="width=device-width">
|
|
<title>ebusd</title>
|
|
<link rel="stylesheet" href="css/jsplumb.css">
|
|
<link rel="stylesheet" href="css/style.css">
|
|
</head>
|
|
<body data-library="dom">
|
|
<div id="main" style="width: 1137px; height: 721px;">
|
|
<div class="ebus" id="ebus">
|
|
</div>
|
|
</div>
|
|
<script src="js/dom.jsPlumb-1.7.5-min.js"></script>
|
|
<script src="js/prototype.js"></script>
|
|
<script>
|
|
jsPlumb.ready(function() {
|
|
var instance = jsPlumb.getInstance({
|
|
DragOptions: { cursor: 'pointer', zIndex: 2000 },
|
|
ConnectionOverlays: [
|
|
[ "Arrow", { location: 1 } ],
|
|
[ "Label", {
|
|
location: 0.5,
|
|
id: "label",
|
|
cssClass: "connLabel"
|
|
}]
|
|
],
|
|
Container: "main"
|
|
});
|
|
var selColor = "orange";
|
|
var offColor = "gray";
|
|
var hotColor = "red";
|
|
var warmColor = "orange";
|
|
var coldColor = "blue";
|
|
var offType = {
|
|
connector: "StateMachine",
|
|
paintStyle: { strokeStyle: offColor, lineWidth: 4 },
|
|
hoverPaintStyle: { strokeStyle: selColor },
|
|
};
|
|
var hotType = {
|
|
connector: "StateMachine",
|
|
paintStyle: { strokeStyle: hotColor, lineWidth: 4 },
|
|
hoverPaintStyle: { strokeStyle: selColor },
|
|
};
|
|
var warmType = {
|
|
connector: "StateMachine",
|
|
paintStyle: { strokeStyle: warmColor, lineWidth: 4 },
|
|
hoverPaintStyle: { strokeStyle: selColor },
|
|
};
|
|
var coldType = {
|
|
connector: "StateMachine",
|
|
paintStyle: { strokeStyle: coldColor, lineWidth: 4 },
|
|
hoverPaintStyle: { strokeStyle: selColor },
|
|
};
|
|
instance.registerConnectionType("off", offType);
|
|
instance.registerConnectionType("hot", hotType);
|
|
instance.registerConnectionType("warm", warmType);
|
|
instance.registerConnectionType("cold", coldType);
|
|
|
|
// this is the paint style for the connecting lines..
|
|
var connectorPaintStyle = {
|
|
lineWidth: 4,
|
|
strokeStyle: "#808080",
|
|
joinstyle: "round",
|
|
outlineColor: "white",
|
|
outlineWidth: 2
|
|
},
|
|
// .. and this is the hover style.
|
|
connectorHoverStyle = {
|
|
lineWidth: 4,
|
|
strokeStyle: selColor,
|
|
outlineWidth: 2,
|
|
outlineColor: "white"
|
|
},
|
|
endpointHoverStyle = {
|
|
fillStyle: selColor,
|
|
strokeStyle: selColor
|
|
},
|
|
// the definition of source endpoints
|
|
sourceEndpoint = {
|
|
endpoint: "Rectangle",
|
|
paintStyle: {
|
|
strokeStyle: offColor,
|
|
fillStyle: "transparent",
|
|
lineWidth: 4,
|
|
width: 12,
|
|
height: 12
|
|
},
|
|
maxConnections: 1,
|
|
isSource: true,
|
|
connectorStyle: connectorPaintStyle,
|
|
hoverPaintStyle: endpointHoverStyle,
|
|
connectorHoverStyle: connectorHoverStyle,
|
|
dragOptions: {}
|
|
},
|
|
// the definition of target endpoints (will appear when the user drags a connection)
|
|
targetEndpoint = {
|
|
endpoint: "Rectangle",
|
|
paintStyle: {
|
|
strokeStyle: offColor,
|
|
fillStyle: "transparent",
|
|
radius: 6,
|
|
lineWidth: 4,
|
|
width: 12,
|
|
height: 12
|
|
},
|
|
hoverPaintStyle: endpointHoverStyle,
|
|
maxConnections: 1,
|
|
dropOptions: { hoverClass: "hover", activeClass: "active" },
|
|
isTarget: true,
|
|
connector: [ "Flowchart", { stub: [20, 20], gap: 6, cornerRadius: 5, alwaysRespectStubs: true } ]
|
|
},
|
|
init = function(connection) {
|
|
connection.getOverlay("label").setLabel(connection.endpoints[0].getParameter('name'));
|
|
};
|
|
|
|
var addEndpoints = function(divId, anchors, needConnections, needUpdates) {
|
|
for (var i = 0; i < anchors.length; i++) {
|
|
var anchor = anchors[i];
|
|
var anchUUID = divId + anchor.name;
|
|
var endpoint = anchor.target ? targetEndpoint : sourceEndpoint;
|
|
if (anchor.distance && endpoint.connector) {
|
|
endpoint = jsPlumbUtil.clone(endpoint);
|
|
if (Array.isArray(anchor.distance)) {
|
|
endpoint.connector[1].stub[0] += anchor.distance[0];
|
|
endpoint.connector[1].stub[1] += anchor.distance[1];
|
|
if (anchor.distance.length>2) {
|
|
endpoint.connector[1].midpoint = anchor.distance[2];
|
|
}
|
|
} else {
|
|
endpoint.connector[1].stub[anchor.target?1:0] += anchor.distance;
|
|
}
|
|
}
|
|
if (anchor.hasOwnProperty('connectWith') && needConnections) {
|
|
needConnections.push(new Array(anchUUID).concat(anchor.connectWith)); // to-circuit, to-endpoint[, item-circuit, item-name[, item-format]]
|
|
}
|
|
var endp = instance.addEndpoint(divId, endpoint, {
|
|
anchor: anchor.anchor, uuid: anchUUID,
|
|
parameters:{name:anchor.name, priority:anchor.priority}
|
|
});
|
|
if (anchor.hasOwnProperty('update') && needUpdates) {
|
|
var update = anchor.update;
|
|
update.endpoint = endp;
|
|
needUpdates.push(update);
|
|
}
|
|
}
|
|
};
|
|
|
|
var each = function(items, fn) {
|
|
for (var i = 0; i < items.length; i++)
|
|
fn(items[i]);
|
|
}
|
|
each(instance.getSelector(".ebus .circuitLabel"), function(item) {
|
|
if (item.getAttribute('topf')) {
|
|
item.style.top = Math.floor(item.parentElement.clientHeight*item.getAttribute('topf'))+'px';
|
|
} else if (item.getAttribute('topmf')) {
|
|
item.style.top = Math.floor(item.parentElement.clientHeight*item.getAttribute('topmf')-item.clientHeight/2)+'px';
|
|
}
|
|
if (item.getAttribute('leftf')) {
|
|
item.style.left = Math.floor(item.parentElement.clientWidth*item.getAttribute('leftf'))+'px';
|
|
} else if (item.getAttribute('leftmf')) {
|
|
item.style.left = Math.floor(item.parentElement.clientWidth*item.getAttribute('leftmf')-item.clientWidth/2)+'px';
|
|
}
|
|
});
|
|
|
|
var items = {};
|
|
var sourceFlow, sourceReturn, hwcReturn, ehpFlow, hcFlow, hwcFlow, hcReturn, mixFlow, mixReturn, storHwcFlow, storHwcReturn;
|
|
// suspend drawing and initialise.
|
|
instance.batch(function() {
|
|
// listen for new connections; initialise them the same way we initialise the connections at startup.
|
|
instance.bind("connection", function(connInfo, originalEvent) {
|
|
init(connInfo.connection);
|
|
});
|
|
|
|
instance.bind("click", function(conn, originalEvent) {
|
|
if (conn.hasType("hot")) {
|
|
conn.toggleType("hot");
|
|
conn.toggleType("cold");
|
|
} else if (conn.hasType("cold")) {
|
|
conn.toggleType("cold");
|
|
} else {
|
|
conn.toggleType("hot");
|
|
}
|
|
});
|
|
|
|
instance.bind("connectionDrag", function(connection) {
|
|
console.log("connection " + connection.id + " is being dragged. suspendedElement is ", connection.suspendedElement, " of type ", connection.suspendedElementType);
|
|
});
|
|
|
|
instance.bind("connectionDragStop", function(connection) {
|
|
console.log("connection " + connection.id + " was dragged");
|
|
});
|
|
|
|
instance.bind("connectionMoved", function(params) {
|
|
console.log("connection " + params.connection.id + " was moved");
|
|
});
|
|
/*sourceFlow.setType("hot");
|
|
sourceReturn.setType("cold");
|
|
mixFlow.setType("hot");
|
|
mixReturn.setType("cold");
|
|
storHwcFlow.setType("hot");
|
|
storHwcReturn.setType("off");*/
|
|
|
|
var heat = false, water = false;
|
|
var switchActions = {
|
|
/*"broadcast/hwcStatus": function(fields){
|
|
console.log("action broadcast/hwcStatus:"+fields);
|
|
if (!fields) return;
|
|
var val = fields[0].value;
|
|
if (val=='on') {
|
|
water = true;
|
|
ehpFlow.addType("hot");
|
|
hcFlow.removeType("hot");
|
|
hwcFlow.addType("hot");
|
|
hcReturn.removeType("cold");
|
|
hwcReturn.addType("cold");
|
|
} else {
|
|
water = false;
|
|
hwcFlow.removeType("hot");
|
|
hwcReturn.removeType("cold");
|
|
if (!heat) {
|
|
ehpFlow.removeType("hot");
|
|
hcFlow.removeType("hot");
|
|
hcReturn.removeType("cold");
|
|
}
|
|
}
|
|
},
|
|
"ehp/HwcHcValve": function(fields) {
|
|
console.log("action ehp/HwcHcValve:"+fields);
|
|
if (!fields) return;
|
|
var val = fields['onoff'].value;
|
|
if (val=='off') { // heat
|
|
heat = true;
|
|
ehpFlow.addType("hot");
|
|
hwcFlow.removeType("hot");
|
|
hcFlow.addType("hot");
|
|
hwcReturn.removeType("cold");
|
|
hcReturn.addType("cold");
|
|
} else {
|
|
water = false;
|
|
hcFlow.removeType("hot");
|
|
hcReturn.removeType("cold");
|
|
if (!water) {
|
|
ehpFlow.removeType("hot");
|
|
hwcFlow.removeType("hot");
|
|
hwcReturn.removeType("cold");
|
|
}
|
|
}
|
|
}*/
|
|
};
|
|
var buildText = function(format, fields) {
|
|
var ret = '';
|
|
if (format) {
|
|
format = format.split(' ');
|
|
for (var i=0; i<format.length; i++) {
|
|
if (ret.length>0) ret += ', ';
|
|
if (format[i][0]!='$') {
|
|
if (format[i]=='\\n')
|
|
ret += "<br>";
|
|
else
|
|
ret += format[i];
|
|
} else if (fields && fields.hasOwnProperty(format[i].substring(1))){
|
|
var field = fields[format[i].substring(1)];
|
|
ret += field.hasOwnProperty('value')&&field.value!=null?field.value:'?';
|
|
if (field.unit) {
|
|
ret += ' '+field.unit;
|
|
}
|
|
} else {
|
|
ret += '?';
|
|
}
|
|
}
|
|
} else if (fields) {
|
|
for (var i in fields) {
|
|
if (ret.length>0) ret += ', ';
|
|
var field = fields[i];
|
|
ret += field.hasOwnProperty('value')&&field.value!=null?field.value:'?';
|
|
if (field && field.unit) {
|
|
ret += ' '+field.unit;
|
|
}
|
|
}
|
|
} else {
|
|
ret += '?';
|
|
}
|
|
return ret;
|
|
}
|
|
var parentElem = document.getElementById("ebus");
|
|
var getFields = function(circuit, name, numeric, verbose, required) {
|
|
var ret = Array();
|
|
new Ajax.Request('/data/'+circuit+(name?'/'+name:'')+'?exact'+(numeric?'&numeric':'')+(verbose?'&verbose':'')+(required?'&required':''),{
|
|
method:'get',
|
|
asynchronous:false,
|
|
onSuccess: function(trans) {
|
|
ret.push(trans.responseJSON[circuit][name].fields);
|
|
}
|
|
});
|
|
return ret?ret[0]:null;
|
|
};
|
|
var checkCondition = function(condition, allMessages) {
|
|
for (var k=0; k+2<condition.length; k+=3) { // "and" loop
|
|
var found = false;
|
|
var fields = allMessages[condition[k]][condition[k+1]].fields;
|
|
var cond = condition[k+2];
|
|
if (!fields || !fields[0] || !fields[0].hasOwnProperty('value') || !isNaN(Array.isArray(cond) ? cond[0] : cond) && isNaN(fields[0].value)) {
|
|
fields = getFields(condition[k], condition[k+1], true, false, true);
|
|
if (!fields || !fields[0] || !fields[0].hasOwnProperty('value')) return false;
|
|
}
|
|
if (Array.isArray(cond)) {
|
|
for (var i=0; i<cond.length; i++) { // "or" loop
|
|
if (fields[0].value==cond[i]) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
} else if (fields[0].value==cond) {
|
|
found = true;
|
|
}
|
|
if (!found) return;
|
|
}
|
|
return true;
|
|
};
|
|
var addItemFin = function(parent, name, messages, allMessages, needEndpoints, needUris, data) {
|
|
if (data.hasOwnProperty('condition') && !checkCondition(data.condition, allMessages)) return;
|
|
var child = document.createElement("div");
|
|
child.className = "circuit";
|
|
if (data.hasOwnProperty('image')) {
|
|
child.className += " back";
|
|
child.style.backgroundImage = "url(img/"+data.image+")";
|
|
if (data.hasOwnProperty('image-pos')) {
|
|
var pos = data['image-pos'];
|
|
child.style.backgroundPosition = pos[0]+'px '+pos[1]+'px';
|
|
}
|
|
}
|
|
if (data.hasOwnProperty('endpoints')) {
|
|
needEndpoints.push({"name": name, "endpoints": data.endpoints});
|
|
}
|
|
child.id = name;
|
|
child.textContent = data.title;
|
|
var width = data['bounds'][2];
|
|
var height = data['bounds'][3];
|
|
child.style.left = ((parent?parent.offsetLeft:0)+data['bounds'][0])+'px';
|
|
child.style.top = ((parent?parent.offsetTop:0)+data['bounds'][1])+'px';
|
|
child.style.width = width+'px';
|
|
child.style.height = height+'px';
|
|
parentElem.appendChild(child);
|
|
for (var i in data.items) {
|
|
var item = data.items[i];
|
|
var mname = item.name;
|
|
var cmessages = messages;
|
|
var circuit = name;
|
|
if (item.circuit) {
|
|
circuit = item.circuit;
|
|
if (!allMessages.hasOwnProperty(circuit)) {
|
|
continue;
|
|
}
|
|
cmessages = allMessages[circuit];
|
|
}
|
|
if (!cmessages.hasOwnProperty(mname)) {
|
|
continue;
|
|
}
|
|
var message = cmessages[mname];
|
|
if (!message.passive) needUris.push(circuit+'/'+mname);
|
|
var ichild = document.createElement("div");
|
|
ichild.className = "circuitLabel";
|
|
ichild.setAttribute('data-id', circuit+'/'+mname);
|
|
if (item.hasOwnProperty('format')) {
|
|
ichild.setAttribute('data-format', item.format);
|
|
}
|
|
ichild.style.left = Math.floor((item.leftc?item.leftc:item.left)*width)+'px';
|
|
ichild.style.top = Math.floor((item.topc?item.topc:item.top)*height)+'px';
|
|
ichild.innerHTML = buildText(item.format, message?message.fields:null);
|
|
child.appendChild(ichild);
|
|
if (item.leftc) {
|
|
ichild.style.left = Math.floor(item.leftc*width-ichild.clientWidth/2)+'px';
|
|
}
|
|
if (item.topc) {
|
|
ichild.style.top = Math.floor(item.topc*height-ichild.clientHeight/2)+'px';
|
|
}
|
|
}
|
|
if (data.hasOwnProperty('sub')) {
|
|
for (var i in data.sub) {
|
|
var sub = data.sub[i];
|
|
addItemFin(child, sub.name, [], allMessages, needEndpoints, needUris, sub);
|
|
}
|
|
}
|
|
};
|
|
var addItem = function(parent, name, messages, allMessages, needEndpoints, needUris) {
|
|
new Ajax.Request('/items/'+name+'.json',{
|
|
method:'get',
|
|
asynchronous:false,
|
|
onSuccess: function(trans) {
|
|
addItemFin(parent, name, messages, allMessages, needEndpoints, needUris, trans.responseJSON);
|
|
}
|
|
});
|
|
};
|
|
new Ajax.Request('/data/?verbose',{
|
|
method:'get',
|
|
onSuccess: function(trans) {
|
|
var needEndpoints = new Array();
|
|
var needUris = new Array();
|
|
var allMessages = trans.responseJSON;
|
|
// create items
|
|
for (var cname in allMessages) {
|
|
if (!allMessages.hasOwnProperty(cname))
|
|
continue;
|
|
addItem(null, cname, allMessages[cname], allMessages, needEndpoints, needUris);
|
|
}
|
|
// add endpoints
|
|
var needConnections = new Array();
|
|
var needUpdates = new Array();
|
|
for (var i=0; i<needEndpoints.length; i++) {
|
|
addEndpoints(needEndpoints[i].name, needEndpoints[i].endpoints, needConnections, needUpdates);
|
|
}
|
|
// establish connections
|
|
var connectionItems = new Array();
|
|
var endpoints = instance.selectEndpoints();
|
|
var first = true;
|
|
for (var i=0; i<needConnections.length; i++) {
|
|
var data = needConnections[i];
|
|
// fromuuid, to-circuit|null, to-endpoint[, item-circuit, item-name[, item-format]]
|
|
var src = data[0], dst;
|
|
if (data[1]==null) {
|
|
if (first) {
|
|
if (i+1>=needConnections.length) {
|
|
first = false;
|
|
i = -1;
|
|
}
|
|
continue;
|
|
}
|
|
var avail = new Array();
|
|
var best = 99;
|
|
endpoints.each(function(endpoint) {
|
|
if (endpoint.getParameter('name')==data[2] && !endpoint.isFull()) {
|
|
var prio = endpoint.getParameter('priority');
|
|
if (avail.length==0 || (isNaN(prio)?prio:99)<best) {
|
|
avail.clear();
|
|
avail.push(endpoint.getUuid());
|
|
best = isNaN(prio)?prio:99;
|
|
}
|
|
}
|
|
});
|
|
dst = avail.length>0 ? avail[0] : null;
|
|
} else {
|
|
dst = data[1]+data[2];
|
|
}
|
|
needConnections.splice(i--, 1);
|
|
if (!dst) continue;
|
|
if (instance.getEndpoint(src).isTarget) {
|
|
src = dst;
|
|
dst = data[0];
|
|
}
|
|
var type = 'off';//TODO dst.indexOf("Flow")>=0?'hot':'cold';
|
|
var conn = instance.connect({uuids: [src, dst], editable: true, type: type});
|
|
if (conn && data.length>4 && data[3]) { // add label
|
|
connectionItems.push({conn: conn, data: data});
|
|
var cmessages = allMessages[data[3]];
|
|
var message = cmessages[data[4]];
|
|
var over = conn.getOverlay("label");
|
|
over.setLabel(buildText(data.length>5?data[5]:null, message.fields));
|
|
var key = data[3]+'/'+data[4];
|
|
if (!needUris[key]) {
|
|
needUris.push(key);
|
|
}
|
|
}
|
|
}
|
|
// TODO needUpdates
|
|
// request all shown values and enable automatic polling
|
|
for (var i=0; i<needUris.length; i++) {
|
|
new Ajax.Request('/data/'+needUris[i]+'?exact&poll=2',{
|
|
method:'get',
|
|
onSuccess: function(trans) {
|
|
var cname = needUris[i].split('/');
|
|
var mname = cname[1];
|
|
cname = cname[0];
|
|
var fields = trans.responseJSON[cname][mname].fields;
|
|
if (fields) {
|
|
var elems = $$('div[data-id="'+needUris[i]+'"]');
|
|
each(elems, function(elem) {
|
|
elem.innerHTML = buildText(elem.getAttribute('data-format'), fields);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
instance.draggable(jsPlumb.getSelector(".ebus .circuit"), { grid: [20, 20] });
|
|
},
|
|
onFailure: function() {
|
|
console.log("initial request failed");
|
|
},
|
|
onException: function(trans, e) {
|
|
console.log(e);
|
|
}
|
|
});
|
|
var running = false, firstrun = true;
|
|
var since = 0;
|
|
var startRequest = function() {
|
|
if (running) return;
|
|
running = true;
|
|
new Ajax.Request('/data/?verbose&since='+since,{
|
|
method:'get',
|
|
onSuccess: function(trans) {
|
|
if (firstrun) {
|
|
firstrun = false;
|
|
}
|
|
for (var cname in trans.responseJSON) {
|
|
if (!trans.responseJSON.hasOwnProperty(cname)) {
|
|
continue;
|
|
}
|
|
var circuit = trans.responseJSON[cname];
|
|
for (var mname in circuit) {
|
|
if (!circuit.hasOwnProperty(mname)) {
|
|
continue;
|
|
}
|
|
var message = circuit[mname];
|
|
var lastup = message.lastup;
|
|
if (lastup>since) {
|
|
since = lastup;
|
|
}
|
|
var fields = message.fields;
|
|
var key = cname+'/'+mname;
|
|
var needed = false;
|
|
if (switchActions.hasOwnProperty(key)) {
|
|
switchActions[key](fields);
|
|
needed = true;
|
|
}
|
|
var elems = $$('div[data-id="'+key+'"]');
|
|
if (elems.length>0) {
|
|
needed = true;
|
|
} else console.log(key+':'+fields);
|
|
if (!fields) {
|
|
continue;
|
|
}
|
|
each(elems, function(elem) {
|
|
elem.innerHTML = buildText(elem.getAttribute('data-format'), fields);
|
|
});
|
|
}
|
|
}
|
|
running = false;
|
|
},
|
|
onFailure: function() {
|
|
console.log("failed");
|
|
running = false;
|
|
},
|
|
onException: function(trans,e) {
|
|
console.log(e);
|
|
running = false;
|
|
}
|
|
});
|
|
};
|
|
startRequest();
|
|
new PeriodicalExecuter(startRequest, 15);
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|