Files
cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/src/runtime/controlled-user-m-simulation.js
2026-07-02 20:25:37 -04:00

220 lines
6.3 KiB
JavaScript

const DEFAULT_ALLOWED_USER_M_CODES = {
M428: {
code: "M428",
label: "TCP kinematics",
kinsType: "tcp",
switchkinsType: 1,
rtcpState: "on",
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/428remap.ngc",
},
M429: {
code: "M429",
label: "Identity kinematics",
kinsType: "identity",
switchkinsType: 0,
rtcpState: "off",
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/429remap.ngc",
},
M430: {
code: "M430",
label: "User kinematics",
kinsType: "userk",
switchkinsType: 2,
rtcpState: "on",
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/430remap.ngc",
},
M128: {
code: "M128",
label: "controlled millturn mill-mode user-M",
kinsType: "mill",
switchkinsType: 0,
rtcpState: "off",
sourceRel: "linuxcnc millturn user-M reference",
},
M129: {
code: "M129",
label: "controlled millturn turn-mode user-M",
kinsType: "turn",
switchkinsType: 1,
rtcpState: "off",
sourceRel: "linuxcnc millturn user-M reference",
},
};
export function createControlledUserMSimulation({ allowedCodes = DEFAULT_ALLOWED_USER_M_CODES } = {}) {
return {
apiName: "web-rtcp-5axis-controlled-user-m-simulation",
ready: true,
processReady: true,
processScope: "web_simulation_only",
hostProcessReady: false,
hostProcessExecution: false,
arbitraryUserMExecution: false,
allowedCodes: Object.fromEntries(
Object.entries(allowedCodes).map(([code, definition]) => [normalizeMCode(code), {
...definition,
code: normalizeMCode(definition.code || code),
}]),
),
events: [],
blockedEvents: [],
semanticBoundary: "controlled_user_m_web_simulation_whitelist_not_host_process",
linuxCncReferences: [
"linuxcnc/src/emc/rs274ngc",
"linuxcnc/src/emc/task/emctask.cc",
"linuxcnc/src/emc/task/emccanon.cc",
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/428remap.ngc",
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/429remap.ngc",
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/430remap.ngc",
],
};
}
export function runControlledUserM(simulation, code, context = {}) {
const state = cloneSimulation(simulation);
const normalizedCode = normalizeMCode(code);
const definition = state.allowedCodes[normalizedCode] || null;
if (!definition) {
const event = createUserMEvent({
code: normalizedCode,
allowed: false,
reason: "not_in_controlled_user_m_whitelist",
context,
});
state.blockedEvents.push(event);
return {
simulation: state,
event,
statePatch: {},
halPatch: {},
};
}
const profileKinsType = definition.kinsType === "tcp"
? tcpKinsTypeForProfile(context.profile)
: definition.kinsType;
const halPatch = {
"motion.switchkins-type": definition.switchkinsType,
"motion.analog-out-03": definition.switchkinsType,
};
const statePatch = {
kinsType: profileKinsType,
rtcpState: definition.rtcpState,
};
const event = createUserMEvent({
code: normalizedCode,
allowed: true,
definition,
context,
halPatch,
statePatch,
});
state.events.push(event);
return {
simulation: state,
event,
statePatch,
halPatch,
};
}
export function createControlledUserMReadiness(simulation) {
const ready = simulation?.processReady === true && simulation?.processScope === "web_simulation_only";
return {
apiName: "web-rtcp-5axis-controlled-user-m-readiness",
ready,
externalUserMProcessReady: ready,
externalUserMProcessScope: ready ? "web_simulation_only" : "not_ready",
hostExternalUserMProcessReady: false,
hostProcessExecution: false,
arbitraryUserMExecution: false,
allowedCodeCount: Object.keys(simulation?.allowedCodes || {}).length,
blockedEventCount: simulation?.blockedEvents?.length || 0,
semanticBoundary: "external_user_m_process_ready_for_web_simulation_only",
};
}
export function extractControlledUserMCodesFromProgram(programText = "") {
const matches = [];
const lines = String(programText).split(/\r?\n/);
lines.forEach((line, index) => {
const stripped = line.replace(/\([^)]*\)/g, " ");
for (const match of stripped.matchAll(/\bM\s*(\d{2,3})\b/gi)) {
const code = normalizeMCode(`M${match[1]}`);
if (Number(match[1]) >= 100 || ["M428", "M429", "M430"].includes(code)) {
matches.push({
code,
line: index + 1,
rawLine: line,
});
}
}
});
return matches;
}
export function runControlledUserMProgramScan(simulation, programText = "", context = {}) {
let state = simulation;
const events = [];
for (const occurrence of extractControlledUserMCodesFromProgram(programText)) {
const result = runControlledUserM(state, occurrence.code, {
...context,
line: occurrence.line,
rawLine: occurrence.rawLine,
});
state = result.simulation;
events.push(result.event);
}
return { simulation: state, events };
}
function createUserMEvent({
code,
allowed,
definition = null,
context = {},
halPatch = {},
statePatch = {},
reason = null,
}) {
return {
apiName: "web-rtcp-5axis-controlled-user-m-event",
code,
allowed,
reason,
label: definition?.label || null,
sourceRel: definition?.sourceRel || context.sourceRel || null,
line: context.line || null,
halPatch,
statePatch,
createdAt: new Date().toISOString(),
semanticBoundary: allowed
? "vendored_or_whitelisted_user_m_web_simulation_event"
: "arbitrary_external_user_m_blocked",
promotionScope: "web_simulation_only",
hostProcessExecution: false,
arbitraryUserMExecution: false,
};
}
function tcpKinsTypeForProfile(profile) {
if (profile?.id === "xyzbc-trt") return "tcp-xyzbc";
return "tcp-xyzac";
}
function normalizeMCode(code) {
const normalized = String(code || "").trim().toUpperCase().replace(/\s+/g, "");
const match = normalized.match(/^M0*(\d+)$/);
if (!match) return normalized;
return `M${Number(match[1])}`;
}
function cloneSimulation(simulation) {
return {
...simulation,
allowedCodes: { ...(simulation.allowedCodes || {}) },
events: [...(simulation.events || [])],
blockedEvents: [...(simulation.blockedEvents || [])],
};
}