mirror of
https://github.com/espressif/openthread.git
synced 2026-10-08 00:37:40 +00:00
This commit adds support for building the Nexus simulator for WebAssembly (WASM) using the Emscripten toolchain. This enables the simulator to run in a web browser environment with a JavaScript-based control interface and visualization. Key implementation details: - Introduced `nexus_wasm.cpp` which defines Emscripten bindings (using Embind) for core simulation controls, including stepping time, node creation, topology orchestration, and state manipulation. - Implemented a `WasmObserver` and a global event queue to capture simulation events (node state changes, link updates, packet events) and expose them to JavaScript via a polling mechanism (`pollEvent`). - Updated the CMake build system to support the `EMSCRIPTEN` platform, configuring specific linker options for ES6 module export, modularization, and memory growth. - Enhanced `build.sh` to allow targeting WASM via `emcmake`. - Guarded file-system-dependent operations in `nexus_pcap.cpp` and adjusted `nexus_core.cpp` to handle WASM-specific constraints where standard I/O or multiple observers might not be applicable. - Added `test_wasm_bindings.mjs`, a Node.js-based smoke test that verifies the integrity of the WASM bindings and event pipeline. - Integrated `nexus-wasm-tests` into the GitHub Actions workflow to ensure continuous verification of the WASM build and functionality.
128 lines
4.7 KiB
JavaScript
128 lines
4.7 KiB
JavaScript
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
async function createTopology(Nexus) {
|
|
console.log("Creating Topology in JavaScript...");
|
|
|
|
const leaderId = Nexus.createNode(350, 100);
|
|
const fedId = Nexus.createNode(100, 500);
|
|
const sedId = Nexus.createNode(350, 500);
|
|
const medId = Nexus.createNode(600, 500);
|
|
const router1Id = Nexus.createNode(200, 300);
|
|
const router2Id = Nexus.createNode(500, 300);
|
|
|
|
Nexus.formNetwork(leaderId);
|
|
|
|
// Use the exported JoinMode enum
|
|
Nexus.joinNetwork(fedId, leaderId, Nexus.JoinMode.AsFed);
|
|
Nexus.joinNetwork(sedId, leaderId, Nexus.JoinMode.AsSed);
|
|
Nexus.joinNetwork(medId, leaderId, Nexus.JoinMode.AsMed);
|
|
Nexus.joinNetwork(router1Id, leaderId, Nexus.JoinMode.AsFtd);
|
|
Nexus.joinNetwork(router2Id, leaderId, Nexus.JoinMode.AsFtd);
|
|
|
|
return { leaderId, fedId, sedId, medId, router1Id, router2Id };
|
|
}
|
|
|
|
async function runTest(modulePath) {
|
|
const absolutePath = path.resolve(modulePath);
|
|
console.log(`Loading Nexus WASM module from: ${absolutePath}`);
|
|
|
|
// Load the Emscripten-generated modularized ES6 module
|
|
const { default: createNexusModule } = await import('file://' + absolutePath);
|
|
|
|
// Initialize the module
|
|
const Nexus = await createNexusModule();
|
|
|
|
await createTopology(Nexus);
|
|
|
|
console.log("Initial simulation time:", Nexus.getNow());
|
|
|
|
console.log("Stepping Simulation...");
|
|
let nodeStateChangedCount = 0;
|
|
let linkUpdateCount = 0;
|
|
let packetEventCount = 0;
|
|
|
|
// Advance simulation and check for events
|
|
for (let i = 0; i < 50; i++) {
|
|
Nexus.stepSimulation(100);
|
|
let event;
|
|
while ((event = Nexus.pollEvent()) !== null) {
|
|
const type = event.type;
|
|
const data = event.data;
|
|
|
|
if (type === "node_state_changed") {
|
|
nodeStateChangedCount++;
|
|
if (nodeStateChangedCount < 10) {
|
|
console.log(`[Event] Node ${data.id} state: ${data.role} at (${data.x}, ${data.y}) RLOC16: ${data.rloc16} ExtAddr: ${data.extAddress}`);
|
|
}
|
|
if (!data.extAddress) {
|
|
throw new Error(`Node ${data.id} state change event missing extAddress`);
|
|
}
|
|
} else if (type === "link_update") {
|
|
linkUpdateCount++;
|
|
} else if (type === "packet_event") {
|
|
packetEventCount++;
|
|
if (packetEventCount < 5) {
|
|
console.log(`[Event] Packet from ${data.srcId} to ${data.dstId} (length: ${data.length}) frame: ${data.frame.length} bytes`);
|
|
}
|
|
if (!data.frame || data.frame.length !== data.length) {
|
|
throw new Error(`Packet event from ${data.srcId} to ${data.dstId} has invalid frame data`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`Received ${nodeStateChangedCount} node_state_changed events`);
|
|
console.log(`Received ${linkUpdateCount} link_update events`);
|
|
console.log(`Received ${packetEventCount} packet_event events`);
|
|
|
|
if (nodeStateChangedCount === 0) {
|
|
throw new Error("Expected node_state_changed events were not received");
|
|
}
|
|
|
|
// Verify getNodeId
|
|
let event;
|
|
// We need to find an event that has extAddress to test getNodeId
|
|
// Since we've already polled all events, we should have some node state changed events.
|
|
// Let's assume the first few events we printed have what we need, or just use the last polled data if available.
|
|
// Actually, let's just create a new node and test it.
|
|
|
|
console.log("Creating a manual node...");
|
|
const nodeId = Nexus.createNode(100, 200);
|
|
console.log("Created node ID:", nodeId);
|
|
|
|
// Step to trigger state change event to get its extAddress
|
|
Nexus.stepSimulation(1);
|
|
let foundExtAddr = null;
|
|
while ((event = Nexus.pollEvent()) !== null) {
|
|
if (event.type === "node_state_changed" && event.data.id === nodeId) {
|
|
foundExtAddr = event.data.extAddress;
|
|
}
|
|
}
|
|
|
|
if (foundExtAddr) {
|
|
const resolvedId = Nexus.getNodeId(foundExtAddr);
|
|
console.log(`Resolved ExtAddr ${foundExtAddr} to ID: ${resolvedId}`);
|
|
if (resolvedId !== nodeId) {
|
|
throw new Error(`getNodeId failed: expected ${nodeId}, got ${resolvedId}`);
|
|
}
|
|
}
|
|
|
|
Nexus.setNodePosition(nodeId, 150, 250);
|
|
Nexus.setNodeEnabled(nodeId, false);
|
|
Nexus.setNodeEnabled(nodeId, true);
|
|
|
|
console.log("Test completed successfully!");
|
|
}
|
|
|
|
const modulePath = process.argv[2];
|
|
if (!modulePath) {
|
|
console.error("Usage: node test_wasm_bindings.mjs <path-to-nexus_live_demo.js>");
|
|
process.exit(1);
|
|
}
|
|
|
|
runTest(modulePath).catch(err => {
|
|
console.error("Test failed:", err);
|
|
process.exit(1);
|
|
});
|