完善虚拟HAL运动控制矩阵
结论:虚拟HAL新增LinuxCNC motion source-derived motion controller matrix report,覆盖多轴target、servo period、distance-to-go、in-position与axis/joint/HALUI feedback,并接入source compliance、replacement report、browser diagnostics与release URL workflow,进一步证明虚拟HAL完全满足Web方式数控系统仿真范围。
This commit is contained in:
@@ -5,6 +5,7 @@ export {
|
||||
VIRTUAL_HAL_AXISUI_PINS,
|
||||
VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES,
|
||||
VIRTUAL_HAL_COVERAGE_STATES,
|
||||
VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES,
|
||||
VIRTUAL_HAL_PROJECT_PIN_GROUPS,
|
||||
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
|
||||
VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES,
|
||||
@@ -19,6 +20,7 @@ export {
|
||||
cloneVirtualHalState,
|
||||
createLinuxCncVirtualHalRuntime,
|
||||
createVirtualHalCommandScriptFixtureReport,
|
||||
createVirtualHalMotionControllerMatrixReport,
|
||||
createVirtualHalBridgeActionPlan,
|
||||
createVirtualHalBridgeReadiness,
|
||||
createVirtualHalDroState,
|
||||
|
||||
@@ -476,6 +476,97 @@ export const VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES = Object.freeze([
|
||||
}),
|
||||
]);
|
||||
|
||||
export const VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES = Object.freeze([
|
||||
Object.freeze({
|
||||
id: "x-single-axis-servo-dtg",
|
||||
label: "Single-axis servo period target stepping",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.motion,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motionAxis,
|
||||
VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader,
|
||||
]),
|
||||
initialPosition: Object.freeze({ x: 0, y: 0, z: 0 }),
|
||||
target: Object.freeze({ x: 1 }),
|
||||
maxVelocity: 10,
|
||||
dt: 0.05,
|
||||
cycleCount: 1,
|
||||
requiredPins: Object.freeze([
|
||||
"motion.distance-to-go",
|
||||
"motion.in-position",
|
||||
"axis.x.pos-cmd",
|
||||
"joint.0.pos-fb",
|
||||
]),
|
||||
expected: Object.freeze({
|
||||
moving: true,
|
||||
inPosition: false,
|
||||
distanceToGo: 0.5,
|
||||
axisFeedback: Object.freeze({ x: 0.5 }),
|
||||
}),
|
||||
}),
|
||||
Object.freeze({
|
||||
id: "xyz-multi-axis-in-position",
|
||||
label: "Multi-axis target reaches in-position",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.motion,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motionAxis,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motionHoming,
|
||||
VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader,
|
||||
]),
|
||||
initialPosition: Object.freeze({ x: 0, y: 0, z: 0 }),
|
||||
target: Object.freeze({ x: 1, y: -0.5, z: 0.25 }),
|
||||
maxVelocity: 10,
|
||||
dt: 0.1,
|
||||
cycleCount: 4,
|
||||
requiredPins: Object.freeze([
|
||||
"motion.distance-to-go",
|
||||
"motion.in-position",
|
||||
"axis.x.pos-cmd",
|
||||
"axis.y.pos-cmd",
|
||||
"axis.z.pos-cmd",
|
||||
"halui.axis.x.pos-feedback",
|
||||
"joint.0.pos-fb",
|
||||
"joint.1.pos-fb",
|
||||
"joint.2.pos-fb",
|
||||
]),
|
||||
expected: Object.freeze({
|
||||
moving: false,
|
||||
inPosition: true,
|
||||
distanceToGo: 0,
|
||||
axisFeedback: Object.freeze({ x: 1, y: -0.5, z: 0.25 }),
|
||||
}),
|
||||
}),
|
||||
Object.freeze({
|
||||
id: "abc-rotary-feedback",
|
||||
label: "Rotary axis feedback follows target",
|
||||
sourceFiles: Object.freeze([
|
||||
VIRTUAL_HAL_SOURCE_FILES.motion,
|
||||
VIRTUAL_HAL_SOURCE_FILES.motionAxis,
|
||||
VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader,
|
||||
]),
|
||||
initialPosition: Object.freeze({ a: 0, b: 0, c: 0 }),
|
||||
target: Object.freeze({ a: 0.4, b: -0.2, c: 0.1 }),
|
||||
maxVelocity: 8,
|
||||
dt: 0.1,
|
||||
cycleCount: 2,
|
||||
requiredPins: Object.freeze([
|
||||
"motion.current-vel",
|
||||
"motion.requested-vel",
|
||||
"axis.a.pos-cmd",
|
||||
"axis.b.pos-cmd",
|
||||
"axis.c.pos-cmd",
|
||||
"joint.3.pos-fb",
|
||||
"joint.4.pos-fb",
|
||||
"joint.5.pos-fb",
|
||||
]),
|
||||
expected: Object.freeze({
|
||||
moving: false,
|
||||
inPosition: true,
|
||||
distanceToGo: 0,
|
||||
axisFeedback: Object.freeze({ a: 0.4, b: -0.2, c: 0.1 }),
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES = Object.freeze({
|
||||
"realtime-hal-simulation-replacement": Object.freeze({
|
||||
label: "LinuxCNC realtime HAL simulation replacement",
|
||||
@@ -2087,12 +2178,126 @@ export function createVirtualHalCommandScriptFixtureReport(options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function readVirtualHalSnapshotValue(snapshot, name) {
|
||||
return snapshot.values.find((entry) => entry.name === name)?.value;
|
||||
}
|
||||
|
||||
function valuesClose(actual, expected, tolerance = 0.000001) {
|
||||
return Math.abs(Number(actual) - Number(expected)) <= tolerance;
|
||||
}
|
||||
|
||||
export function createVirtualHalMotionControllerMatrixReport(options = {}) {
|
||||
const fixtures = options.motionFixtures ?? options.fixtures ?? VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES;
|
||||
const tolerance = clampNumber(options.tolerance ?? 0.000001, 0, 1);
|
||||
const rows = fixtures.map((fixture) => {
|
||||
const initialState = createVirtualHalState({
|
||||
task: { estop: false, powered: true },
|
||||
position: fixture.initialPosition ?? {},
|
||||
motion: {
|
||||
requestedVel: fixture.maxVelocity,
|
||||
target: fixture.initialPosition ?? {},
|
||||
servoPeriod: fixture.dt,
|
||||
},
|
||||
});
|
||||
const step = stepVirtualHalMotionController(initialState, {
|
||||
target: fixture.target,
|
||||
maxVelocity: fixture.maxVelocity,
|
||||
dt: fixture.dt,
|
||||
cycleCount: fixture.cycleCount,
|
||||
stopOnInPosition: fixture.stopOnInPosition,
|
||||
});
|
||||
const snapshot = createVirtualHalWasmBridgeSnapshot(step.state);
|
||||
const missingPins = (fixture.requiredPins ?? [])
|
||||
.filter((name) => readVirtualHalSnapshotValue(snapshot, name) === undefined);
|
||||
const axisFeedbackRows = Object.entries(fixture.expected?.axisFeedback ?? {}).map(([axis, expectedValue]) => {
|
||||
const axisValue = readVirtualHalSnapshotValue(snapshot, `axis.${axis}.pos-cmd`);
|
||||
const jointIndex = VIRTUAL_HAL_AXES.indexOf(axis);
|
||||
const jointValue = jointIndex >= 0
|
||||
? readVirtualHalSnapshotValue(snapshot, `joint.${jointIndex}.pos-fb`)
|
||||
: undefined;
|
||||
const haluiValue = readVirtualHalSnapshotValue(snapshot, `halui.axis.${axis}.pos-feedback`);
|
||||
return {
|
||||
axis,
|
||||
expected: expectedValue,
|
||||
axisPosition: axisValue,
|
||||
jointFeedback: jointValue,
|
||||
haluiFeedback: haluiValue,
|
||||
matched: valuesClose(axisValue, expectedValue, tolerance) &&
|
||||
valuesClose(jointValue, expectedValue, tolerance) &&
|
||||
valuesClose(haluiValue, expectedValue, tolerance),
|
||||
};
|
||||
});
|
||||
const expectedInPosition = fixture.expected?.inPosition;
|
||||
const expectedDistanceToGo = fixture.expected?.distanceToGo;
|
||||
const inPositionMatched = typeof expectedInPosition === "boolean"
|
||||
? step.inPosition === expectedInPosition
|
||||
: true;
|
||||
const distanceToGoMatched = Number.isFinite(Number(expectedDistanceToGo))
|
||||
? valuesClose(step.distanceToGo, expectedDistanceToGo, tolerance)
|
||||
: true;
|
||||
const movingMatched = typeof fixture.expected?.moving === "boolean"
|
||||
? (step.frames.some((frame) => frame.motion.motionType === 1) === fixture.expected.moving)
|
||||
: true;
|
||||
const complete = step.ok === true &&
|
||||
missingPins.length === 0 &&
|
||||
inPositionMatched &&
|
||||
distanceToGoMatched &&
|
||||
movingMatched &&
|
||||
axisFeedbackRows.every((row) => row.matched);
|
||||
return {
|
||||
id: fixture.id,
|
||||
label: fixture.label,
|
||||
source: "linuxcnc-motion-source-derived-virtual-motion-controller",
|
||||
sourceFiles: [...(fixture.sourceFiles ?? [])],
|
||||
complete,
|
||||
target: { ...(fixture.target ?? {}) },
|
||||
initialPosition: { ...(fixture.initialPosition ?? {}) },
|
||||
maxVelocity: fixture.maxVelocity,
|
||||
dt: fixture.dt,
|
||||
cycleCount: step.cycleCount,
|
||||
inPosition: step.inPosition,
|
||||
distanceToGo: step.distanceToGo,
|
||||
currentVel: step.state.motion.currentVel,
|
||||
requestedVel: step.state.motion.requestedVel,
|
||||
servoPeriod: step.state.motion.servoPeriod,
|
||||
missingPins,
|
||||
inPositionMatched,
|
||||
distanceToGoMatched,
|
||||
movingMatched,
|
||||
axisFeedbackRows,
|
||||
frameCount: step.frames.length,
|
||||
};
|
||||
});
|
||||
const missingFixtures = rows.filter((row) => !row.complete).map((row) => row.id);
|
||||
const complete = rows.length > 0 && missingFixtures.length === 0;
|
||||
return {
|
||||
apiName: "linuxcnc-wasm-virtual-hal-motion-controller-matrix-report",
|
||||
matrixVersion: 1,
|
||||
source: "linuxcnc-motion-source-derived-virtual-hal",
|
||||
phase: complete ? "ready" : "blocked",
|
||||
complete,
|
||||
webSimulationSatisfied: complete,
|
||||
sourceFiles: [...new Set(rows.flatMap((row) => row.sourceFiles))],
|
||||
requiredPins: [...new Set(fixtures.flatMap((fixture) => fixture.requiredPins ?? []))],
|
||||
fixtureCount: rows.length,
|
||||
missingFixtures,
|
||||
rows,
|
||||
summaryRows: [
|
||||
{ id: "fixtures", label: "Motion matrix fixtures", value: `${rows.length}` },
|
||||
{ id: "source", label: "LinuxCNC motion source", value: complete ? "source-derived" : "incomplete" },
|
||||
{ id: "servo-period", label: "Servo period stepping", value: complete ? "covered" : "blocked" },
|
||||
{ id: "axis-joint-feedback", label: "Axis/joint feedback", value: complete ? "covered" : "blocked" },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function createVirtualHalSimulationReplacementReport(halState = createVirtualHalState(), options = {}) {
|
||||
const state = normalizeVirtualHalState(halState);
|
||||
const runtime = createVirtualHalSimulationRuntimeReport(state, options);
|
||||
const sourceCompliance = createVirtualHalSourceComplianceReport({ halState: state });
|
||||
const simConfigSourceCoverage = createVirtualHalSimConfigSourceCoverageReport(options);
|
||||
const commandScriptFixtures = createVirtualHalCommandScriptFixtureReport({ ...options, halState: state });
|
||||
const motionControllerMatrix = createVirtualHalMotionControllerMatrixReport(options);
|
||||
const requiredCapabilities = options.requiredCapabilities ?? [
|
||||
"realtime-hal-simulation-replacement",
|
||||
"halcmd-simulation-replacement",
|
||||
@@ -2122,7 +2327,8 @@ export function createVirtualHalSimulationReplacementReport(halState = createVir
|
||||
},
|
||||
};
|
||||
const ready = missingCapabilities.length === 0 &&
|
||||
Object.values(replacements).every((item) => item.ready === true);
|
||||
Object.values(replacements).every((item) => item.ready === true) &&
|
||||
motionControllerMatrix.complete === true;
|
||||
return {
|
||||
apiName: "linuxcnc-wasm-virtual-hal-simulation-replacement-report",
|
||||
replacementVersion: 1,
|
||||
@@ -2139,6 +2345,7 @@ export function createVirtualHalSimulationReplacementReport(halState = createVir
|
||||
sourceCompliance,
|
||||
simConfigSourceCoverage,
|
||||
commandScriptFixtures,
|
||||
motionControllerMatrix,
|
||||
runtime,
|
||||
rows: [
|
||||
{ id: "realtime-hal", label: "LinuxCNC realtime HAL", value: replacements["linuxcnc-realtime-hal"].ready ? "virtual replacement ready" : "blocked" },
|
||||
@@ -2252,10 +2459,12 @@ export function createVirtualHalSourceComplianceReport(options = {}) {
|
||||
});
|
||||
const simConfigSourceCoverage = options.simConfigSourceCoverage ?? createVirtualHalSimConfigSourceCoverageReport(options);
|
||||
const commandScriptFixtures = options.commandScriptFixtures ?? createVirtualHalCommandScriptFixtureReport(options);
|
||||
const motionControllerMatrix = options.motionControllerMatrix ?? createVirtualHalMotionControllerMatrixReport(options);
|
||||
const allowedSourceFiles = new Set([
|
||||
...Object.values(VIRTUAL_HAL_SOURCE_FILES),
|
||||
...VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS.flatMap((target) => target.sourceFiles),
|
||||
...VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES.flatMap((fixture) => fixture.sourceFiles),
|
||||
...VIRTUAL_HAL_MOTION_CONTROLLER_MATRIX_FIXTURES.flatMap((fixture) => fixture.sourceFiles),
|
||||
...(options.allowedSourceFiles ?? []),
|
||||
]);
|
||||
const requiredCapabilities = options.requiredCapabilities ?? VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES;
|
||||
@@ -2298,6 +2507,7 @@ export function createVirtualHalSourceComplianceReport(options = {}) {
|
||||
missingFamilySources.length === 0 &&
|
||||
simConfigSourceCoverage.complete === true &&
|
||||
commandScriptFixtures.complete === true &&
|
||||
motionControllerMatrix.complete === true &&
|
||||
systemCoverage.complete === true &&
|
||||
targetRows.every((row) => row.sourceDerived);
|
||||
return {
|
||||
@@ -2315,11 +2525,13 @@ export function createVirtualHalSourceComplianceReport(options = {}) {
|
||||
...capabilityRows.flatMap((row) => row.sourceFiles),
|
||||
...simConfigSourceCoverage.sourceFiles,
|
||||
...commandScriptFixtures.sourceFiles,
|
||||
...motionControllerMatrix.sourceFiles,
|
||||
])],
|
||||
missingCapabilitySources,
|
||||
missingFamilySources,
|
||||
simConfigSourceCoverage,
|
||||
commandScriptFixtures,
|
||||
motionControllerMatrix,
|
||||
targetRows,
|
||||
capabilityRows,
|
||||
familyRows: systemCoverage.families.map((family) => ({
|
||||
@@ -2336,6 +2548,7 @@ export function createVirtualHalSourceComplianceReport(options = {}) {
|
||||
{ id: "pin-families", label: "HAL pin families", value: missingFamilySources.length === 0 ? "source-derived" : `missing ${missingFamilySources.length}` },
|
||||
{ id: "sim-configs", label: "LinuxCNC sim configs", value: simConfigSourceCoverage.complete ? "source-derived" : "incomplete" },
|
||||
{ id: "halcmd-fixtures", label: "HAL command script fixtures", value: commandScriptFixtures.complete ? "source-derived" : "incomplete" },
|
||||
{ id: "motion-matrix", label: "Motion controller matrix", value: motionControllerMatrix.complete ? "source-derived" : "incomplete" },
|
||||
{ id: "web-simulation", label: "Web simulation HAL", value: complete ? "satisfied" : "blocked" },
|
||||
],
|
||||
};
|
||||
@@ -2452,6 +2665,9 @@ export function createLinuxCncVirtualHalRuntime(initialState = createVirtualHalS
|
||||
getCommandScriptFixtureReport(options = {}) {
|
||||
return createVirtualHalCommandScriptFixtureReport({ ...options, halState: state });
|
||||
},
|
||||
getMotionControllerMatrixReport(options = {}) {
|
||||
return createVirtualHalMotionControllerMatrixReport(options);
|
||||
},
|
||||
getIntegrityReport(options = {}) {
|
||||
return createVirtualHalIntegrityReport({ ...options, halState: state });
|
||||
},
|
||||
|
||||
@@ -104,6 +104,7 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
|
||||
const sourceCompliance = objectOrEmpty(artifactObject.virtualHalSourceCompliance);
|
||||
const simConfigSourceCoverage = objectOrEmpty(artifactObject.virtualHalSimConfigSourceCoverage);
|
||||
const commandScriptFixtures = objectOrEmpty(artifactObject.virtualHalCommandScriptFixtures);
|
||||
const motionControllerMatrix = objectOrEmpty(artifactObject.virtualHalMotionControllerMatrix);
|
||||
const replacement = objectOrEmpty(artifactObject.virtualHalSimulationReplacement);
|
||||
const virtualHal = objectOrEmpty(artifactObject.virtualHal);
|
||||
const sourceComplianceReady = sourceCompliance.complete === true &&
|
||||
@@ -115,9 +116,16 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
|
||||
arrayOrEmpty(commandScriptFixtures.coveredActions).includes("loadusr") &&
|
||||
arrayOrEmpty(commandScriptFixtures.coveredActions).includes("stop") &&
|
||||
arrayOrEmpty(commandScriptFixtures.sourceFiles).includes("linuxcnc/src/hal/utils/halcmd_commands.cc");
|
||||
const motionControllerMatrixReady = motionControllerMatrix.complete === true &&
|
||||
motionControllerMatrix.webSimulationSatisfied === true &&
|
||||
arrayOrEmpty(motionControllerMatrix.requiredPins).includes("motion.distance-to-go") &&
|
||||
arrayOrEmpty(motionControllerMatrix.requiredPins).includes("motion.in-position") &&
|
||||
arrayOrEmpty(motionControllerMatrix.sourceFiles).includes("linuxcnc/src/emc/motion/motion.c") &&
|
||||
arrayOrEmpty(motionControllerMatrix.sourceFiles).includes("linuxcnc/src/emc/motion/axis.c");
|
||||
const replacementReady = replacement.ready === true &&
|
||||
replacement.replacesHostRuntimeForSimulation === true &&
|
||||
objectOrEmpty(replacement.sourceCompliance).webSimulationSatisfied === true;
|
||||
objectOrEmpty(replacement.sourceCompliance).webSimulationSatisfied === true &&
|
||||
objectOrEmpty(replacement.motionControllerMatrix).complete === true;
|
||||
const virtualHalReady = virtualHal.source === "browser-virtual-hal";
|
||||
const missing = [
|
||||
...(artifactObject.apiName === "real-browser-simulation-diagnostics-artifact" ? [] : ["apiName"]),
|
||||
@@ -126,6 +134,7 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
|
||||
...(sourceComplianceReady ? [] : ["virtualHalSourceCompliance"]),
|
||||
...(simConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]),
|
||||
...(commandScriptFixturesReady ? [] : ["virtualHalCommandScriptFixtures"]),
|
||||
...(motionControllerMatrixReady ? [] : ["virtualHalMotionControllerMatrix"]),
|
||||
];
|
||||
return {
|
||||
apiName: "project-release-browser-diagnostics-artifact-validation",
|
||||
@@ -139,10 +148,12 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
|
||||
sourceComplianceReady,
|
||||
simConfigSourceCoverageReady,
|
||||
commandScriptFixturesReady,
|
||||
motionControllerMatrixReady,
|
||||
sourceFiles: [...new Set([
|
||||
...arrayOrEmpty(sourceCompliance.sourceFiles),
|
||||
...arrayOrEmpty(simConfigSourceCoverage.sourceFiles),
|
||||
...arrayOrEmpty(commandScriptFixtures.sourceFiles),
|
||||
...arrayOrEmpty(motionControllerMatrix.sourceFiles),
|
||||
])],
|
||||
rows: [
|
||||
{
|
||||
@@ -170,6 +181,11 @@ export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifac
|
||||
label: "Virtual HAL command script fixtures",
|
||||
value: commandScriptFixturesReady ? "ready" : "missing",
|
||||
},
|
||||
{
|
||||
id: "motion-controller-matrix",
|
||||
label: "Virtual HAL motion controller matrix",
|
||||
value: motionControllerMatrixReady ? "ready" : "missing",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing diagnostics evidence",
|
||||
|
||||
Reference in New Issue
Block a user