diff --git a/src/emc/task/taskclass.cc b/src/emc/task/taskclass.cc index c657448c79f..25dc5d47fca 100644 --- a/src/emc/task/taskclass.cc +++ b/src/emc/task/taskclass.cc @@ -587,14 +587,15 @@ int Task::emcToolSetOffset(int idx, int toolno, const EmcPose& offset, double di if (0 != tooldata_save(tooltable_filename)) { emcioStatus.status = RCS_STATUS::ERROR; } - //TODO - // if (io_db_mode == DB_ACTIVE) { - // int pno = idx; // for random_toolchanger - // if (!random_toolchanger) { pno = tdata.pocketno; } - // if (tooldata_db_notify(TOOL_OFFSET,toolno,pno,tdata)) { - // UNEXPECTED_MSG; - // } - // } + if (db_mode == tooldb_t::DB_ACTIVE) { + // tooldata_save() does not write tool table entries in db mode, + // so the db_program must be notified to persist the new offsets + int pno = idx; // for random_toolchanger + if (!random_toolchanger) { pno = tdata.pocketno; } + if (tooldata_db_notify(TOOL_OFFSET,toolno,pno,tdata)) { + UNEXPECTED_MSG; + } + } return 0; } diff --git a/tests/tooldb/db-g10-offset/.gitignore b/tests/tooldb/db-g10-offset/.gitignore new file mode 100644 index 00000000000..211e02568a8 --- /dev/null +++ b/tests/tooldb/db-g10-offset/.gitignore @@ -0,0 +1,2 @@ +db_tools.txt +db_cmds.log diff --git a/tests/tooldb/db-g10-offset/README b/tests/tooldb/db-g10-offset/README new file mode 100644 index 00000000000..e22c5da186c --- /dev/null +++ b/tests/tooldb/db-g10-offset/README @@ -0,0 +1,6 @@ +Verify that tool offsets set with G10 L1/L10/L11 are persisted when an +[EMCIO]DB_PROGRAM is configured. + +In db mode with a nonrandom toolchanger no tool table file is written, so the +db program must be notified of the new offsets ('p' command). Without that +notification the offsets only live in memory and are lost on the next start. diff --git a/tests/tooldb/db-g10-offset/base.inc b/tests/tooldb/db-g10-offset/base.inc new file mode 100644 index 00000000000..70e31728ae5 --- /dev/null +++ b/tests/tooldb/db-g10-offset/base.inc @@ -0,0 +1,98 @@ +[EMC] +# The version string for this INI file. +VERSION = 1.1 +DEBUG = 0x0 + +[RS274NGC] +PARAMETER_FILE = sim.var + +[EMCMOT] +EMCMOT = motmod +COMM_TIMEOUT = 4.0 +BASE_PERIOD = 0 +SERVO_PERIOD = 1000000 + +[TASK] +TASK = milltask +CYCLE_TIME = 0.001 + +[HAL] +# the library file provides the estop and toolchange loopbacks needed here +HALFILE = LIB:core_sim.hal + +[TRAJ] +NO_FORCE_HOMING=1 +COORDINATES = XYZ +HOME = 0 0 0 +LINEAR_UNITS = inch +ANGULAR_UNITS = degree +DEFAULT_LINEAR_VELOCITY = 1.2 +MAX_LINEAR_ACCELERATION = 123.45 +MAX_LINEAR_VELOCITY = 45.67 + +[EMCIO] +# no TOOL_TABLE: tool data is provided by the db program +TOOL_CHANGE_QUILL_UP = 1 +RANDOM_TOOLCHANGER = 0 +DB_PROGRAM = ./db_test.py + +[KINS] +KINEMATICS = trivkins coordinates=XYZ +JOINTS = 3 + +[AXIS_X] +MIN_LIMIT = -40.0 +MAX_LIMIT = 40.0 +MAX_VELOCITY = 4 +MAX_ACCELERATION = 1000.0 + +[JOINT_0] +TYPE = LINEAR +HOME = 0.000 +MAX_VELOCITY = 4 +MAX_ACCELERATION = 1000.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -40.0 +MAX_LIMIT = 40.0 +FERROR = 0.050 +MIN_FERROR = 0.010 + +[AXIS_Y] +MIN_LIMIT = -40.0 +MAX_LIMIT = 40.0 +MAX_VELOCITY = 4 +MAX_ACCELERATION = 1000.0 + +[JOINT_1] +TYPE = LINEAR +HOME = 0.000 +MAX_VELOCITY = 4 +MAX_ACCELERATION = 1000.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -40.0 +MAX_LIMIT = 40.0 +FERROR = 0.050 +MIN_FERROR = 0.010 + +[AXIS_Z] +MIN_LIMIT = -40.0 +MAX_LIMIT = 40.0 +MAX_VELOCITY = 4 +MAX_ACCELERATION = 1000.0 + +[JOINT_2] +TYPE = LINEAR +HOME = 0.0 +MAX_VELOCITY = 4 +MAX_ACCELERATION = 1000.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -40.0 +MAX_LIMIT = 40.0 +FERROR = 0.050 +MIN_FERROR = 0.010 diff --git a/tests/tooldb/db-g10-offset/checkresult b/tests/tooldb/db-g10-offset/checkresult new file mode 100755 index 00000000000..24dc9aa53e3 --- /dev/null +++ b/tests/tooldb/db-g10-offset/checkresult @@ -0,0 +1,2 @@ +#!/bin/sh +exit 0 # test failure is indicated by test.sh exit value diff --git a/tests/tooldb/db-g10-offset/db_test.py b/tests/tooldb/db-g10-offset/db_test.py new file mode 100755 index 00000000000..7cfef350941 --- /dev/null +++ b/tests/tooldb/db-g10-offset/db_test.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# Minimal [EMCIO]DB_PROGRAM used by this test. +# Tool data is kept in a flat file (db_tools.txt) so that it survives a +# restart of LinuxCNC, and every received command is appended to db_cmds.log. +# Note: stdout is the pipe to LinuxCNC, so logging goes to a file. + +import os + +from tooldb import tooldb_callbacks +from tooldb import tooldb_tools +from tooldb import tooldb_loop + +savefile = "db_tools.txt" +logfile = "db_cmds.log" + +initial_tools = { + 1: "T1 P1 D0.1 Z0.5", + 2: "T2 P2 D0.2 Z1.5", + 3: "T3 P3 D0.3 Z2.5", +} + +tools = {} + +def log(msg): + with open(logfile,"a") as f: + f.write("%s\n"%msg) + +def save(): + with open(savefile,"w") as f: + for tno in sorted(tools): + f.write("%s\n"%tools[tno]) + +def load(): + if not os.path.exists(savefile): + tools.update(initial_tools) + save() + return + with open(savefile) as f: + for line in f: + line = line.strip() + if not line: continue + tools[toolno_of(line)] = line + +def toolno_of(toolline): + for item in toolline.upper().split(): + if item.startswith("T"): return int(item[1:]) + raise ValueError("no toolno in <%s>"%toolline) + +def merge(toolline,params): + # apply the letter parameters of params to toolline + D = dict((item[0],item[1:]) for item in toolline.upper().split()) + for item in params.upper().split(): + if item.startswith(";"): break + D[item[0]] = item[1:] + letters = ["T","P"] + sorted(set(D) - set(["T","P"])) + return " ".join("%s%s"%(letter,D[letter]) for letter in letters) + +# 'g' interface command +def get_tool(tno): + log("g %s"%tools[tno]) + return tools[tno] + +# 'p' interface command +def put_tool(tno,params): + log("p %s"%params) + tools[tno] = merge(tools.get(tno,"T%d P%d"%(tno,tno)),params) + save() + +# 'l' interface command +def load_spindle(tno,params): + log("l %s"%params) + +# 'u' interface command +def unload_spindle(tno,params): + log("u %s"%params) + +load() +tooldb_callbacks(get_tool,put_tool,load_spindle,unload_spindle) +tooldb_tools(sorted(tools)) +tooldb_loop() diff --git a/tests/tooldb/db-g10-offset/dbtest_lib.py b/tests/tooldb/db-g10-offset/dbtest_lib.py new file mode 100644 index 00000000000..fbb6e18af10 --- /dev/null +++ b/tests/tooldb/db-g10-offset/dbtest_lib.py @@ -0,0 +1,82 @@ +# helpers shared by the ui scripts of this test + +import sys +import time + +import linuxcnc + +savefile = "db_tools.txt" + + +def wait_for_linuxcnc_startup(status, timeout=10.0): + """Poll the Status buffer waiting for it to look initialized, rather + than just allocated (all-zero).""" + start_time = time.time() + while time.time() - start_time < timeout: + status.poll() + if (status.angular_units == 0.0) \ + or (status.axis_mask == 0) \ + or (status.cycle_time == 0.0) \ + or (status.exec_state != linuxcnc.EXEC_DONE) \ + or (status.interp_state != linuxcnc.INTERP_IDLE) \ + or (status.inpos is False) \ + or (status.linear_units == 0.0) \ + or (status.max_acceleration == 0.0) \ + or (status.max_velocity == 0.0) \ + or (status.program_units == 0.0) \ + or (status.rapidrate == 0.0) \ + or (status.state != linuxcnc.RCS_DONE) \ + or (status.task_state != linuxcnc.STATE_ESTOP): + time.sleep(0.1) + else: + return + raise RuntimeError("timeout waiting for linuxcnc startup") + + +def start_machine(): + c = linuxcnc.command() + s = linuxcnc.stat() + wait_for_linuxcnc_startup(s) + c.state(linuxcnc.STATE_ESTOP_RESET) + c.state(linuxcnc.STATE_ON) + c.home(-1) + c.wait_complete() + c.mode(linuxcnc.MODE_MDI) + c.wait_complete() + return (c, s) + + +def fail(msg): + print("FAIL: %s" % msg) + sys.exit(1) + + +def check(what, got, expected): + if abs(got - expected) > 1e-6: + fail("%s: expected %.6f, got %.6f" % (what, expected, got)) + print("ok: %s = %.6f" % (what, got)) + + +def tool_table_zoffset(s, idx): + s.poll() + return s.tool_table[idx].zoffset + + +def db_zoffset(toolno, timeout=0): + """Z offset recorded by the db program for toolno, or None.""" + deadline = time.time() + timeout + while True: + try: + with open(savefile) as f: + for line in f: + items = line.upper().split() + if "T%d" % toolno not in items: + continue + for item in items: + if item.startswith("Z"): + return float(item[1:]) + except IOError: + pass + if time.time() >= deadline: + return None + time.sleep(0.1) diff --git a/tests/tooldb/db-g10-offset/test-ui.py b/tests/tooldb/db-g10-offset/test-ui.py new file mode 100755 index 00000000000..28cfded060b --- /dev/null +++ b/tests/tooldb/db-g10-offset/test-ui.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +# With an [EMCIO]DB_PROGRAM configured and a nonrandom toolchanger, no tool +# table file is written, so G10 L1/L10/L11 offsets are persisted only if the +# db program is notified ('p' command). + +import sys + +from dbtest_lib import start_machine, check, fail, tool_table_zoffset, db_zoffset + +(c, s) = start_machine() + +# initial values come from the db program +check("tool 3 zoffset at startup", tool_table_zoffset(s, 3), 2.5) +check("db zoffset for tool 3 at startup", db_zoffset(3), 2.5) + +# 1) G10 L1 on a tool that is not in the spindle +c.mdi("G10 L1 P3 Z-12.345") +c.wait_complete() + +check("tool 3 zoffset after G10 L1", tool_table_zoffset(s, 3), -12.345) + +got = db_zoffset(3, timeout=5) +if got is None: + fail("db program was not notified of the G10 L1 offset for tool 3") +check("db zoffset for tool 3 after G10 L1", got, -12.345) + +# 2) G10 L1 on the tool that is in the spindle +c.mdi("T2 M6") +c.wait_complete() +s.poll() +if s.tool_in_spindle != 2: + fail("expected tool 2 in spindle, got %d" % s.tool_in_spindle) + +c.mdi("G10 L1 P2 Z-6.789") +c.wait_complete() + +check("tool 2 zoffset after G10 L1", tool_table_zoffset(s, 2), -6.789) +check("spindle zoffset after G10 L1", tool_table_zoffset(s, 0), -6.789) + +got = db_zoffset(2, timeout=5) +if got is None: + fail("db program was not notified of the G10 L1 offset for tool 2") +check("db zoffset for tool 2 after G10 L1", got, -6.789) + +# unload the tool so the db program sees a clean shutdown +c.mdi("T0 M6") +c.wait_complete() + +sys.exit(0) diff --git a/tests/tooldb/db-g10-offset/test.ini b/tests/tooldb/db-g10-offset/test.ini new file mode 100644 index 00000000000..2f2a135c371 --- /dev/null +++ b/tests/tooldb/db-g10-offset/test.ini @@ -0,0 +1,5 @@ +# apply G10 L1 tool offsets with an [EMCIO]DB_PROGRAM configured +[DISPLAY] +DISPLAY = ./test-ui.py + +#INCLUDE base.inc diff --git a/tests/tooldb/db-g10-offset/test.sh b/tests/tooldb/db-g10-offset/test.sh new file mode 100755 index 00000000000..d67ba664c8d --- /dev/null +++ b/tests/tooldb/db-g10-offset/test.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -x +set -e + +rm -f sim.var* db_tools.txt db_cmds.log + +# run 1: apply G10 L1 offsets (db program creates db_tools.txt) +linuxcnc -r test.ini + +# run 2: the offsets must be restored from the db program +linuxcnc -r verify.ini diff --git a/tests/tooldb/db-g10-offset/verify-ui.py b/tests/tooldb/db-g10-offset/verify-ui.py new file mode 100755 index 00000000000..99124db2143 --- /dev/null +++ b/tests/tooldb/db-g10-offset/verify-ui.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +# Second run: the tool data now comes from the db program, and must carry the +# offsets written by test-ui.py in the previous run. + +import sys + +from dbtest_lib import start_machine, check, tool_table_zoffset + +(c, s) = start_machine() + +check("tool 3 zoffset after restart", tool_table_zoffset(s, 3), -12.345) +check("tool 2 zoffset after restart", tool_table_zoffset(s, 2), -6.789) +check("tool 1 zoffset after restart", tool_table_zoffset(s, 1), 0.5) + +sys.exit(0) diff --git a/tests/tooldb/db-g10-offset/verify.ini b/tests/tooldb/db-g10-offset/verify.ini new file mode 100644 index 00000000000..ac5c4245e00 --- /dev/null +++ b/tests/tooldb/db-g10-offset/verify.ini @@ -0,0 +1,5 @@ +# restart: the offsets applied by test.ini must come back from the db program +[DISPLAY] +DISPLAY = ./verify-ui.py + +#INCLUDE base.inc