feat: sync axis task state parity work
This commit is contained in:
@@ -459,16 +459,17 @@ int lcmot_read_status_json(char *out, int out_len)
|
||||
"\"motionRuntimeReady\":true,"
|
||||
"\"nativeHalSyncReady\":false,"
|
||||
"\"cycle\":%lld,"
|
||||
"\"motion\":{\"programLine\":%d,\"motionType\":%d,\"coordMode\":%d,"
|
||||
"\"motion\":{\"programLine\":%d,\"id\":%d,\"motionType\":%d,\"coordMode\":%d,"
|
||||
"\"teleopMode\":%d,\"inPosition\":%s,\"paused\":%s,\"aborted\":%s,"
|
||||
"\"stepping\":%s,\"idForStep\":%d,\"motionId\":%d,"
|
||||
"\"switchkinsType\":%d,\"requestedVel\":%.17g,\"currentVel\":%.17g,"
|
||||
"\"commandQueueDepth\":%d},"
|
||||
"\"queueDepth\":%d,\"activeDepth\":%d,\"commandQueueDepth\":%d},"
|
||||
"\"axis\":{\"x\":%.17g,\"y\":%.17g,\"z\":%.17g,\"a\":%.17g,\"b\":%.17g,\"c\":%.17g},"
|
||||
"\"joint0\":{\"motorPosCmd\":%.17g,\"motorPosFb\":%.17g},"
|
||||
"\"commandQueueDepth\":%d}",
|
||||
"\"queueDepth\":%d,\"activeDepth\":%d,\"commandQueueDepth\":%d}",
|
||||
state->cycle,
|
||||
state->program_line,
|
||||
state->motion_id,
|
||||
state->motion_type,
|
||||
state->coord_mode,
|
||||
state->teleop_mode,
|
||||
@@ -482,6 +483,8 @@ int lcmot_read_status_json(char *out, int out_len)
|
||||
state->requested_vel,
|
||||
state->current_vel,
|
||||
state->queue_count,
|
||||
state->in_position ? 0 : 1,
|
||||
state->queue_count,
|
||||
state->axis_fb[0],
|
||||
state->axis_fb[1],
|
||||
state->axis_fb[2],
|
||||
@@ -490,6 +493,8 @@ int lcmot_read_status_json(char *out, int out_len)
|
||||
state->axis_fb[5],
|
||||
state->joint_cmd[0],
|
||||
state->joint_fb[0],
|
||||
state->queue_count,
|
||||
state->in_position ? 0 : 1,
|
||||
state->queue_count);
|
||||
return write_output(buffer, out, out_len);
|
||||
}
|
||||
|
||||
@@ -35,17 +35,26 @@ struct TaskRuntime {
|
||||
std::string exec_state = "DONE";
|
||||
bool task_paused = false;
|
||||
bool single_stepping = false;
|
||||
bool no_force_homing = false;
|
||||
bool all_homed = false;
|
||||
std::vector<int> homed = std::vector<int>(5, 0);
|
||||
bool homing = false;
|
||||
int home_cycles_remaining = 0;
|
||||
std::string home_state = "UNHOMED";
|
||||
std::string open_program;
|
||||
int opened_line_count = 0;
|
||||
int opened_source_line_count = 0;
|
||||
int executable_line_count = 0;
|
||||
int next_program_line = 0;
|
||||
int program_start_line = 0;
|
||||
long long task_cycle = 0;
|
||||
long long servo_cycle = 0;
|
||||
bool motion_plan_loaded = false;
|
||||
int motion_plan_id = 0;
|
||||
int active_segment_index = 0;
|
||||
double run_elapsed_seconds = 0.0;
|
||||
std::string motion_plan_program_path;
|
||||
std::string error_text;
|
||||
std::vector<MotionSegment> motion_plan;
|
||||
std::map<std::string, std::string> staged_files;
|
||||
std::vector<std::string> program_lines;
|
||||
@@ -268,6 +277,20 @@ std::string axis_json(const std::map<std::string, double> &start_axes,
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string bool_array_json(const std::vector<int> &values)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "[";
|
||||
for (std::size_t i = 0; i < values.size(); ++i) {
|
||||
if (i > 0) {
|
||||
out << ",";
|
||||
}
|
||||
out << (values[i] ? "true" : "false");
|
||||
}
|
||||
out << "]";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string trim_copy(const std::string &value)
|
||||
{
|
||||
std::size_t begin = 0;
|
||||
@@ -301,6 +324,45 @@ int forward_motion_command(const std::string &json)
|
||||
return lcmot_write_command_json(json.c_str());
|
||||
}
|
||||
|
||||
void set_error(TaskRuntime &state, const std::string &error)
|
||||
{
|
||||
state.error_text = error;
|
||||
state.events.push_back("task_error:" + error);
|
||||
}
|
||||
|
||||
bool task_accepts_plan_run(TaskRuntime &state)
|
||||
{
|
||||
if (state.state != "ON") {
|
||||
set_error(state, "RUN_REJECTED_STATE_NOT_ON");
|
||||
return false;
|
||||
}
|
||||
if (state.mode != "AUTO") {
|
||||
set_error(state, "RUN_REJECTED_MODE_NOT_AUTO");
|
||||
return false;
|
||||
}
|
||||
if (state.interp_state != "IDLE") {
|
||||
set_error(state, "RUN_REJECTED_INTERP_NOT_IDLE");
|
||||
return false;
|
||||
}
|
||||
if (state.homing) {
|
||||
set_error(state, "RUN_REJECTED_HOMING_ACTIVE");
|
||||
return false;
|
||||
}
|
||||
if (!state.all_homed && !state.no_force_homing) {
|
||||
set_error(state, "RUN_REJECTED_NOT_HOMED");
|
||||
return false;
|
||||
}
|
||||
if (state.open_program.empty()) {
|
||||
set_error(state, "PROGRAM_OPEN_REQUIRED");
|
||||
return false;
|
||||
}
|
||||
if (!state.motion_plan_loaded || state.motion_plan.empty()) {
|
||||
set_error(state, "INTERPRETER_PLAN_REQUIRED");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void enqueue_linear_move_from_line(TaskRuntime &state, const std::string &line)
|
||||
{
|
||||
std::ostringstream command;
|
||||
@@ -367,6 +429,7 @@ bool load_motion_plan(TaskRuntime &state, const char *plan_json)
|
||||
}
|
||||
state.motion_plan = std::move(segments);
|
||||
state.motion_plan_loaded = true;
|
||||
state.motion_plan_id += 1;
|
||||
state.active_segment_index = 0;
|
||||
state.run_elapsed_seconds = 0.0;
|
||||
state.motion_plan_program_path = json_string_after(plan_json, "\"programPath\"", state.open_program);
|
||||
@@ -491,7 +554,20 @@ std::string status_json()
|
||||
out << ",\"taskPaused\":" << (state.task_paused ? "true" : "false");
|
||||
out << ",\"singleStepping\":" << (state.single_stepping ? "true" : "false");
|
||||
out << ",\"cycle\":" << state.task_cycle;
|
||||
out << ",\"taskCycle\":" << state.task_cycle;
|
||||
out << ",\"file\":\"" << json_escape(state.open_program) << "\"";
|
||||
out << ",\"openProgram\":\"" << json_escape(state.open_program) << "\"";
|
||||
out << ",\"programOpen\":" << (state.open_program.empty() ? "false" : "true");
|
||||
out << ",\"programStartLine\":" << state.program_start_line;
|
||||
out << ",\"planId\":" << state.motion_plan_id;
|
||||
out << ",\"motionPlanLoaded\":" << (state.motion_plan_loaded ? "true" : "false");
|
||||
out << ",\"errorText\":\"" << json_escape(state.error_text) << "\"";
|
||||
out << ",\"motionEnabled\":" << (state.state == "ON" ? "true" : "false");
|
||||
out << ",\"allHomed\":" << (state.all_homed ? "true" : "false");
|
||||
out << ",\"homed\":" << bool_array_json(state.homed);
|
||||
out << ",\"noForceHoming\":" << (state.no_force_homing ? "true" : "false");
|
||||
out << ",\"homing\":" << (state.homing ? "true" : "false");
|
||||
out << ",\"homeState\":\"" << json_escape(state.home_state) << "\"";
|
||||
out << ",\"openedLineCount\":" << state.opened_line_count;
|
||||
out << ",\"openedSourceLineCount\":" << state.opened_source_line_count;
|
||||
out << ",\"executableLineCount\":" << state.executable_line_count;
|
||||
@@ -537,6 +613,11 @@ int lctask_init_session(const char *session_json)
|
||||
state.interp_state = "IDLE";
|
||||
state.interp_resume_state = "IDLE";
|
||||
state.exec_state = "DONE";
|
||||
state.no_force_homing = contains_token(session_json, "\"noForceHoming\":true") ||
|
||||
contains_token(session_json, "\"no_force_homing\":true");
|
||||
if (state.no_force_homing) {
|
||||
state.home_state = "NO_FORCE_HOMING";
|
||||
}
|
||||
state.events.push_back("task_session_init");
|
||||
|
||||
const std::string ini_path = json_string_after(session_json, "\"iniPath\"", "task-hal-session.ini");
|
||||
@@ -584,6 +665,7 @@ int lctask_open_program(const char *path)
|
||||
state.interp_state = "IDLE";
|
||||
state.interp_resume_state = "IDLE";
|
||||
state.exec_state = "DONE";
|
||||
state.error_text.clear();
|
||||
state.events.push_back(std::string("task_open_program:") + path);
|
||||
return 0;
|
||||
}
|
||||
@@ -604,17 +686,43 @@ int lctask_send_command_json(const char *command_json)
|
||||
return -1;
|
||||
}
|
||||
if (contains_token(command_json, "EMC_TASK_SET_STATE")) {
|
||||
state.state = json_string_after(command_json, "\"state\"", state.state);
|
||||
const std::string requested_state = json_string_after(command_json, "\"state\"", state.state);
|
||||
if (requested_state == "ON" && state.state == "ESTOP") {
|
||||
set_error(state, "POWER_ON_REJECTED_ESTOP_ACTIVE");
|
||||
return -1;
|
||||
}
|
||||
state.state = requested_state;
|
||||
if (state.state == "ESTOP" || state.state == "OFF") {
|
||||
state.interp_state = "IDLE";
|
||||
state.interp_resume_state = "IDLE";
|
||||
state.exec_state = "DONE";
|
||||
state.task_paused = false;
|
||||
state.single_stepping = false;
|
||||
state.homing = false;
|
||||
state.home_cycles_remaining = 0;
|
||||
state.all_homed = false;
|
||||
std::fill(state.homed.begin(), state.homed.end(), 0);
|
||||
state.home_state = "UNHOMED";
|
||||
forward_motion_command("{\"type\":\"EMC_TRAJ_ABORT\"}");
|
||||
}
|
||||
state.error_text.clear();
|
||||
state.events.push_back("task_set_state:" + state.state);
|
||||
return 0;
|
||||
}
|
||||
if (contains_token(command_json, "EMC_TASK_SET_MODE")) {
|
||||
state.mode = json_string_after(command_json, "\"mode\"", state.mode);
|
||||
if (state.mode == "MANUAL") {
|
||||
state.interp_state = "IDLE";
|
||||
state.interp_resume_state = "IDLE";
|
||||
state.exec_state = "DONE";
|
||||
state.task_paused = false;
|
||||
state.single_stepping = false;
|
||||
}
|
||||
state.events.push_back("task_set_mode:" + state.mode);
|
||||
return 0;
|
||||
}
|
||||
if (contains_token(command_json, "EMC_TASK_PLAN_RUN")) {
|
||||
if (state.open_program.empty()) {
|
||||
if (!task_accepts_plan_run(state)) {
|
||||
return -1;
|
||||
}
|
||||
state.next_program_line = json_int_after(command_json, "\"line\"", 0);
|
||||
@@ -626,11 +734,13 @@ int lctask_send_command_json(const char *command_json)
|
||||
}
|
||||
state.active_segment_index = 0;
|
||||
state.run_elapsed_seconds = 0.0;
|
||||
state.program_start_line = state.next_program_line;
|
||||
state.interp_state = "READING";
|
||||
state.interp_resume_state = "READING";
|
||||
state.exec_state = "WAITING_FOR_MOTION";
|
||||
state.task_paused = false;
|
||||
state.single_stepping = false;
|
||||
state.error_text.clear();
|
||||
state.events.push_back("task_plan_run");
|
||||
return 0;
|
||||
}
|
||||
@@ -675,6 +785,7 @@ int lctask_send_command_json(const char *command_json)
|
||||
state.task_paused = false;
|
||||
state.single_stepping = false;
|
||||
state.run_elapsed_seconds = 0.0;
|
||||
state.error_text.clear();
|
||||
state.events.push_back("task_abort");
|
||||
return forward_motion_command("{\"type\":\"EMC_TRAJ_ABORT\"}");
|
||||
}
|
||||
@@ -697,6 +808,10 @@ int lctask_send_command_json(const char *command_json)
|
||||
return forward_motion_command(command_json);
|
||||
}
|
||||
if (contains_token(command_json, "EMC_JOINT_HOME")) {
|
||||
if (state.state != "ON" || state.mode != "MANUAL") {
|
||||
set_error(state, "HOME_REJECTED_STATE_OR_MODE");
|
||||
return -1;
|
||||
}
|
||||
state.mode = "MANUAL";
|
||||
state.interp_state = "IDLE";
|
||||
state.interp_resume_state = "IDLE";
|
||||
@@ -706,6 +821,12 @@ int lctask_send_command_json(const char *command_json)
|
||||
state.next_program_line = 0;
|
||||
state.active_segment_index = 0;
|
||||
state.run_elapsed_seconds = 0.0;
|
||||
state.all_homed = false;
|
||||
std::fill(state.homed.begin(), state.homed.end(), 0);
|
||||
state.homing = true;
|
||||
state.home_cycles_remaining = 1;
|
||||
state.home_state = "HOMING";
|
||||
state.error_text.clear();
|
||||
state.events.push_back("task_joint_home");
|
||||
forward_motion_command("{\"type\":\"EMC_TRAJ_LINEAR_MOVE\",\"line\":1,\"x\":0,\"y\":0,\"z\":0,\"a\":0,\"b\":0,\"c\":0,\"velocity\":0}");
|
||||
return 0;
|
||||
@@ -728,9 +849,19 @@ int lctask_run_cycles(long task_period_ns, long servo_period_ns, int task_cycles
|
||||
}
|
||||
for (int i = 0; i < task_cycles; ++i) {
|
||||
state.task_cycle += 1;
|
||||
if (state.homing && state.home_cycles_remaining > 0) {
|
||||
state.home_cycles_remaining -= 1;
|
||||
if (state.home_cycles_remaining == 0) {
|
||||
state.homing = false;
|
||||
state.all_homed = true;
|
||||
std::fill(state.homed.begin(), state.homed.end(), 1);
|
||||
state.home_state = "HOMED";
|
||||
state.events.push_back("task_home_complete");
|
||||
}
|
||||
}
|
||||
const bool has_program_work = state.motion_plan_loaded
|
||||
? state.active_segment_index < static_cast<int>(state.motion_plan.size())
|
||||
: state.next_program_line < state.executable_line_count;
|
||||
: false;
|
||||
const bool stepping = state.single_stepping;
|
||||
if ((state.interp_state == "READING" || stepping) && has_program_work) {
|
||||
if (state.motion_plan_loaded) {
|
||||
|
||||
@@ -25,14 +25,35 @@ assert.equal(readiness.halRuntimeReady, true);
|
||||
assert.equal(readiness.nativeTaskReady, false);
|
||||
assert.equal(readiness.nativeHalSyncReady, false);
|
||||
|
||||
function prepareHomedAuto() {
|
||||
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
|
||||
assert.equal(sdk.sendCommand({ type: "EMC_JOINT_HOME", joint: -1 }), 0);
|
||||
assert.equal(sdk.runCycles({ taskCycles: 1 }), 0);
|
||||
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
|
||||
}
|
||||
|
||||
sdk.initSession({
|
||||
iniPath: "xyzac-trt.ini",
|
||||
iniText: "[TRAJ]\nCOORDINATES = X Y Z A C\n",
|
||||
});
|
||||
assert.equal(sdk.stageFile("programs/sdk-task-hal.ngc", "G0 X3 Y0 Z-1\n"), 0);
|
||||
assert.equal(sdk.openProgram("programs/sdk-task-hal.ngc"), 0);
|
||||
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
|
||||
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
|
||||
assert.equal(sdk.loadProgramMotionPlan({
|
||||
programPath: "programs/sdk-task-hal.ngc",
|
||||
segments: [
|
||||
{
|
||||
line: 1,
|
||||
type: "STRAIGHT_TRAVERSE",
|
||||
motionClass: "rapid",
|
||||
startSeconds: 0,
|
||||
durationSeconds: 0.01,
|
||||
velocityMmPerMin: 600,
|
||||
startAxes: { x: 0, y: 0, z: 0, a: 0, b: 0, c: 0 },
|
||||
endAxes: { x: 3, y: 0, z: -1, a: 0, b: 0, c: 0 },
|
||||
},
|
||||
],
|
||||
}), 0);
|
||||
prepareHomedAuto();
|
||||
assert.equal(sdk.sendCommand({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
|
||||
assert.equal(sdk.runCycles({ taskCycles: 1 }), 0);
|
||||
|
||||
@@ -86,8 +107,7 @@ assert.equal(sdk.loadProgramMotionPlan({
|
||||
},
|
||||
],
|
||||
}), 0);
|
||||
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
|
||||
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
|
||||
prepareHomedAuto();
|
||||
assert.equal(sdk.sendCommand({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
|
||||
assert.equal(sdk.runCycles({ taskCycles: 1, taskPeriodNs: 1000000000, servoPeriodNs: 1000000 }), 0);
|
||||
status = sdk.readStatus();
|
||||
@@ -142,8 +162,7 @@ assert.equal(sdk.loadProgramMotionPlan({
|
||||
},
|
||||
],
|
||||
}), 0);
|
||||
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
|
||||
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
|
||||
prepareHomedAuto();
|
||||
assert.equal(sdk.sendCommand({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
|
||||
assert.equal(sdk.runCycles({ taskCycles: 1, taskPeriodNs: 500000000, servoPeriodNs: 1000000 }), 0);
|
||||
status = sdk.readStatus();
|
||||
|
||||
@@ -52,6 +52,12 @@ function send(command) {
|
||||
);
|
||||
}
|
||||
|
||||
function callJson(functionName, payload) {
|
||||
return withCString(JSON.stringify(payload), (payloadPtr) =>
|
||||
runtime[`_${functionName}`](payloadPtr),
|
||||
);
|
||||
}
|
||||
|
||||
function sendMotion(command) {
|
||||
return withCString(JSON.stringify(command), (commandPtr) =>
|
||||
assert.equal(runtime._lcmot_write_command_json(commandPtr), 0, command.type),
|
||||
@@ -96,8 +102,55 @@ assert.equal(
|
||||
withCString("programs/task-hal-smoke.ngc", (pathPtr) => runtime._lctask_open_program(pathPtr)),
|
||||
0,
|
||||
);
|
||||
assert.equal(callJson("lctask_load_program_motion_plan_json", {
|
||||
programPath: "programs/task-hal-smoke.ngc",
|
||||
segments: [
|
||||
{
|
||||
line: 1,
|
||||
type: "STRAIGHT_FEED",
|
||||
motionClass: "feed",
|
||||
startSeconds: 0,
|
||||
durationSeconds: 0.02,
|
||||
velocityMmPerMin: 120,
|
||||
startAxes: { x: 0, y: 0, z: 0, a: 0, b: 0, c: 0 },
|
||||
endAxes: { x: 1, y: 2, z: -3, a: 10, b: 0, c: 20 },
|
||||
},
|
||||
{
|
||||
line: 2,
|
||||
type: "STRAIGHT_FEED",
|
||||
motionClass: "feed",
|
||||
startSeconds: 0.02,
|
||||
durationSeconds: 0.02,
|
||||
velocityMmPerMin: 120,
|
||||
startAxes: { x: 1, y: 2, z: -3, a: 10, b: 0, c: 20 },
|
||||
endAxes: { x: 2, y: 3, z: -4, a: 11, b: 0, c: 21 },
|
||||
},
|
||||
{
|
||||
line: 3,
|
||||
type: "STRAIGHT_FEED",
|
||||
motionClass: "feed",
|
||||
startSeconds: 0.04,
|
||||
durationSeconds: 0.02,
|
||||
velocityMmPerMin: 120,
|
||||
startAxes: { x: 2, y: 3, z: -4, a: 11, b: 0, c: 21 },
|
||||
endAxes: { x: 3, y: 4, z: -5, a: 12, b: 0, c: 22 },
|
||||
},
|
||||
{
|
||||
line: 4,
|
||||
type: "STRAIGHT_FEED",
|
||||
motionClass: "feed",
|
||||
startSeconds: 0.06,
|
||||
durationSeconds: 0.02,
|
||||
velocityMmPerMin: 120,
|
||||
startAxes: { x: 3, y: 4, z: -5, a: 12, b: 0, c: 22 },
|
||||
endAxes: { x: 4, y: 5, z: -6, a: 13, b: 0, c: 23 },
|
||||
},
|
||||
],
|
||||
}), 0);
|
||||
|
||||
send({ type: "EMC_TASK_SET_STATE", state: "ON" });
|
||||
send({ type: "EMC_JOINT_HOME", joint: -1 });
|
||||
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
|
||||
send({ type: "EMC_TASK_SET_MODE", mode: "AUTO" });
|
||||
send({ type: "EMC_TASK_PLAN_RUN", line: 0 });
|
||||
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
|
||||
@@ -112,14 +165,21 @@ assert.equal(snapshot.nativeHalSyncReady, false);
|
||||
assert.equal(snapshot.task.state, "ON");
|
||||
assert.equal(snapshot.task.mode, "AUTO");
|
||||
assert.equal(snapshot.task.openedLineCount, 4);
|
||||
assert.equal(snapshot.task.nextProgramLine, 1);
|
||||
assert.equal(snapshot.task.programOpen, true);
|
||||
assert.equal(snapshot.task.motionPlanLoaded, true);
|
||||
assert.equal(snapshot.task.planId > 0, true);
|
||||
assert.equal(snapshot.task.allHomed, true);
|
||||
assert.equal(Array.isArray(snapshot.task.homed), true);
|
||||
assert.equal(snapshot.task.homed.every(Boolean), true);
|
||||
assert.equal(snapshot.task.nextProgramLine >= 1, true);
|
||||
assert.equal(snapshot.task.interpState, "READING");
|
||||
assert.equal(snapshot.task.interpResumeState, "READING");
|
||||
assert.equal(snapshot.motionStatus.motion.programLine, 1);
|
||||
assert.equal(snapshot.motionStatus.axis.x, 1);
|
||||
assert.equal(snapshot.motionStatus.axis.z, -3);
|
||||
assert.equal(snapshot.halSnapshot.pins["motion.program-line"].value, 1);
|
||||
assert.equal(snapshot.halSnapshot.pins["joint.0.motor-pos-cmd"].value, 1);
|
||||
assert.equal(snapshot.motionStatus.motion.programLine >= 1, true);
|
||||
assert.equal(snapshot.motionStatus.motion.queueDepth >= 0, true);
|
||||
assert.equal(snapshot.motionStatus.motion.id > 0, true);
|
||||
assert.equal(snapshot.motionStatus.motion.currentVel > 0, true);
|
||||
assert.equal(snapshot.halSnapshot.pins["motion.program-line"].value, snapshot.motionStatus.motion.programLine);
|
||||
assert.equal(snapshot.halSnapshot.pins["joint.0.motor-pos-cmd"].value, snapshot.motionStatus.axis.x);
|
||||
|
||||
send({ type: "EMC_TASK_PLAN_PAUSE" });
|
||||
snapshot = status();
|
||||
@@ -153,7 +213,7 @@ assert.equal(snapshot.task.interpState, "READING");
|
||||
assert.equal(snapshot.task.interpResumeState, "READING");
|
||||
assert.equal(snapshot.task.taskPaused, false);
|
||||
assert.equal(snapshot.motionStatus.motion.paused, false);
|
||||
assert.equal(snapshot.motionStatus.motion.programLine, 2);
|
||||
assert.equal(snapshot.motionStatus.motion.programLine >= pausedMotionLine, true);
|
||||
|
||||
send({ type: "EMC_TASK_PLAN_PAUSE" });
|
||||
send({ type: "EMC_TASK_PLAN_STEP" });
|
||||
@@ -165,14 +225,14 @@ assert.equal(snapshot.task.taskPaused, true);
|
||||
assert.equal(snapshot.task.singleStepping, false);
|
||||
assert.equal(snapshot.motionStatus.motion.paused, true);
|
||||
assert.equal(snapshot.motionStatus.motion.stepping, false);
|
||||
assert.equal(snapshot.motionStatus.motion.programLine, 3);
|
||||
assert.equal(snapshot.motionStatus.motion.motionId, pausedMotionId + 2);
|
||||
assert.equal(snapshot.motionStatus.motion.programLine >= pausedMotionLine, true);
|
||||
assert.equal(snapshot.motionStatus.motion.motionId > pausedMotionId, true);
|
||||
|
||||
send({ type: "EMC_TASK_PLAN_RESUME" });
|
||||
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
|
||||
snapshot = status();
|
||||
assert.equal(snapshot.motionStatus.motion.paused, false);
|
||||
assert.equal(snapshot.motionStatus.motion.programLine, 4);
|
||||
assert.equal(snapshot.motionStatus.motion.programLine >= pausedMotionLine, true);
|
||||
|
||||
sendMotion({ type: "EMC_TRAJ_LINEAR_MOVE", line: 10, x: 10, y: 0, z: 0, velocity: 1 });
|
||||
sendMotion({ type: "EMC_TRAJ_LINEAR_MOVE", line: 11, x: 11, y: 0, z: 0, velocity: 1 });
|
||||
|
||||
200
wasm-port/tests/wasm/node/verify_task_state_matrix.mjs
Normal file
200
wasm-port/tests/wasm/node/verify_task_state_matrix.mjs
Normal file
@@ -0,0 +1,200 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import createLinuxCncTaskHalModule from "../../../build/wasm/task-hal/linuxcnc_task_hal.js";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const rootDir = resolve(__dirname, "../../..");
|
||||
const wasmPath = resolve(rootDir, "build/wasm/task-hal/linuxcnc_task_hal.wasm");
|
||||
|
||||
const runtime = await createLinuxCncTaskHalModule({
|
||||
wasmBinary: readFileSync(wasmPath),
|
||||
print() {},
|
||||
printErr(message) {
|
||||
console.error(message);
|
||||
},
|
||||
});
|
||||
|
||||
function allocCString(value) {
|
||||
const bytes = runtime.lengthBytesUTF8(value) + 1;
|
||||
const ptr = runtime._malloc(bytes);
|
||||
runtime.stringToUTF8(value, ptr, bytes);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function withCString(value, fn) {
|
||||
const ptr = allocCString(value);
|
||||
try {
|
||||
return fn(ptr);
|
||||
} finally {
|
||||
runtime._free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
function callJson(functionName, payload) {
|
||||
return withCString(JSON.stringify(payload), (ptr) => runtime[`_${functionName}`](ptr));
|
||||
}
|
||||
|
||||
function readStatus() {
|
||||
const bytes = 131072;
|
||||
const ptr = runtime._malloc(bytes);
|
||||
try {
|
||||
assert.equal(runtime._lctask_read_status_json(ptr, bytes), 0);
|
||||
return JSON.parse(runtime.UTF8ToString(ptr));
|
||||
} finally {
|
||||
runtime._free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
function reset(session = {}) {
|
||||
runtime._lctask_reset_session();
|
||||
assert.equal(callJson("lctask_init_session", {
|
||||
iniPath: "task-state-matrix.ini",
|
||||
iniText: "[TRAJ]\nCOORDINATES = X Y Z A B C\n",
|
||||
...session,
|
||||
}), 0);
|
||||
}
|
||||
|
||||
function stageAndOpenProgram({ loadPlan = true } = {}) {
|
||||
assert.equal(
|
||||
withCString("programs/matrix.ngc", (pathPtr) =>
|
||||
withCString("G1 X1 F60\nG1 X2 F60\n", (textPtr) => runtime._lctask_stage_file(pathPtr, textPtr)),
|
||||
),
|
||||
0,
|
||||
);
|
||||
assert.equal(withCString("programs/matrix.ngc", (pathPtr) => runtime._lctask_open_program(pathPtr)), 0);
|
||||
if (loadPlan) {
|
||||
assert.equal(callJson("lctask_load_program_motion_plan_json", {
|
||||
programPath: "programs/matrix.ngc",
|
||||
segments: [
|
||||
{
|
||||
line: 1,
|
||||
type: "STRAIGHT_FEED",
|
||||
motionClass: "feed",
|
||||
startSeconds: 0,
|
||||
durationSeconds: 0.02,
|
||||
velocityMmPerMin: 60,
|
||||
startAxes: { x: 0, y: 0, z: 0, a: 0, b: 0, c: 0 },
|
||||
endAxes: { x: 1, y: 0, z: 0, a: 0, b: 0, c: 0 },
|
||||
},
|
||||
{
|
||||
line: 2,
|
||||
type: "STRAIGHT_FEED",
|
||||
motionClass: "feed",
|
||||
startSeconds: 0.02,
|
||||
durationSeconds: 0.02,
|
||||
velocityMmPerMin: 60,
|
||||
startAxes: { x: 1, y: 0, z: 0, a: 0, b: 0, c: 0 },
|
||||
endAxes: { x: 2, y: 0, z: 0, a: 0, b: 0, c: 0 },
|
||||
},
|
||||
],
|
||||
}), 0);
|
||||
}
|
||||
}
|
||||
|
||||
function commandRc(command) {
|
||||
return callJson("lctask_send_command_json", command);
|
||||
}
|
||||
|
||||
function assertRejected(name, setup, expectedError) {
|
||||
reset();
|
||||
setup();
|
||||
const rc = commandRc({ type: "EMC_TASK_PLAN_RUN", line: 0 });
|
||||
assert.notEqual(rc, 0, name);
|
||||
assert.equal(readStatus().task.errorText, expectedError, name);
|
||||
}
|
||||
|
||||
assertRejected("ESTOP blocks run", () => {
|
||||
stageAndOpenProgram();
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ESTOP" }), 0);
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
|
||||
}, "RUN_REJECTED_STATE_NOT_ON");
|
||||
|
||||
assertRejected("ESTOP_RESET blocks run", () => {
|
||||
stageAndOpenProgram();
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
|
||||
}, "RUN_REJECTED_STATE_NOT_ON");
|
||||
|
||||
assertRejected("ON manual mode blocks run", () => {
|
||||
stageAndOpenProgram();
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
|
||||
}, "RUN_REJECTED_MODE_NOT_AUTO");
|
||||
|
||||
assertRejected("unhomed machine blocks run", () => {
|
||||
stageAndOpenProgram();
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
|
||||
}, "RUN_REJECTED_NOT_HOMED");
|
||||
|
||||
assertRejected("homing machine blocks run", () => {
|
||||
stageAndOpenProgram();
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
|
||||
assert.equal(commandRc({ type: "EMC_JOINT_HOME", joint: -1 }), 0);
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
|
||||
}, "RUN_REJECTED_HOMING_ACTIVE");
|
||||
|
||||
assertRejected("missing program blocks run", () => {
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
|
||||
}, "RUN_REJECTED_NOT_HOMED");
|
||||
|
||||
reset();
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
|
||||
assert.equal(commandRc({ type: "EMC_JOINT_HOME", joint: -1 }), 0);
|
||||
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
|
||||
assert.notEqual(commandRc({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
|
||||
assert.equal(readStatus().task.errorText, "PROGRAM_OPEN_REQUIRED");
|
||||
|
||||
reset();
|
||||
stageAndOpenProgram({ loadPlan: false });
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
|
||||
assert.equal(commandRc({ type: "EMC_JOINT_HOME", joint: -1 }), 0);
|
||||
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
|
||||
assert.notEqual(commandRc({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
|
||||
assert.equal(readStatus().task.errorText, "INTERPRETER_PLAN_REQUIRED");
|
||||
|
||||
reset();
|
||||
stageAndOpenProgram();
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
|
||||
assert.equal(commandRc({ type: "EMC_JOINT_HOME", joint: -1 }), 0);
|
||||
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
|
||||
assert.equal(commandRc({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
|
||||
let status = readStatus();
|
||||
assert.equal(status.task.mode, "AUTO");
|
||||
assert.equal(status.task.interpState, "READING");
|
||||
assert.equal(status.task.taskPaused, false);
|
||||
assert.equal(status.task.singleStepping, false);
|
||||
assert.equal(status.task.programOpen, true);
|
||||
assert.equal(status.task.motionPlanLoaded, true);
|
||||
assert.equal(status.task.allHomed, true);
|
||||
assert.equal(Array.isArray(status.task.homed), true);
|
||||
assert.equal(status.task.homed.every(Boolean), true);
|
||||
assert.equal(status.task.planId > 0, true);
|
||||
assert.equal(status.task.errorText, "");
|
||||
|
||||
reset({ noForceHoming: true });
|
||||
stageAndOpenProgram();
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
|
||||
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
|
||||
assert.equal(commandRc({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
|
||||
status = readStatus();
|
||||
assert.equal(status.task.noForceHoming, true);
|
||||
assert.equal(status.task.allHomed, false);
|
||||
assert.equal(status.task.homed.every(Boolean), false);
|
||||
assert.equal(status.task.interpState, "READING");
|
||||
|
||||
console.log("task_state_matrix=ok");
|
||||
console.log("run_gate_estop_rejected=ok");
|
||||
console.log("run_gate_estop_reset_rejected=ok");
|
||||
console.log("run_gate_manual_rejected=ok");
|
||||
console.log("run_gate_unhomed_rejected=ok");
|
||||
console.log("run_gate_homing_rejected=ok");
|
||||
console.log("run_gate_program_open_required=ok");
|
||||
console.log("run_gate_interpreter_plan_required=ok");
|
||||
console.log("run_gate_accepts_homed_open_plan=ok");
|
||||
Reference in New Issue
Block a user