mirror of
https://github.com/wisplite/BLE-mesh-sim.git
synced 2026-10-30 13:32:21 -05:00
bunch of QoL stuff and full packet forwarding/visualization (this is so cool!)
This commit is contained in:
+25
-1
@@ -12,7 +12,26 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="rowFlex" style="display: flex; flex-direction: row; align-items: start; justify-content: center; width: 100%; height: 100%;">
|
<div id="rowFlex" style="display: flex; flex-direction: row; align-items: start; justify-content: center; width: 100%; height: 100%;">
|
||||||
<div id="propertyViewer">
|
<div id="propertyViewer">
|
||||||
|
<div id="propertyViewerHeader">Simulation Properties</div>
|
||||||
|
<hr/>
|
||||||
|
<details id="simpleSettings">
|
||||||
|
<summary>Simple Settings</summary>
|
||||||
|
<div id="simpleSettingsContent">
|
||||||
|
<label for="ttl">TTL</label>
|
||||||
|
<input type="number" id="ttl" value="5" autocomplete="off">
|
||||||
|
<label for="dropPenalty">Drop Penalty</label>
|
||||||
|
<input type="number" id="dropPenalty" value="1.2" autocomplete="off">
|
||||||
|
<label for="connectionDistance">Connection Distance</label>
|
||||||
|
<input type="number" id="connectionDistance" value="250" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details id="engineSettings">
|
||||||
|
<summary>Engine Settings</summary>
|
||||||
|
<div id="engineSettingsContent">
|
||||||
|
<label for="updateInterval">Update Interval</label>
|
||||||
|
<input type="number" id="updateInterval" value="10" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
<div id="centerColumn" style="display: flex; flex-direction: column; align-items: center; justify-content: center; width: 70%; height: 100%; margin: 0 auto;">
|
<div id="centerColumn" style="display: flex; flex-direction: column; align-items: center; justify-content: center; width: 70%; height: 100%; margin: 0 auto;">
|
||||||
<div id="simContainer"></div>
|
<div id="simContainer"></div>
|
||||||
@@ -20,10 +39,15 @@
|
|||||||
<div id="controlButtons">
|
<div id="controlButtons">
|
||||||
<button id="addNode" class="controlButton">Add Node</button>
|
<button id="addNode" class="controlButton">Add Node</button>
|
||||||
<button id="sendPacket" class="controlButton">Send Packet</button>
|
<button id="sendPacket" class="controlButton">Send Packet</button>
|
||||||
|
<button id="resetTTLColors" class="controlButton">Reset TTL Colors</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="simToggles">
|
<div id="simToggles">
|
||||||
<input type="checkbox" id="showRanges" class="simToggle">
|
<input type="checkbox" id="showRanges" class="simToggle">
|
||||||
<label for="showRanges">Show Ranges</label>
|
<label for="showRanges">Show Ranges</label>
|
||||||
|
<input type="checkbox" id="showTTL" class="simToggle" checked>
|
||||||
|
<label for="showTTL">Show TTL Colors</label>
|
||||||
|
<input type="checkbox" id="quickPlace" class="simToggle">
|
||||||
|
<label for="quickPlace">Quick Place</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,52 @@
|
|||||||
var UPDATE_INTERVAL = 10;
|
var UPDATE_INTERVAL = 10;
|
||||||
var CONNECTION_DISTANCE = 250;
|
var CONNECTION_DISTANCE = 250;
|
||||||
var DROP_PENTALTY = 1.2;
|
var DROP_PENALTY = 1.2;
|
||||||
|
var TTL = 5;
|
||||||
|
|
||||||
|
function smoothColorTransition(color1, color2, min, max, current) {
|
||||||
|
// Clamp current between min and max
|
||||||
|
const clamped = Math.min(Math.max(current, min), max);
|
||||||
|
|
||||||
|
// Convert hex to RGB
|
||||||
|
const hexToRgb = (hex) => {
|
||||||
|
hex = hex.replace(/^#/, "");
|
||||||
|
if (hex.length === 3) {
|
||||||
|
hex = hex
|
||||||
|
.split("")
|
||||||
|
.map((c) => c + c)
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
const num = parseInt(hex, 16);
|
||||||
|
return {
|
||||||
|
r: (num >> 16) & 255,
|
||||||
|
g: (num >> 8) & 255,
|
||||||
|
b: num & 255,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert RGB to hex
|
||||||
|
const rgbToHex = (r, g, b) =>
|
||||||
|
"#" +
|
||||||
|
[r, g, b]
|
||||||
|
.map((x) => {
|
||||||
|
const hex = x.toString(16);
|
||||||
|
return hex.length === 1 ? "0" + hex : hex;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
const c1 = hexToRgb(color1);
|
||||||
|
const c2 = hexToRgb(color2);
|
||||||
|
|
||||||
|
// Normalize factor (0 → min, 1 → max)
|
||||||
|
const t = (clamped - min) / (max - min);
|
||||||
|
|
||||||
|
// Interpolate each channel
|
||||||
|
const r = Math.round(c1.r + (c2.r - c1.r) * t);
|
||||||
|
const g = Math.round(c1.g + (c2.g - c1.g) * t);
|
||||||
|
const b = Math.round(c1.b + (c2.b - c1.b) * t);
|
||||||
|
|
||||||
|
return rgbToHex(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
var nodes = new vis.DataSet([
|
var nodes = new vis.DataSet([
|
||||||
]);
|
]);
|
||||||
@@ -51,11 +97,65 @@ var onEdgeEngine = new CompleteNodeOnEdgeEngine(network, nodes, dotNodes, edges,
|
|||||||
onEdgeEngine.createEdgesTable();
|
onEdgeEngine.createEdgesTable();
|
||||||
onEdgeEngine.initMovement();
|
onEdgeEngine.initMovement();
|
||||||
onEdgeEngine.setArrivalCallback(({ from, to, dot }) => {
|
onEdgeEngine.setArrivalCallback(({ from, to, dot }) => {
|
||||||
// Example: log arrival; dot removal is handled by engine
|
if (nodeTable[to].packetCache.includes(dot.id.split('-')[0])) {
|
||||||
// For flood behavior, you can spawn new dots here if needed
|
return;
|
||||||
console.log(`Dot ${dot.id} arrived from ${from} to ${to}`);
|
}
|
||||||
|
// update color of receiving node for visualization
|
||||||
|
nodeTable[to].packetCache.push(dot.id.split('-')[0]);
|
||||||
|
var packetId = dot.id.split('-')[0];
|
||||||
|
var ttl = parseInt(dot.id.split('-')[2]);
|
||||||
|
ttl--;
|
||||||
|
if (document.getElementById('showTTL').checked) {
|
||||||
|
nodes.update({id: to, color: {background: smoothColorTransition('#eb4034', '#40eb34', 0, TTL, ttl)}});
|
||||||
|
} else {
|
||||||
|
nodes.update({id: to, color: {background: '#97c2fc'}});
|
||||||
|
}
|
||||||
|
if (ttl > 0) {
|
||||||
|
const neighborsSnapshot = nodeTable[to].outgoing.slice();
|
||||||
|
for (let neighbor of neighborsSnapshot) {
|
||||||
|
if (neighbor === from) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
console.log(`Sending packet ${packetId} from ${to} to ${neighbor}`);
|
||||||
|
var packetShift = generateRealisticLabel();
|
||||||
|
var movingNode = {
|
||||||
|
id: `${packetId}-${packetShift}-${ttl}`,
|
||||||
|
label: packetId + `-${ttl}`,
|
||||||
|
shape: dot.shape,
|
||||||
|
size: dot.size,
|
||||||
|
color: dot.color,
|
||||||
|
};
|
||||||
|
onEdgeEngine.createDotNode(movingNode, to, neighbor);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (document.getElementById('showTTL').checked) {
|
||||||
|
markNeighborsAsFailed(to, from, packetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const markNeighborsAsFailed = (nodeId, from, packetId, visited = new Set()) => {
|
||||||
|
if (visited.has(nodeId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visited.add(nodeId);
|
||||||
|
|
||||||
|
const neighborsSnapshot = (nodeTable[nodeId]?.outgoing?.slice()) || [];
|
||||||
|
for (let neighbor of neighborsSnapshot) {
|
||||||
|
if (visited.has(neighbor)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (nodeTable[neighbor]?.packetCache?.includes(packetId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (neighbor === from) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
nodes.update({id: neighbor, color: {background: 'white'}});
|
||||||
|
markNeighborsAsFailed(neighbor, nodeId, packetId, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function nodeSelectUpdateHandler(properties) {
|
function nodeSelectUpdateHandler(properties) {
|
||||||
// get containers
|
// get containers
|
||||||
const incomingTable = document.getElementById('incomingConnections');
|
const incomingTable = document.getElementById('incomingConnections');
|
||||||
@@ -259,7 +359,7 @@ function main() {
|
|||||||
if (nodeTable[existingNode].incoming.length == 0) {
|
if (nodeTable[existingNode].incoming.length == 0) {
|
||||||
existingScore += 10000;
|
existingScore += 10000;
|
||||||
}
|
}
|
||||||
if (existingScore * DROP_PENTALTY < score.score) {
|
if (existingScore * DROP_PENALTY < score.score) {
|
||||||
//console.log(`Dropping ${existingNode} from ${node1} for better ${score.node}`);
|
//console.log(`Dropping ${existingNode} from ${node1} for better ${score.node}`);
|
||||||
const outIdx = nodeTable[node1].outgoing.indexOf(existingNode);
|
const outIdx = nodeTable[node1].outgoing.indexOf(existingNode);
|
||||||
if (outIdx !== -1) {
|
if (outIdx !== -1) {
|
||||||
@@ -297,17 +397,45 @@ main();
|
|||||||
document.getElementById('addNode').addEventListener('click', function() {
|
document.getElementById('addNode').addEventListener('click', function() {
|
||||||
const nodeId = generateRealisticLabel();
|
const nodeId = generateRealisticLabel();
|
||||||
nodes.add({id: nodeId, label: nodeId});
|
nodes.add({id: nodeId, label: nodeId});
|
||||||
nodeTable[nodeId] = {'incoming': [], 'outgoing': []};
|
nodeTable[nodeId] = {'incoming': [], 'outgoing': [], 'packetCache': []};
|
||||||
|
});
|
||||||
|
|
||||||
|
network.on('click', function(properties) {
|
||||||
|
if (properties.nodes.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (document.getElementById('quickPlace').checked) {
|
||||||
|
const nodeId = generateRealisticLabel();
|
||||||
|
nodes.add({id: nodeId, label: nodeId, x: properties.pointer.canvas.x, y: properties.pointer.canvas.y});
|
||||||
|
nodeTable[nodeId] = {'incoming': [], 'outgoing': [], 'packetCache': []};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
network.on('doubleClick', function(properties) {
|
||||||
|
if (document.getElementById('quickPlace').checked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (properties.nodes.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nodeId = generateRealisticLabel();
|
||||||
|
nodes.add({id: nodeId, label: nodeId, x: properties.pointer.canvas.x, y: properties.pointer.canvas.y});
|
||||||
|
nodeTable[nodeId] = {'incoming': [], 'outgoing': [], 'packetCache': []};
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('sendPacket').addEventListener('click', function() {
|
document.getElementById('sendPacket').addEventListener('click', function() {
|
||||||
|
if (selectedNode == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
onEdgeEngine.createEdgesTable();
|
onEdgeEngine.createEdgesTable();
|
||||||
const packetId = generateRealisticLabel();
|
const packetId = generateRealisticLabel();
|
||||||
let packetShift = 0;
|
var packetShift = generateRealisticLabel();
|
||||||
|
let ttl = TTL;
|
||||||
|
nodes.update({id: selectedNode, color: {background: '#97c2fc'}});
|
||||||
for (let connection of nodeTable[selectedNode].outgoing) {
|
for (let connection of nodeTable[selectedNode].outgoing) {
|
||||||
var movingNode = {
|
var movingNode = {
|
||||||
id: `${packetId}-${packetShift}`,
|
id: `${packetId}-${packetShift}-${ttl}`,
|
||||||
label: packetId,
|
label: packetId + `-${ttl}`,
|
||||||
shape: 'dot',
|
shape: 'dot',
|
||||||
size: 6,
|
size: 6,
|
||||||
color: {
|
color: {
|
||||||
@@ -316,6 +444,29 @@ document.getElementById('sendPacket').addEventListener('click', function() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
onEdgeEngine.createDotNode(movingNode, selectedNode, connection);
|
onEdgeEngine.createDotNode(movingNode, selectedNode, connection);
|
||||||
packetShift++;
|
nodeTable[selectedNode].packetCache.push(packetId);
|
||||||
|
packetShift = generateRealisticLabel();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('resetTTLColors').addEventListener('click', function() {
|
||||||
|
for (let node of nodes.get()) {
|
||||||
|
nodes.update({id: node.id, color: {background: '#97c2fc'}});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('updateInterval').addEventListener('change', function() {
|
||||||
|
UPDATE_INTERVAL = parseInt(this.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('connectionDistance').addEventListener('change', function() {
|
||||||
|
CONNECTION_DISTANCE = parseInt(this.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('dropPenalty').addEventListener('change', function() {
|
||||||
|
DROP_PENALTY = parseFloat(this.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('ttl').addEventListener('change', function() {
|
||||||
|
TTL = parseInt(this.value);
|
||||||
});
|
});
|
||||||
@@ -66,4 +66,28 @@ html, body {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#simpleSettingsContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#engineSettingsContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#simpleSettings {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#engineSettings {
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user