Enforce LinuxCNC WASM authority for XYZAC frontend

This commit is contained in:
2026-09-02 07:43:47 +08:00
parent c60e3a1e8f
commit 6b689fd69e
36 changed files with 514 additions and 210 deletions

View File

@@ -205,6 +205,7 @@ static void state_defaults(sim_io *io) {
io->state.spindles[index].max_rpm = 100000.0;
io->state.spindles[index].accel_rpm_s = old.spindles[index].accel_rpm_s;
io->state.spindles[index].tolerance_rpm = old.spindles[index].tolerance_rpm;
io->state.spindles[index].override_scale = 1.0;
io->state.spindles[index].at_speed = 1;
}
}
@@ -256,7 +257,7 @@ int sim_io_spindle_command(sim_io *io, uint32_t spindle, double rpm, uint32_t en
if (io->state.estop) { set_last(io, SIM_IO_ERR_ESTOP); return SIM_IO_ERR_ESTOP; }
if (!io->state.ready) { set_last(io, SIM_IO_ERR_NOT_READY); return SIM_IO_ERR_NOT_READY; }
state = &io->state.spindles[spindle];
state->command_rpm = enabled && !state->inhibit && !state->amp_fault ? rpm : 0.0;
state->command_rpm = enabled && !state->inhibit && !state->amp_fault ? rpm * state->override_scale : 0.0;
state->enabled = enabled ? 1U : 0U;
set_last(io, SIM_IO_OK);
mirror_pins(io);
@@ -295,6 +296,21 @@ int sim_io_spindle_brake(sim_io *io, uint32_t spindle, uint32_t engaged) {
return SIM_IO_OK;
}
int sim_io_spindle_set_override(sim_io *io, uint32_t spindle, double scale) {
sim_io_spindle_state *state;
double base;
if (!valid_spindle(io, spindle) || !isfinite(scale) || scale < 0.0 || scale > 2.0) return SIM_IO_ERR_RANGE;
if (io->state.estop) { set_last(io, SIM_IO_ERR_ESTOP); return SIM_IO_ERR_ESTOP; }
if (!io->state.ready) { set_last(io, SIM_IO_ERR_NOT_READY); return SIM_IO_ERR_NOT_READY; }
state = &io->state.spindles[spindle];
base = state->override_scale > 0.0 ? state->command_rpm / state->override_scale : 0.0;
state->override_scale = scale;
state->command_rpm = state->enabled && !state->inhibit && !state->amp_fault ? base * scale : 0.0;
set_last(io, SIM_IO_OK);
mirror_pins(io);
return SIM_IO_OK;
}
int sim_io_spindle_set_inhibit(sim_io *io, uint32_t spindle, uint32_t inhibit) {
if (!valid_spindle(io, spindle) || inhibit > 1U) return SIM_IO_ERR_ARGUMENT;
io->state.spindles[spindle].inhibit = inhibit;
@@ -607,9 +623,9 @@ uint32_t sim_io_canonical(const sim_io *io, char *output, uint32_t capacity) {
const sim_io_spindle_state *spindle = &io->state.spindles[index];
int part = snprintf(destination + (output && (uint32_t)written < capacity ? written : 0),
output && (uint32_t)written < capacity ? capacity - (uint32_t)written : 0,
"%s{\"at_speed\":%u,\"brake\":%u,\"command_rpm\":%.9g,\"forward\":%u,\"reverse\":%u,\"speed_rpm\":%.9g,\"tick\":%u}",
"%s{\"at_speed\":%u,\"brake\":%u,\"command_rpm\":%.9g,\"enabled\":%u,\"forward\":%u,\"inhibit\":%u,\"override\":%.9g,\"reverse\":%u,\"speed_rpm\":%.9g,\"tick\":%u}",
index ? "," : "", spindle->at_speed, spindle->brake, spindle->command_rpm,
spindle->forward, spindle->reverse, spindle->speed_rpm, spindle->tick);
spindle->enabled, spindle->forward, spindle->inhibit, spindle->override_scale, spindle->reverse, spindle->speed_rpm, spindle->tick);
if (part < 0) return 0; written += part;
}
{ int part = snprintf(destination + (output && (uint32_t)written < capacity ? written : 0),

View File

@@ -64,6 +64,7 @@ int sim_kinematics_home_limit_home(sim_kinematics_home_limit *home_limit,
memcpy(home_limit->state.joints, joints, sizeof(double) * home_limit->state.joint_count);
home_limit->state.pose = pose;
home_limit->state.home_sequence = home_sequence;
home_limit->state.homed_mask = home_limit->state.joint_count >= 32 ? UINT32_MAX : (UINT32_C(1) << home_limit->state.joint_count) - 1U;
home_limit->state.homed = 1;
home_limit->state.tick += 1;
home_limit->state.last_error = SIM_KIN_HOME_OK;
@@ -72,6 +73,33 @@ int sim_kinematics_home_limit_home(sim_kinematics_home_limit *home_limit,
return SIM_KIN_HOME_OK;
}
int sim_kinematics_home_limit_home_joint(sim_kinematics_home_limit *home_limit,
uint32_t home_sequence, uint32_t joint) {
sim_posemath_pose pose;
double joints[SIM_KIN_MAX_JOINTS] = {0};
int result;
if (home_limit == 0 || home_limit->kinematics == 0 || joint >= home_limit->state.joint_count) return SIM_KIN_HOME_ERR_ARGUMENT;
if (home_sequence > 0xffffu) return SIM_KIN_HOME_ERR_SEQUENCE;
if (home_limit->state.homed_mask != 0) memcpy(joints, home_limit->state.joints, sizeof(double) * home_limit->state.joint_count);
else {
result = sim_kinematics_home(home_limit->kinematics, joints, home_limit->state.joint_count, &pose);
if (result != SIM_KIN_OK) return SIM_KIN_HOME_ERR_FORWARD;
}
joints[joint] = 0.0;
result = sim_kinematics_forward(home_limit->kinematics, joints, home_limit->state.joint_count, &pose);
if (result != SIM_KIN_OK) return SIM_KIN_HOME_ERR_FORWARD;
memcpy(home_limit->state.joints, joints, sizeof(double) * home_limit->state.joint_count);
home_limit->state.pose = pose;
home_limit->state.home_sequence = home_sequence;
home_limit->state.homed_mask |= UINT32_C(1) << joint;
home_limit->state.homed = home_limit->state.homed_mask == (home_limit->state.joint_count >= 32 ? UINT32_MAX : (UINT32_C(1) << home_limit->state.joint_count) - 1U);
home_limit->state.tick += 1;
home_limit->state.last_error = SIM_KIN_HOME_OK;
home_limit->state.limit_error = SIM_KIN_OK;
home_limit->state.kinematics_error = SIM_KIN_OK;
return SIM_KIN_HOME_OK;
}
int sim_kinematics_home_limit_check(sim_kinematics_home_limit *home_limit,
const double *joints,
uint32_t joint_count) {

View File

@@ -18,6 +18,10 @@ struct sim_linuxcnc_tp {
emcmot_config_t config;
double axis_velocity[9];
double axis_acceleration[9];
double axis_min[9];
double axis_max[9];
double velocity_scale;
double jog_scale;
uint32_t tick;
uint32_t last_active_id;
int just_completed;
@@ -144,7 +148,11 @@ int32_t sim_linuxcnc_tp_init(sim_linuxcnc_tp *planner, double cycle_time, double
for (int axis = 0; axis < 9; ++axis) {
planner->axis_velocity[axis] = axis < 3 ? fmin(20.0, velocity_limit) : (axis == 3 || axis == 5) ? fmin(30.0, velocity_limit) : velocity_limit;
planner->axis_acceleration[axis] = acceleration_limit;
planner->axis_min[axis] = -INFINITY;
planner->axis_max[axis] = INFINITY;
}
planner->velocity_scale = 1.0;
planner->jog_scale = 1.0;
planner->status.feed_scale = 1.0; planner->status.rapid_scale = 1.0; planner->status.net_feed_scale = 1.0;
planner->status.enables_new = FS_ENABLED | FH_ENABLED; planner->status.enables_queued = planner->status.enables_new;
planner->status.motionFlag = EMCMOT_MOTION_ENABLE_BIT | EMCMOT_MOTION_COORD_BIT | EMCMOT_MOTION_INPOS_BIT;
@@ -176,6 +184,64 @@ int32_t sim_linuxcnc_tp_set_feed_override(sim_linuxcnc_tp *planner, double scale
planner->status.net_feed_scale = scale; planner->status.feed_scale = scale; return 0;
}
int32_t sim_linuxcnc_tp_set_velocity_scale(sim_linuxcnc_tp *planner, double scale) {
if (planner == NULL || !planner->initialized || !isfinite(scale) || scale < 0.06 || scale > 1.0) return -1211;
active_planner = planner;
if (tpSetVlimit(&planner->tp, planner->config.limitVel * scale) != TP_ERR_OK) return -1212;
planner->velocity_scale = scale;
return 0;
}
int32_t sim_linuxcnc_tp_set_jog_scale(sim_linuxcnc_tp *planner, double scale) {
if (planner == NULL || !planner->initialized || !isfinite(scale) || scale < 0.06 || scale > 1.0) return -1213;
planner->jog_scale = scale;
return 0;
}
int32_t sim_linuxcnc_tp_set_axis_limit(sim_linuxcnc_tp *planner, int axis, double minimum, double maximum) {
if (planner == NULL || !planner->initialized || axis < 0 || axis >= 9 || !isfinite(minimum) || !isfinite(maximum) || minimum >= maximum) return -1214;
planner->axis_min[axis] = minimum;
planner->axis_max[axis] = maximum;
return 0;
}
int32_t sim_linuxcnc_tp_jog(sim_linuxcnc_tp *planner, int axis, int direction, double increment, uint32_t tag) {
EmcPose end;
double *value;
double velocity;
struct state_tag_t state_tag = {0};
if (planner == NULL || !planner->initialized || axis < 0 || axis >= 9 || (direction != -1 && direction != 1) || !isfinite(increment) || increment <= 0.0) return -1215;
active_planner = planner;
end = planner->tp.goalPos;
value = axis == 0 ? &end.tran.x : axis == 1 ? &end.tran.y : axis == 2 ? &end.tran.z : axis == 3 ? &end.a : axis == 4 ? &end.b : axis == 5 ? &end.c : axis == 6 ? &end.u : axis == 7 ? &end.v : &end.w;
*value += (double)direction * increment;
if (!isfinite(*value) || *value < planner->axis_min[axis] || *value > planner->axis_max[axis]) return -1217;
velocity = planner->axis_velocity[axis] * planner->jog_scale;
state_tag.fields_float[GM_FIELD_FLOAT_LINE_NUMBER] = (float)tag;
if (tpSetId(&planner->tp, (int)tag) != TP_ERR_OK) return -1218;
return tpAddLine(&planner->tp, end, TC_LINEAR, velocity, velocity, planner->axis_acceleration[axis], planner->status.enables_new, 0, -1, state_tag) == TP_ERR_OK ? 0 : -1219;
}
double sim_linuxcnc_tp_velocity_scale(const sim_linuxcnc_tp *planner) {
return planner == NULL || !planner->initialized ? 0.0 : planner->velocity_scale;
}
double sim_linuxcnc_tp_velocity_limit(const sim_linuxcnc_tp *planner) {
return planner == NULL || !planner->initialized ? 0.0 : planner->tp.vLimit;
}
double sim_linuxcnc_tp_jog_scale(const sim_linuxcnc_tp *planner) {
return planner == NULL || !planner->initialized ? 0.0 : planner->jog_scale;
}
double sim_linuxcnc_tp_jog_velocity(const sim_linuxcnc_tp *planner, int axis) {
return planner == NULL || !planner->initialized || axis < 0 || axis >= 9 ? 0.0 : planner->axis_velocity[axis] * planner->jog_scale;
}
double sim_linuxcnc_tp_feed_override(const sim_linuxcnc_tp *planner) {
return planner == NULL || !planner->initialized ? 0.0 : planner->status.net_feed_scale;
}
int32_t sim_linuxcnc_tp_set_dynamics(sim_linuxcnc_tp *planner, double acceleration_limit) {
if (planner == NULL || !planner->initialized || !isfinite(acceleration_limit) || acceleration_limit <= 0.0) return -1217;
active_planner = planner;
@@ -195,7 +261,11 @@ int32_t sim_linuxcnc_tp_abort(sim_linuxcnc_tp *planner) {
active_planner = planner;
planner->last_active_id = 0;
planner->just_completed = 0;
return tpAbort(&planner->tp) == TP_ERR_OK ? 0 : -1223;
if (tpAbort(&planner->tp) != TP_ERR_OK) return -1223;
// A browser PROGRAM_ABORT has no background emcmot servo thread after
// the command response. Complete the source TP abort transition here so
// the authoritative snapshot cannot retain an active queue indefinitely.
return tpClear(&planner->tp) == TP_ERR_OK ? 0 : -1223;
}
int32_t sim_linuxcnc_tp_set_position(sim_linuxcnc_tp *planner, const sim_posemath_pose *position) {

View File

@@ -26,8 +26,8 @@ static int is_side_effect(const char *kind) {
"LOAD_WORKSPACE", "SIM_STEP", "COMMAND",
"PROGRAM_LOAD", "PROGRAM_RUN", "PROGRAM_PAUSE", "PROGRAM_RESUME",
"PROGRAM_ABORT", "PROGRAM_STEP", "PROGRAM_SEEK", "MDI_EXECUTE",
"JOG_START", "JOG_STOP", "HOME", "SET_MODE", "SET_OVERRIDE",
"SPINDLE_COMMAND", "COOLANT_COMMAND", "TOOL_PREPARE", "TOOL_CHANGE",
"JOG_START", "JOG_STOP", "HOME", "SET_MODE", "SET_OVERRIDE", "SET_JOG_SCALE", "SET_MAX_VELOCITY", "SET_SPINDLE_OVERRIDE",
"SPINDLE_COMMAND", "SPINDLE_BRAKE", "SET_SPINDLE_OVERRIDE", "COOLANT_COMMAND", "TOOL_PREPARE", "TOOL_CHANGE",
"PROBE_CONFIG", "HAL_COMMAND", "HAL_SET", "PLC_START", "PLC_STOP",
"PROJECT_SAVE", "IMPORT", "EXPORT", "CHECKPOINT", "RECOVER",
"SUBSCRIBE", "UNSUBSCRIBE", "CANCEL", NULL,

View File

@@ -228,6 +228,17 @@ int32_t sim_task_command_gate(const sim_task *task, uint32_t command,
/* Feed override is an online trajectory control. LinuxCNC accepts it
while AUTO is running or paused, provided the machine is powered. */
return io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : machine_on ? SIM_TASK_GATE_ALLOWED : SIM_TASK_GATE_MACHINE_OFF;
case SIM_TASK_GATE_SPINDLE:
return machine_on && idle && task->state.mode == SIM_TASK_MODE_MANUAL ? SIM_TASK_GATE_ALLOWED : io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : !machine_on ? SIM_TASK_GATE_MACHINE_OFF : SIM_TASK_GATE_MODE_STATE;
case SIM_TASK_GATE_COOLANT:
return io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : machine_on ? SIM_TASK_GATE_ALLOWED : SIM_TASK_GATE_MACHINE_OFF;
case SIM_TASK_GATE_TOOL:
case SIM_TASK_GATE_PROBE:
return machine_on && idle && task->state.mode == SIM_TASK_MODE_MANUAL ? SIM_TASK_GATE_ALLOWED : io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : !machine_on ? SIM_TASK_GATE_MACHINE_OFF : SIM_TASK_GATE_MODE_STATE;
case SIM_TASK_GATE_TOUCH_OFF:
return machine_on && idle && homed && task->state.mode == SIM_TASK_MODE_MANUAL ? SIM_TASK_GATE_ALLOWED : io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : !machine_on ? SIM_TASK_GATE_MACHINE_OFF : !homed ? SIM_TASK_GATE_UNHOMED : SIM_TASK_GATE_MODE_STATE;
case SIM_TASK_GATE_TOOL_TOUCH_OFF:
return machine_on && idle && homed && requested == 1U && task->state.mode == SIM_TASK_MODE_MANUAL ? SIM_TASK_GATE_ALLOWED : io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : !machine_on ? SIM_TASK_GATE_MACHINE_OFF : !homed ? SIM_TASK_GATE_UNHOMED : SIM_TASK_GATE_MODE_STATE;
case SIM_TASK_GATE_MDI:
if (io_estop || !machine_on) return io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : SIM_TASK_GATE_MACHINE_OFF;
if (!idle) return SIM_TASK_GATE_INTERP_ACTIVE;