From 18f86cfd1e7adfde4461eabcd534b13d2e763dd9 Mon Sep 17 00:00:00 2001 From: Johan Date: Sat, 21 Mar 2026 23:48:08 +0100 Subject: [PATCH] adding files --- my-3d/3d_print_filter.py | 58 + my-3d/comms.py | 140 + my-3d/custom-m-codes/M102 | 4 + my-3d/custom-m-codes/M103 | 4 + my-3d/custom-m-codes/M104 | 12 + my-3d/custom-m-codes/M105 | 3 + my-3d/custom-m-codes/M106 | 6 + my-3d/custom-m-codes/M107 | 4 + my-3d/custom-m-codes/M109 | 28 + my-3d/custom-m-codes/M140 | 6 + my-3d/custom-m-codes/M190 | 3 + my-3d/custom-m-codes/M84 | 4 + my-3d/custom-m-codes/disable_steppers.ngc | 4 + my-3d/custom-m-codes/do_nothing.ngc | 3 + my-3d/custom-m-codes/enable_steppers.ngc | 4 + my-3d/custom-m-codes/stop_idle_hold.ngc | 4 + my-3d/custom.hal | 40 + my-3d/custom_postgui.hal | 13 + my-3d/custompanel.xml | 126 + .../hal_extruder_temp_ctrl.py | 0 my-3d/linuxcnc.var | 119 + my-3d/my-3d.hal | 121 + my-3d/my-3d.ini | 204 ++ my-draw.hal => my-3d/my-draw.hal | 0 my-draw.ini => my-3d/my-draw.ini | 0 my-3d/tool.tbl | 4 + my-3d/watchdog.py | 93 + my-emco-compact-5/comms.py | 140 + my-emco-compact-5/custom-m-codes/M103 | 4 + my-emco-compact-5/custom-m-codes/M199 | 5 + .../custom-m-codes/extend_m0.ngc | 37 + .../custom-m-codes/extend_m1.ngc | 34 + .../custom-m-codes/mysetspeed.ngc | 31 + .../custom-m-codes/myspindlegain.ngc | 6 + .../custom-m-codes/myspindlespeeddown.ngc | 12 + .../custom-m-codes/myspindlespeedup.ngc | 11 + .../custom-m-codes/myspindlestart.ngc | 10 + my-emco-compact-5/custom-m-codes/mytouch.ngc | 23 + my-emco-compact-5/custom.hal | 127 + my-emco-compact-5/custom_postgui.hal | 18 + my-emco-compact-5/emcopos.txt | 16 + my-emco-compact-5/func-btn.xml | 5 + my-emco-compact-5/gladevcp-handler.py | 141 + my-emco-compact-5/linuxcnc.var | 122 + my-emco-compact-5/macros/LICENSE | 339 ++ my-emco-compact-5/macros/LatheMacro.svg | 3057 +++++++++++++++++ my-emco-compact-5/macros/README | 77 + .../__pycache__/lathehandler.cpython-37.pyc | Bin 0 -> 7405 bytes my-emco-compact-5/macros/boring.ngc | 64 + my-emco-compact-5/macros/chamfer.ngc | 77 + my-emco-compact-5/macros/drilling.ngc | 49 + my-emco-compact-5/macros/facing.ngc | 45 + my-emco-compact-5/macros/grooving.ngc | 32 + my-emco-compact-5/macros/lathe_macros.ini | 165 + my-emco-compact-5/macros/lathehandler.py | 219 ++ my-emco-compact-5/macros/lathemacro.ui | 2937 ++++++++++++++++ my-emco-compact-5/macros/radius.ngc | 76 + my-emco-compact-5/macros/threading.ngc | 59 + my-emco-compact-5/macros/turning.ngc | 66 + my-emco-compact-5/mpg.glade | 82 + my-emco-compact-5/mpg.hal | 1 + my-emco-compact-5/mpg.xml | 10 + my-emco-compact-5/my-emco-compact-5.hal | 101 + my-emco-compact-5/my-emco-compact-5.ini | 169 + my-emco-compact-5/my-emco-compact-5.pref | 85 + my-emco-compact-5/postgui_call_list.hal | 6 + my-emco-compact-5/python/remap.py | 1 + my-emco-compact-5/python/stdglue.py | 672 ++++ my-emco-compact-5/python/toplevel.py | 1 + my-emco-compact-5/serialEventHandler.py | 347 ++ my-emco-compact-5/speeds.png | Bin 0 -> 193334 bytes .../spindle_speed_selector.glade | 114 + my-emco-compact-5/spindle_speed_selector.hal | 12 + my-emco-compact-5/sync-files.sh | 9 + my-emco-compact-5/test_ladder.clp | 334 ++ my-emco-compact-5/tool.tbl | 4 + my-emco-compact-5/watchdog.py | 93 + my-emco/Misc/kopplingar.txt | 45 + my-emco/Misc/pid-tuning.ods | Bin 0 -> 8992 bytes my-emco/comms.py | 137 + my-emco/custom-m-codes/M101 | 4 + my-emco/custom-m-codes/M102 | 4 + my-emco/custom-m-codes/M103 | 4 + my-emco/custom-m-codes/mychange.ngc | 19 + my-emco/custom-m-codes/myspindlestart.ngc | 9 + my-emco/custom-m-codes/mytouch.ngc | 30 + my-emco/custom.hal | 140 + my-emco/custom_postgui.hal | 38 + my-emco/emco-buttons.xml | 158 + my-emco/fake_encoder.py | 126 + my-emco/func-btn.xml | 6 + my-emco/gladevcp-handler.py | 138 + my-emco/linuxcnc.var | 120 + my-emco/luber.hal | 16 + my-emco/luber.py | 234 ++ my-emco/luber.xml | 6 + my-emco/mpg.xml | 8 + my-emco/my-emco.hal | 110 + my-emco/my-emco.ini | 169 + my-emco/my-emco.ui | 508 +++ my-emco/postgui_backup.hal | 4 + my-emco/serialEventHandler.py | 281 ++ my-emco/test_ladder.clp | 301 ++ my-emco/tool.tbl | 9 + my-emco/watchdog.py | 94 + 105 files changed, 13770 insertions(+) create mode 100644 my-3d/3d_print_filter.py create mode 100644 my-3d/comms.py create mode 100644 my-3d/custom-m-codes/M102 create mode 100644 my-3d/custom-m-codes/M103 create mode 100644 my-3d/custom-m-codes/M104 create mode 100644 my-3d/custom-m-codes/M105 create mode 100644 my-3d/custom-m-codes/M106 create mode 100644 my-3d/custom-m-codes/M107 create mode 100644 my-3d/custom-m-codes/M109 create mode 100644 my-3d/custom-m-codes/M140 create mode 100644 my-3d/custom-m-codes/M190 create mode 100644 my-3d/custom-m-codes/M84 create mode 100644 my-3d/custom-m-codes/disable_steppers.ngc create mode 100644 my-3d/custom-m-codes/do_nothing.ngc create mode 100644 my-3d/custom-m-codes/enable_steppers.ngc create mode 100644 my-3d/custom-m-codes/stop_idle_hold.ngc create mode 100644 my-3d/custom.hal create mode 100644 my-3d/custom_postgui.hal create mode 100644 my-3d/custompanel.xml rename hal_extruder_temp_ctrl.py => my-3d/hal_extruder_temp_ctrl.py (100%) create mode 100644 my-3d/linuxcnc.var create mode 100644 my-3d/my-3d.hal create mode 100644 my-3d/my-3d.ini rename my-draw.hal => my-3d/my-draw.hal (100%) rename my-draw.ini => my-3d/my-draw.ini (100%) create mode 100644 my-3d/tool.tbl create mode 100644 my-3d/watchdog.py create mode 100644 my-emco-compact-5/comms.py create mode 100644 my-emco-compact-5/custom-m-codes/M103 create mode 100644 my-emco-compact-5/custom-m-codes/M199 create mode 100644 my-emco-compact-5/custom-m-codes/extend_m0.ngc create mode 100644 my-emco-compact-5/custom-m-codes/extend_m1.ngc create mode 100644 my-emco-compact-5/custom-m-codes/mysetspeed.ngc create mode 100644 my-emco-compact-5/custom-m-codes/myspindlegain.ngc create mode 100644 my-emco-compact-5/custom-m-codes/myspindlespeeddown.ngc create mode 100644 my-emco-compact-5/custom-m-codes/myspindlespeedup.ngc create mode 100644 my-emco-compact-5/custom-m-codes/myspindlestart.ngc create mode 100644 my-emco-compact-5/custom-m-codes/mytouch.ngc create mode 100644 my-emco-compact-5/custom.hal create mode 100644 my-emco-compact-5/custom_postgui.hal create mode 100644 my-emco-compact-5/emcopos.txt create mode 100644 my-emco-compact-5/func-btn.xml create mode 100644 my-emco-compact-5/gladevcp-handler.py create mode 100644 my-emco-compact-5/linuxcnc.var create mode 100644 my-emco-compact-5/macros/LICENSE create mode 100644 my-emco-compact-5/macros/LatheMacro.svg create mode 100644 my-emco-compact-5/macros/README create mode 100644 my-emco-compact-5/macros/__pycache__/lathehandler.cpython-37.pyc create mode 100644 my-emco-compact-5/macros/boring.ngc create mode 100644 my-emco-compact-5/macros/chamfer.ngc create mode 100644 my-emco-compact-5/macros/drilling.ngc create mode 100644 my-emco-compact-5/macros/facing.ngc create mode 100644 my-emco-compact-5/macros/grooving.ngc create mode 100644 my-emco-compact-5/macros/lathe_macros.ini create mode 100644 my-emco-compact-5/macros/lathehandler.py create mode 100644 my-emco-compact-5/macros/lathemacro.ui create mode 100644 my-emco-compact-5/macros/radius.ngc create mode 100644 my-emco-compact-5/macros/threading.ngc create mode 100644 my-emco-compact-5/macros/turning.ngc create mode 100644 my-emco-compact-5/mpg.glade create mode 100644 my-emco-compact-5/mpg.hal create mode 100644 my-emco-compact-5/mpg.xml create mode 100644 my-emco-compact-5/my-emco-compact-5.hal create mode 100644 my-emco-compact-5/my-emco-compact-5.ini create mode 100644 my-emco-compact-5/my-emco-compact-5.pref create mode 100644 my-emco-compact-5/postgui_call_list.hal create mode 100644 my-emco-compact-5/python/remap.py create mode 100644 my-emco-compact-5/python/stdglue.py create mode 100644 my-emco-compact-5/python/toplevel.py create mode 100644 my-emco-compact-5/serialEventHandler.py create mode 100644 my-emco-compact-5/speeds.png create mode 100644 my-emco-compact-5/spindle_speed_selector.glade create mode 100644 my-emco-compact-5/spindle_speed_selector.hal create mode 100644 my-emco-compact-5/sync-files.sh create mode 100644 my-emco-compact-5/test_ladder.clp create mode 100644 my-emco-compact-5/tool.tbl create mode 100644 my-emco-compact-5/watchdog.py create mode 100644 my-emco/Misc/kopplingar.txt create mode 100644 my-emco/Misc/pid-tuning.ods create mode 100644 my-emco/comms.py create mode 100644 my-emco/custom-m-codes/M101 create mode 100644 my-emco/custom-m-codes/M102 create mode 100644 my-emco/custom-m-codes/M103 create mode 100644 my-emco/custom-m-codes/mychange.ngc create mode 100644 my-emco/custom-m-codes/myspindlestart.ngc create mode 100644 my-emco/custom-m-codes/mytouch.ngc create mode 100644 my-emco/custom.hal create mode 100644 my-emco/custom_postgui.hal create mode 100644 my-emco/emco-buttons.xml create mode 100644 my-emco/fake_encoder.py create mode 100644 my-emco/func-btn.xml create mode 100644 my-emco/gladevcp-handler.py create mode 100644 my-emco/linuxcnc.var create mode 100644 my-emco/luber.hal create mode 100644 my-emco/luber.py create mode 100644 my-emco/luber.xml create mode 100644 my-emco/mpg.xml create mode 100644 my-emco/my-emco.hal create mode 100644 my-emco/my-emco.ini create mode 100644 my-emco/my-emco.ui create mode 100644 my-emco/postgui_backup.hal create mode 100644 my-emco/serialEventHandler.py create mode 100644 my-emco/test_ladder.clp create mode 100644 my-emco/tool.tbl create mode 100644 my-emco/watchdog.py diff --git a/my-3d/3d_print_filter.py b/my-3d/3d_print_filter.py new file mode 100644 index 0000000..9a26233 --- /dev/null +++ b/my-3d/3d_print_filter.py @@ -0,0 +1,58 @@ +#! /usr/bin/python3 +"""changes all words for controlling filament advancment 'E' to +more appropriet LinuxCNC join 'U' words +also changes argument names for the user defined M-codes""" +import sys + +def main(argv): + + openfile = open(argv, 'r') + file_in = openfile.readlines() + openfile.close() + + file_out = [] + for l in file_in: + line = l.rstrip('\n') + #print(line) + #if line[0] == ';': + # print('hej') + if line.find('E') != -1: + words = line.split(' ') + + newArray = [] + for i in words: + if i != '': + if i[0] == 'E': + i = i.replace('E', 'U', 1) + + if len(i) > 0: + newArray.append(i) + + line = ' '.join(newArray) + + elif line.find('M104 S') != -1: + line = line.replace('M104 S', 'M104 P', 1) + + elif line.find('M106 S') != -1: + line = line.replace('M106 S', 'M106 P', 1) + + elif line.find('M109 S') != -1: + line = line.replace('M109 S', 'M109 P', 1) + + elif line.find('M140 S') != -1: + line = line.replace('M140 S', 'M140 P', 1) + + # Cura seems to forget to divide the wait-value (Pxyz) with 1000 + # giving really long dwell-times + elif line.find('G4') != -1: + words = line.split('P') + words[1] = str(float(words[1])/1000) + line = 'P'.join(words) + + file_out.append(line) + + for item in file_out: + print(item) + +if __name__ == '__main__': + main(sys.argv[1]) diff --git a/my-3d/comms.py b/my-3d/comms.py new file mode 100644 index 0000000..5dd93d3 --- /dev/null +++ b/my-3d/comms.py @@ -0,0 +1,140 @@ +#!/usr/bin/python3 +# list available ports with 'python -m serial.tools.list_ports' +import serial +import watchdog + +## Default values ## +BAUDRATE = 38400 +"""Default value for the baudrate in Baud (int).""" + +PARITY = 'N' #serial.PARITY_NONE +"""Default value for the parity. See the pySerial module for documentation. Defaults to serial.PARITY_NONE""" + +BYTESIZE = 8 +"""Default value for the bytesize (int).""" + +STOPBITS = 1 +"""Default value for the number of stopbits (int).""" + +TIMEOUT = 0.05 +"""Default value for the timeout value in seconds (float).""" + +CLOSE_PORT_AFTER_EACH_CALL = False +"""Default value for port closure setting.""" + + +class Message: + """'container for messages. keeps two strings and """ + def __init__(self, name = '', data = ''): + self.name = name + self.data = data + + def __repr__(self): + return 'msg: ' + self.name + ' val: ' + self.data + + def copy(self, msg): + self.name = msg.name + self.data = msg.data + +class instrument: + """rs232 port""" + + def __init__(self, + port, + msg_handler, + watchdog_enabled = False, + watchdog_timeout = 2, + watchdog_periodicity = 0.5): + self.serial = serial.Serial() + self.serial.port = port + self.serial.baudrate = BAUDRATE + self.serial.parity = PARITY + self.serial.bytesize = BYTESIZE + self.serial.stopbits = STOPBITS + self.serial.xonxoff = False # disable software flow control + self.serial.timeout = TIMEOUT + self.portOpened = False + self.msg_hdlr = msg_handler + + self.watchdog_daemon = watchdog.WatchDogDaemon(watchdog_timeout, + watchdog_timeout, + watchdog_enabled) + self.watchdog_daemon.reset = self._watchdogClose #register watchdog reset function + self.closed_by_watchdog = False + + self.open() + + def open(self): + try: + self.serial.open() + self.portOpened = True + print('comms::opening port') + except serial.SerialException: + self.portOpened = False + print('unable to open port...') + + def close(self): + self.serial.close() + self.portOpened = False + print('comms::closeing port') + + def dataReady(self): + if self.portOpened: + return self.serial.in_waiting + else: + return False + + def readMessages(self): + """reads serial port. creates an array of events + output: array of events: + """ + if self.closed_by_watchdog: + self.closed_by_watchdog = False + self.open() + + while self.dataReady(): + msg_str = self._read().split('_', 1) + + if msg_str[0] != '': + self.msg_hdlr(Message(*msg_str)) + self.watchdog_daemon.ping() + + def generateEvent(self, name, data = ''): + self.writeMessage(Message(name, data)) + + def writeMessage(self, m): + self._write(m.name) + if m.data != '': + #self._write(m.data) + self._write('_' + str(m.data)) + + self._write('\n') + + def enableWatchdog(self, enable): + self.watchdog_daemon.setEnabled(enable) + + def _write(self, str): + if self.portOpened == True: + #serial expects a byte-array and not a string + self.serial.write(''.join(str).encode('utf-8', 'ignore')) + + def _read(self): + """ returns string read from serial port """ + b = '' + if self.portOpened == True: + b = self.serial.read_until() #blocks until '\n' received or timeout + + return b.decode('utf-8', 'ignore') #convert byte array to string + + def _watchdogClose(self): + self.closed_by_watchdog = True + self.close() + + def _is_number(self, s): + """ helper function to evaluate if input text represents an integer or not """ + try: + int(s) + return True + except ValueError: + return False + \ No newline at end of file diff --git a/my-3d/custom-m-codes/M102 b/my-3d/custom-m-codes/M102 new file mode 100644 index 0000000..7cc6bca --- /dev/null +++ b/my-3d/custom-m-codes/M102 @@ -0,0 +1,4 @@ +#!/bin/bash +# file to turn on parport pin XX to close the IO break out bord relay +halcmd setp and2.0.in1 1 +exit 0 diff --git a/my-3d/custom-m-codes/M103 b/my-3d/custom-m-codes/M103 new file mode 100644 index 0000000..7358070 --- /dev/null +++ b/my-3d/custom-m-codes/M103 @@ -0,0 +1,4 @@ +#!/bin/bash +# file to turn on parport pin XX to close the IO break out bord relay +halcmd setp and2.0.in1 0 +exit 0 diff --git a/my-3d/custom-m-codes/M104 b/my-3d/custom-m-codes/M104 new file mode 100644 index 0000000..2c6393e --- /dev/null +++ b/my-3d/custom-m-codes/M104 @@ -0,0 +1,12 @@ +#!/usr/bin/python3 +# M104: Set Extruder Temperature +import sys +import hal + +_dummy = hal.component("dummy") + +P = sys.argv[1] +setVal = str(int(float(P))) #convert from float-in-a-string to int-in-a-string +hal.set_p("my-temp-ctrl.ref-temp", setVal) + +print("M104 P" + setVal) diff --git a/my-3d/custom-m-codes/M105 b/my-3d/custom-m-codes/M105 new file mode 100644 index 0000000..35b4020 --- /dev/null +++ b/my-3d/custom-m-codes/M105 @@ -0,0 +1,3 @@ +#!/bin/bash +#do nothing +exit 0 diff --git a/my-3d/custom-m-codes/M106 b/my-3d/custom-m-codes/M106 new file mode 100644 index 0000000..c706178 --- /dev/null +++ b/my-3d/custom-m-codes/M106 @@ -0,0 +1,6 @@ +#!/bin/bash +# M106: Set Extruder fan PWM +temp=$1 +echo fan-pwm $temp +halcmd sets fan-pwm-value $temp +exit 0 diff --git a/my-3d/custom-m-codes/M107 b/my-3d/custom-m-codes/M107 new file mode 100644 index 0000000..137261b --- /dev/null +++ b/my-3d/custom-m-codes/M107 @@ -0,0 +1,4 @@ +#!/bin/bash +# M107: Fan Off +halcmd sets fan-pwm-value 0.0 +exit 0 diff --git a/my-3d/custom-m-codes/M109 b/my-3d/custom-m-codes/M109 new file mode 100644 index 0000000..0b4252f --- /dev/null +++ b/my-3d/custom-m-codes/M109 @@ -0,0 +1,28 @@ +#!/bin/bash +# M109: Set Extruder Temperature and Wait +float_ref=$1 +printf -v int_ref %.0f "$float_ref" +curr=$(halcmd getp my-temp-ctrl.curr-temp) + +halcmd setp my-temp-ctrl.ref-temp $int_ref +echo curr $curr +echo ref $int_ref + +CNT=0 +let ref=int_ref-2 +# do until reference temp is greater or equal to current temp +echo curr $curr, ref $ref +until [ $curr -ge $ref ] +do + sleep 1 + curr=$(halcmd getp my-temp-ctrl.curr-temp) + echo curr $curr, ref $ref + echo cnt $CNT + let CNT=CNT+1 + if [ "$CNT" = 60 ]; then + echo timeout + exit 0 + fi +done +echo M109 P$int_ref +exit 0 diff --git a/my-3d/custom-m-codes/M140 b/my-3d/custom-m-codes/M140 new file mode 100644 index 0000000..2437348 --- /dev/null +++ b/my-3d/custom-m-codes/M140 @@ -0,0 +1,6 @@ +#!/bin/bash +# M140: Set Bed Temperature (Fast) +v=$1 +echo bed-pwm $v +halcmd sets bed-pwm-value $v +exit 0 diff --git a/my-3d/custom-m-codes/M190 b/my-3d/custom-m-codes/M190 new file mode 100644 index 0000000..35b4020 --- /dev/null +++ b/my-3d/custom-m-codes/M190 @@ -0,0 +1,3 @@ +#!/bin/bash +#do nothing +exit 0 diff --git a/my-3d/custom-m-codes/M84 b/my-3d/custom-m-codes/M84 new file mode 100644 index 0000000..7358070 --- /dev/null +++ b/my-3d/custom-m-codes/M84 @@ -0,0 +1,4 @@ +#!/bin/bash +# file to turn on parport pin XX to close the IO break out bord relay +halcmd setp and2.0.in1 0 +exit 0 diff --git a/my-3d/custom-m-codes/disable_steppers.ngc b/my-3d/custom-m-codes/disable_steppers.ngc new file mode 100644 index 0000000..3e0df53 --- /dev/null +++ b/my-3d/custom-m-codes/disable_steppers.ngc @@ -0,0 +1,4 @@ +o sub +M103 +o endsub +M2 diff --git a/my-3d/custom-m-codes/do_nothing.ngc b/my-3d/custom-m-codes/do_nothing.ngc new file mode 100644 index 0000000..41f6847 --- /dev/null +++ b/my-3d/custom-m-codes/do_nothing.ngc @@ -0,0 +1,3 @@ +o sub +o endsub +M2 diff --git a/my-3d/custom-m-codes/enable_steppers.ngc b/my-3d/custom-m-codes/enable_steppers.ngc new file mode 100644 index 0000000..2dc1016 --- /dev/null +++ b/my-3d/custom-m-codes/enable_steppers.ngc @@ -0,0 +1,4 @@ +o sub +M102 +o endsub +M2 diff --git a/my-3d/custom-m-codes/stop_idle_hold.ngc b/my-3d/custom-m-codes/stop_idle_hold.ngc new file mode 100644 index 0000000..3e0df53 --- /dev/null +++ b/my-3d/custom-m-codes/stop_idle_hold.ngc @@ -0,0 +1,4 @@ +o sub +M103 +o endsub +M2 diff --git a/my-3d/custom.hal b/my-3d/custom.hal new file mode 100644 index 0000000..e834c7e --- /dev/null +++ b/my-3d/custom.hal @@ -0,0 +1,40 @@ +loadusr -Wn my-temp-ctrl python3 hal_extruder_temp_ctrl.py --port=/dev/ttyACM0 -c my-temp-ctrl +loadrt pwmgen output_type=0,0 +loadrt conv_float_u32 count=2 +loadrt and2 count=2 #enabling steppers, enabling extruder + +addf conv-float-u32.0 servo-thread +addf conv-float-u32.1 servo-thread +addf pwmgen.make-pulses base-thread +addf pwmgen.update servo-thread +addf and2.0 servo-thread +addf and2.1 servo-thread + +sets spindle-at-speed true + +### PWM for hot bed +# pwmgen.0.value connected and controlled in M140 +net bed-pwm-value => conv-float-u32.0.in +net bed-pwm-value => pwmgen.0.value +setp pwmgen.0.pwm-freq 100.0 +setp pwmgen.0.scale 512 #duty_cycle = (value/scale) + offset, with 1.0 meaning 100%, now with scale 1020 max duty is 25% +setp pwmgen.0.offset 0 +setp pwmgen.0.enable 1 +setp pwmgen.0.dither-pwm true +net bed-pwm-out pwmgen.0.pwm => parport.0.pin-14-out + +### PWM for extruder fan +# pwmgen.1.value connected and controlled in M106, M107 +net fan-pwm-value conv-float-u32.1.in +net fan-pwm-value pwmgen.1.value +setp pwmgen.1.pwm-freq 100.0 +setp pwmgen.1.scale 255 #duty_cycle = (value/scale) + offset, with 1.0 meaning 100% +setp pwmgen.1.offset 0 +setp pwmgen.1.enable 1 +setp pwmgen.1.dither-pwm true +net fan-pwm-out pwmgen.1.pwm => parport.0.pin-16-out + +### enable steppers +net machine-is-on halui.machine.is-on => and2.0.in0 +net and-out and2.0.out => parport.0.pin-17-out +setp and2.0.in1 1 # start enabled, in1 is written by M102(enable) and M103(disable) diff --git a/my-3d/custom_postgui.hal b/my-3d/custom_postgui.hal new file mode 100644 index 0000000..367deff --- /dev/null +++ b/my-3d/custom_postgui.hal @@ -0,0 +1,13 @@ +# Include your customized HAL commands here +# The commands in this file are run after the AXIS GUI (including PyVCP panel) starts +### enable extruder +net machine-is-on => and2.1.in0 +net enable-chkbtn and2.1.in1 <= pyvcp.enable-chkbtn +net temp-ctrl-enable and2.1.out => my-temp-ctrl.enable + +net temp-ctrl-ref my-temp-ctrl.ref-temp-out => pyvcp.ref-temp +net temp-ctrl-curr my-temp-ctrl.curr-temp => pyvcp.curr-temp + +net bed-pwm-u32value conv-float-u32.0.out => pyvcp.bed-pwm +net fan-pwm-u32value conv-float-u32.1.out => pyvcp.fan-pwm + diff --git a/my-3d/custompanel.xml b/my-3d/custompanel.xml new file mode 100644 index 0000000..58b6a75 --- /dev/null +++ b/my-3d/custompanel.xml @@ -0,0 +1,126 @@ + + + + ("Helvetica",12) + + + RIDGE + 2 + + "enable-chkbtn" + " Enable Extruder Temp Ctrl" + ("Helvetica",10) + 1 + + + + + RIDGE + 2 + + + "ref-temp" + ("Helvetica",10) + "6d" + 6 + + + + + RIDGE + 2 + + + "curr-temp" + ("Helvetica",10) + "6d" + 6 + + + + RIDGE + 2 + + + "fan-pwm" + ("Helvetica",10) + "6d" + 6 + + + + + ("Helvetica",12) + + RIDGE + 2 + + + "bed-pwm" + ("Helvetica",10) + "6d" + 6 + + + + + ("Helvetica",12) + RIDGE + 2 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hal_extruder_temp_ctrl.py b/my-3d/hal_extruder_temp_ctrl.py similarity index 100% rename from hal_extruder_temp_ctrl.py rename to my-3d/hal_extruder_temp_ctrl.py diff --git a/my-3d/linuxcnc.var b/my-3d/linuxcnc.var new file mode 100644 index 0000000..e19eb6b --- /dev/null +++ b/my-3d/linuxcnc.var @@ -0,0 +1,119 @@ +5161 0.000000 +5162 0.000000 +5163 0.000000 +5164 0.000000 +5165 0.000000 +5166 0.000000 +5167 0.000000 +5168 0.000000 +5169 0.000000 +5181 0.000000 +5182 0.000000 +5183 0.000000 +5184 0.000000 +5185 0.000000 +5186 0.000000 +5187 0.000000 +5188 0.000000 +5189 0.000000 +5210 1.000000 +5211 0.000000 +5212 0.000000 +5213 0.000000 +5214 0.000000 +5215 0.000000 +5216 0.000000 +5217 270.164780 +5218 0.000000 +5219 0.000000 +5220 1.000000 +5221 7.154117 +5222 -1.718917 +5223 0.627583 +5224 0.000000 +5225 0.000000 +5226 0.000000 +5227 0.000000 +5228 0.000000 +5229 0.000000 +5230 0.000000 +5241 0.000000 +5242 0.000000 +5243 0.000000 +5244 0.000000 +5245 0.000000 +5246 0.000000 +5247 0.000000 +5248 0.000000 +5249 0.000000 +5250 0.000000 +5261 0.000000 +5262 0.000000 +5263 0.000000 +5264 0.000000 +5265 0.000000 +5266 0.000000 +5267 0.000000 +5268 0.000000 +5269 0.000000 +5270 0.000000 +5281 0.000000 +5282 0.000000 +5283 0.000000 +5284 0.000000 +5285 0.000000 +5286 0.000000 +5287 0.000000 +5288 0.000000 +5289 0.000000 +5290 0.000000 +5301 0.000000 +5302 0.000000 +5303 0.000000 +5304 0.000000 +5305 0.000000 +5306 0.000000 +5307 0.000000 +5308 0.000000 +5309 0.000000 +5310 0.000000 +5321 0.000000 +5322 0.000000 +5323 0.000000 +5324 0.000000 +5325 0.000000 +5326 0.000000 +5327 0.000000 +5328 0.000000 +5329 0.000000 +5330 0.000000 +5341 0.000000 +5342 0.000000 +5343 0.000000 +5344 0.000000 +5345 0.000000 +5346 0.000000 +5347 0.000000 +5348 0.000000 +5349 0.000000 +5350 0.000000 +5361 0.000000 +5362 0.000000 +5363 0.000000 +5364 0.000000 +5365 0.000000 +5366 0.000000 +5367 0.000000 +5368 0.000000 +5369 0.000000 +5370 0.000000 +5381 0.000000 +5382 0.000000 +5383 0.000000 +5384 0.000000 +5385 0.000000 +5386 0.000000 +5387 0.000000 +5388 0.000000 +5389 0.000000 +5390 0.000000 diff --git a/my-3d/my-3d.hal b/my-3d/my-3d.hal new file mode 100644 index 0000000..1e9080b --- /dev/null +++ b/my-3d/my-3d.hal @@ -0,0 +1,121 @@ +# Generated by stepconf 1.1 at Fri May 1 19:07:04 2020 +# If you make changes to this file, they will be +# overwritten when you run stepconf again +loadrt [KINS]KINEMATICS +#autoconverted trivkins +loadrt [EMCMOT]EMCMOT base_period_nsec=[EMCMOT]BASE_PERIOD servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS +loadrt hal_parport cfg="0 out" +setp parport.0.reset-time 5000 +loadrt stepgen step_type=0,0,0,0 +#loadrt pwmgen output_type=0 + +addf parport.0.read base-thread +addf stepgen.make-pulses base-thread +#addf pwmgen.make-pulses base-thread +addf parport.0.write base-thread +addf parport.0.reset base-thread + +addf stepgen.capture-position servo-thread +addf motion-command-handler servo-thread +addf motion-controller servo-thread +addf stepgen.update-freq servo-thread +#addf pwmgen.update servo-thread + +#net spindle-cmd-rpm => pwmgen.0.value +#net spindle-on <= spindle.0.on => pwmgen.0.enable +#net spindle-pwm <= pwmgen.0.pwm +#setp pwmgen.0.pwm-freq 100.0 +#setp pwmgen.0.scale 9333.33333333 +#setp pwmgen.0.offset 0.0928571428571 +#setp pwmgen.0.dither-pwm true +#net spindle-cmd-rpm <= spindle.0.speed-out +#net spindle-cmd-rpm-abs <= spindle.0.speed-out-abs +#net spindle-cmd-rps <= spindle.0.speed-out-rps +#net spindle-cmd-rps-abs <= spindle.0.speed-out-rps-abs +net spindle-at-speed => spindle.0.at-speed +#net spindle-cw <= spindle.0.forward + +net xdir => parport.0.pin-02-out +net xstep => parport.0.pin-03-out +setp parport.0.pin-03-out-reset 1 +net ydir => parport.0.pin-04-out +net ystep => parport.0.pin-05-out +setp parport.0.pin-05-out-reset 1 +setp parport.0.pin-06-out-invert 1 +net zdir => parport.0.pin-06-out +net zstep => parport.0.pin-07-out +setp parport.0.pin-07-out-reset 1 +setp parport.0.pin-08-out-invert 1 +net udir => parport.0.pin-08-out +net ustep => parport.0.pin-09-out +setp parport.0.pin-09-out-reset 1 + + +#net spindle-pwm => parport.0.pin-08-out +#net spindle-cw => parport.0.pin-17-out +net min-home-x <= parport.0.pin-11-in +net max-home-y <= parport.0.pin-10-in +net max-home-z <= parport.0.pin-12-in + +setp stepgen.0.position-scale [JOINT_0]SCALE +setp stepgen.0.steplen 1 +setp stepgen.0.stepspace 0 +setp stepgen.0.dirhold 35000 +setp stepgen.0.dirsetup 35000 +setp stepgen.0.maxaccel [JOINT_0]STEPGEN_MAXACCEL +net xpos-cmd joint.0.motor-pos-cmd => stepgen.0.position-cmd +net xpos-fb stepgen.0.position-fb => joint.0.motor-pos-fb +net xstep <= stepgen.0.step +net xdir <= stepgen.0.dir +net xenable joint.0.amp-enable-out => stepgen.0.enable +net min-home-x => joint.0.home-sw-in +net min-home-x => joint.0.neg-lim-sw-in + +setp stepgen.1.position-scale [JOINT_1]SCALE +setp stepgen.1.steplen 1 +setp stepgen.1.stepspace 0 +setp stepgen.1.dirhold 35000 +setp stepgen.1.dirsetup 35000 +setp stepgen.1.maxaccel [JOINT_1]STEPGEN_MAXACCEL +net ypos-cmd joint.1.motor-pos-cmd => stepgen.1.position-cmd +net ypos-fb stepgen.1.position-fb => joint.1.motor-pos-fb +net ystep <= stepgen.1.step +net ydir <= stepgen.1.dir +net yenable joint.1.amp-enable-out => stepgen.1.enable +net max-home-y => joint.1.home-sw-in +net max-home-y => joint.1.pos-lim-sw-in + +setp stepgen.2.position-scale [JOINT_2]SCALE +setp stepgen.2.steplen 1 +setp stepgen.2.stepspace 0 +setp stepgen.2.dirhold 35000 +setp stepgen.2.dirsetup 35000 +setp stepgen.2.maxaccel [JOINT_2]STEPGEN_MAXACCEL +net zpos-cmd joint.2.motor-pos-cmd => stepgen.2.position-cmd +net zpos-fb stepgen.2.position-fb => joint.2.motor-pos-fb +net zstep <= stepgen.2.step +net zdir <= stepgen.2.dir +net zenable joint.2.amp-enable-out => stepgen.2.enable +net max-home-z => joint.2.home-sw-in +net max-home-z => joint.2.pos-lim-sw-in + +setp stepgen.3.position-scale [JOINT_3]SCALE +setp stepgen.3.steplen 1 +setp stepgen.3.stepspace 0 +setp stepgen.3.dirhold 35000 +setp stepgen.3.dirsetup 35000 +setp stepgen.3.maxaccel [JOINT_3]STEPGEN_MAXACCEL +net upos-cmd joint.3.motor-pos-cmd => stepgen.3.position-cmd +net upos-fb stepgen.3.position-fb => joint.3.motor-pos-fb +net ustep <= stepgen.3.step +net udir <= stepgen.3.dir +net uenable joint.3.amp-enable-out => stepgen.3.enable + +net estop-out <= iocontrol.0.user-enable-out +net estop-out => iocontrol.0.emc-enable-in + +loadusr -W hal_manualtoolchange +net tool-change iocontrol.0.tool-change => hal_manualtoolchange.change +net tool-changed iocontrol.0.tool-changed <= hal_manualtoolchange.changed +net tool-number iocontrol.0.tool-prep-number => hal_manualtoolchange.number +net tool-prepare-loopback iocontrol.0.tool-prepare => iocontrol.0.tool-prepared diff --git a/my-3d/my-3d.ini b/my-3d/my-3d.ini new file mode 100644 index 0000000..22d5321 --- /dev/null +++ b/my-3d/my-3d.ini @@ -0,0 +1,204 @@ +# This config file was created 2021-02-04 16:39:45.407451 by the update_ini script +# The original config files may be found in the /home/johan/linuxcnc/configs/test/test.old directory + +# Generated by stepconf 1.1 at Fri May 1 19:07:04 2020 +# If you make changes to this file, they will be +# overwritten when you run stepconf again + +[EMC] +# The version string for this INI file. +VERSION = 1.1 + +MACHINE = my-3d +DEBUG = 0 + +[DISPLAY] +DISPLAY = axis +EDITOR = gedit +POSITION_OFFSET = RELATIVE +POSITION_FEEDBACK = ACTUAL +ARCDIVISION = 64 +GRIDS = 10mm 20mm 50mm 100mm 1in 2in 5in 10in +MAX_FEED_OVERRIDE = 1.2 +MIN_SPINDLE_OVERRIDE = 0.5 +MAX_SPINDLE_OVERRIDE = 1.2 +DEFAULT_LINEAR_VELOCITY = 2.50 +MIN_LINEAR_VELOCITY = 0 +MAX_LINEAR_VELOCITY = 25.00 +DEFAULT_ANGULAR_VELOCITY = 36.00 +MIN_ANGULAR_VELOCITY = 0 +MAX_ANGULAR_VELOCITY = 360.00 +INTRO_GRAPHIC = linuxcnc.gif +INTRO_TIME = 5 +PROGRAM_PREFIX = /home/johan/linuxcnc/nc_files +INCREMENTS = 5mm 1mm .5mm .1mm .05mm .01mm .005mm +PYVCP = custompanel.xml +GEOMETRY = XYZ + +[FILTER] +PROGRAM_EXTENSION = .png,.gif,.jpg Greyscale Depth Image +PROGRAM_EXTENSION = .py Python Script +PROGRAM_EXTENSION = .gcode 3D Printer + +png = image-to-gcode +gif = image-to-gcode +jpg = image-to-gcode +py = python +gcode = python /home/johan/linuxcnc/configs/my-3d/3d_print_filter.py + +[RS274NGC] +PARAMETER_FILE = linuxcnc.var +SUBROUTINE_PATH=/home/johan/linuxcnc/configs/my-3d/custom-m-codes +USER_M_PATH=/home/johan/linuxcnc/configs/my-3d/custom-m-codes +REMAP=M17 modalgroup=10 ngc=enable_steppers +REMAP=M18 modalgroup=10 ngc=disable_steppers +REMAP=M84 modalgroup=10 ngc=stop_idle_hold +REMAP=M82 modalgroup=10 ngc=do_nothing + +[EMCMOT] +EMCMOT = motmod +COMM_TIMEOUT = 1.0 +BASE_PERIOD = 100000 +SERVO_PERIOD = 1000000 + +[TASK] +TASK = milltask +CYCLE_TIME = 0.010 + +[HAL] +HALUI = halui +HALFILE = my-3d.hal +HALFILE = custom.hal +POSTGUI_HALFILE = custom_postgui.hal + +[HALUI] + +[TRAJ] +COORDINATES = XYZU +JOINTS = 3 +HOME = 0 0 0 0 +LINEAR_UNITS = mm +ANGULAR_UNITS = degree +DEFAULT_LINEAR_VELOCITY = 25.0 +MAX_LINEAR_VELOCITY = 25.0 +DEFAULT_LINEAR_ACCELERATION = 50.0 +MAX_LINEAR_ACCELERATION = 50.0 + +[EMCIO] +EMCIO = io +CYCLE_TIME = 0.100 +TOOL_TABLE = tool.tbl + + +[KINS] +KINEMATICS = trivkins coordinates=XYZU +#This is a best-guess at the number of joints, it should be checked +JOINTS = 4 + +# X axis +#prescale = 2, steps/rev = 200, 2 mm/rev +#scale = 200*2/2 steps/mm +[AXIS_X] +MIN_LIMIT = -90.0 +MAX_LIMIT = 90.0 +MAX_VELOCITY = 25.0 +MAX_ACCELERATION = 50.0 + +[JOINT_0] +TYPE = LINEAR +HOME = 0.0 +MAX_VELOCITY = 25.0 +MAX_ACCELERATION = 50.0 +STEPGEN_MAXACCEL = 62.5 +SCALE = 200.0 +FERROR = 1 +MIN_FERROR = .25 +MIN_LIMIT = -90.0 +MAX_LIMIT = 90.0 +HOME_OFFSET = -91.400000 +HOME_SEARCH_VEL = -10.0 +HOME_LATCH_VEL = -1.50 +HOME_IGNORE_LIMITS = YES +HOME_SEQUENCE = 0 + +# Y axis +#prescale = 2, steps/rev = 200, 2 mm/rev +#scale = 200*2/2 steps/mm +[AXIS_Y] +MIN_LIMIT = -90.0 +MAX_LIMIT = 90.0 +MAX_VELOCITY = 25.0 +MAX_ACCELERATION = 50.0 + +[JOINT_1] +TYPE = LINEAR +HOME = 0.0 +MAX_VELOCITY = 25.0 +MAX_ACCELERATION = 50.0 +STEPGEN_MAXACCEL = 62.5 +SCALE = 200.0 +FERROR = 1 +MIN_FERROR = .25 +MIN_LIMIT = -90.0 +MAX_LIMIT = 90.0 +HOME_OFFSET = 66.00000 +HOME_SEARCH_VEL = 10.0 +HOME_LATCH_VEL = 2.5 +HOME_IGNORE_LIMITS = YES +HOME_SEQUENCE = 0 + +# Z axis +#prescale = 8, steps/rev = 200, 1 mm/rev +#scale = 200*8/1 = 1600 steps/mm +[AXIS_Z] +MIN_LIMIT = -10.0 +MAX_LIMIT = 51.7 +MAX_VELOCITY = 6.0 +MAX_ACCELERATION = 50.0 + +[JOINT_2] +TYPE = LINEAR +HOME = 48.0 +MAX_VELOCITY = 6.0 +MAX_ACCELERATION = 50.0 +STEPGEN_MAXACCEL = 62.5 +SCALE = 1600.0 +FERROR = 1 +MIN_FERROR = .25 +MIN_LIMIT = -10.0 +MAX_LIMIT = 51.7 +HOME_OFFSET = 51.7 +HOME_SEARCH_VEL = 7.500000 +HOME_LATCH_VEL = 0.312500 +HOME_IGNORE_LIMITS = YES +HOME_SEQUENCE = 0 + +# U axis +#prescale = 4, steps/rev = 200, 34.069 mm/rev (D=10.85, pi*10.85=34.069) +#scale = 200*4/34.069 steps/mm +[AXIS_U] +MIN_LIMIT = -1e99 +MAX_LIMIT = 1e99 +MAX_VELOCITY = 30 +MAX_ACCELERATION = 50.0 + +[JOINT_3] +TYPE = LINEAR +HOME = 0.000 +MAX_VELOCITY = 30 +MAX_ACCELERATION = 50.0 +STEPGEN_MAXACCEL = 62.5 +SCALE = 23.5 +BACKLASH = 0.000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -1e99 +MAX_LIMIT = 1e99 +FERROR = 1 +MIN_FERROR = .25 +HOME_OFFSET = 0.0 +HOME_SEARCH_VEL = 0 +HOME_LATCH_VEL = 0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = YES +HOME_SEQUENCE = 0 +HOME_IS_SHARED = NO diff --git a/my-draw.hal b/my-3d/my-draw.hal similarity index 100% rename from my-draw.hal rename to my-3d/my-draw.hal diff --git a/my-draw.ini b/my-3d/my-draw.ini similarity index 100% rename from my-draw.ini rename to my-3d/my-draw.ini diff --git a/my-3d/tool.tbl b/my-3d/tool.tbl new file mode 100644 index 0000000..507e208 --- /dev/null +++ b/my-3d/tool.tbl @@ -0,0 +1,4 @@ +T1 P1 Z50 D12.0 ;12mm end mill +T2 P2 Z24.1 D8.0 ;8mm end mill +T3 P3 Z26 D4.2 ;m5 tap drill +T99999 P99999 Z0.1 ;big tool number diff --git a/my-3d/watchdog.py b/my-3d/watchdog.py new file mode 100644 index 0000000..d780c7d --- /dev/null +++ b/my-3d/watchdog.py @@ -0,0 +1,93 @@ +#! /usr/bin/python3 +import time +import threading +import sys + +class WatchDog(): + def __init__(self, timeout): + self.timeout = timeout + self.last_ping_time = time.time() + + def ping(self): + self.last_ping_time = time.time() + + def check(self): + if time.time() - self.last_ping_time > self.timeout: + self.last_ping_time = time.time() #reset tick time + return True + else: + return False + + def insideMargin(self): + if time.time() - self.last_ping_time <= self.timeout: + return True + else: + self.last_ping_time = time.time() #reset tick time + return False + + +class WatchDogDaemon(threading.Thread): + def __init__(self, timeout, periodicity, enable = True): + self.wd = WatchDog(timeout) + self.periodicity = periodicity + self.enabled = enable + self._start() + + def _start(self): + threading.Thread.__init__(self) + self.daemon = True + self.start() + + def ping(self): + self.wd.ping() + + def run(self): + print("Starting watchdog deamon...") + while(self.enabled): + time.sleep(self.periodicity) + + if not self.wd.insideMargin(): + self.reset() + + print("stopping watchdog deamon...") + + def setEnabled(self, enabled): + if self.enabled == False and enabled == True: + self.enabled = True + self.wd.ping() # reset tick time + self._start() + + if enabled == False: + self.enabled = False + + def reset(self): + """to be overriden by client""" + pass + + +def reset(): + print('reset') + +def main(): + i = 0 + wdd = WatchDogDaemon(2, 0.5, False) + wdd.reset = reset + try: + while 1: + time.sleep(1) + print('main_' + str(i)) + print(wdd.is_alive()) + i = i+1 + + if i == 5 or i == 15: + wdd.setEnabled(True) + if i == 10: + wdd.setEnabled(False) + + wdd.ping() + + except KeyboardInterrupt: + raise SystemExit + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/my-emco-compact-5/comms.py b/my-emco-compact-5/comms.py new file mode 100644 index 0000000..62775db --- /dev/null +++ b/my-emco-compact-5/comms.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# list available ports with 'python -m serial.tools.list_ports' +import serial +import watchdog + +## Default values ## +BAUDRATE = 38400 +"""Default value for the baudrate in Baud (int).""" + +PARITY = 'N' #serial.PARITY_NONE +"""Default value for the parity. See the pySerial module for documentation. Defaults to serial.PARITY_NONE""" + +BYTESIZE = 8 +"""Default value for the bytesize (int).""" + +STOPBITS = 1 +"""Default value for the number of stopbits (int).""" + +TIMEOUT = 0.05 +"""Default value for the timeout value in seconds (float).""" + +CLOSE_PORT_AFTER_EACH_CALL = False +"""Default value for port closure setting.""" + + +class Message: + """'container for messages. keeps two strings and """ + def __init__(self, name = '', data = ''): + self.name = name + self.data = data + + def __repr__(self): + return 'msg: ' + self.name + ' val: ' + self.data + + def copy(self, msg): + self.name = msg.name + self.data = msg.data + +class instrument: + """rs232 port""" + + def __init__(self, + port, + msg_handler, + watchdog_enabled = False, + watchdog_timeout = 2, + watchdog_periodicity = 0.5): + self.serial = serial.Serial() + self.serial.port = port + self.serial.baudrate = BAUDRATE + self.serial.parity = PARITY + self.serial.bytesize = BYTESIZE + self.serial.stopbits = STOPBITS + self.serial.xonxoff = False # disable software flow control + self.serial.timeout = TIMEOUT + self.portOpened = False + self.msg_hdlr = msg_handler + + self.watchdog_daemon = watchdog.WatchDogDaemon(watchdog_timeout, + watchdog_timeout, + watchdog_enabled) + self.watchdog_daemon.reset = self._watchdogClose #register watchdog reset function + self.closed_by_watchdog = False + + self.open() + + def open(self): + try: + self.serial.open() + self.portOpened = True + print('comms::opening port') + except serial.SerialException: + self.portOpened = False + print('unable to open port...') + + def close(self): + self.serial.close() + self.portOpened = False + print('comms::closeing port') + + def dataReady(self): + if self.portOpened: + return self.serial.in_waiting + else: + return False + + def readMessages(self): + """reads serial port. creates an array of events + output: array of events: + """ + if self.closed_by_watchdog: + self.closed_by_watchdog = False + self.open() + + while self.dataReady(): + msg_str = self._read().split('_', 1) + + if msg_str[0] != '': + self.msg_hdlr(Message(*msg_str)) + self.watchdog_daemon.ping() + + def generateEvent(self, name, data = ''): + self.writeMessage(Message(name, data)) + + def writeMessage(self, m): + self._write(m.name) + if m.data != '': + #self._write(m.data) + self._write('_' + str(m.data)) + + self._write('\n') + + def enableWatchdog(self, enable): + self.watchdog_daemon.setEnabled(enable) + + def _write(self, str): + if self.portOpened == True: + #serial expects a byte-array and not a string + self.serial.write(''.join(str).encode('utf-8', 'ignore')) + + def _read(self): + """ returns string read from serial port """ + b = '' + if self.portOpened == True: + b = self.serial.read_until() #blocks until '\n' received or timeout + + return b.decode('utf-8', 'ignore') #convert byte array to string + + def _watchdogClose(self): + self.closed_by_watchdog = True + self.close() + + def _is_number(self, s): + """ helper function to evaluate if input text represents an integer or not """ + try: + int(s) + return True + except ValueError: + return False + diff --git a/my-emco-compact-5/custom-m-codes/M103 b/my-emco-compact-5/custom-m-codes/M103 new file mode 100644 index 0000000..56dcc29 --- /dev/null +++ b/my-emco-compact-5/custom-m-codes/M103 @@ -0,0 +1,4 @@ +#!/bin/bash +# axis reload after +axis-remote --reload +exit 0 diff --git a/my-emco-compact-5/custom-m-codes/M199 b/my-emco-compact-5/custom-m-codes/M199 new file mode 100644 index 0000000..5ed5ffc --- /dev/null +++ b/my-emco-compact-5/custom-m-codes/M199 @@ -0,0 +1,5 @@ +#!/bin/bash +# set spindle speed gain corresponding to selected motor-spindle-belt-wheel +halcmd setp scale.1.gain $1 +halcmd setp scale.1.offset $2 +exit 0 \ No newline at end of file diff --git a/my-emco-compact-5/custom-m-codes/extend_m0.ngc b/my-emco-compact-5/custom-m-codes/extend_m0.ngc new file mode 100644 index 0000000..8aa33a6 --- /dev/null +++ b/my-emco-compact-5/custom-m-codes/extend_m0.ngc @@ -0,0 +1,37 @@ +; extend M0 to: +; turn off spindle, flood, mist if optional stop = 1 and block delete is off +; +; on cycle restart: +; turn on spindle +; turn mist on if it was on before the m1 +; turn flood on if it was on before the m1 + +o sub + ;(debug, extend_m0:) + + ; record whether mist/flood were on + # = #<_mist> + # = #<_flood> + # = #<_spindle_on> + + M5 M9 ; stop spindle, mist+flood off + + m0 (refer to builtin m0) + + ; restore mist, flood setting + o100 if [#] + m7 + o100 endif + + o200 if [#] + m8 + o200 endif + + o300 if [#] + m3 ; spindle on + o300 endif + + ;(debug, extend_m0 done) + +o endsub +m2 diff --git a/my-emco-compact-5/custom-m-codes/extend_m1.ngc b/my-emco-compact-5/custom-m-codes/extend_m1.ngc new file mode 100644 index 0000000..4b1bee9 --- /dev/null +++ b/my-emco-compact-5/custom-m-codes/extend_m1.ngc @@ -0,0 +1,34 @@ +; extend M1 to: +; turn off spindle, flood, mist if optional stop = 1 and block delete is off +; +; on cycle restart: +; turn on spindle +; turn mist on if it was on before the m1 +; turn flood on if it was on before the m1 + +o sub +(debug, extend_m1:) + +; record whether mist/flood were on +# = #<_mist> +# = #<_flood> + +/M5 M9 ; stop spindle, mist+flood off + +/m1 (refer to builtin m1) + +; restore mist, flood setting +/o100 if [#] +/ m7 +/o100 endif + +/o200 if [#] +/ m8 +/o200 endif + +/M3 ; spindle on + +(debug, extend_m1 done) + +o endsub +m2 diff --git a/my-emco-compact-5/custom-m-codes/mysetspeed.ngc b/my-emco-compact-5/custom-m-codes/mysetspeed.ngc new file mode 100644 index 0000000..434184a --- /dev/null +++ b/my-emco-compact-5/custom-m-codes/mysetspeed.ngc @@ -0,0 +1,31 @@ +o sub + (DEBUG, S# setspeed) + o10 if [# LE 950] + o11 if [#4997 NE 1] + (MSG, Set spindle speed selection belt in AC1 position) + O call [0.48483] [20.16466] + #4997 = 1 + o11 endif + o10 else + o20 if [# LE 1500] + o21 if [#4997 NE 2] + (MSG, Set spindle speed selection belt in AC2 position) + O call [0.32571] [-8.05714] + #4997 = 2 + o21 endif + o20 else + o30 if [# LE 2400] + o31 if [#4997 NE 3] + (MSG, Set spindle speed selection belt in AC3 position) + O call [0.20098] [22.64117] + #4997 = 3 + o31 endif + o30 else + (MSG, Unsupported spindle speed. will continue anyway. Safest is probably to abort and regenerate program) + o30 endif + o20 endif + o10 endif + S# + M0 +o endsub [1] +M2 diff --git a/my-emco-compact-5/custom-m-codes/myspindlegain.ngc b/my-emco-compact-5/custom-m-codes/myspindlegain.ngc new file mode 100644 index 0000000..be08e27 --- /dev/null +++ b/my-emco-compact-5/custom-m-codes/myspindlegain.ngc @@ -0,0 +1,6 @@ +o sub + M199 P#1 Q#2 + #4998 = #1 + #4999 = #2 +o endsub +M2 diff --git a/my-emco-compact-5/custom-m-codes/myspindlespeeddown.ngc b/my-emco-compact-5/custom-m-codes/myspindlespeeddown.ngc new file mode 100644 index 0000000..c3db13b --- /dev/null +++ b/my-emco-compact-5/custom-m-codes/myspindlespeeddown.ngc @@ -0,0 +1,12 @@ +o sub + o10 if [#<_spindle_on> EQ 1] + O20 if [#<_rpm> GT 100] + # = [#<_rpm> - 100] + O20 else + # = 0 + M5 + O20 endif + S# + o10 endif +o endsub +M2 diff --git a/my-emco-compact-5/custom-m-codes/myspindlespeedup.ngc b/my-emco-compact-5/custom-m-codes/myspindlespeedup.ngc new file mode 100644 index 0000000..524a5f4 --- /dev/null +++ b/my-emco-compact-5/custom-m-codes/myspindlespeedup.ngc @@ -0,0 +1,11 @@ +o sub + ;o10 if [#<_spindle_on> EQ 1] + # = [#<_rpm>+100] + O10 if [# GT 4500] + # = 4500 + O10 endif + S# + M3 + ;o10 endif +o endsub +M2 diff --git a/my-emco-compact-5/custom-m-codes/myspindlestart.ngc b/my-emco-compact-5/custom-m-codes/myspindlestart.ngc new file mode 100644 index 0000000..7efa9a8 --- /dev/null +++ b/my-emco-compact-5/custom-m-codes/myspindlestart.ngc @@ -0,0 +1,10 @@ +o sub + o10 if [#<_spindle_on> EQ 1] + M5 + o10 else + S800 + M3 + o10 endif + ;M103 +o endsub +M2 \ No newline at end of file diff --git a/my-emco-compact-5/custom-m-codes/mytouch.ngc b/my-emco-compact-5/custom-m-codes/mytouch.ngc new file mode 100644 index 0000000..3f1f92c --- /dev/null +++ b/my-emco-compact-5/custom-m-codes/mytouch.ngc @@ -0,0 +1,23 @@ +o sub + o10 if [EXISTS[#<_hal[jog-axis-sel]>]] + # = #<_hal[jog-axis-sel]> + ;(DEBUG, my-mpg.axis-selector: #) + + (touch off) + o20 if [# EQ 0] + G10 L20 P0 X0 + M103 + (MSG, touch off x axis) + o20 endif + + (touch off z axis) + o30 if [# EQ 1] + G10 L20 P0 Z0 + M103 + (MSG, touch off z axis) + o30 endif + o10 else + (DEBUG, didn't exist) + o10 endif +o endsub +M2 diff --git a/my-emco-compact-5/custom.hal b/my-emco-compact-5/custom.hal new file mode 100644 index 0000000..5deafb3 --- /dev/null +++ b/my-emco-compact-5/custom.hal @@ -0,0 +1,127 @@ +# Include your custom HAL commands here +# This file will not be overwritten when you run stepconf again +loadrt near names=spindle-at-speed +loadrt siggen num_chan=1 #spincle speed control +loadrt and2 count=4 # 0 used in e-stop chain, + # 1,2,3 used för connecting spindle speed gain selection +loadrt classicladder_rt +loadrt timedelay count=1 + +addf spindle-at-speed servo-thread +addf scale.1 servo-thread +addf scale.2 servo-thread +addf scale.3 servo-thread +addf scale.4 servo-thread +addf siggen.0.update servo-thread +addf and2.0 servo-thread +addf and2.1 servo-thread +addf and2.2 servo-thread +addf and2.3 servo-thread +addf classicladder.0.refresh servo-thread +addf timedelay.0 servo-thread + +loadusr -Wn my-mpg python3 serialEventHandler.py --port=/dev/ttyUSB0 -c my-mpg mpg.xml +loadusr classicladder test_ladder.clp --nogui +#loadusr classicladder test_ladder.clp + +### spindle at speed monitoring ### +setp spindle-at-speed.scale 0.98 +setp spindle-at-speed.difference 10 +net spindle-cmd-rpm => spindle-at-speed.in1 +#net spindle-rpm-filtered-low-res => spindle-at-speed.in2 # moved to postgui + +### temp... until encoder works... +#net spindle-ready <= spindle-at-speed.out => spindle.0.at-speed +net spindle-ready => spindle.0.at-speed +sets spindle-ready 1 +### + +### spindle cw start/stop ### +net spindle-cw <= spindle.0.forward +net spindle-cw => parport.0.pin-14-out # turn on spindle +### spindle ccw start/stop ### +net spindle-ccw <= spindle.0.reverse +net spindle-ccw => parport.0.pin-17-out # turn on spindle ccw + +### spindle speed ctrl ### +# gain and offset is set via M199/O when homed +#setp scale.1.gain 0.473 +#setp scale.1.offset 26.5 +setp siggen.0.amplitude 1 +setp siggen.0.offset 0 + +net spindle-cmd-rpm-abs => scale.1.in +net scaled-spindle-cmd scale.1.out => siggen.0.frequency +net spindle-freq siggen.0.clock => parport.0.pin-16-out + + +### e-stop chain ### +net estop-internal <= iocontrol.0.user-enable-out +#estop-external connected to parport.0.pin-11-in-not + +net estop-internal => and2.0.in0 +net estop-external => and2.0.in1 +net estop-chain and2.0.out => iocontrol.0.emc-enable-in + + +### pendant ########################################## + +# mpg spindle speed control +net spindle-rpm-cmd-up classicladder.0.out-08 halui.mdi-command-00 +net spindle-rpm-cmd-down classicladder.0.out-09 halui.mdi-command-01 + +# axis selector connects to x, z axis enable +net jog-axis-sel my-mpg.axis-selector +net jog-axis-sel classicladder.0.s32in-00 + +net jog-x-enable classicladder.0.out-00 +net jog-z-enable classicladder.0.out-01 +net jog-x-enable halui.axis.x.select +net jog-z-enable halui.axis.z.select + +# jog scale selector/velocity mode +setp scale.2.gain 0.01 +setp scale.2.offset 0 +net jog-scale-sel my-mpg.scale-selector +net jog-scale-sel classicladder.0.s32in-01 +net jog-increment-100 classicladder.0.floatout-00 scale.2.in +net jog-increment halui.axis.selected.increment scale.2.out + +# jogging plus and minus +setp halui.axis.jog-speed 800 +net mpg-jog-plus-in my-mpg.jog-pos-btn => classicladder.0.in-00 +net mpg-jog-minus-in my-mpg.jog-neg-btn => classicladder.0.in-01 + +net mpg-jog-plus-inc classicladder.0.out-04 => halui.axis.selected.increment-plus +net mpg-jog-minus-inc classicladder.0.out-06 => halui.axis.selected.increment-minus +net mpg-jog-plus classicladder.0.out-05 => halui.axis.selected.plus +net mpg-jog-minus classicladder.0.out-07 => halui.axis.selected.minus + +# connect Func-button +net func-button my-mpg.func-btn halui.mdi-command-02 + +# connect E-Stop button +net estop-button halui.estop.activate <= my-mpg.estop-btn + +# joystic +setp scale.3.gain 0.01 +setp scale.3.offset 0 +setp scale.4.gain 0.01 +setp scale.4.offset 0 +net joystick-x my-mpg.joystick-x scale.3.in +net joystick-x-scaled scale.3.out halui.axis.x.analog + +net joystick-z my-mpg.joystick-z scale.4.in +net joystick-z-scaled scale.4.out halui.axis.z.analog + +### Misc ################################################# + +# all-homed used to enable some of the panels and trigger some other stuff +net all-homed motion.is-all-homed + +# set the saved spindle speed selection gain +#setp timedelay.0.on-delay 1 +net all-homed timedelay.0.in +net all-homed-delayed timedelay.0.out +net all-homed-delayed halui.mdi-command-06 + diff --git a/my-emco-compact-5/custom_postgui.hal b/my-emco-compact-5/custom_postgui.hal new file mode 100644 index 0000000..b4383ee --- /dev/null +++ b/my-emco-compact-5/custom_postgui.hal @@ -0,0 +1,18 @@ +# Include your custom_postgui HAL commands here +# This file will not be overwritten when you run stepconf again + +# Filtering and scaling... +setp encoder.1.counter-mode true +setp encoder.1.position-scale 1 +setp scale.0.gain 60 +setp lowpass.0.gain .07 +net spindle-index encoder.1.phase-A +net spindle-vel-rps-low-res encoder.1.velocity => lowpass.0.in + +net spindle-rps-filtered-low-res lowpass.0.out scale.0.in +net spindle-rpm-filtered-low-res scale.0.out +net spindle-rpm-filtered-low-res gmoccapy.spindle_feedback_bar + +net spindle-rpm-filtered-low-res => spindle-at-speed.in2 +net spindle-ready => gmoccapy.spindle_at_speed_led + diff --git a/my-emco-compact-5/emcopos.txt b/my-emco-compact-5/emcopos.txt new file mode 100644 index 0000000..705672d --- /dev/null +++ b/my-emco-compact-5/emcopos.txt @@ -0,0 +1,16 @@ +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 +0.00000000000000000 diff --git a/my-emco-compact-5/func-btn.xml b/my-emco-compact-5/func-btn.xml new file mode 100644 index 0000000..3977d1d --- /dev/null +++ b/my-emco-compact-5/func-btn.xml @@ -0,0 +1,5 @@ + + Touch off selected axisO<mytouch>call + Return to safe ZG53 G0 Z0 + Return to homeG53 G0 X0 Y0 Z0 + \ No newline at end of file diff --git a/my-emco-compact-5/gladevcp-handler.py b/my-emco-compact-5/gladevcp-handler.py new file mode 100644 index 0000000..2a148ea --- /dev/null +++ b/my-emco-compact-5/gladevcp-handler.py @@ -0,0 +1,141 @@ +#!/usr/bin/ python3 + +import gi +gi.require_version("Gtk", "3.0") + +from gi.repository import Gtk +import linuxcnc +import hal +import hal_glib +import xml.etree.ElementTree as ET + +debug = 0 + +"""class ToolTableParser: + def __init__(self, f): + self.f = open(f, 'r') + self.list = Gtk.ListStore(int, str, str) + self._parse_file() + self.f.close() + + def __repr__(self): + ret_str = '' + for l in self.list: + ret_str += str(l) + '\n' + return ret_str + + def get_parsed_data(self): + return self.list + + def _parse_file(self): + lines = self.f.readlines() + + i = 0 + for line in lines: + tool_nbr = line.split(' ')[0] + tool_descr = tool_nbr + ' ' + line.split(';')[1] + self.list.append([i, tool_descr, tool_nbr]) + i = i+1 +""" + +class XmlParser: + def __init__(self, f): + self.tree = [] + self.list = Gtk.ListStore(int, str, str) + + self._parse_file(f) + + def get_parsed_data(self): + return self.list + + def _parse_file(self, f): + self.tree = ET.parse(f) + root = self.tree.getroot() + + i = 0 + for func in root.iter('function'): + gcode = func.find('gcode') + + # create the LinuxCNC hal pin and create mapping dictionary binding incomming events with data and the hal pins + if gcode is not None: + self.list.append([i, func.text, gcode.text]) + i = i+1 + +class HandlerClass: + + def on_destroy(self,obj,data=None): + print("on_destroy, combobox active=%d" %(self.combo.get_active())) + self.halcomp.exit() # avoid lingering HAL component + Gtk.main_quit() + + """def on_changed(self, combobox, data=None): + if self.tool_combo_initiated: + model = combobox.get_model() + tool_change_cmd = 'M6' + ' ' + model[combobox.get_active()][2] + ' ' + 'G43' + self._send_mdi(tool_change_cmd) + print(tool_change_cmd)""" + + def __init__(self, halcomp, builder, useropts): + self.linuxcnc_status = linuxcnc.stat() + self.linuxcnc_cmd = linuxcnc.command() + self.halcomp = halcomp + self.builder = builder + self.useropts = useropts + + self.trigger1 = hal_glib.GPin(halcomp.newpin('trigger_pin', hal.HAL_BIT, hal.HAL_IN)) + self.trigger1.connect('value-changed', self._trigger_change) + + #self.trigger2 = hal_glib.GPin(halcomp.newpin('saved-tool-pin', hal.HAL_U32, hal.HAL_IN)) + #self.trigger2.connect('value-changed', self._init_tool_combo) + + func_list = XmlParser('func-btn.xml').get_parsed_data() + self.func_combo = self.builder.get_object('func-btn-combo') + self.func_combo.set_model(func_list) + self.func_combo.set_entry_text_column(1) + self.func_combo.set_active(0) + + #tool_list = ToolTableParser('tool.tbl').get_parsed_data() + #self.tool_combo = self.builder.get_object('tool-combo') + #self.tool_combo.set_model(tool_list) + #self.tool_combo.set_entry_text_column(2) + #self.tool_combo.set_active(0) + + renderer_text = Gtk.CellRendererText() + self.func_combo.pack_start(renderer_text, True) + #self.tool_combo.pack_start(renderer_text, True) + #self.tool_combo_initiated = False + + def _trigger_change(self, pin, userdata = None): + #setp gladevcp.trigger_pin 1 + #print "pin value changed to: " + str(pin.get()) + #print "pin name= " + pin.get_name() + #print "pin type= " + str(pin.get_type()) + #print "active " + str(self.combo.get_active()) + if pin.get() is True: + model = self.func_combo.get_model() + self._send_mdi(model[self.func_combo.get_active()][2]) + + #def _init_tool_combo(self, pin, userdata = None): + #setp gladevcp.saved_tool_pin 2 + # self.tool_combo.set_active(pin.get()) + # self.tool_combo_initiated = True + + + def _ok_for_mdi(self): + self.linuxcnc_status.poll() + return not self.linuxcnc_status.estop and self.linuxcnc_status.enabled and (self.linuxcnc_status.homed.count(1) == self.linuxcnc_status.joints) and (self.linuxcnc_status.interp_state == linuxcnc.INTERP_IDLE) + + def _send_mdi(self, mdi_cmd_str): + if self._ok_for_mdi(): + self.linuxcnc_cmd.mode(linuxcnc.MODE_MDI) + self.linuxcnc_cmd.wait_complete() # wait until mode switch executed + self.linuxcnc_cmd.mdi(mdi_cmd_str) + +def get_handlers(halcomp, builder, useropts): + + global debug + for cmd in useropts: + exec(cmd in globals()) + + return [HandlerClass(halcomp, builder, useropts)] + diff --git a/my-emco-compact-5/linuxcnc.var b/my-emco-compact-5/linuxcnc.var new file mode 100644 index 0000000..eb5e5bf --- /dev/null +++ b/my-emco-compact-5/linuxcnc.var @@ -0,0 +1,122 @@ +4997 1.000000 +4998 0.484830 +4999 20.164660 +5161 0.000000 +5162 0.000000 +5163 0.000000 +5164 0.000000 +5165 0.000000 +5166 0.000000 +5167 0.000000 +5168 0.000000 +5169 0.000000 +5181 0.000000 +5182 0.000000 +5183 0.000000 +5184 0.000000 +5185 0.000000 +5186 0.000000 +5187 0.000000 +5188 0.000000 +5189 0.000000 +5210 0.000000 +5211 0.000000 +5212 0.000000 +5213 0.000000 +5214 0.000000 +5215 0.000000 +5216 0.000000 +5217 0.000000 +5218 0.000000 +5219 0.000000 +5220 1.000000 +5221 -8.934820 +5222 0.000000 +5223 8.596005 +5224 0.000000 +5225 0.000000 +5226 0.000000 +5227 0.000000 +5228 0.000000 +5229 0.000000 +5230 0.000000 +5241 0.000000 +5242 0.000000 +5243 0.000000 +5244 0.000000 +5245 0.000000 +5246 0.000000 +5247 0.000000 +5248 0.000000 +5249 0.000000 +5250 0.000000 +5261 0.000000 +5262 0.000000 +5263 0.000000 +5264 0.000000 +5265 0.000000 +5266 0.000000 +5267 0.000000 +5268 0.000000 +5269 0.000000 +5270 0.000000 +5281 0.000000 +5282 0.000000 +5283 0.000000 +5284 0.000000 +5285 0.000000 +5286 0.000000 +5287 0.000000 +5288 0.000000 +5289 0.000000 +5290 0.000000 +5301 0.000000 +5302 0.000000 +5303 0.000000 +5304 0.000000 +5305 0.000000 +5306 0.000000 +5307 0.000000 +5308 0.000000 +5309 0.000000 +5310 0.000000 +5321 0.000000 +5322 0.000000 +5323 0.000000 +5324 0.000000 +5325 0.000000 +5326 0.000000 +5327 0.000000 +5328 0.000000 +5329 0.000000 +5330 0.000000 +5341 0.000000 +5342 0.000000 +5343 0.000000 +5344 0.000000 +5345 0.000000 +5346 0.000000 +5347 0.000000 +5348 0.000000 +5349 0.000000 +5350 0.000000 +5361 0.000000 +5362 0.000000 +5363 0.000000 +5364 0.000000 +5365 0.000000 +5366 0.000000 +5367 0.000000 +5368 0.000000 +5369 0.000000 +5370 0.000000 +5381 0.000000 +5382 0.000000 +5383 0.000000 +5384 0.000000 +5385 0.000000 +5386 0.000000 +5387 0.000000 +5388 0.000000 +5389 0.000000 +5390 0.000000 diff --git a/my-emco-compact-5/macros/LICENSE b/my-emco-compact-5/macros/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/my-emco-compact-5/macros/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/my-emco-compact-5/macros/LatheMacro.svg b/my-emco-compact-5/macros/LatheMacro.svg new file mode 100644 index 0000000..dc974c4 --- /dev/null +++ b/my-emco-compact-5/macros/LatheMacro.svg @@ -0,0 +1,3057 @@ + +image/svg+xml diff --git a/my-emco-compact-5/macros/README b/my-emco-compact-5/macros/README new file mode 100644 index 0000000..ea8b65e --- /dev/null +++ b/my-emco-compact-5/macros/README @@ -0,0 +1,77 @@ +This folder contains a number of simulated lathe configs. + +the "lathemacro" config offers a number of simple macros to perform +the most common lathe operations. + +The GUI that controls the macros can be viewed by clicking the "Cycles" +tab top left of the graphical preview window. + +There are two load-time options to control the tab behaviour: +1) norun will hide the action button, use this if you want to use only +a physical button (connected to gladevcp.cycle-start) to start the macros +(Strongly recommended, especially with Touchy) +2) notouch will allow keyboard editing of the spinboxes. Otherwise the +custom numeric keyboard will be shown. + +An example loadrt line, as used here in the Gmoccapy demo is: + +[DISPLAY] +EMBED_TAB_COMMAND = halcmd loadusr -Wn gladevcp gladevcp -c gladevcp -U notouch=1 -U norun=0 -u lathehandler.py -x {XID} lathemacro.ui + +The window will resize slowly if you grab the corner and move the mouse +inside the window. It's not as bad as it was, but still needs work. +You may need to click the unmaximise button in the toolbar to get a +window border to be able to use the resize handles. + +To relocate the files outside of the config directory (i.e. the direcrory containing the INI file) use, for example, +"macros/lathehandeler.py" and "macros/lathemacro.ui" in the EMBED_TAB_COMMAND above. The system will look for the +LatheMacro.svg file in the same path as the lathehandler.py file. If you need to relocate or rename this file then +use "-U svgfile=/full/path/to/svgfile.svg" in the embed command. + +Notes on the keyboard: +As well as the obvious functions and unit conversions, it can be used to +enter fractions. For example if you type 1.1/2 it will automatically +update to display 1.5000 and 16.17/64 will show 16.2656. +This can be used in a limited way to halve the onscreen value eg for +entering radius instead of diameter. +However it only works for whole numbers: 100/2 will become 50 but +3.14149/2 is interpreted as 3 and 14 thousand halves so won't work. + +Notes on adding your own cycles: +Create a new G-code subroutine in the same format as the existing ones. +In Glade add a new tab to the 'tabs1' notebook and give it a name matching +the new cycle. +Edit the action button (inside an eventbox) to call the new G-code sub. +Rename the action button to match the tab name and append '.action' eg +MyCycle.action + +Create new artwork. I used Fusion360, the models are here: +https://a360.co/3uFPZNv +and the drawings are here: +https://a360.co/3uFPZNv +Esport the drawing page as PDF and import into the lathemacro.svg file in +Inkscape. You will need to resize. Add your own arrows and annotations. + +Save the new layer in a layer named "layerN" (lower case) where N is the +tab number, starting at zero. You will need to invoke the XML editor for +this (Shift-Cmd-X on Mac) + +The entry boxes are positioned relative to a 1500 x 1000 image; the +original size of the SVG. So you can hover your mouse over the image in +Inkscape to determine the coordinates. +In the in the case of on-drawing controls the coordinates are entered as +an XML comment in the Tooltip for the control in x,y format (The surface speed, +tool and coolant do not need this, they are in a fixed table) + +An example: + +', c.get_tooltip_markup()) + if len(m) > 0: + x1 = int(m[0][0]); y1 = int(m[0][1]) + c.set_margin_left(max(0, w * x1/1500)) + c.set_margin_top(max(0, h * y1/1000)) + + + # decide if our window is active to mask the cycle-start hardware button + # FIXME: This is probably not as reliable as one might wish. + def event(self,w,event): + if w.is_active(): + if w.has_toplevel_focus() : + self.active = True + else: + self.active = False + + # Capture notify events + def on_map_event(self, widget, data=None): + top = widget.get_toplevel() + top.connect('notify', self.event) + + def on_destroy(self,obj,data=None): + self.ini.save_state(self) + + def on_restore_defaults(self,button,data=None): + ''' + example callback for 'Reset to defaults' button + currently unused + ''' + self.ini.create_default_ini() + self.ini.restore_state(self) + + def __init__(self, halcomp,builder,useropts): + self.halcomp = halcomp + self.builder = builder + self.ini_filename = 'savestate.sav' + self.defaults = { IniFile.vars: dict(), + IniFile.widgets : widget_defaults(select_widgets(self.builder.get_objects(), + hal_only=False,output_only = True)) + } + self.ini = IniFile(self.ini_filename,self.defaults,self.builder) + self.ini.restore_state(self) + + # A pin to use a physical switch to start the cycle + self.cycle_start = hal_glib.GPin(halcomp.newpin('cycle-start', hal.HAL_BIT, hal.HAL_IN)) + self.cycle_start.connect('value-changed', self.cycle_pin) + + # This catches the signal from Touchy to say that the tab is exposed + t = self.builder.get_object('macrobox') + t.connect('map-event',self.on_map_event) + t.add_events(Gdk.EventMask.STRUCTURE_MASK) + + self.cmd = linuxcnc.command() + + # This connects the expose event to re-draw and scale the SVG frames + t = self.builder.get_object('tabs1') + t.connect_after("draw", self.on_expose) + t.connect("destroy", Gtk.main_quit) + t.add_events(Gdk.EventMask.STRUCTURE_MASK) + print(svgfile) + self.svg = Rsvg.Handle().new_from_file(svgfile) + self.active = True + + # handle Useropts + if norun: + for c in range(0,6): + print(c) + print( f'tab{c}.action') + self.builder.get_object(f'tab{c}.action').set_visible(False) + + def show_keyb(self, obj, data=None): + if notouch: return False + self.active_ctrl = obj + self.keyb = self.builder.get_object('keyboard') + self.entry = self.builder.get_object('entry1') + self.entry.modify_font(Pango.FontDescription("courier 42")) + self.entry.set_text("") + resp = self.keyb.run() + return True + + def keyb_prev_click(self, obj, data=None): + self.entry.set_text(self.active_ctrl.get_text()) + + def keyb_number_click(self, obj, data=None): + data = self.entry.get_text() + data = data + obj.get_label() + if any( x in data for x in [ '/2', '/4', '/8', '/16', '/32', '/64', '/128']): + v = [0] + [float(x) for x in data.replace('/','.').split('.')] + data = f'{v[-3] + v[-2]/v[-1]:6.7}' + self.entry.set_text(data) + + def keyb_pm_click(self, obj, data=None): + data = self.entry.get_text() + if data[0] == '-': + data = data[1:] + else: + data = '-' + data + self.entry.set_text(data) + + def keyb_convert_click(self, obj, data=None): + v = float(self.entry.get_text()) + op = obj.get_label() + if op == 'in->mm': + self.entry.set_text(f'{v * 25.4:6.4}') + elif op == 'mm->in': + self.entry.set_text(f'{v / 25.4:6.4}') + elif op == 'tpi->pitch': + self.entry.set_text(f'{25.4 / v:6.4}') + elif op == 'pitch->tpi': + self.entry.set_text(f'{25.4 / v:6.4}') + + def keyb_del_click(self, obj, data=None): + data = self.entry.get_text() + data = data[:-1] + self.entry.set_text(data) + + def keyb_clear_click(self, obj, data=None): + self.entry.set_text('') + + def keyb_cancel_click(self, obj, data=None): + self.keyb.hide() + + def keyb_ok_click(self, obj, data=None): + if self.entry.get_text() != '': + self.active_ctrl.set_value(float(self.entry.get_text())) + self.keyb.hide() + + def set_alpha(self, obj, data = None): + cr = obj.get_property('window').cairo_create() + cr.set_source_rgba(1.0, 1.0, 1.0, 0.0) + + def cycle_pin(self, pin, data = None): + if pin.get() == 0: + return + if self.active: + nb = self.builder.get_object('tabs1') + print('current tab', nb.get_current_page()) + c = self.builder.get_object(f"tab{nb.get_current_page()}.action") + if c is not None: + self.cmd.abort() + self.cmd.mode(linuxcnc.MODE_MDI) + self.cmd.wait_complete() + c.emit('clicked') + print(c.get_name(), "clicked") + + def testing(self, obj, data = None): + print('event', data) + +def get_handlers(halcomp,builder,useropts): + + global debug + for cmd in useropts: + print(cmd) + exec(cmd, globals()) + + set_debug(debug) + return [HandlerClass(halcomp,builder,useropts)] + + + diff --git a/my-emco-compact-5/macros/lathemacro.ui b/my-emco-compact-5/macros/lathemacro.ui new file mode 100644 index 0000000..f9261e8 --- /dev/null +++ b/my-emco-compact-5/macros/lathemacro.ui @@ -0,0 +1,2937 @@ + + + + + + + -89 + 89 + 1 + 10 + + + 100 + 1 + 10 + + + 6 + 0.14999999999999999 + 0.029999999999999999 + + + 100 + 10 + + + 200 + 100 + 10 + 10 + + + 100 + 1 + 1 + 10 + + + -1000 + 1000 + 10 + + + -1000 + 1000 + 1 + 10 + + + 250 + 1 + 0.25 + 10 + + + -20 + 250 + 1 + 10 + + + 200 + 100 + 10 + 10 + + + 100 + 1 + 1 + 10 + + + -2000 + 2000 + 1 + + + -2000 + 2000 + 10 + 1 + + + 2 + 0.050000000000000003 + 0.029999999999999999 + + + 2 + 50 + 2 + 0.5 + + + 200 + 100 + 10 + 10 + + + 100 + 1 + 1 + 10 + + + -2000 + 2000 + 1 + + + -89 + 89 + 1 + 10 + + + 20 + 1 + 0.050000000000000003 + 10 + + + 6 + 0.14999999999999999 + 0.02 + 10 + + + 100 + 250 + 10 + 10 + + + 100 + 1 + 1 + 10 + + + -1000 + 1000 + 1 + 10 + + + -1000 + 1000 + 1 + 10 + + + 2 + 0.029999999999999999 + 0.029999999999999999 + + + 200 + 100 + 10 + 10 + + + 100 + 9 + 1 + 10 + + + -2000 + 2000 + 1 + + + 250 + 1 + 0.25 + 10 + + + -20 + 250 + 1 + 10 + + + 200 + 100 + 10 + 10 + + + 100 + 1 + 1 + 10 + + + -2000 + 2000 + 1 + + + 25 + 1 + 0.25 + + + 200 + 50 + 10 + 10 + + + 100 + 5 + 1 + 10 + + + -2000 + 2000 + 1 + + + -2000 + 2000 + 1 + + + -90 + 90 + 1 + 10 + + + 3 + 1 + 0.01 + 10 + + + 2 + 0.14999999999999999 + 0.029999999999999999 + + + 100 + 1 + 10 + + + 20 + 500 + 100 + 10 + 10 + + + 100 + 1 + 1 + 10 + + + -20 + 250 + 15 + 1 + 10 + + + -2000 + 2000 + 1 + + + gtk-media-play + O<boring> call [${bore.x-f}] [${bore.sf-f}] [${bore.cut-f}] [${bore.feed-f}] [${bore.z-f}] [${bore.rad-f}] [${bore.angle-f}] [${bore.tool-s}] [${bore.coolant}] + + + Set Tool + M61 Q${bore.tool-s} G43 H${bore.tool-s} + + + O<chamfer>call [${chamfer.x-f}] [${chamfer.sf-f}] [0.5] [0] [${chamfer.z-f}] [${chamfer.tool-s}] [0] [${chamfer.size-f}] [${chamfer.fo}] [${chamfer.fi}] [${chamfer.bo}] [${chamfer.coolant}] + + + M61 Q${chamfer.tool-s} G43 H${chamfer.tool-s} + + + O<drilling> call [${drill.dia-f}] [${drill.sf-f}] [${drill.feed-f}] [${drill.z-f}] [${drill.peck-f}] [${drill.tool-s}] [${drill.coolant}] + + + Set Tool + Set Tool + M61 Q${drill.tool-s} G43 H${drill.tool-s} + + + O<facing>call [${face.x-f}] [${face.sf-f}] [${face.cut-f}] [${face.feed-f}] [${face.z-f}] [${face.angle-s}] [${face.tool-s}] [${face.coolant}] + + + Set Tool + M61 Q${face.tool-s} G43 H${face.tool-s} + + + O<grooving> call [${groove.x-f}] [${groove.sf-f}] [${groove.feed-f}] [${groove.tool-s}] [${groove.coolant}] + + + Set Tool + Set Tool + M61 Q${groove.tool-s} G43 H${groove.tool-s} + + + False + 5 + dialog + + + + + + True + False + 2 + + + True + False + end + + + OK + 100 + 50 + True + True + True + + + + False + False + 0 + + + + + Cancel + 100 + 50 + True + True + True + + + + False + False + 1 + + + + + False + True + end + 0 + + + + + 60 + True + True + + False + False + + + False + False + 1 + + + + + True + False + 5 + 4 + + + 1 + 50 + 50 + True + True + True + + + + + + 2 + 50 + 20 + True + True + True + + + + 1 + 2 + + + + + 3 + 50 + 20 + True + True + True + + + + 2 + 3 + + + + + DEL + 50 + 20 + True + True + True + + + + 3 + 4 + + + + + 4 + 50 + 50 + True + True + True + + + + 1 + 2 + + + + + 5 + 50 + 20 + True + True + True + + + + 1 + 2 + 1 + 2 + + + + + 6 + 50 + 20 + True + True + True + + + + 2 + 3 + 1 + 2 + + + + + CLR + 50 + 20 + True + True + True + + + + 3 + 4 + 1 + 2 + + + + + 7 + 50 + 50 + True + True + True + + + + 2 + 3 + + + + + 8 + 50 + 20 + True + True + True + + + + 1 + 2 + 2 + 3 + + + + + 9 + 50 + 20 + True + True + True + + + + 2 + 3 + 2 + 3 + + + + + PREV + 50 + 20 + True + True + True + + + + 3 + 4 + 2 + 3 + + + + + +/- + 50 + 50 + True + True + True + + + + 3 + 4 + + + + + 0 + 50 + 20 + True + True + True + + + + 1 + 2 + 3 + 4 + + + + + . + 50 + 20 + True + True + True + + + + 2 + 3 + 3 + 4 + + + + + / + 50 + 20 + True + True + True + + + + 3 + 4 + 3 + 4 + + + + + in->mm + 80 + 40 + True + True + True + + + + 4 + 5 + 4 + 4 + + + + + mm->in + 80 + 40 + True + True + True + + + + 1 + 2 + 4 + 5 + 4 + 4 + + + + + pitch->tpi + 80 + 40 + True + True + True + + + + 2 + 3 + 4 + 5 + 4 + 4 + + + + + tpi->pitch + 80 + 40 + True + True + True + + + + 3 + 4 + 4 + 5 + 4 + 4 + + + + + True + True + 2 + + + + + + button23 + button24 + + + + O<radius> call [${radius.x-f}] [${radius.sf-f}] [0.5] [0][${radius.z-f}] [${radius.tool-s}] [0] [${radius.rad-f}] [${radius.fo}] [${radius.fi}] [${radius.bo}] [${radius.coolant}] + + + Set Tool + M61 Q${radius.tool-s} G43 H${radius.tool-s} + + + O<threading>call [${thread.x-f}] [${thread.sf-f}] [${thread.tool-s}] [${thread.pitch-f}] [${thread.z-f}] [${thread.internal}] [${thread.external}] [${thread.coolant}] + + + M61 Q${thread.tool-s} G43 H${thread.tool-s} + + + O<turning> call [${turn.x-f}] [${turn.sf-f}] [${turn.cut-f}] [${turn.feed-f}] [${turn.z-f}] [${turn.rad-f}] [${turn.angle-f}] [${turn.tool-s}] [${turn.coolant}] + + + Set Tool + M61 Q${turn.tool-s} G43 H${turn.tool-s} + + + False + GDK_EXPOSURE_MASK | GDK_STRUCTURE_MASK + 200 + 100 + False + + + + + + True + False + + False + + + True + True + GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_FOCUS_CHANGE_MASK | GDK_STRUCTURE_MASK | GDK_PROPERTY_CHANGE_MASK | GDK_VISIBILITY_NOTIFY_MASK | GDK_PROXIMITY_IN_MASK | GDK_PROXIMITY_OUT_MASK | GDK_SUBSTRUCTURE_MASK | GDK_SCROLL_MASK + left + + + + True + False + + + True + False + gtk-missing-image + + + -1 + + + + + 50 + 20 + True + True + <!--1000,654-->Finish Diameter + start + start + + 7 + 0.000 + False + False + False + TurnXAdj + 4 + True + + + + + + 50 + 20 + True + True + <!--635,687-->Feed per Rev + start + start + + 5 + 0.000 + False + False + TurnFeedAdj + 3 + True + + + + 1 + + + + + 50 + 20 + True + True + <!--800,230-->End Radius + start + start + + 5 + 0.000 + False + False + TurnRadAdj + 4 + True + + + + 2 + + + + + 50 + 20 + True + True + <!--821,863-->Cut per pass + start + start + + 5 + 0.000 + False + False + TurnCutAdj + 3 + True + + + + 3 + + + + + 50 + 20 + True + True + <!--125,424-->Finish Z + start + start + + 7 + 0.000 + False + False + TurnZAdj + 4 + True + + + + 4 + + + + + 50 + 20 + True + True + <!--520,474-->Taper angle + start + start + + 5 + 0.0000 + False + False + False + TurnAngAdj + 3 + True + + + + 5 + + + + + 100 + 40 + True + False + end + end + + + gtk-media-play + False + turn.go + 50 + 20 + True + True + True + True + + + + + 10 + + + + + True + False + end + start + + + 50 + 20 + True + True + + 0 + False + False + TurnToolAdj + + + + 1 + 0 + + + + + Set Tool + turn.toolchange + 50 + 20 + True + True + True + True + + + 0 + 0 + + + + + True + False + center + Speed + 1 + + + 0 + 1 + + + + + 50 + 20 + True + True + end + start + + 5 + 20 + False + False + TurnSFAdj + 20 + + + + 1 + 1 + + + + + Coolant + True + True + False + True + + + 1 + 2 + + + + + + + + 11 + + + + + False + + + + + True + False + Turning + + + False + + + + + True + False + + + True + False + gtk-missing-image + + + -1 + + + + + 50 + 20 + True + True + <!--700,280-->Taper Angle + start + start + + 5 + 0.0000 + False + False + BoreAngleAdj + 3 + + + + + + 50 + 20 + True + True + <!--421,520-->Run-out radius + start + start + + 5 + 0.000 + False + False + BoreRadAdj + 4 + + + + 1 + + + + + 50 + 20 + True + True + <!--1080,616-->Finish Diameter + start + start + + 7 + 0.000 + False + False + BoreXAdj + 4 + + + + 2 + + + + + 50 + 20 + True + True + <!--900,825-->Diameter Increment + start + start + + 5 + 0.000 + False + False + BoreCutAdj + 4 + + + + 3 + + + + + 50 + 20 + True + True + <!--530,820-->Feed per rev + start + start + + 5 + 0.000 + False + False + BoreFeedAdj + 4 + + + + 4 + + + + + 100 + 40 + True + False + end + end + + + gtk-media-play + False + bore.go + 50 + 20 + True + True + True + True + True + + + + + 7 + + + + + 50 + 20 + True + True + <!--273,267-->Finish Z + start + start + + 7 + 0.000 + False + False + BoreZAdj + 4 + + + + 9 + + + + + True + False + end + start + + + 50 + 20 + True + True + end + start + + 5 + 0 + False + False + BoreSFAdj + + + + 1 + 1 + + + + + 50 + 20 + True + True + + 0 + False + False + BoreToolAdj + + + + 1 + 0 + + + + + Set Tool + bore.toolchange + 50 + 20 + True + True + True + True + + + 0 + 0 + + + + + True + False + center + Speed + 1 + + + 0 + 1 + + + + + Coolant + True + True + False + True + + + 1 + 2 + + + + + + + + 11 + + + + + 1 + False + + + + + True + False + Boring + + + 1 + False + + + + + True + False + + + True + False + gtk-missing-image + + + -1 + + + + + 50 + 20 + True + True + <!--156,404-->Finish Z + start + start + + 7 + 0.000 + False + False + FaceZAdj + 4 + + + + 2 + + + + + 50 + 20 + True + True + <!--1115,626-->Finish Diameter + start + start + + 7 + 0.000 + False + False + FaceXAdj + 4 + + + + 3 + + + + + 50 + 20 + True + True + <!--876,886-->Feed per Rev + start + start + + 5 + 0.0000 + False + False + FaceFeedAdj + 3 + + + + 4 + + + + + 50 + 20 + True + True + <!--408,760-->Cut per pass + start + start + + 5 + 0.000 + False + False + FaceCutAdj + 3 + + + + 5 + + + + + 50 + 20 + True + True + <!--1044,364-->Face Angle + start + start + + 5 + 0.0000 + False + False + FaceAngleAdj + 3 + + + + 6 + + + + + 100 + 40 + True + False + end + end + False + + + gtk-media-play + False + face.go + 50 + 20 + True + True + True + True + True + + + + + 9 + + + + + True + False + end + start + + + 50 + 20 + True + True + end + start + + 5 + 0 + False + False + FaceSFAdj + + + + 1 + 1 + + + + + 50 + 20 + True + True + + 0 + False + False + FaceToolAdj + + + + 1 + 0 + + + + + Set Tool + face.toolchange + 50 + 20 + True + True + True + True + + + 0 + 0 + + + + + True + False + center + Speed + 1 + + + 0 + 1 + + + + + Coolant + True + True + False + True + + + 1 + 2 + + + + + + + + 10 + + + + + Face + 2 + False + + + + + True + False + Facing + + + 2 + False + + + + + True + False + + + True + False + gtk-missing-image + + + -1 + + + + + 50 + 20 + True + True + <!--1018,674-->Radius Inner or Outer Diameter + start + start + + 7 + 0.000 + False + False + RadXAdj + 4 + + + + + + 50 + 20 + True + True + <!--202,334-->Radius Z position + start + start + + 7 + 0.000 + False + False + RadiusZAdj + 4 + + + + 1 + + + + + 50 + 20 + True + True + <!--848,451-->Radius + start + start + + 5 + 0.000 + False + False + RadRadAdj + 3 + + + + 2 + + + + + 100 + 40 + True + False + <!--863,629-->Front Inside Radius + start + start + + + 50 + 20 + True + True + False + True + True + + + + + 3 + + + + + 100 + 40 + True + False + <!--763,680-->Front Outside Radius + start + start + + + 50 + 20 + True + True + False + True + radius.fi + + + + + 4 + + + + + 100 + 40 + True + False + <!--335,448-->Back Outside Radius + start + start + + + 50 + 20 + True + True + False + True + radius.fi + + + + + 5 + + + + + 100 + 40 + True + False + end + end + + + radius.go + 50 + 20 + True + True + True + True + + + True + False + gtk-media-play + + + + + + + 10 + + + + + True + False + end + start + + + 50 + 20 + True + True + end + start + + 5 + 0 + False + False + RadiusSFAdj + + + + 1 + 1 + + + + + 50 + 20 + True + True + + 5 + 0 + False + False + RadiusToolAdj + + + + 1 + 0 + + + + + Set Tool + radius.toolchange + 50 + 20 + True + True + True + True + + + 0 + 0 + + + + + True + False + center + Speed + 1 + + + 0 + 1 + + + + + Coolant + True + True + False + True + + + 1 + 2 + + + + + + + + 11 + + + + + Radius + 3 + False + + + + + True + False + Radius + + + 3 + False + + + + + True + False + + + True + False + gtk-missing-image + + + -1 + + + + + 50 + 20 + True + True + <!--202,334-->Chamfer Z position + start + start + + 7 + 0.000 + False + False + ChamferZAdj + 4 + + + + + + 50 + 20 + True + True + <!--1112,287-->Chamfer size + start + start + + 5 + 0.0000 + False + False + ChamChamAdj + 3 + + + + 1 + + + + + 50 + 20 + True + True + <!--1033,678-->Chamfer Diametric position + start + start + + 7 + 0.000 + False + False + ChamXAdj + 4 + + + + 2 + + + + + 100 + 40 + True + False + <!--335,448-->Back outside chamfer + start + start + + + 50 + 20 + True + True + False + <!--335,448-->Chamfer Z position + True + True + + + + + 3 + + + + + 100 + 40 + True + False + <!--763,680-->Front Outside Chamfer + start + start + + + 50 + 20 + True + True + False + True + chamfer.bo + + + + + 4 + + + + + 100 + 40 + True + False + <!--863,629-->Front Inside Chamfer + start + start + + + 50 + 16 + True + True + False + 0 + True + chamfer.bo + + + + + 5 + + + + + 100 + 40 + True + False + end + end + + + False + chamfer.go + 50 + 20 + True + True + True + True + + + True + False + gtk-media-play + + + + + + + 10 + + + + + True + False + end + start + + + True + False + center + Speed + 1 + + + 0 + 1 + + + + + Coolant + True + True + False + True + + + 1 + 2 + + + + + Set Tool + False + chamfer.toolchange + 50 + 20 + True + True + True + True + + + 0 + 0 + + + + + 50 + 20 + True + True + start + start + + 5 + 0 + False + False + ChamferSFAdj + + + + 1 + 1 + + + + + 50 + 20 + True + True + start + start + + 0 + False + False + ChamferToolAdj + + + + 1 + 0 + + + + + + + + 11 + + + + + Chamfer + 4 + False + + + + + True + False + Chamfer + + + 4 + False + + + + + True + False + + + True + False + gtk-missing-image + + + -1 + + + + + 50 + 20 + True + True + <!--268,341-->Finish Z position + start + start + + 7 + 0.000 + False + False + ThreadZAdj + 4 + + + + + + 50 + 20 + True + True + <!--1010,753-->Radius Z position + start + start + + 7 + 0.000 + False + False + ThreadXAdj + 4 + + + + 1 + + + + + 50 + 20 + True + False + <!--652,737-->External Thread + start + start + + + True + True + False + True + True + + + + + 2 + + + + + 50 + 20 + True + True + <!--192,616-->Radius Z position + start + start + + 7 + 0.0000 + False + False + ThreadPitchAdj + 4 + + + + 4 + + + + + 50 + 20 + True + False + <!--754,603-->Internal Thread + start + start + + + True + True + False + True + thread.external + + + + + 6 + + + + + 50 + 20 + True + False + end + end + + + False + thread.go + 50 + 20 + True + True + True + True + + + True + False + gtk-media-play + + + + + + + 7 + + + + + True + False + end + start + + + True + False + center + Speed + 1 + + + 0 + 1 + + + + + Coolant + True + True + False + True + + + 1 + 2 + + + + + 50 + 20 + True + True + start + start + + 5 + 20 + False + False + ThreadSFAdj + 20 + + + + 1 + 1 + + + + + Set Tool + False + thread.toolchange + 50 + 20 + True + True + True + True + + + 0 + 0 + + + + + 50 + 20 + True + True + start + start + + 0 + False + False + ThreadToolAdj + + + + 1 + 0 + + + + + + + + 9 + + + + + 5 + False + + + + + True + False + Thread + + + 5 + False + + + + + True + False + + + True + False + gtk-missing-image + + + -1 + + + + + 100 + 40 + True + False + end + end + + + False + groove.go + 50 + 20 + True + True + True + True + + + True + False + gtk-media-play + + + + + + + 4 + + + + + 50 + 20 + True + True + <!--1084,672-->Finish Diameter + start + start + + 7 + 0.000 + False + False + GrooveXAdj + 4 + + + + 5 + + + + + True + False + end + start + + + 50 + 20 + True + True + + 0 + False + False + GrooveToolAdj + + + + 1 + 0 + + + + + Set Tool + groove.toolchange + 50 + 20 + True + True + True + True + + + 0 + 0 + + + + + True + False + center + Speed + 1 + + + 0 + 1 + + + + + Coolant + True + True + False + True + + + 1 + 2 + + + + + 50 + 20 + True + True + end + start + + 5 + 20 + False + False + GrooveSFAdj + 20 + + + + 1 + 1 + + + + + + + + 8 + + + + + 50 + 20 + True + True + <!--648,864-->Feed per rev + start + start + + 5 + 0.0000 + False + False + GrooveFeedAdj + 3 + + + + 4 + + + + + 6 + + + + + True + False + Groove + + + 6 + False + + + + + True + False + + + True + False + gtk-missing-image + + + -1 + + + + + 100 + 40 + True + False + end + end + + + False + drill.go + 50 + 20 + True + True + True + True + + + True + False + gtk-media-play + + + + + + + 3 + + + + + True + False + end + start + + + 50 + 20 + True + True + end + start + + 5 + 0 + False + False + DrillSFAdj + + + + 1 + 1 + + + + + 50 + 20 + True + True + + 0 + False + False + DrillToolAdj + + + + 1 + 0 + + + + + Set Tool + drill.toolchange + 50 + 20 + True + True + True + True + + + 0 + 0 + + + + + True + False + center + Speed + 1 + + + 0 + 1 + + + + + Coolant + True + True + False + True + + + 1 + 2 + + + + + + + + 3 + + + + + 50 + 20 + True + True + <!--770,900-->Feed per Rev + start + start + + 5 + 0.0000 + False + False + DrillFeedAdj + 3 + + + + 2 + + + + + 50 + 20 + True + True + <!--260,250-->Finish Z + start + start + + 7 + 0.000 + False + False + DrillZAdj + 4 + + + + 3 + + + + + 50 + 20 + True + True + <!--1200,270-->Drill Diameter + start + start + + 7 + 0.000 + False + False + DrillDiaAdj + 4 + + + + 4 + + + + + 50 + 20 + True + True + <!--300,711-->Peck distance + start + start + + 5 + 0.000 + False + False + DrillPeckAdj + 0.25 + 3 + 2 + + + + 5 + + + + + 7 + + + + + True + False + Drill + + + 7 + False + + + + + + + + diff --git a/my-emco-compact-5/macros/radius.ngc b/my-emco-compact-5/macros/radius.ngc new file mode 100644 index 0000000..4ba6e52 --- /dev/null +++ b/my-emco-compact-5/macros/radius.ngc @@ -0,0 +1,76 @@ +;radius + +O sub + +G8 ; Lathe radius Mode +G18 ; XZ Plane +G21 ; Metric Units +G90 ; Absolute Distance + + +M6 T#6 G43 + +#1 = [#1 / 2] ; because of radius mode +#14 = [#<_x>] (starting X) +#13 = [#<_z>] (starting Z) + +G96 D2400 S#2 ; Constant Surface Speed Mode +M3 +g95 F0.1 ; Feed-Per-Rev Mode + +O90 IF [#12 GT 0.5] + M8 +O90 ENDIF + +#20 = 0 +O101 if [#9 GT 0.5] ; Front outside + o100 while [[#20 + #3] lt #8] + #20 = [#20 + #3] + g0 x[#1 - #20] z#13 + g1 z#5 + g3 x#1 z[#5 - #20] K[-#20] + g1 x #14 + g0 z#13 + o100 endwhile + g0 x#14 z#13 + g0 x[#1 - #8] + g1 z#5 + g3 x#1 z[#5 - #8] K[-#8] + g1 x #14 + g0 z#13 +O101 elseif [#10 GT 0.5] ; front inside + o102 while [[#20 + #3] lt #8] + #20 = [#20 + #3] + g0 x[#1 + #20] z#13 + g1 z#5 + g2 x#1 z[#5 - #20] K[-#20] + g1 x #14 + g0 z#13 + o102 endwhile + g0 x#14 z#13 + g0 x[#1 + #8] + g1 z#5 + g2 x#1 z[#5 - #8] K[-#8] + g1 x #14 + g0 z#13 +O101 elseif [#11 GT 0.5] ; back outside + o103 while [[#20 + #3] lt #8] + #20 = [#20 + #3] + g0 x[#1 - #20] z#13 + g1 z#5 + g2 x#1 z[#5 + #20] K#20 + g1 x #14 + g0 z#13 + o103 endwhile + g0 x#14 z#13 + g0 x[#1 - #8] + g1 z#5 + g2 x#1 z[#5 + #8] K#8 + g1 x #14 + g0 z#13 +O101 endif +M5 M9 +G7 +O endsub +m2 +% diff --git a/my-emco-compact-5/macros/threading.ngc b/my-emco-compact-5/macros/threading.ngc new file mode 100644 index 0000000..71c05ef --- /dev/null +++ b/my-emco-compact-5/macros/threading.ngc @@ -0,0 +1,59 @@ +;threading + +O sub + +G7 ; Lathe Diameter Mode +G18 ; XZ Plane +G21 ; Metric Units +G90 ; Absolute Distance + + +M6 T#3 G43 + +#14 = [#<_x> * 2] (starting X) +#13 = #<_z> (starting Z) + +G96 D200 S#2 ; Constant Surface Speed Mode +M3 +g95 F0.25 ; Feed-Per-Rev Mode + +O90 IF [#8 GT 0.5] + M8 +O90 ENDIF + +g4p1 ; Wait to reach speed + +;Threading + O51 IF [#6 GT 0.5] + # = [#1] + # = [#1 - 1.3 * #4] + ;g1X [# - 1] ;thread truncation + ;g0 Z #13 + ;g1 X # + ;g1 Z #5 + G0 X[# - 1] + g0 Z #13 + #3 = [#4 * 1.3] + (debug, INTERNAL Threading thread dia-#1 start-#13 finish-#5 Pitch-#4 Depth-#3) + g1X [# - 1] + g76 p#4 z#5 i1 j1 k#3 h3 r1.5 q29.5 e0 l0 + + O51 ELSE + # = [#1 - 0.108 * #4] + # = [#1 - 1.0825 * #4] + (debug, EXTERNAL Threading OD = # ID = #) + #3 = [#4 * 1.0825] + g1X [# + 1] ;final thread truncation + g0 z#13 + g1 X # + g1 Z #5 + G0 X[# +1] + G0 Z #13 + g76 p#4 z#5 i-1 j1 k#3 h3 r1.5 q29.5 e0 l0 + + O51 ENDIF + G0 Z #13 + m5 M9 +O endsub + +M2 diff --git a/my-emco-compact-5/macros/turning.ngc b/my-emco-compact-5/macros/turning.ngc new file mode 100644 index 0000000..8dc3ec0 --- /dev/null +++ b/my-emco-compact-5/macros/turning.ngc @@ -0,0 +1,66 @@ +;Turning + +O sub + +G8 ; Radius mode (easier maths) +G18 ; XZ Plane +G21 ; Metric Units +G90 ; Absolute Distance +G91.1 ; but not for arcs + +M6 T#8 G43 + +#1 = [#1 / 2] ; because of radius mode +#14 = [#<_x>] (starting X) +#13 = #<_z> (starting Z) + +#20 = [#6 * SIN[#7]] +#21 = [#6 * COS[#7]] +#22 = [#6 / COS[#7]] +#23 = [#5 + #6 - #20] +#24 = [[#13 - #23] * TAN[#7]] + +G96 D2500 S#2 ; Constant Surface Speed Mode +m3 ;Start Spindle +g95 F#4 ; Feed-Per-Rev Mode + +O90 IF [#9 GT 0.5] + M8 +O90 ENDIF +g4p1 ; Wait to reach speed + + O100 WHILE [#14 GT [#1 + #3 / 2]] + g0 X #14 + #14=[#14-#3 / 2] + G1 X #14 + G1 Z #23 X[#14 + #24] + O101 IF [#6 GT 0] + G2 Z#5 X[#14 + #24 + #21] I#21 K#20 + G1 X[#14 + #24 + #21 + #3/2] + O101 ELSE + G1 X[#14 + #24 + [#3 * .6]] + O101 ENDIF + O104 IF [#7 LT 0] + G0 X#14 + O104 ENDIF + G0 Z[#13] + O100 ENDWHILE + + G0 x#1 + G1 Z #23 X[#1 + #24] + O102 IF [#6 GT 0] + G2 Z#5 X[#1 + #24 + #21] I#21 K#20 + G1 X[#1 + #24 + #21 + #3] + O102 ELSE + G1 X[#1 + #24 + #3] + O102 ENDIF + O106 IF [#7 LT 0] + G0 X#14 + O106 ENDIF + M9 + G0 Z #13 + G0 X #1 ; For touch-off + M5 + G7 +O endsub +M2 diff --git a/my-emco-compact-5/mpg.glade b/my-emco-compact-5/mpg.glade new file mode 100644 index 0000000..2ab8ef9 --- /dev/null +++ b/my-emco-compact-5/mpg.glade @@ -0,0 +1,82 @@ + + + + + + + Goto Machine Zero + G53 G0 X0 Y0 Z0 + + + G53 G0 Z88 + + + False + + + + + + True + False + 2 + + + True + False + 0 + + + True + False + 35 + + + True + False + Func Button: + + + 0 + 0 + + + + + True + False + 0 + 0 + + + + 1 + + + + + 1 + 0 + + + + + + + True + False + <b>MPG</b> + True + + + + + False + False + 0 + + + + + + diff --git a/my-emco-compact-5/mpg.hal b/my-emco-compact-5/mpg.hal new file mode 100644 index 0000000..9323893 --- /dev/null +++ b/my-emco-compact-5/mpg.hal @@ -0,0 +1 @@ +net all-homed mpg.mpg \ No newline at end of file diff --git a/my-emco-compact-5/mpg.xml b/my-emco-compact-5/mpg.xml new file mode 100644 index 0000000..364bbc0 --- /dev/null +++ b/my-emco-compact-5/mpg.xml @@ -0,0 +1,10 @@ + + "axis-selector"selas32 + "scale-selector"selss32 + "jog-pos-btn"jposbit + "jog-neg-btn"jnegbit + "estop-btn"estbit + "func-btn"funcbit + "joystick-x"xfloat + "joystick-z"zfloat + diff --git a/my-emco-compact-5/my-emco-compact-5.hal b/my-emco-compact-5/my-emco-compact-5.hal new file mode 100644 index 0000000..c67afd2 --- /dev/null +++ b/my-emco-compact-5/my-emco-compact-5.hal @@ -0,0 +1,101 @@ +# Generated by stepconf 1.1 at Mon Mar 14 20:22:34 2022 +# If you make changes to this file, they will be +# overwritten when you run stepconf again +loadrt [KINS]KINEMATICS +loadrt [EMCMOT]EMCMOT base_period_nsec=[EMCMOT]BASE_PERIOD servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS +loadrt encoder num_chan=2 # 0 used for native speed sense, 1 used for testing new encoder +loadrt hal_parport cfg="0 out" +setp parport.0.reset-time 5000 +loadrt stepgen step_type=6,6 +loadrt lowpass +loadrt scale count=5 # 0 used to convert spindle speed from rps to rpm, + # 1 used in cusom.hal to adjust spindle set-speed fequency + # 2 used in cusom.hal for mpg scale/increment selection + # 3 & 4 used in custom.hal for mgp joystic + +addf parport.0.read base-thread +addf encoder.update-counters base-thread +addf stepgen.make-pulses base-thread +addf parport.0.write base-thread +addf parport.0.reset base-thread + +addf stepgen.capture-position servo-thread +addf encoder.capture-position servo-thread +addf motion-command-handler servo-thread +addf motion-controller servo-thread +addf lowpass.0 servo-thread +addf scale.0 servo-thread +addf stepgen.update-freq servo-thread + +net spindle-cmd-rpm <= spindle.0.speed-out +net spindle-cmd-rpm-abs <= spindle.0.speed-out-abs +net spindle-cmd-rps <= spindle.0.speed-out-rps +net spindle-cmd-rps-abs <= spindle.0.speed-out-rps-abs + +net xphasea => parport.0.pin-02-out +net xphaseb => parport.0.pin-03-out +net xphasec => parport.0.pin-04-out +net xphased => parport.0.pin-05-out + +net zphasea => parport.0.pin-06-out +net zphaseb => parport.0.pin-07-out +net zphasec => parport.0.pin-08-out +net zphased => parport.0.pin-09-out + +setp parport.0.pin-01-out-invert 1 +setp parport.0.pin-01-out-reset true +setp parport.0.pin-01-out true # 74ls374 clock pin + +# spindle encoder stuff +setp encoder.0.counter-mode true +setp encoder.0.position-scale 100 +net spindle-index parport.0.pin-12-in-not +net spindle-ppr parport.0.pin-10-in-not +net spindle-ppr encoder.0.phase-A +net spindle-index encoder.0.phase-Z +net spindle-pos spindle.0.revs encoder.0.position-interpolated +net spindle-index-enable spindle.0.index-enable encoder.0.index-enable +net spindle-vel encoder.0.velocity spindle.0.speed-in + +# Filtering and scaling... moved to custom_postgui +#setp scale.0.gain 60 +#setp lowpass.0.gain .07 +#net spindle-vel lowpass.0.in +#net spindle-rps-filtered lowpass.0.out scale.0.in +#net spindle-rpm-filtered scale.0.out + +net estop-external <= parport.0.pin-11-in-not + +setp stepgen.0.position-scale [JOINT_0]SCALE +setp stepgen.0.steplen 20000 +setp stepgen.0.dirdelay 20000 +setp stepgen.0.maxaccel [JOINT_0]STEPGEN_MAXACCEL +net xpos-cmd joint.0.motor-pos-cmd => stepgen.0.position-cmd +net xpos-fb stepgen.0.position-fb => joint.0.motor-pos-fb +net xphasea <= stepgen.0.phase-A +net xphaseb <= stepgen.0.phase-B +net xphasec <= stepgen.0.phase-C +net xphased <= stepgen.0.phase-D +net xenable joint.0.amp-enable-out => stepgen.0.enable + +setp stepgen.1.position-scale [JOINT_1]SCALE +setp stepgen.1.steplen 20000 +setp stepgen.1.dirdelay 20000 +setp stepgen.1.maxaccel [JOINT_1]STEPGEN_MAXACCEL +net zpos-cmd joint.1.motor-pos-cmd => stepgen.1.position-cmd +net zpos-fb stepgen.1.position-fb => joint.1.motor-pos-fb +net zphasea <= stepgen.1.phase-A +net zphaseb <= stepgen.1.phase-B +net zphasec <= stepgen.1.phase-C +net zphased <= stepgen.1.phase-D +net zenable joint.1.amp-enable-out => stepgen.1.enable + +# moved to custom.hal +#net estop-out <= iocontrol.0.user-enable-out +#net estop-ext => iocontrol.0.emc-enable-in + +loadusr -W hal_manualtoolchange +net tool-change iocontrol.0.tool-change => hal_manualtoolchange.change +net tool-changed iocontrol.0.tool-changed <= hal_manualtoolchange.changed +net tool-number iocontrol.0.tool-prep-number => hal_manualtoolchange.number +net tool-prepare-loopback iocontrol.0.tool-prepare => iocontrol.0.tool-prepared diff --git a/my-emco-compact-5/my-emco-compact-5.ini b/my-emco-compact-5/my-emco-compact-5.ini new file mode 100644 index 0000000..ae5213c --- /dev/null +++ b/my-emco-compact-5/my-emco-compact-5.ini @@ -0,0 +1,169 @@ +# Generated by stepconf 1.1 at Mon Mar 14 20:22:34 2022 +# If you make changes to this file, they will be +# overwritten when you run stepconf again + +[EMC] +MACHINE = my-emco-compact-5 +DEBUG = 0 +VERSION = 1.1 + +[DISPLAY] +DISPLAY = gmoccapy +EDITOR = gedit +POSITION_OFFSET = RELATIVE +POSITION_FEEDBACK = ACTUAL +ARCDIVISION = 64 +GRIDS = 10mm 20mm 50mm 100mm 1in 2in 5in 10in +MAX_FEED_OVERRIDE = 1.2 +MIN_SPINDLE_OVERRIDE = 0.5 +MAX_SPINDLE_OVERRIDE = 1.2 +DEFAULT_LINEAR_VELOCITY = 1.25 +MIN_LINEAR_VELOCITY = 0 +MAX_LINEAR_VELOCITY = 12.50 +INTRO_GRAPHIC = linuxcnc.gif +INTRO_TIME = 5 +PROGRAM_PREFIX = /home/johan/linuxcnc/nc_files +INCREMENTS = 1mm .1mm .01mm +#INCREMENTS = 5mm 1mm .5mm .1mm .05mm .01mm +LATHE = 1 +MIN_ANGULAR_VELOCITY = 0.1 +MAX_ANGULAR_VELOCITY = 3600 +DEFAULT_ANGULAR_VELOCITY = 360 +DEFAULT_SPINDLE_SPEED = 200 +CYCLE_TIME = 150 +DEFAULT_SPINDLE_0_SPEED = 200 +MIN_SPINDLE_0_SPEED = 100 +MAX_SPINDLE_0_SPEED = 2500 +MAX_SPINDLE_0_OVERRIDE = 1 +MIN_SPINDLE_0_OVERRIDE = 0.5 + +EMBED_TAB_NAME = Spindle Speed Selection +EMBED_TAB_LOCATION = ntb_preview +EMBED_TAB_COMMAND = gladevcp -H spindle_speed_selector.hal -x {XID} spindle_speed_selector.glade + +EMBED_TAB_NAME = Macro +EMBED_TAB_LOCATION = ntb_preview +EMBED_TAB_COMMAND = halcmd loadusr -Wn gladevcp gladevcp -c gladevcp -U notouch=1 -U norun=0 -u macros/lathehandler.py -x {XID} macros/lathemacro.ui + +EMBED_TAB_NAME = MPG +EMBED_TAB_LOCATION = ntb_preview +EMBED_TAB_COMMAND = gladevcp -u gladevcp-handler.py -H mpg.hal -x {XID} mpg.glade + +[PYTHON] +TOPLEVEL=python/toplevel.py +PATH_APPEND=python +#TOPLEVEL=/home/johan/linuxcnc/configs/sim.gmoccapy.lathe_configs/python/toplevel.py +#PATH_APPEND=/home/johan/linuxcnc/configs/sim.gmoccapy.lathe_configs/python + +[FILTER] +PROGRAM_EXTENSION = .png,.gif,.jpg Greyscale Depth Image +PROGRAM_EXTENSION = .py Python Script +PROGRAM_EXTENSION = .nc,.tap G-Code File +png = image-to-gcode +gif = image-to-gcode +jpg = image-to-gcode +py = python + +[TASK] +TASK = milltask +CYCLE_TIME = 0.010 + +[RS274NGC] +PARAMETER_FILE = linuxcnc.var +RS274NGC_STARTUP_CODE = G7 G18 +SUBROUTINE_PATH=custom-m-codes +USER_M_PATH=custom-m-codes +REMAP=S prolog=setspeed_prolog ngc=mysetspeed epilog=setspeed_epilog +REMAP=M0 modalgroup=4 ngc=extend_m0 +REMAP=M1 modalgroup=4 ngc=extend_m1 + +[EMCMOT] +# base freq = 20kHz +# servo freq = 1kHz +EMCMOT = motmod +COMM_TIMEOUT = 1.0 +#BASE_PERIOD = 35000 +BASE_PERIOD = 50000 +SERVO_PERIOD = 1000000 + +[HAL] +HALUI = halui +HALFILE = my-emco-compact-5.hal +HALFILE = custom.hal +POSTGUI_HALFILE = custom_postgui.hal + +[HALUI] +# add halui MDI commands here (max 64) +MDI_COMMAND = O call [100] ;0, connected to mpg button +MDI_COMMAND = O call [100] ;1, connected to mpg button +MDI_COMMAND = O call ;2, connected to mpg button +MDI_COMMAND = O call [0.48483] [20.16466] ;3, spindle speed selection AC1 +MDI_COMMAND = O call [0.32571] [-8.05714] ;4, spindle speed selection AC2 +MDI_COMMAND = O call [0.20098] [22.64117] ;5, spindle speed selection AC3 +MDI_COMMAND = O call [#4998] [#4999] ;6, called to set saved spindle + ; speed selection after homing + +[KINS] +KINEMATICS = trivkins coordinates=XZ +JOINTS = 2 + +[TRAJ] +COORDINATES = X Z +LINEAR_UNITS = mm +ANGULAR_UNITS = degree +DEFAULT_LINEAR_VELOCITY = 2.5 +MAX_LINEAR_VELOCITY = 18 +NO_FORCE_HOMING = 1 +DEFAULT_LINEAR_ACCELERATION = 50.0 +POSITION_FILE = emcopos.txt + +[EMCIO] +EMCIO = io +CYCLE_TIME = 0.100 +TOOL_TABLE = tool.tbl + +[AXIS_X] +MIN_LIMIT = -2500.0 +MAX_LIMIT = 2500.0 +MAX_VELOCITY = 12.5 +MAX_ACCELERATION = 500 + +[JOINT_0] +TYPE = LINEAR +HOME = 0.0 +MAX_VELOCITY = 12.5 +MAX_ACCELERATION = 500 +STEPGEN_MAXACCEL = 625 + +#steps/rev = 72, 0.125 mm/9 steps => 72*(0.125/9) mm/rev +#scale = 72/[72*0.125/9] = 72/(72/72) = 72 steps/mm +SCALE = 72 + +FERROR = 1.0 +MIN_FERROR = 0.25 +MIN_LIMIT = -2500.0 +MAX_LIMIT = 2500.0 +HOME_OFFSET = 0.0 + +[AXIS_Z] +MIN_LIMIT = -2500.0 +MAX_LIMIT = 2500.0 +MAX_VELOCITY = 12.5 +MAX_ACCELERATION = 500 + +[JOINT_1] +TYPE = LINEAR +HOME = 0.0 +MAX_VELOCITY = 12.5 +MAX_ACCELERATION = 500 +STEPGEN_MAXACCEL = 625 + +#steps/rev = 72, 0.125 mm/9 steps => 72*(0.125/9) mm/rev +#scale = 72/[72*0.125/9] = 72 steps/mm +SCALE = 72 + +FERROR = 1.0 +MIN_FERROR = 0.25 +MIN_LIMIT = -2500.0 +MAX_LIMIT = 2500.0 +HOME_OFFSET = 0.0 diff --git a/my-emco-compact-5/my-emco-compact-5.pref b/my-emco-compact-5/my-emco-compact-5.pref new file mode 100644 index 0000000..bff4627 --- /dev/null +++ b/my-emco-compact-5/my-emco-compact-5.pref @@ -0,0 +1,85 @@ +[DEFAULT] +dro_digits = 3 +dro_size = 28 +abs_color = #0000FF +rel_color = #000000 +dtg_color = #FFFF00 +homed_color = #00FF00 +unhomed_color = #FF0000 +enable_dro = False +scale_jog_vel = 7.5 +scale_spindle_override = 1 +scale_feed_override = 1 +scale_rapid_override = 1 +spindle_bar_min = 0.0 +spindle_bar_max = 6000.0 +turtle_jog_factor = 20 +hide_turtle_jog_button = False +unlock_code = 123 +toggle_readout = True +spindle_start_rpm = 300.0 +view = y +blockheight = 0.0 +open_file = +screen1 = window +x_pos = 40 +y_pos = 30 +width = 979 +height = 750 +use_toolmeasurement = False +gtk_theme = Follow System Theme +grid_size = 1.0 +mouse_btn_mode = 4 +hide_cursor = False +system_name_tool = Tool +system_name_g5x = G5x +system_name_rot = Rot +system_name_g92 = G92 +system_name_g54 = G54 +system_name_g55 = G55 +system_name_g56 = G56 +system_name_g57 = G57 +system_name_g58 = G58 +system_name_g59 = G59 +system_name_g59.1 = G59.1 +system_name_g59.2 = G59.2 +system_name_g59.3 = G59.3 +jump_to_dir = /home/johan +show_keyboard_on_offset = False +show_keyboard_on_tooledit = False +show_keyboard_on_edit = False +show_keyboard_on_mdi = False +x_pos_popup = 45.0 +y_pos_popup = 55 +width_popup = 250.0 +max_messages = 10 +message_font = sans 10 +use_frames = True +reload_tool = False +blockdel = False +show_offsets = False +show_dtg = False +view_tool_path = True +view_dimension = True +run_from_line = no_run +unlock_way = use +show_preview_on_offset = False +use_keyboard_shortcuts = False +tool_in_spindle = 3 +radius offset_axis_x = 0 +offset_axis_x = 19.25 +offset_axis_z = 0.0 +hide_tooltips = False +diameter offset_axis_x = 0 +kbd_height = 250 +kbd_width = 880 +kbd_set_height = False +kbd_set_width = False +hide_titlebar = False +icon_theme = classic +gcode_theme = classic +audio_enabled = True +audio_alert = /usr/share/sounds/freedesktop/stereo/dialog-warning.oga +audio_error = /usr/share/sounds/freedesktop/stereo/dialog-error.oga +show_keyboard_on_file_selection = False + diff --git a/my-emco-compact-5/postgui_call_list.hal b/my-emco-compact-5/postgui_call_list.hal new file mode 100644 index 0000000..cc704b3 --- /dev/null +++ b/my-emco-compact-5/postgui_call_list.hal @@ -0,0 +1,6 @@ +# These files are loaded post GUI, in the order they appear +# Generated by stepconf 1.1 at Mon Mar 14 20:22:34 2022 +# If you make changes to this file, they will be +# overwritten when you run stepconf again + +source custom_postgui.hal diff --git a/my-emco-compact-5/python/remap.py b/my-emco-compact-5/python/remap.py new file mode 100644 index 0000000..e1f6685 --- /dev/null +++ b/my-emco-compact-5/python/remap.py @@ -0,0 +1 @@ +from stdglue import * diff --git a/my-emco-compact-5/python/stdglue.py b/my-emco-compact-5/python/stdglue.py new file mode 100644 index 0000000..d501af0 --- /dev/null +++ b/my-emco-compact-5/python/stdglue.py @@ -0,0 +1,672 @@ +#NOTE: +# The legacy names *selected_pocket* and *current_pocket* actually reference +# a sequential tooldata index for tool items loaded from a tool +# table ([EMCIO]TOOL_TABLE) or via a tooldata database ([EMCIO]DB_PROGRAM) + +# stdglue - canned prolog and epilog functions for the remappable builtin codes (T,M6,M61,S,F) +# +# we dont use argspec to avoid the generic error message of the argspec prolog and give more +# concise ones here + +# cycle_prolog,cycle_epilog: generic code-independent support glue for oword sub cycles +# +# these are provided as starting point - for more concise error message you would better +# write a prolog specific for the code +# +# Usage: +#REMAP=G84.3 modalgroup=1 argspec=xyzqp prolog=cycle_prolog ngc=g843 epilog=cycle_epilog + +import emccanon +from interpreter import * +from emccanon import MESSAGE +throw_exceptions = 1 + +# used so screens can get info. +# add this to toplevel to call it: + +# import remap +# def __init__(self): +# if self.task: +# remap.build_hal(self) + +def build_hal(self): + import hal + try: + h=hal.component('remapStat') + h.newpin("tool", hal.HAL_S32, hal.HAL_OUT) + h.newpin("wear", hal.HAL_S32, hal.HAL_OUT) + h.ready() + self.hal_tool_comp = h + except Exception as e: + print(e) + +# REMAP=S prolog=setspeed_prolog ngc=setspeed epilog=setspeed_epilog +# exposed parameter: # + +def setspeed_prolog(self,**words): + try: + c = self.blocks[self.remap_level] + if not c.s_flag: + self.set_errormsg("S requires a value") + return INTERP_ERROR + self.params["speed"] = c.s_number + except Exception as e: + self.set_errormsg("S/setspeed_prolog: %s)" % (e)) + return INTERP_ERROR + return INTERP_OK + +def setspeed_epilog(self,**words): + try: + if not self.value_returned: + r = self.blocks[self.remap_level].executing_remap + self.set_errormsg("the %s remap procedure %s did not return a value" + % (r.name,r.remap_ngc if r.remap_ngc else r.remap_py)) + return INTERP_ERROR + if self.return_value < -TOLERANCE_EQUAL: # 'less than 0 within interp's precision' + self.set_errormsg("S: remap procedure returned %f" % (self.return_value)) + return INTERP_ERROR + if self.blocks[self.remap_level].builtin_used: + pass + #print "---------- S builtin recursion, nothing to do" + else: + self.speed[0] = self.params["speed"] + emccanon.enqueue_SET_SPINDLE_SPEED(self.speed[0]) + return INTERP_OK + except Exception as e: + self.set_errormsg("S/setspeed_epilog: %s)" % (e)) + return INTERP_ERROR + return INTERP_OK + +# REMAP=F prolog=setfeed_prolog ngc=setfeed epilog=setfeed_epilog +# exposed parameter: # + +def setfeed_prolog(self,**words): + try: + c = self.blocks[self.remap_level] + if not c.f_flag: + self.set_errormsg("F requires a value") + return INTERP_ERROR + self.params["feed"] = c.f_number + except Exception as e: + self.set_errormsg("F/setfeed_prolog: %s)" % (e)) + return INTERP_ERROR + return INTERP_OK + +def setfeed_epilog(self,**words): + try: + if not self.value_returned: + r = self.blocks[self.remap_level].executing_remap + self.set_errormsg("the %s remap procedure %s did not return a value" + % (r.name,r.remap_ngc if r.remap_ngc else r.remap_py)) + return INTERP_ERROR + if self.blocks[self.remap_level].builtin_used: + pass + #print "---------- F builtin recursion, nothing to do" + else: + self.feed_rate = self.params["feed"] + emccanon.enqueue_SET_FEED_RATE(self.feed_rate) + return INTERP_OK + except Exception as e: + self.set_errormsg("F/setfeed_epilog: %s)" % (e)) + return INTERP_ERROR + return INTERP_OK + +# REMAP=T prolog=prepare_prolog ngc=prepare epilog=prepare_epilog +# exposed parameters: # # + +def prepare_prolog(self,**words): + try: + cblock = self.blocks[self.remap_level] + if not cblock.t_flag: + self.set_errormsg("T requires a tool number") + return INTERP_ERROR + tool = cblock.t_number + if tool: + (status, pocket) = self.find_tool_pocket(tool) + if status != INTERP_OK: + self.set_errormsg("T%d: pocket not found" % (tool)) + return status + else: + pocket = -1 # this is a T0 - tool unload + self.params["tool"] = tool + self.params["pocket"] = pocket + return INTERP_OK + except Exception as e: + self.set_errormsg("T%d/prepare_prolog: %s" % (int(words['t']), e)) + return INTERP_ERROR + +def prepare_epilog(self, **words): + try: + if not self.value_returned: + r = self.blocks[self.remap_level].executing_remap + self.set_errormsg("the %s remap procedure %s did not return a value" + % (r.name,r.remap_ngc if r.remap_ngc else r.remap_py)) + return INTERP_ERROR + if self.blocks[self.remap_level].builtin_used: + #print "---------- T builtin recursion, nothing to do" + return INTERP_OK + else: + if self.return_value > 0: + self.selected_tool = int(self.params["tool"]) + self.selected_pocket = int(self.params["pocket"]) + emccanon.SELECT_TOOL(self.selected_tool) + return INTERP_OK + else: + self.set_errormsg("T%d: aborted (return code %.1f)" % (int(self.params["tool"]),self.return_value)) + return INTERP_ERROR + except Exception as e: + self.set_errormsg("T%d/prepare_epilog: %s" % (tool,e)) + return INTERP_ERROR + +# REMAP=M6 modalgroup=6 prolog=change_prolog ngc=change epilog=change_epilog +# exposed parameters: +# # +# # +# # +# # + +def change_prolog(self, **words): + try: + # this is relevant only when using iocontrol-v2. + if self.params[5600] > 0.0: + if self.params[5601] < 0.0: + self.set_errormsg("Toolchanger hard fault %d" % (int(self.params[5601]))) + return INTERP_ERROR + print("change_prolog: Toolchanger soft fault %d" % int(self.params[5601])) + + if self.selected_pocket < 0: + self.set_errormsg("M6: no tool prepared") + return INTERP_ERROR + if self.cutter_comp_side: + self.set_errormsg("Cannot change tools with cutter radius compensation on") + return INTERP_ERROR + self.params["tool_in_spindle"] = self.current_tool + self.params["selected_tool"] = self.selected_tool + self.params["current_pocket"] = self.current_pocket + self.params["selected_pocket"] = self.selected_pocket + return INTERP_OK + except Exception as e: + self.set_errormsg("M6/change_prolog: %s" % (e)) + return INTERP_ERROR + +def change_epilog(self, **words): + try: + if not self.value_returned: + r = self.blocks[self.remap_level].executing_remap + self.set_errormsg("the %s remap procedure %s did not return a value" + % (r.name,r.remap_ngc if r.remap_ngc else r.remap_py)) + yield INTERP_ERROR + # this is relevant only when using iocontrol-v2. + if self.params[5600] > 0.0: + if self.params[5601] < 0.0: + self.set_errormsg("Toolchanger hard fault %d" % (int(self.params[5601]))) + yield INTERP_ERROR + print("change_epilog: Toolchanger soft fault %d" % int(self.params[5601])) + + if self.blocks[self.remap_level].builtin_used: + #print "---------- M6 builtin recursion, nothing to do" + yield INTERP_OK + else: + if self.return_value > 0.0: + # commit change + self.selected_pocket = int(self.params["selected_pocket"]) + emccanon.CHANGE_TOOL(self.selected_pocket) + self.current_pocket = self.selected_pocket + self.selected_pocket = -1 + self.selected_tool = -1 + # cause a sync() + self.set_tool_parameters() + self.toolchange_flag = True + yield INTERP_EXECUTE_FINISH + else: + # yield to print any messages from the NGC program + yield INTERP_EXECUTE_FINISH + self.set_errormsg("M6 aborted (return code %.1f)" % (self.return_value)) + yield INTERP_ERROR + except Exception as e: + self.set_errormsg("M6/change_epilog: %s" % (e)) + yield INTERP_ERROR + +# REMAP=M61 modalgroup=6 prolog=settool_prolog ngc=settool epilog=settool_epilog +# exposed parameters: # # + +def settool_prolog(self,**words): + try: + c = self.blocks[self.remap_level] + if not c.q_flag: + self.set_errormsg("M61 requires a Q parameter") + return INTERP_ERROR + tool = int(c.q_number) + if tool < -TOLERANCE_EQUAL: # 'less than 0 within interp's precision' + self.set_errormsg("M61: Q value < 0") + return INTERP_ERROR + (status,pocket) = self.find_tool_pocket(tool) + if status != INTERP_OK: + self.set_errormsg("M61 failed: requested tool %d not in table" % (tool)) + return status + self.params["tool"] = tool + self.params["pocket"] = pocket + return INTERP_OK + except Exception as e: + self.set_errormsg("M61/settool_prolog: %s)" % (e)) + return INTERP_ERROR + +def settool_epilog(self,**words): + try: + if not self.value_returned: + r = self.blocks[self.remap_level].executing_remap + self.set_errormsg("the %s remap procedure %s did not return a value" + % (r.name,r.remap_ngc if r.remap_ngc else r.remap_py)) + return INTERP_ERROR + + if self.blocks[self.remap_level].builtin_used: + #print "---------- M61 builtin recursion, nothing to do" + return INTERP_OK + else: + if self.return_value > 0.0: + self.current_tool = int(self.params["tool"]) + self.current_pocket = int(self.params["pocket"]) + emccanon.CHANGE_TOOL_NUMBER(self.current_pocket) + # cause a sync() + self.tool_change_flag = True + self.set_tool_parameters() + else: + self.set_errormsg("M61 aborted (return code %.1f)" % (self.return_value)) + return INTERP_ERROR + except Exception as e: + self.set_errormsg("M61/settool_epilog: %s)" % (e)) + return INTERP_ERROR + +# educational alternative: M61 remapped to an all-Python handler +# demo - this really does the same thing as the builtin (non-remapped) M61 +# +# REMAP=M61 modalgroup=6 python=set_tool_number + +def set_tool_number(self, **words): + try: + c = self.blocks[self.remap_level] + if c.q_flag: + toolno = int(c.q_number) + else: + self.set_errormsg("M61 requires a Q parameter") + return status + (status,pocket) = self.find_tool_pocket(toolno) + if status != INTERP_OK: + self.set_errormsg("M61 failed: requested tool %d not in table" % (toolno)) + return status + if words['q'] > -TOLERANCE_EQUAL: # 'greater equal 0 within interp's precision' + self.current_pocket = pocket + self.current_tool = toolno + emccanon.CHANGE_TOOL_NUMBER(pocket) + # cause a sync() + self.tool_change_flag = True + self.set_tool_parameters() + return INTERP_OK + else: + self.set_errormsg("M61 failed: Q=%4" % (toolno)) + return INTERP_ERROR + except Exception as e: + self.set_errormsg("M61/set_tool_number: %s" % (e)) + return INTERP_ERROR + +_uvw = ("u","v","w","a","b","c") +_xyz = ("x","y","z","a","b","c") +# given a plane, return sticky words, incompatible axis words and plane name +# sticky[0] is also the movement axis +_compat = { + emccanon.CANON_PLANE_XY : (("z","r"),_uvw,"XY"), + emccanon.CANON_PLANE_YZ : (("x","r"),_uvw,"YZ"), + emccanon.CANON_PLANE_XZ : (("y","r"),_uvw,"XZ"), + emccanon.CANON_PLANE_UV : (("w","r"),_xyz,"UV"), + emccanon.CANON_PLANE_VW : (("u","r"),_xyz,"VW"), + emccanon.CANON_PLANE_UW : (("v","r"),_xyz,"UW")} + +# extract and pass parameters from current block, merged with extra parameters on a continuation line +# keep tjose parameters across invocations +# export the parameters into the oword procedure +def cycle_prolog(self,**words): + # self.sticky_params is assumed to have been initialized by the + # init_stgdlue() method below + global _compat + try: + # determine whether this is the first or a subsequent call + c = self.blocks[self.remap_level] + r = c.executing_remap + if c.g_modes[1] == r.motion_code: + # first call - clear the sticky dict + self.sticky_params[r.name] = dict() + + self.params["motion_code"] = c.g_modes[1] + + (sw,incompat,plane_name) =_compat[self.plane] + for (word,value) in list(words.items()): + # inject current parameters + self.params[word] = value + # record sticky words + if word in sw: + if self.debugmask & 0x00080000: print("%s: record sticky %s = %.4f" % (r.name,word,value)) + self.sticky_params[r.name][word] = value + if word in incompat: + return "%s: Cannot put a %s in a canned cycle in the %s plane" % (r.name, word.upper(), plane_name) + + # inject sticky parameters which were not in words: + for (key,value) in list(self.sticky_params[r.name].items()): + if not key in words: + if self.debugmask & 0x00080000: print("%s: inject sticky %s = %.4f" % (r.name,key,value)) + self.params[key] = value + + if not "r" in self.sticky_params[r.name]: + return "%s: cycle requires R word" % (r.name) + else: + if self.sticky_params[r.name]['r'] <= 0.0: + return "%s: R word must be > 0 if used (%.4f)" % (r.name, words["r"]) + + if "l" in words: + # checked in interpreter during block parsing + # if l <= 0 or l not near an int + self.params["l"] = words["l"] + + if "p" in words: + p = words["p"] + if p < 0.0: + return "%s: P word must be >= 0 if used (%.4f)" % (r.name, p) + self.params["p"] = p + + if self.feed_rate == 0.0: + return "%s: feed rate must be > 0" % (r.name) + if self.feed_mode == INVERSE_TIME: + return "%s: Cannot use inverse time feed with canned cycles" % (r.name) + if self.cutter_comp_side: + return "%s: Cannot use canned cycles with cutter compensation on" % (r.name) + return INTERP_OK + + except Exception as e: + raise + return "cycle_prolog failed: %s" % (e) + +# make sure the next line has the same motion code, unless overridden by a +# new G-code +def cycle_epilog(self,**words): + try: + c = self.blocks[self.remap_level] + self.motion_mode = c.executing_remap.motion_code # retain the current motion mode + return INTERP_OK + except Exception as e: + return "cycle_epilog failed: %s" % (e) + +# this should be called from TOPLEVEL __init__() +def init_stdglue(self): + self.sticky_params = dict() + +##################################### +# pure python remaps +##################################### + +# REMAP=M6 python=ignore_m6 +# +# m5 silently ignored +# +def ignore_m6(self,**words): + try: + return INTERP_OK + except Exception as e: + return "Ignore M6 failed: %s" % (e) + +# REMAP=T python=index_lathe_tool_with_wear +# +# uses T101 for tool 1, wear 1 no M6 needed +# tool offsets for tool 1 and tool 10001 are added together. +# +def index_lathe_tool_with_wear(self,**words): + # only run this if we are really moving the machine + # skip this if running task for the screen + if not self.task: + yield INTERP_OK + try: + # check there is a tool number from the Gcode + cblock = self.blocks[self.remap_level] + if not cblock.t_flag: + self.set_errormsg("T requires a tool number") + yield INTERP_ERROR + tool_raw = int(cblock.t_number) + + # interpret the raw tool number into tool and wear number + # If it's less then 100 someone forgot to add the wear #, so we added it automatically + # separate out tool number (tool) and wear number (wear), add 10000 to wear number + if tool_raw <100: + tool_raw=tool_raw*100 + tool = int(tool_raw/100) + wear = 10000 + tool_raw % 100 + + # uncomment for debugging + #print'***tool#',cblock.t_number,'toolraw:',tool_raw,'tool split:',tool,'wear split',wear + if tool: + # check for tool number entry in tool file + (status, pocket) = self.find_tool_pocket(tool) + if status != INTERP_OK: + self.set_errormsg("T%d: tool entry not found" % (tool)) + yield status + else: + tool = -1 + pocket = -1 + wear = -1 + self.params["tool"] = tool + self.params["pocket"] = pocket + self.params["wear"] = wear + try: + self.hal_tool_comp['tool']= tool_raw + self.hal_tool_comp['wear']= wear + except: + pass + # index tool immediately to tool number + self.selected_tool = int(self.params["tool"]) + self.selected_pocket = int(self.params["pocket"]) + emccanon.SELECT_TOOL(self.selected_tool) + if self.selected_pocket < 0: + self.set_errormsg("T0 not valid") + yield INTERP_ERROR + if self.cutter_comp_side: + self.set_errormsg("Cannot change tools with cutter radius compensation on") + yield INTERP_ERROR + self.params["tool_in_spindle"] = self.current_tool + self.params["selected_tool"] = self.selected_tool + self.params["current_pocket"] = self.current_pocket + self.params["selected_pocket"] = self.selected_pocket + + # change tool + try: + self.selected_pocket = int(self.params["selected_pocket"]) + emccanon.CHANGE_TOOL(self.selected_pocket) + self.current_pocket = self.selected_pocket + self.selected_pocket = -1 + self.selected_tool = -1 + # cause a sync() + self.set_tool_parameters() + self.toolchange_flag = True + except: + self.set_errormsg("T change aborted (return code %.1f)" % (self.return_value)) + yield INTERP_ERROR + + # add tool offset + self.execute("g43 h%d"% tool) + # if the wear offset is specified, add it's offset + try: + if wear>10000: + self.execute("g43.2 h%d"% wear) + yield INTERP_OK + except: + self.set_errormsg("Tool change aborted - No wear %d entry found in tool table" %wear) + yield INTERP_ERROR + except: + self.set_errormsg("Tool change aborted (return code %.1f)" % (self.return_value)) + yield INTERP_ERROR + + +# REMAP=M6 modalgroup=10 python=tool_probe_m6 +# +# auto tool probe on m6 +# move to tool change position for toolchange +# wait for acknowledge of tool change +# move to tool setter probe position +# probe tool on tool setter +# move back to tool change position +# set offsets +# based on Versaprobe remap +# +# param 5000 holds the work piece height +# param 4999 should be set to 1 if the +# machine is based in imperial +# +# required INI settings +# (Abs coordinates/ machine based units) +# +#[CHANGE_POSITION] +#X = 5 +#Y = 0 +#Z = 0 + +#[TOOLSENSOR] +#X = 5.00 +#Y = -1 +#Z = -1 +#PROBEHEIGHT = 2.3 +#MAXPROBE = -3 +#SEARCH_VEL = 20 +#PROBE_VEL = 5 + +def tool_probe_m6(self, **words): + + # only run this if we are really moving the machine + # skip this if running task for the screen + if not self.task: + yield INTERP_OK + + IMPERIAL_BASED = not(bool(self.params['_metric_machine'])) + + try: + # we need to be in machine based units + # if we aren't - switch + # remember so we can switch back later + switchUnitsFlag = False + if bool(self.params["_imperial"]) != IMPERIAL_BASED: + print ("not right Units: {}".format(bool(self.params["_imperial"]))) + if IMPERIAL_BASED: + print ("switched Units to imperial") + self.execute("G20") + else: + print ("switched Units to metric") + self.execute("G21") + switchUnitsFlag = True + + self.params["tool_in_spindle"] = self.current_tool + self.params["selected_tool"] = self.selected_tool + self.params["current_pocket"] = self.current_pocket + self.params["selected_pocket"] = self.selected_pocket + + # cancel tool offset + self.execute("G49") + + # change tool where ever we are + # user sets toolchange position prior to toolchange + # we will return here after + + try: + self.selected_pocket = int(self.params["selected_pocket"]) + emccanon.CHANGE_TOOL(self.selected_pocket) + self.current_pocket = self.selected_pocket + self.selected_pocket = -1 + self.selected_tool = -1 + # cause a sync() + self.set_tool_parameters() + self.toolchange_flag = True + except InterpreterException as e: + self.set_errormsg("tool_probe_m6 remap error: %s" % (e)) + yield INTERP_ERROR + + yield INTERP_EXECUTE_FINISH + + # record current position; probably should record every axis + self.params[4999] = emccanon.GET_EXTERNAL_POSITION_X() + self.params[4998] = emccanon.GET_EXTERNAL_POSITION_Y() + self.params[4997] = emccanon.GET_EXTERNAL_POSITION_Z() + + try: + # move to tool probe position (from INI) + self.execute("G90") + self.execute("G53 G0 X[#<_ini[TOOLSENSOR]X>] Y[#<_ini[TOOLSENSOR]Y>]") + self.execute("G53 G0 Z[#<_ini[TOOLSENSOR]Z>]") + + # set incremental mode + self.execute("G91") + + # course probe + self.execute("F [#<_ini[TOOLSENSOR]SEARCH_VEL>]") + self.execute("G38.2 Z [#<_ini[TOOLSENSOR]MAXPROBE>]") + + # Wait for results + yield INTERP_EXECUTE_FINISH + + # FIXME if there is an error it never comes back + # which leaves linuxcnc in g91 state + if self.params[5070] == 0 or self.return_value > 0.0: + self.execute("G90") + self.set_errormsg("tool_probe_m6 remap error:") + yield INTERP_ERROR + + # rapid up off trigger point to do it again + if bool(self.params["_imperial"]): + f = 0.25 + else: + f = 4.0 + self.execute("G0 Z{}".format(f)) + + self.execute("F [#<_ini[TOOLSENSOR]PROBE_VEL>]") + self.execute("G38.2 Z-0.5") + yield INTERP_EXECUTE_FINISH + + # FIXME if there is an error it never comes back + # which leaves linuxcnc in g91 state + if self.params[5070] == 0 or self.return_value > 0.0: + self.execute("G90") + self.set_errormsg("tool_probe_m6 remap error:") + yield INTERP_ERROR + + # set back absolute state + self.execute("G90") + + # return to recorded tool change position + self.execute("G53 G0 Z[#4997]") + yield INTERP_EXECUTE_FINISH + self.execute("G53 G0 X[#4999] Y[#4998]") + + # adjust tool offset from calculations + proberesult = self.params[5063] + probeheight = self.params["_ini[TOOLSENSOR]PROBEHEIGHT"] + workheight = self.params[5000] + + adj = proberesult - probeheight + workheight + self.execute("G10 L1 P# Z{}".format(adj)) + + # apply tool offset + self.execute("G43") + + # if we switched units for tool change - switch back + if switchUnitsFlag: + if IMPERIAL_BASED: + self.execute("G21") + print ("switched Units back to metric") + else: + self.execute("G20") + print ("switched Units back to imperial") + + except InterpreterException as e: + msg = "%d: '%s' - %s" % (e.line_number,e.line_text, e.error_message) + print (msg) + yield INTERP_ERROR + + except: + self.set_errormsg("tool_probe_m6 remap error." ) + yield INTERP_ERROR + + + diff --git a/my-emco-compact-5/python/toplevel.py b/my-emco-compact-5/python/toplevel.py new file mode 100644 index 0000000..0dc38fa --- /dev/null +++ b/my-emco-compact-5/python/toplevel.py @@ -0,0 +1 @@ +import remap diff --git a/my-emco-compact-5/serialEventHandler.py b/my-emco-compact-5/serialEventHandler.py new file mode 100644 index 0000000..8514707 --- /dev/null +++ b/my-emco-compact-5/serialEventHandler.py @@ -0,0 +1,347 @@ +#! /usr/bin/python +"""usage serialEventHanlder.py -h -c -d/--debug= -p/--port= in_file.xml +in_file - input xml-file describing what knobs and/or button are on the pendant +-c # name of component in HAL. 'my-mpg' default +-d/--debug= # debug level, default 0 +-p/--port= # serial port to use. '/dev/ttyUSB0' default +-h # Help +python serialEventHandler.py -w mpg_pendant/config/mpg.xml +""" + +### https://docs.python.org/2/library/xml.etree.elementtree.html + +import time +import getopt +import sys +import comms +import xml.etree.ElementTree as ET +import hal +from collections import namedtuple + +class Pin: + """ General representation of a Pin and it's data""" + def __init__(self, name, type, observer = None): + self.name = name # HAL pin name + self.val = 0 # current value of pin, e.g. 1 - on, 0 - off + self.type = type # type (string read from xml) + self.observer = None + + if observer != None: + self.attach(observer) + + def __repr__(self): + return 'pin name: ' + self.name + '\tval: ' + str(self.val) + '\ttype: ' + self.type + + def attach(self, observer): + self.observer = observer + + def _notify(self): + if self.observer != None: + self.observer.update(self.name, self.val) + + def update_hal(self, v): + """ to be overriden in child-class""" + pass + + def set(self, v): + pass + + def _type_saturate(self, type, val): + """ helper function to convert type read from xml to HAL-type """ + retVal = 0 + + if type == 'bit': + if val >= 1: + retVal = 1 + + if type == 'float': + retVal = val + + if type == 's32': + retVal = val + + if type == 'u32': + retVal = val + + return retVal + + def _get_hal_type(self, str): + """ helper function to convert type read from xml to HAL-type """ + retVal = '' + + if str == 'bit': + retVal = hal.HAL_BIT + + if str == 'float': + retVal = hal.HAL_FLOAT + + if str == 's32': + retVal = hal.HAL_S32 + + if str == 'u32': + retVal = hal.HAL_U32 + + return retVal + +class InPin(Pin): + """ Specialization of Pin-class""" + def __init__(self, hal_ref, name, type, observer = None): + Pin.__init__(self, name, type, observer) + + hal_ref.newpin(name, self._get_hal_type(type), hal.HAL_IN) # create the user space HAL-pin + + def __repr__(self): + return 'Input pin ' + Pin.__repr__(self) + + def update_hal(self, hal): + if self.val != hal[self.name]: + self.val = hal[self.name] + self._notify() + +class OutPin(Pin): + """ Specialization of Pin-class""" + def __init__(self, hal_ref, name, type, observer = None): + Pin.__init__(self, name, type, observer) + + hal_ref.newpin(name, self._get_hal_type(type), hal.HAL_OUT) # create the user space HAL-pin + + def __repr__(self): + return 'Output pin ' + Pin.__repr__(self) + + def update_hal(self, hal): + hal[self.name] = self.val + + def set(self, v): + try: + self.val = self._type_saturate(self.type, int(v)) + except ValueError: + print('OutPin::set() value error catched on: ' + self.name) + +class Observer: + """ container for notification-function """ + def __init__(self, update_cb): + self.update_cb = update_cb + + def update(self, name, val): + #pass + try: + #print 'observer::update name: ' + name + ' val: ' + str(val) + self.update_cb(name, val) + except ValueError: + print('Observer::notify() value error catched on: ' + self.name) + +class HALComponentWrapper: + def __init__(self, name): + self.pin_dict = {} # dictionary used to map event to pin + self.hal = hal.component(name) # instanciate the HAL-component + self.observer = None + + def __repr__(self): + tmp_str = '' + for k in self.pin_dict: + tmp_str += 'event: ' + k + '\t' + str(self.pin_dict[k]) + '\n' + return tmp_str + + def __getitem__(self, key): + if key in self.pin_dict: + return self.pin_dict[key].val + + def __setitem__(self, key, val): + self.set_pin(key, val) + + def add_pin(self, event_name, hal_name, type, direction = 'out'): + self.pin_dict[event_name] = self._createPin(hal_name, type, direction) + + def event_set_pin(self, event): + """ updates pin value with new data + input: pin name, set value' + output: nothing. """ + if event.name in self.pin_dict: + self.pin_dict[event.name].set(event.data) + + def set_pin(self, key, value): + """ updates pin value with new data + input: event name, set value' + output: nothing. """ + if key in self.pin_dict: + self.pin_dict[key].set(value) + + def setReady(self): + self.hal.ready() + + def update_hal(self): + for key in self.pin_dict: + self.pin_dict[key].update_hal(self.hal) + + def attach(self, observer): + self.observer = observer + + def _createPin(self, hal_name, type, direction): + """ factory function to create pin""" + if direction == 'in': + return InPin(self.hal, hal_name, type, Observer(self.notify)) + + if direction == 'out': + return OutPin(self.hal, hal_name, type) + + def notify(self, hal_name, val): + # convert pin-name to event-name + for key in self.pin_dict: + if self.pin_dict[key].name == hal_name and self.observer != None: + self.observer.update(key, val) + +class OptParser: + def __init__(self, argv): + self.xml_file = '' # input xml-file describing what knobs and/or button are on the pendant + self.name = 'my-mpg' # default name of component in HAL + self.port = '/dev/ttyUSB0' # default serial port to use + self.watchdog_reset = False + + self._get_options(argv) + + def __repr__(self): + return 'xml_file: ' + self.xml_file + '\tname: ' + self.name + '\tport: ' + self.port + + def _get_options(self, argv): + try: + opts, args = getopt.getopt(argv, "hwp:c:", ["input=", "port="]) + except getopt.GetoptError as err: + # print help information and exit: + print(err) # will print something like "option -a not recognized" + sys.exit(2) + + ### parse input command line + for o, a in opts: + if o == "-h": + self._usage() + sys.exit() + if o == "-c": + self.name = a + elif o == "--input": + self.xml_file = a + elif o in ("-p", "--port"): + self.port = a + elif o == "-w": + self.watchdog_reset = True + else: + print(o, a) + assert False, "unhandled option" + + if self.xml_file == '': + if len(sys.argv) < 2: + self._usage() + sys.exit(2) + else: + self.xml_file = argv[-1] + + def get_name(self): + return self.name + + def get_port(self): + return self.port + + def get_XML_file(self): + return self.xml_file + + def get_watchdog_reset(self): + return self.watchdog_reset + + def _usage(self): + """ print command line options """ + print("usage serialEventHandler.py -h -c -d/--debug= -p/--port= in_file.xml\n"\ + "in_file - input xml-file describing what knobs and/or button are on the pendant\n"\ + "-c # name of component in HAL. 'mpg' default\n"\ + "-p/--port= # default serial port to use. '/dev/ttyS2' default\n"\ + "-w # start watchdog deamon" \ + "-h # Help test") + +""" Parser data container""" +HalPin = namedtuple("HalPin", ['name', 'event', 'type', 'direction']) + +class XmlParser: + def __init__(self, f): + self.tree = [] + self.parsed_data = [] #array of named tuples (HalPin) + + self._parse_file(f) + + def __repr__(self): + tmp_str = '' + + for element in self.parsed_data: + tmp_str += 'name: ' + element.name + '\t' + 'event: ' + element.event + '\t' + 'type: ' + element.type + '\n' + return tmp_str + + def get_parsed_data(self): + return self.parsed_data + + def _parse_file(self, f): + self.tree = ET.parse(f) + root = self.tree.getroot() + + for halpin in root.iter('halpin'): + name = halpin.text.strip('"') + type = 'u32' if halpin.find('type') is None else halpin.find('type').text + event = name if halpin.find('event') is None else halpin.find('event').text + direction = 'out' if halpin.find('direction') is None else halpin.find('direction').text + + if self._check_supported_HAL_type(type) and self._check_supported_HAL_direction(direction): + self.parsed_data.append(HalPin(name, event, type, direction)) + + def _check_supported_HAL_type(self, str): + """ helper function to check if type is supported """ + retVal = False + + if str == 'bit' or str == 'float' or str == 's32' or str == 'u32': + retVal = True + + return retVal + + def _check_supported_HAL_direction(self, str): + """ helper function to check if direction is supported """ + retVal = False + + if str == 'in' or str == 'out': + retVal = True + + return retVal + +################################################ +def main(): + optParser = OptParser(sys.argv[1:]) + componentName = optParser.get_name() + portName = optParser.get_port() + xmlFile = optParser.get_XML_file() + watchdogEnabled = optParser.get_watchdog_reset() + print(optParser) + + xmlParser = XmlParser(xmlFile) + + c = HALComponentWrapper(componentName) #HAL adaptor, takes care of mapping incomming events to actual hal-pin + serialEventGenerator = comms.instrument(portName, c.event_set_pin, watchdogEnabled, 5, 1) #serial adaptor + c.attach(Observer(serialEventGenerator.generateEvent)) + + # add/create the HAL-pins from parsed xml and attach them to the adaptor event handler + parsed_data = xmlParser.get_parsed_data() + for pin in parsed_data: + c.add_pin(pin.event, pin.name, pin.type, pin.direction) + + print(c) + + # ready signal to HAL, component and it's pins are ready created + c.setReady() + + time.sleep(0.5) + + try: + while 1: + serialEventGenerator.readMessages() #blocks until '\n' received or timeout + c.update_hal() + + time.sleep(0.1) + + except KeyboardInterrupt: + raise SystemExit + +if __name__ == '__main__': + main() diff --git a/my-emco-compact-5/speeds.png b/my-emco-compact-5/speeds.png new file mode 100644 index 0000000000000000000000000000000000000000..d8b44cf10e33670d2fd49512cffd1978fcd8ce7e GIT binary patch literal 193334 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

Z%3Z*=oPri9h{-KX)K2h4~G`5qJg1 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 X)DRo}^+?G500000NkvXXu0mjf(_V0F literal 0 HcmV?d00001 diff --git a/my-emco-compact-5/spindle_speed_selector.glade b/my-emco-compact-5/spindle_speed_selector.glade new file mode 100644 index 0000000..7f6145d --- /dev/null +++ b/my-emco-compact-5/spindle_speed_selector.glade @@ -0,0 +1,114 @@ + + + + + + + False + + + + + + True + False + vertical + + + True + False + spindle speed selection: + + + False + True + 0 + + + + + True + False + + + True + False + speeds.png + 3 + + + False + True + 0 + + + + + True + False + + + AC2 + True + True + False + 15 + True + True + radio1 + + + 0 + 1 + + + + + AC3 + True + True + False + 15 + True + True + radio1 + + + 0 + 2 + + + + + AC1 + True + True + False + 15 + True + True + + + 0 + 0 + + + + + False + True + 10 + 1 + + + + + False + True + 1 + + + + + + diff --git a/my-emco-compact-5/spindle_speed_selector.hal b/my-emco-compact-5/spindle_speed_selector.hal new file mode 100644 index 0000000..4e53df7 --- /dev/null +++ b/my-emco-compact-5/spindle_speed_selector.hal @@ -0,0 +1,12 @@ +net all-homed spindle_speed_selector.table1 +net all-homed and2.1.in0 +net all-homed and2.2.in0 +net all-homed and2.3.in0 + +net spindle-speed-selector-panel-radio1 spindle_speed_selector.radio1 and2.1.in1 +net spindle-speed-selector-panel-radio2 spindle_speed_selector.radio2 and2.2.in1 +net spindle-speed-selector-panel-radio3 spindle_speed_selector.radio3 and2.3.in1 + +net and2-1-out and2.1.out halui.mdi-command-03 +net and2-2-out and2.2.out halui.mdi-command-04 +net and2-3-out and2.3.out halui.mdi-command-05 diff --git a/my-emco-compact-5/sync-files.sh b/my-emco-compact-5/sync-files.sh new file mode 100644 index 0000000..1008572 --- /dev/null +++ b/my-emco-compact-5/sync-files.sh @@ -0,0 +1,9 @@ +find . -type f -name "*.py"|xargs python3 ~/git-clones/linuxcnc-configs/sync-file.py -p -d -c ~/git-clones/linuxcnc-configs/my-emco-compact-5 +find . -type f -name "*.ngc"|xargs python3 ~/git-clones/linuxcnc-configs/sync-file.py -p -d -c ~/git-clones/linuxcnc-configs/my-emco-compact-5 +find . -type f -name "*.hal"|xargs python3 ~/git-clones/linuxcnc-configs/sync-file.py -p -d -c ~/git-clones/linuxcnc-configs/my-emco-compact-5 +find . -type f -name "*.ini"|xargs python3 ~/git-clones/linuxcnc-configs/sync-file.py -p -d -c ~/git-clones/linuxcnc-configs/my-emco-compact-5 +find . -type f -name "*.sh"|xargs python3 ~/git-clones/linuxcnc-configs/sync-file.py -p -d -c ~/git-clones/linuxcnc-configs/my-emco-compact-5 +find . -type f -name "*.clp"|xargs python3 ~/git-clones/linuxcnc-configs/sync-file.py -p -c ~/git-clones/linuxcnc-configs/my-emco-compact-5 +find . -type f -name "*.tbl"|xargs python3 ~/git-clones/linuxcnc-configs/sync-file.py -p -d -c ~/git-clones/linuxcnc-configs/my-emco-compact-5 +find . -type f -name "*.var"|xargs python3 ~/git-clones/linuxcnc-configs/sync-file.py -p -d -c ~/git-clones/linuxcnc-configs/my-emco-compact-5 +find . -type f -name "*.xml"|xargs python3 ~/git-clones/linuxcnc-configs/sync-file.py -p -d -c ~/git-clones/linuxcnc-configs/my-emco-compact-5 diff --git a/my-emco-compact-5/test_ladder.clp b/my-emco-compact-5/test_ladder.clp new file mode 100644 index 0000000..51d5cc7 --- /dev/null +++ b/my-emco-compact-5/test_ladder.clp @@ -0,0 +1,334 @@ +_FILES_CLASSICLADDER +_FILE-com_params.txt +MODBUS_MASTER_SERIAL_PORT= +MODBUS_MASTER_SERIAL_SPEED=9600 +MODBUS_MASTER_SERIAL_DATABITS=8 +MODBUS_MASTER_SERIAL_STOPBITS=1 +MODBUS_MASTER_SERIAL_PARITY=0 +MODBUS_ELEMENT_OFFSET=0 +MODBUS_MASTER_SERIAL_USE_RTS_TO_SEND=0 +MODBUS_MASTER_TIME_INTER_FRAME=100 +MODBUS_MASTER_TIME_OUT_RECEIPT=500 +MODBUS_MASTER_TIME_AFTER_TRANSMIT=0 +MODBUS_DEBUG_LEVEL=0 +MODBUS_MAP_COIL_READ=0 +MODBUS_MAP_COIL_WRITE=0 +MODBUS_MAP_INPUT=0 +MODBUS_MAP_HOLDING=0 +MODBUS_MAP_REGISTER_READ=0 +MODBUS_MAP_REGISTER_WRITE=0 +_/FILE-com_params.txt +_FILE-timers.csv +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +_/FILE-timers.csv +_FILE-rung_0.csv +#VER=2.0 +#LABEL= +#COMMENT= +#PREVRUNG=4 +#NEXTRUNG=-1 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 +1-0-50/0 , 99-0-0/0 , 99-0-0/0 , 20-0-0/20 , 9-0-0/0 , 9-0-0/0 , 99-0-0/0 , 99-0-0/0 , 20-0-0/0 , 50-0-60/4 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 9-1-0/0 , 99-0-0/0 , 99-0-0/0 , 20-0-0/9 , 50-0-60/5 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 +1-0-50/1 , 99-0-0/0 , 99-0-0/0 , 20-0-0/21 , 9-0-0/0 , 9-0-0/0 , 99-0-0/0 , 99-0-0/0 , 20-0-0/11 , 50-0-60/6 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 9-1-0/0 , 99-0-0/0 , 99-0-0/0 , 20-0-0/13 , 50-0-60/7 +_/FILE-rung_0.csv +_FILE-timers_iec.csv +1,0,0 +1,0,0 +1,0,0 +1,0,0 +1,0,0 +1,0,0 +1,0,0 +1,0,0 +1,0,0 +1,0,0 +_/FILE-timers_iec.csv +_FILE-ioconf.csv +#VER=1.0 +_/FILE-ioconf.csv +_FILE-sections.csv +#VER=1.0 +#NAME000=Prog1 +000,0,-1,1,0,0 +_/FILE-sections.csv +_FILE-arithmetic_expressions.csv +#VER=2.0 +0000,@270/1@<3 +0001,@270/0@=1 +0002,@270/0@=0 +0003,@270/0@=3 +0004,@270/0@=2 +0005,@270/1@=0 +0006,@310/0@=10 +0007,@270/1@=1 +0008,@310/0@=1 +0009,@270/1@=3 +0010,@270/1@=2 +0011,@270/1@<3 +0012,@310/0@=100 +0013,@270/1@=3 +0020,@270/0@<2 +0021,@270/0@<2 +0022,@270/0@=2 +0024,@270/0@=2 +_/FILE-arithmetic_expressions.csv +_FILE-counters.csv +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +_/FILE-counters.csv +_FILE-rung_2.csv +#VER=2.0 +#LABEL= +#COMMENT= +#PREVRUNG=1 +#NEXTRUNG=4 +99-0-0/0 , 99-0-0/0 , 20-0-0/5 , 9-0-0/5 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 99-0-0/0 , 99-0-0/0 , 60-0-0/8 +0-0-0/0 , 0-0-0/0 , 0-0-0/6 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/9 +99-0-0/0 , 99-0-0/0 , 20-0-0/7 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 99-0-0/0 , 99-0-0/0 , 60-0-0/6 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/13 +99-0-0/0 , 99-0-0/0 , 20-0-0/10 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 99-0-0/0 , 99-0-0/12 , 60-0-60/12 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 +_/FILE-rung_2.csv +_FILE-rung_4.csv +#VER=2.0 +#LABEL= +#COMMENT= +#PREVRUNG=2 +#NEXTRUNG=0 +1-0-50/0 , 9-0-0/0 , 99-0-0/0 , 99-0-0/0 , 20-0-0/22 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 50-0-60/8 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 +1-0-50/1 , 9-0-0/0 , 99-0-0/0 , 99-0-0/0 , 20-0-0/24 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 50-0-60/9 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/23 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 +_/FILE-rung_4.csv +_FILE-rung_1.csv +#VER=2.0 +#LABEL= +#COMMENT= +#PREVRUNG=2 +#NEXTRUNG=2 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 +99-0-0/0 , 99-0-0/0 , 20-0-0/2 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 50-0-60/0 +99-0-0/0 , 99-0-0/0 , 20-0-0/1 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 50-0-60/1 +99-0-0/0 , 99-0-0/0 , 20-0-0/4 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 50-0-60/2 +99-0-0/0 , 99-0-0/0 , 20-0-0/3 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 50-0-60/3 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 +_/FILE-rung_1.csv +_FILE-monostables.csv +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +_/FILE-monostables.csv +_FILE-symbols.csv +#VER=1.0 +%I0,%jog-plus,no signal connected +%I1,%jog-minu, +%I2,%sp-sel1, +%I3,%sp-sel2, +%I4,%sp-sel3, +%I5,%I5, +%I6,%I6, +%I7,%I7, +%I8,%I8, +%I9,%I9, +%I10,%I10, +%I11,%I11, +%I12,%I12, +%I13,%I13, +%I14,%I14, +%Q0,%x-axis, +%Q1,%z-axis, +%Q2,%s-ctrl, +%Q3,%t-ctrl, +%Q4,%jog-inc+, +%Q5,%jog+, +%Q6,%jog-inc-, +%Q7,%jog-, +%Q8,%Q8, +%Q9,%Q9, +%Q10,%Q10, +%Q11,%Q11, +%Q12,%Q12, +%Q13,%Q13, +%Q14,%Q14, +%B0,%B0, +%B1,%B1, +%B2,%B2, +%B3,%B3, +%B4,%B4, +%B5,%B5, +%B6,%B6, +%B7,%B7, +%B8,%B8, +%B9,%B9, +%B10,%B10, +%B11,%B11, +%B12,%B12, +%B13,%B13, +%B14,%B14, +%B15,%B15, +%B16,%B16, +%B17,%B17, +%B18,%B18, +%B19,%B19, +%W0,%W0, +%W1,%W1, +%W2,%W2, +%W3,%W3, +%W4,%W4, +%W5,%W5, +%W6,%W6, +%W7,%W7, +%W8,%W8, +%W9,%W9, +%W10,%W10, +%W11,%W11, +%W12,%W12, +%W13,%W13, +%W14,%W14, +%W15,%W15, +%W16,%W16, +%W17,%W17, +%W18,%W18, +%W19,%W19, +%IW0,%sel-a, +%IW1,%sel-s, +%IW2,%IW2, +%IW3,%IW3, +%IW4,%IW4, +%IW5,%IW5, +%IW6,%IW6, +%IW7,%IW7, +%IW8,%IW8, +%IW9,%IW9, +%QW0,%QW0, +%QW1,%QW1, +%QW2,%QW2, +%QW3,%QW3, +%QW4,%QW4, +%QW5,%QW5, +%QW6,%QW6, +%QW7,%QW7, +%QW8,%QW8, +%QW9,%QW9, +%IF0,%IF0, +%IF1,%IF1, +%IF2,%IF2, +%IF3,%IF3, +%IF4,%IF4, +%IF5,%IF5, +%IF6,%IF6, +%IF7,%IF7, +%IF8,%IF8, +%IF9,%IF9, +%QF0,%scale, +%QF1,%QF1, +%QF2,%QF2, +%QF3,%QF3, +%QF4,%QF4, +%QF5,%QF5, +%QF6,%QF6, +%QF7,%QF7, +%QF8,%QF8, +%QF9,%QF9, +%T0,%T0,Old Timer +%T1,%T1,Old Timer +%T2,%T2,Old Timer +%T3,%T3,Old Timer +%T4,%T4,Old Timer +%T5,%T5,Old Timer +%T6,%T6,Old Timer +%T7,%T7,Old Timer +%T8,%T8,Old Timer +%T9,%T9,Old Timer +%TM0,%TM0,New Timer +%TM1,%TM1,New Timer +%TM2,%TM2,New Timer +%TM3,%TM3,New Timer +%TM4,%TM4,New Timer +%TM5,%TM5,New Timer +%TM6,%TM6,New Timer +%TM7,%TM7,New Timer +%TM8,%TM8,New Timer +%TM9,%TM9,New Timer +%M0,%M0,One-shot +%M1,%M1,One-shot +%M2,%M2,One-shot +%M3,%M3,One-shot +%M4,%M4,One-shot +%M5,%M5,One-shot +%M6,%M6,One-shot +%M7,%M7,One-shot +%M8,%M8,One-shot +%M9,%M9,One-shot +%C0,%C0,Counter +%C1,%C1,Counter +%C2,%C2,Counter +%C3,%C3,Counter +%C4,%C4,Counter +%C5,%C5,Counter +%C6,%C6,Counter +%C7,%C7,Counter +%C8,%C8,Counter +%C9,%C9,Counter +%E0,%E0,Error Flag Bit +%E1,%E1,Error Flag Bit +%E2,%E2,Error Flag Bit +%E3,%E3,Error Flag Bit +%E4,%E4,Error Flag Bit +%E5,%E5,Error Flag Bit +%E6,%E6,Error Flag Bit +%E7,%E7,Error Flag Bit +%E8,%E8,Error Flag Bit +%E9,%E9,Error Flag Bit +_/FILE-symbols.csv +_FILE-general.txt +PERIODIC_REFRESH=50 +SIZE_NBR_RUNGS=100 +SIZE_NBR_BITS=20 +SIZE_NBR_WORDS=20 +SIZE_NBR_TIMERS=10 +SIZE_NBR_MONOSTABLES=10 +SIZE_NBR_COUNTERS=10 +SIZE_NBR_TIMERS_IEC=10 +SIZE_NBR_PHYS_INPUTS=15 +SIZE_NBR_PHYS_OUTPUTS=15 +SIZE_NBR_ARITHM_EXPR=100 +SIZE_NBR_SECTIONS=10 +SIZE_NBR_SYMBOLS=160 +_/FILE-general.txt +_FILE-modbusioconf.csv +#VER=1.0 +_/FILE-modbusioconf.csv +_FILE-sequential.csv +#VER=1.0 +_/FILE-sequential.csv +_/FILES_CLASSICLADDER diff --git a/my-emco-compact-5/tool.tbl b/my-emco-compact-5/tool.tbl new file mode 100644 index 0000000..10c1982 --- /dev/null +++ b/my-emco-compact-5/tool.tbl @@ -0,0 +1,4 @@ +T1 P1 X0.0 Y0.0 Z0.0 A0.0 B0.0 C0.0 U0.0 V0.0 W0.0 D0.4 I87.0 J32.0 Q2.0 ;Right hand +T2 P2 X0.0 Y0.0 Z0.0 A0.0 B0.0 C0.0 U0.0 V0.0 W0.0 D0.4 I93.0 J148.0 Q1.0 ;Left hand +T3 P3 X0.0 Y0.0 Z0.0 A0.0 B0.0 C0.0 U0.0 V0.0 W0.0 D0.4 I117.0 J63.0 Q6.0 ;Neutral +T4 P4 X0.0 Y0.0 Z0.0 A0.0 B0.0 C0.0 U0.0 V0.0 W0.0 D0.1 I0.0 J0.0 Q6.0 ;Stick diff --git a/my-emco-compact-5/watchdog.py b/my-emco-compact-5/watchdog.py new file mode 100644 index 0000000..365d086 --- /dev/null +++ b/my-emco-compact-5/watchdog.py @@ -0,0 +1,93 @@ +#! /usr/bin/python +import time +import threading +import sys + +class WatchDog(): + def __init__(self, timeout): + self.timeout = timeout + self.last_ping_time = time.time() + + def ping(self): + self.last_ping_time = time.time() + + def check(self): + if time.time() - self.last_ping_time > self.timeout: + self.last_ping_time = time.time() #reset tick time + return True + else: + return False + + def insideMargin(self): + if time.time() - self.last_ping_time <= self.timeout: + return True + else: + self.last_ping_time = time.time() #reset tick time + return False + + +class WatchDogDaemon(threading.Thread): + def __init__(self, timeout, periodicity, enable = True): + self.wd = WatchDog(timeout) + self.periodicity = periodicity + self.enabled = enable + self._start() + + def _start(self): + threading.Thread.__init__(self) + self.daemon = True + self.start() + + def ping(self): + self.wd.ping() + + def run(self): + print("Starting watchdog deamon...") + while(self.enabled): + time.sleep(self.periodicity) + + if not self.wd.insideMargin(): + self.reset() + + print("stopping watchdog deamon...") + + def setEnabled(self, enabled): + if self.enabled == False and enabled == True: + self.enabled = True + self.wd.ping() # reset tick time + self._start() + + if enabled == False: + self.enabled = False + + def reset(self): + """to be overriden by client""" + pass + + +def reset(): + print('reset') + +def main(): + i = 0 + wdd = WatchDogDaemon(2, 0.5, False) + wdd.reset = reset + try: + while 1: + time.sleep(1) + print('main_' + str(i)) + print(wdd.is_alive()) + i = i+1 + + if i == 5 or i == 15: + wdd.setEnabled(True) + if i == 10: + wdd.setEnabled(False) + + wdd.ping() + + except KeyboardInterrupt: + raise SystemExit + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/my-emco/Misc/kopplingar.txt b/my-emco/Misc/kopplingar.txt new file mode 100644 index 0000000..c38b282 --- /dev/null +++ b/my-emco/Misc/kopplingar.txt @@ -0,0 +1,45 @@ +Märkning I/O Beskrivning +---------------------------------------------------------------------------- + P1 + P2 X step + P3 X dir + P4 Y step + P5 Y dir + P6 Z step + P7 Z dir + +R1 P8 (i/o-kort) Relä spindel CW +R2 P9 -"- Relä oljepump, spindel +M2 P14 -"- MOS-FET tryckluftsventil dimsmörjning + P16 -"- PWM spindel +M1 P17 -"- MOS-FET tryckluftsventil, spindel + + +P1 P10 -"- Z proximity sensor, home position +P2 P11 -"- Y proximity sensor, home and max position +P3 P12 -"- X proximity sensor, home and max position + P13 + P15 + +brun/PHASE1(A) encoder phaseA+ +grå/PHASE2(B) encoder phaseB+ +röd/PHASE3(Z) encoder phaseZ+, index pulse +blå 5V supply encoder +2st vit GND encoder + + + + + + +#P4 X rotations sensor, per ledstångsvarvvarv +#P5 Y rotations sensor, per ledstångsvarvvarv +#P6 Z rotations sensor, per ledstångsvarvvarv + +#COL1 P12 (i/o-kort) collector transistor1, downsampled phaseA +#COL2 P13 (i/o-kort) collector transistor2, downsampled phaseB +#COL3 P15 (i/o-kort) collector transistor3, pulse stretched phaseZ index + +#BASE1 D3 (arduino) base transistor1, downsampled phaseA +#BASE2 D5 (arduino) base transistor2, downsampled phaseB +#BASE3 D7 (arduino) base transistor3, downsampled phaseZ index diff --git a/my-emco/Misc/pid-tuning.ods b/my-emco/Misc/pid-tuning.ods new file mode 100644 index 0000000000000000000000000000000000000000..1fef7fb71a768cfb9c5ccc7f160d402ff6541cad GIT binary patch literal 8992 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 bPn?uL0;{$fF$tcG2KN!gU5xac!N2`4xiF%< literal 0 HcmV?d00001 diff --git a/my-emco/comms.py b/my-emco/comms.py new file mode 100644 index 0000000..fe22c55 --- /dev/null +++ b/my-emco/comms.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# list available ports with 'python -m serial.tools.list_ports' +import serial +import watchdog + +## Default values ## +BAUDRATE = 38400 +"""Default value for the baudrate in Baud (int).""" + +PARITY = 'N' #serial.PARITY_NONE +"""Default value for the parity. See the pySerial module for documentation. Defaults to serial.PARITY_NONE""" + +BYTESIZE = 8 +"""Default value for the bytesize (int).""" + +STOPBITS = 1 +"""Default value for the number of stopbits (int).""" + +TIMEOUT = 0.05 +"""Default value for the timeout value in seconds (float).""" + +CLOSE_PORT_AFTER_EACH_CALL = False +"""Default value for port closure setting.""" + + +class Message: + """'container for messages. keeps two strings and """ + def __init__(self, name = '', data = ''): + self.name = name + self.data = data + + def __repr__(self): + return 'msg: ' + self.name + ' val: ' + self.data + + def copy(self, msg): + self.name = msg.name + self.data = msg.data + +class instrument: + """rs232 port""" + + def __init__(self, + port, + msg_handler, + watchdog_enabled = False, + watchdog_timeout = 2, + watchdog_periodicity = 0.5): + self.serial = serial.Serial() + self.serial.port = port + self.serial.baudrate = BAUDRATE + self.serial.parity = PARITY + self.serial.bytesize = BYTESIZE + self.serial.stopbits = STOPBITS + self.serial.xonxoff = False # disable software flow control + self.serial.timeout = TIMEOUT + self.portOpened = False + self.msg_hdlr = msg_handler + + self.watchdog_daemon = watchdog.WatchDogDaemon(watchdog_timeout, + watchdog_timeout, + watchdog_enabled) + self.watchdog_daemon.reset = self._watchdogClose #register watchdog reset function + self.closed_by_watchdog = False + + self.open() + + def open(self): + try: + self.serial.open() + self.portOpened = True + print 'comms::opening port' + except serial.SerialException: + self.portOpened = False + print 'unable to open port...' + + def close(self): + self.serial.close() + self.portOpened = False + print 'comms::closeing port' + + def dataReady(self): + if self.portOpened: + return self.serial.in_waiting + else: + return False + + def readMessages(self): + """reads serial port. creates an array of events + output: array of events: + """ + if self.closed_by_watchdog: + self.closed_by_watchdog = False + self.open() + + while self.dataReady(): + msg_str = self._read().split('_', 1) + + if msg_str[0] != '': + self.msg_hdlr(Message(*msg_str)) + self.watchdog_daemon.ping() + + def generateEvent(self, name, data = ''): + self.writeMessage(Message(name, data)) + + def writeMessage(self, m): + self._write(m.name) + if m.data != '': + self._write('_' + str(m.data)) + self._write('\n') + + def enableWatchdog(self, enable): + self.watchdog_daemon.setEnabled(enable) + + def _write(self, str): + if self.portOpened == True: + #serial expects a byte-array and not a string + self.serial.write(''.join(str).encode('utf-8', 'ignore')) + + def _read(self): + """ returns string read from serial port """ + b = '' + if self.portOpened == True: + b = self.serial.read_until() #blocks until '\n' received or timeout + + return b.decode('utf-8', 'ignore') #convert byte array to string + + def _watchdogClose(self): + self.closed_by_watchdog = True + self.close() + + def _is_number(self, s): + """ helper function to evaluate if input text represents an integer or not """ + try: + int(s) + return True + except ValueError: + return False diff --git a/my-emco/custom-m-codes/M101 b/my-emco/custom-m-codes/M101 new file mode 100644 index 0000000..c3873f9 --- /dev/null +++ b/my-emco/custom-m-codes/M101 @@ -0,0 +1,4 @@ +#!/bin/bash +# file to turn on parport pin 17 to open the spindle air valve +halcmd setp parport.0.pin-17-out True +exit 0 diff --git a/my-emco/custom-m-codes/M102 b/my-emco/custom-m-codes/M102 new file mode 100644 index 0000000..f377f79 --- /dev/null +++ b/my-emco/custom-m-codes/M102 @@ -0,0 +1,4 @@ +#!/bin/bash +# file to turn off parport pin 17 to open the spindle air valve +halcmd setp parport.0.pin-17-out False +exit 0 diff --git a/my-emco/custom-m-codes/M103 b/my-emco/custom-m-codes/M103 new file mode 100644 index 0000000..56dcc29 --- /dev/null +++ b/my-emco/custom-m-codes/M103 @@ -0,0 +1,4 @@ +#!/bin/bash +# axis reload after +axis-remote --reload +exit 0 diff --git a/my-emco/custom-m-codes/mychange.ngc b/my-emco/custom-m-codes/mychange.ngc new file mode 100644 index 0000000..ec9603c --- /dev/null +++ b/my-emco/custom-m-codes/mychange.ngc @@ -0,0 +1,19 @@ +o sub + o10 if [#<_current_tool> NE #<_selected_tool>] + ;(DEBUG, current tool: #<_current_tool>, selected_tool: #<_selected_tool>) + # = [#<_z>] ; save position + #4999 = #<_selected_tool> ; store selected tool to use M61 at start-up + G53 G0 Z87 ; rapid to tool change location + M6 + ;G53 G0 Z50 + ;M101 ; engage spindle air blower + ;G53 G0 Z35 + ;M102 ; disengage spindle air blower + ;(DEBUG, saved-z: #) + G43 + G0 Z[#] ; restore position + o10 else + (DEBUG, tool #<_selected_tool> already in spindle) + o10 endif +o endsub +M2 \ No newline at end of file diff --git a/my-emco/custom-m-codes/myspindlestart.ngc b/my-emco/custom-m-codes/myspindlestart.ngc new file mode 100644 index 0000000..877d7b7 --- /dev/null +++ b/my-emco/custom-m-codes/myspindlestart.ngc @@ -0,0 +1,9 @@ +o sub + o10 if [#<_spindle_on> EQ 1] + M5 + o10 else + S800 + M3 + o10 endif +o endsub +M2 \ No newline at end of file diff --git a/my-emco/custom-m-codes/mytouch.ngc b/my-emco/custom-m-codes/mytouch.ngc new file mode 100644 index 0000000..aa161fd --- /dev/null +++ b/my-emco/custom-m-codes/mytouch.ngc @@ -0,0 +1,30 @@ +o sub + o10 if [EXISTS[#<_hal[jog-axis-sel]>]] + # = #<_hal[jog-axis-sel]> + ;(DEBUG, my-mpg.axis-selector: #) + + (touch off z axis) + o20 if [# EQ 0] + G10 L20 P0 Z0 + M103 + (MSG, touch off z axis) + o20 endif + + (touch off y axis) + o30 if [# EQ 1] + G10 L20 P0 Y0 + M103 + (MSG, touch off y axis) + o30 endif + + (touch off x axis) + o40 if [# EQ 2] + G10 L20 P0 X0 + M103 + (MSG, touch off x axis) + o40 endif + o10 else + (DEBUG, didn't exist) + o10 endif +o endsub +M2 \ No newline at end of file diff --git a/my-emco/custom.hal b/my-emco/custom.hal new file mode 100644 index 0000000..9d5e70d --- /dev/null +++ b/my-emco/custom.hal @@ -0,0 +1,140 @@ +# Include your customized HAL commands here +# This file will not be overwritten when you run stepconf again + +loadrt scale count=2 # 0 used to convert spindle rps-to-rpm, 1 used to scale jog-scale output from classicladder +loadrt near names=spindle-at-speed +loadrt pid names=spindle-speed-ctrl +loadrt limit2 names=spindle-ramp +loadrt not count=1 # used for ramping spindle speed +loadrt and2 count=3 # and2.0 used for MPG run-button, and2.1 for e-stop-chain, and2.2 for setting tool after homeing +loadrt classicladder_rt +loadrt conv_float_u32 # user in postgui for converting analog signal to integer + +loadusr -Wn my-mpg python serialEventHandler.py --port=/dev/ttyUSB0 -c my-mpg mpg.xml +loadusr -Wn my-encoder python fake_encoder.py --port=/dev/ttyS0 -c my-encoder +loadusr classicladder test_ladder.clp --nogui + +addf scale.0 servo-thread +addf scale.1 servo-thread +addf spindle-at-speed servo-thread +addf spindle-speed-ctrl.do-pid-calcs servo-thread +addf spindle-ramp servo-thread +addf not.0 servo-thread +addf and2.0 servo-thread +addf and2.1 servo-thread +addf and2.2 servo-thread +addf classicladder.0.refresh servo-thread +addf conv-float-u32.0 servo-thread + +### spindel speed ramping +setp spindle-ramp.maxv 500 +net spindle-cmd-rpm => spindle-ramp.in +net spindle-ramped <= spindle-ramp.out => pwmgen.0.value +net spindle-on not.0.in +net spindle-off not.0.out +net spindle-off spindle-ramp.load +net spindle-ramped => spindle-at-speed.in2 + +### spindle at speed monitoring ### +setp spindle-at-speed.scale 0.98 +setp spindle-at-speed.difference 10 +setp scale.0.gain 60 # rps to rpm +setp scale.0.offset 0 + +net spindle-velocity-rpm <= scale.0.out +net spindle-cmd-rpm => spindle-at-speed.in1 +#net spindle-velocity-rpm => spindle-at-speed.in2 +net spindle-ramped => spindle-at-speed.in2 +net spindle-ready <= spindle-at-speed.out => spindle.0.at-speed + +### encoder ### +net spindle-position my-encoder.position => spindle.0.revs +net spindle-velocity-rps my-encoder.velocity +net spindle-velocity-rps => spindle.0.speed-in +net spindle-velocity-rps => scale.0.in +net spindle-index-enable my-encoder.index-enable <=> spindle.0.index-enable +net watchdog-enable my-encoder.watchdog-enable # connected to checkbox in postgui + +### PID ctrl ### +net spindle-cmd-rpm => spindle-speed-ctrl.command +setp spindle-speed-ctrl.Pgain 2.5 +setp spindle-speed-ctrl.Igain 3 +setp spindle-speed-ctrl.Dgain 2.3 +setp spindle-speed-ctrl.FF0 0 +setp spindle-speed-ctrl.FF1 1.5 +setp spindle-speed-ctrl.maxoutput 4500 +setp spindle-speed-ctrl.deadband 25 +#setp spindle-speed-ctrl.maxerror 1000 +setp spindle-speed-ctrl.enable 0 + +net spindle-velocity-rpm => spindle-speed-ctrl.feedback +#net spindle-speed-ctrl-out spindle-speed-ctrl.output => pwmgen.0.value +net spindle-on => spindle-speed-ctrl.enable #from spindle.0.on +net spindle-index-enable => spindle-speed-ctrl.index-enable + +### mist coolant ctrl ### +net mist-cmd iocontrol.0.coolant-mist => parport.0.pin-14-out + +### e-stop chain ### +net estop-internal <= iocontrol.0.user-enable-out +net estop-external <= parport.0.pin-13-in-not + +net estop-internal => and2.1.in0 +net estop-external => and2.1.in1 +net estop-chain and2.1.out => iocontrol.0.emc-enable-in + +## restoring tool after homing ################################################# +# note, logic assumes z-axis homed first, when x and y axis' are +# homed, all axis are done +net x-homed halui.joint.0.is-homed and2.2.in0 +net y-homed halui.joint.1.is-homed and2.2.in1 +net xy-homed and2.2.out +net xy-homed halui.mdi-command-02 +net xy-homed halui.mdi-command-03 + +### pendant ########################################## +# jog wheel connects to x, y, z axis +net mpg-jog-counts joint.0.jog-counts axis.x.jog-counts <= my-mpg.jog-counts +net mpg-jog-counts joint.1.jog-counts axis.y.jog-counts +net mpg-jog-counts joint.2.jog-counts axis.z.jog-counts + +# axis selector connects to x, y, z axis enable +net jog-axis-sel classicladder.0.s32in-00 +net jog-x-enable classicladder.0.out-00 +net jog-y-enable classicladder.0.out-01 +net jog-z-enable classicladder.0.out-02 + +net jog-axis-sel my-mpg.axis-selector +net jog-x-enable axis.x.jog-enable +net jog-y-enable axis.y.jog-enable +net jog-z-enable axis.z.jog-enable + +# jog scale selector connects to x, y, z axis jog scale and jog velocity mode +setp scale.1.gain 0.01 +setp scale.1.offset 0 +net jog-scale-100 classicladder.0.floatout-00 scale.1.in +net jog-scale-sel classicladder.0.s32in-01 +net jog-scale scale.1.out + +net jog-scale-sel my-mpg.scale-selector +net jog-scale joint.0.jog-scale axis.x.jog-scale +net jog-scale joint.1.jog-scale axis.y.jog-scale +net jog-scale joint.2.jog-scale axis.z.jog-scale + +net vel-mode-sel classicladder.0.out-03 +net vel-mode-sel joint.0.jog-vel-mode axis.x.jog-vel-mode +net vel-mode-sel joint.1.jog-vel-mode axis.y.jog-vel-mode +net vel-mode-sel joint.2.jog-vel-mode axis.z.jog-vel-mode + +### connect the buttons +# connect Func-button +net func-button my-mpg.func-btn #connected to gladevcp-component in postgui + +# connect Run button +net run-button halui.mode.auto and2.0.in0 <= my-mpg.prog-run-btn +net program-run-ok and2.0.in1 <= halui.mode.is-auto +net remote-program-run halui.program.run <= and2.0.out + +# connect E-Stop button +net estop-button halui.estop.activate <= my-mpg.estop-btn + diff --git a/my-emco/custom_postgui.hal b/my-emco/custom_postgui.hal new file mode 100644 index 0000000..0713853 --- /dev/null +++ b/my-emco/custom_postgui.hal @@ -0,0 +1,38 @@ +# Include your customized HAL commands here +# The commands in this file are run after the AXIS GUI (including PyVCP panel) starts + +### virtual panel ############################################################## + +## connect the frame-enable-signals +net xy-homed gladevcp.commands +net xy-homed gladevcp.tool +net xy-homed gladevcp.mpg +setp gladevcp.lube 1 + +## connect spindle frame +net spindle-cmd-rpm => gladevcp.spindle-ref-rpm +net spindle-velocity-rpm => gladevcp.spindle-curr-rpm +net spindle-velocity-rpm => gladevcp.spindle-rpm-hbar +#net spindle-duty pwmgen.0.curr-dc => pyvcp.spindle-pwm-duty +net spindle-ready => gladevcp.led-spindle-at-speed +net watchdog-enable gladevcp.wd-chkbtn + +## connect command frame +net tc-pos halui.mdi-command-01 <= gladevcp.tc-button +net panel-rth-button halui.mdi-command-00 <= gladevcp.rth-button + +## connect the current tool bumber +net current-tool-number iocontrol.0.tool-number => gladevcp.current-tool + +#set the gladevcp-tool-combo to show current tool at start +net set-start-tool motion.analog-out-00 conv-float-u32.0.in +net start-tool-pin conv-float-u32.0.out gladevcp.saved-tool-pin + +## connect the mpg frame +net func-button gladevcp.trigger_pin + +## connect the luber frame +net luber-acc-dist gladevcp.acc-distance +net luber-cmd gladevcp.lube-cmd +net luber-ext-req gladevcp.lube-cmd-btn +net luber-reset gladevcp.lube-reset-btn diff --git a/my-emco/emco-buttons.xml b/my-emco/emco-buttons.xml new file mode 100644 index 0000000..23113b7 --- /dev/null +++ b/my-emco/emco-buttons.xml @@ -0,0 +1,158 @@ + + + ("Helvetica",16) + + + + RIDGE + 3 + + + + + + + RIDGE + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RIDGE + 3 + + RIDGE + 2 + + + "spindle-ref-rpm" + "+4.2f" + + + + RIDGE + 2 + + + "spindle-curr-rpm" + "+4.2f" + + + + RIDGE + 2 + + + "spindle-pwm-duty" + "+4.2f" + + + + RIDGE + 2 + + + "led-spindle-at-speed" + "green" + "red" + + + + RIDGE + 2 + + + "current-tool" + "2d" + + + + + + + "wd-chkbtn" + "Watchdog Enable " + 1 + + + + diff --git a/my-emco/fake_encoder.py b/my-emco/fake_encoder.py new file mode 100644 index 0000000..ccf09da --- /dev/null +++ b/my-emco/fake_encoder.py @@ -0,0 +1,126 @@ +#! /usr/bin/python + +import sys +import comms +import hal +import getopt +import time + +class FakeEncoder: + def __init__(self, dT, scale): + self._position = 0 # scaled value from count + self._velocity = 0 # units per sec, i.e. rps + self._dT = dT # delta time between samples [s] + self._scale = scale # nbr of pulses / rev + + def clear(self): + self._position = 0 + print 'FakeEncoder::clearing position data' + + def handleEvent(self, event): + if (event.name == 'pos'): + self._velocity = float(event.data)/(self._scale*self._dT) #pos per dT to rps + self._position += float(event.data)/self._scale + + def getVelocity(self): + return self._velocity + + def getPosition(self): + return self._position + + +class HalAdapter: + def __init__(self, name, clear_cb): + self.h = hal.component(name) + self.clearCallback = clear_cb + + self.h.newpin("velocity", hal.HAL_FLOAT, hal.HAL_OUT) + self.h.newpin("position", hal.HAL_FLOAT, hal.HAL_OUT) + self.h.newpin("index-enable", hal.HAL_BIT, hal.HAL_IO) + self.h.newpin("watchdog-enable", hal.HAL_BIT, hal.HAL_IN) + self.h.ready() + + def update(self, vel, pos): + self.h['velocity'] = vel + self.h['position'] = pos + + if self.h['index-enable'] == 1: + self.clearCallback() + self.h['position'] = 0 + self.h['index-enable'] = 0 + + def isWatchdogEnabled(self): + return self.h['watchdog-enable'] + +class OptParser: + def __init__(self, argv): + self.name = 'my-encoder' # default name of component in HAL + self.port = '/dev/ttyUSB1' # default serial port to use + + self._getOptions(argv) + + def __repr__(self): + return 'name: ' + self.name + '\tport: ' + self.port + + def _getOptions(self, argv): + if argv != []: + try: + opts, args = getopt.getopt(argv, "hp:c:", ["port="]) + except getopt.GetoptError as err: + # print help information and exit: + print(err) # will print something like "option -a not recognized" + sys.exit(2) + + ### parse input command line + for o, a in opts: + if o == "-h": + self._usage() + sys.exit() + if o == "-c": + self.name = a + elif o in ("-p", "--port"): + self.port = a + else: + print o, a + assert False, "unhandled option" + + + def getName(self): + return self.name + + def getPort(self): + return self.port + + def _usage(self): + """ print command line options """ + print "usage serial_mpg.py -h -c -p/--port= \n"\ + "-c # name of component in HAL. 'mpg' default\n"\ + "-p/--port= # default serial port to use. '/dev/ttyS2' default\n"\ + "-h # print this test" + + +def main(): + optParser = OptParser(sys.argv[1:]) + componentName = optParser.getName() + portName = optParser.getPort() + + print optParser + + fakeEncoder = FakeEncoder(0.05, 500) + speedCounter = comms.instrument(portName, fakeEncoder.handleEvent, False) #serial adaptor, watchdog disabled + halAdapter = HalAdapter(componentName, fakeEncoder.clear) + + try: + while 1: + speedCounter.enableWatchdog(halAdapter.isWatchdogEnabled()) + speedCounter.readMessages() + + halAdapter.update(fakeEncoder.getVelocity(), fakeEncoder.getPosition()) + + time.sleep(0.05) + + except KeyboardInterrupt: + raise SystemExit + +if __name__ == '__main__': + main() diff --git a/my-emco/func-btn.xml b/my-emco/func-btn.xml new file mode 100644 index 0000000..db5afeb --- /dev/null +++ b/my-emco/func-btn.xml @@ -0,0 +1,6 @@ + + Touch off selected axisO<mytouch>call + Spindle Start/StopO<myspindlestart>call + Return to safe ZG53 G0 Z0 + Return to homeG53 G0 X0 Y0 Z0 + \ No newline at end of file diff --git a/my-emco/gladevcp-handler.py b/my-emco/gladevcp-handler.py new file mode 100644 index 0000000..bd9de0d --- /dev/null +++ b/my-emco/gladevcp-handler.py @@ -0,0 +1,138 @@ +#!/usr/bin/ python + +import gtk +import gobject +import linuxcnc +import hal +import hal_glib +import xml.etree.ElementTree as ET + +debug = 0 + +class ToolTableParser: + def __init__(self, f): + self.f = open(f, 'r') + self.list = gtk.ListStore(int, str, str) + self._parse_file() + self.f.close() + + def __repr__(self): + ret_str = '' + for l in self.list: + ret_str += str(l) + '\n' + return ret_str + + def get_parsed_data(self): + return self.list + + def _parse_file(self): + lines = self.f.readlines() + + i = 0 + for line in lines: + tool_nbr = line.split(' ')[0] + tool_descr = tool_nbr + ' ' + line.split(';')[1] + self.list.append([i, tool_descr, tool_nbr]) + i = i+1 + +class XmlParser: + def __init__(self, f): + self.tree = [] + self.list = gtk.ListStore(int, str, str) + + self._parse_file(f) + + def get_parsed_data(self): + return self.list + + def _parse_file(self, f): + self.tree = ET.parse(f) + root = self.tree.getroot() + + i = 0 + for func in root.iter('function'): + gcode = func.find('gcode') + + # create the LinuxCNC hal pin and create mapping dictionary binding incomming events with data and the hal pins + if gcode is not None: + self.list.append([i, func.text, gcode.text]) + i = i+1 + +class HandlerClass: + + def on_destroy(self,obj,data=None): + print "on_destroy, combobox active=%d" %(self.combo.get_active()) + self.halcomp.exit() # avoid lingering HAL component + gtk.main_quit() + + def on_changed(self, combobox, data=None): + if self.tool_combo_initiated: + model = combobox.get_model() + tool_change_cmd = 'M6' + ' ' + model[combobox.get_active()][2] + ' ' + 'G43' + self._send_mdi(tool_change_cmd) + print tool_change_cmd + + def __init__(self, halcomp, builder, useropts): + self.linuxcnc_status = linuxcnc.stat() + self.linuxcnc_cmd = linuxcnc.command() + self.halcomp = halcomp + self.builder = builder + self.useropts = useropts + + self.trigger1 = hal_glib.GPin(halcomp.newpin('trigger_pin', hal.HAL_BIT, hal.HAL_IN)) + self.trigger1.connect('value-changed', self._trigger_change) + + self.trigger2 = hal_glib.GPin(halcomp.newpin('saved-tool-pin', hal.HAL_U32, hal.HAL_IN)) + self.trigger2.connect('value-changed', self._init_tool_combo) + + func_list = XmlParser('func-btn.xml').get_parsed_data() + self.func_combo = self.builder.get_object('func-btn-combo') + self.func_combo.set_model(func_list) + self.func_combo.set_entry_text_column(1) + self.func_combo.set_active(0) + + tool_list = ToolTableParser('tool.tbl').get_parsed_data() + self.tool_combo = self.builder.get_object('tool-combo') + self.tool_combo.set_model(tool_list) + self.tool_combo.set_entry_text_column(2) + self.tool_combo.set_active(0) + + renderer_text = gtk.CellRendererText() + self.func_combo.pack_start(renderer_text, True) + self.tool_combo.pack_start(renderer_text, True) + self.tool_combo_initiated = False + + def _trigger_change(self, pin, userdata = None): + #setp gladevcp.trigger_pin 1 + #print "pin value changed to: " + str(pin.get()) + #print "pin name= " + pin.get_name() + #print "pin type= " + str(pin.get_type()) + #print "active " + str(self.combo.get_active()) + if pin.get() is True: + model = self.func_combo.get_model() + self._send_mdi(model[self.func_combo.get_active()][2]) + + def _init_tool_combo(self, pin, userdata = None): + #setp gladevcp.saved_tool_pin 2 + self.tool_combo.set_active(pin.get()) + self.tool_combo_initiated = True + + + def _ok_for_mdi(self): + self.linuxcnc_status.poll() + return not self.linuxcnc_status.estop and self.linuxcnc_status.enabled and (self.linuxcnc_status.homed.count(1) == self.linuxcnc_status.joints) and (self.linuxcnc_status.interp_state == linuxcnc.INTERP_IDLE) + + def _send_mdi(self, mdi_cmd_str): + if self._ok_for_mdi(): + self.linuxcnc_cmd.mode(linuxcnc.MODE_MDI) + self.linuxcnc_cmd.wait_complete() # wait until mode switch executed + self.linuxcnc_cmd.mdi(mdi_cmd_str) + +def get_handlers(halcomp, builder, useropts): + + global debug + for cmd in useropts: + exec cmd in globals() + + return [HandlerClass(halcomp, builder, useropts)] + diff --git a/my-emco/linuxcnc.var b/my-emco/linuxcnc.var new file mode 100644 index 0000000..cd15c49 --- /dev/null +++ b/my-emco/linuxcnc.var @@ -0,0 +1,120 @@ +4999 7.000000 +5161 0.000000 +5162 0.000000 +5163 0.000000 +5164 0.000000 +5165 0.000000 +5166 0.000000 +5167 0.000000 +5168 0.000000 +5169 0.000000 +5181 0.000000 +5182 0.000000 +5183 0.000000 +5184 0.000000 +5185 0.000000 +5186 0.000000 +5187 0.000000 +5188 0.000000 +5189 0.000000 +5210 0.000000 +5211 0.000000 +5212 0.000000 +5213 0.000000 +5214 0.000000 +5215 0.000000 +5216 0.000000 +5217 0.000000 +5218 0.000000 +5219 0.000000 +5220 1.000000 +5221 23.092490 +5222 -2.040103 +5223 -105.797929 +5224 0.000000 +5225 0.000000 +5226 0.000000 +5227 0.000000 +5228 0.000000 +5229 0.000000 +5230 0.000000 +5241 -6.758898 +5242 32.897192 +5243 -21.519200 +5244 0.000000 +5245 0.000000 +5246 0.000000 +5247 0.000000 +5248 0.000000 +5249 0.000000 +5250 0.000000 +5261 25.241102 +5262 32.897192 +5263 -21.519200 +5264 0.000000 +5265 0.000000 +5266 0.000000 +5267 0.000000 +5268 0.000000 +5269 0.000000 +5270 0.000000 +5281 57.241102 +5282 32.897192 +5283 -21.519200 +5284 0.000000 +5285 0.000000 +5286 0.000000 +5287 0.000000 +5288 0.000000 +5289 0.000000 +5290 0.000000 +5301 0.000000 +5302 0.000000 +5303 0.000000 +5304 0.000000 +5305 0.000000 +5306 0.000000 +5307 0.000000 +5308 0.000000 +5309 0.000000 +5310 0.000000 +5321 0.000000 +5322 0.000000 +5323 0.000000 +5324 0.000000 +5325 0.000000 +5326 0.000000 +5327 0.000000 +5328 0.000000 +5329 0.000000 +5330 0.000000 +5341 0.000000 +5342 0.000000 +5343 0.000000 +5344 0.000000 +5345 0.000000 +5346 0.000000 +5347 0.000000 +5348 0.000000 +5349 0.000000 +5350 0.000000 +5361 0.000000 +5362 0.000000 +5363 0.000000 +5364 0.000000 +5365 0.000000 +5366 0.000000 +5367 0.000000 +5368 0.000000 +5369 0.000000 +5370 0.000000 +5381 0.000000 +5382 0.000000 +5383 0.000000 +5384 0.000000 +5385 0.000000 +5386 0.000000 +5387 0.000000 +5388 0.000000 +5389 0.000000 +5390 0.000000 diff --git a/my-emco/luber.hal b/my-emco/luber.hal new file mode 100644 index 0000000..53cefb4 --- /dev/null +++ b/my-emco/luber.hal @@ -0,0 +1,16 @@ +loadusr -Wn my-luber python luber.py -c my-luber luber.xml + +net x-vel joint.0.vel-cmd => my-luber.x-vel +net y-vel joint.1.vel-cmd => my-luber.y-vel +net z-vel joint.2.vel-cmd => my-luber.z-vel + +setp my-luber.lube-level-ok 1 +net iocontrol.0.lube-level <= my-luber.lube-level-alarm + +net luber-reset my-luber.reset +net luber-cmd my-luber.lube-cmd => parport.0.pin-09-out + +## create the gui signals +net luber-acc-dist my-luber.accumulated-distance +net luber-ext-req my-luber.lube-ext-req +net luber-reset my-luber.reset \ No newline at end of file diff --git a/my-emco/luber.py b/my-emco/luber.py new file mode 100644 index 0000000..4f2de05 --- /dev/null +++ b/my-emco/luber.py @@ -0,0 +1,234 @@ +#! /usr/bin/python + +import sys +import getopt +import xml.etree.ElementTree as ET +import hal +import time +from collections import namedtuple + +class Pin: + """ Representation of a Pin and it's data""" + def __init__(self, type, dir): + self.val = 0 # current value of pin, e.g. 1 - on, 0 - off + self.type = type # type (string read from xml) + self.dir = dir + + def __repr__(self): + return 'val: ' + str(self.val) + '\ttype: ' + self.type + '\tdir: ' + self.dir + +class HalAdapter: + def __init__(self, name): + self.h = hal.component(name) + self.h.newpin("velocity", hal.HAL_FLOAT, hal.HAL_OUT) + self.h.newpin('x-vel', hal.HAL_FLOAT, hal.HAL_IN) + self.h.newpin('y-vel', hal.HAL_FLOAT, hal.HAL_IN) + self.h.newpin('z-vel', hal.HAL_FLOAT, hal.HAL_IN) + self.h.newpin('lube-level-ok',hal.HAL_BIT, hal.HAL_IN) + self.h.newpin('reset', hal.HAL_BIT, hal.HAL_IN) + self.h.newpin('lube-ext-req', hal.HAL_BIT, hal.HAL_IN) + self.h.newpin('lube-cmd', hal.HAL_BIT, hal.HAL_OUT) + self.h.newpin('lube-level-alarm', hal.HAL_BIT, hal.HAL_OUT) + self.h.newpin('accumulated-distance', hal.HAL_FLOAT, hal.HAL_OUT) + self.h.ready() + + def __repr__(self): + tmp_str = '' + return tmp_str + + def is_lube_level_ok(self): + return self.h['lube-level-ok'] + + def is_reset(self): + return self.h['reset'] + + def is_lube_ext_req(self): + return self.h['lube-ext-req'] + + def get_velocities(self): + velocities = namedtuple("velocities", ["x", "y", "z"]) + return velocities( + self.h['x-vel'], + self.h['y-vel'], + self.h['z-vel']) + + def set_lube_on(self, request): + if request >= 1: + self.h['lube-cmd'] = 1 + else: + self.h['lube-cmd'] = 0 + + def set_lube_level_alarm(self, level_ok): + if level_ok >= 1: + self.h['lube-level-alarm'] = 1 + else: + self.h['lube-level-alarm'] = 0 + + def set_accumulated_distance(self, d): + self.h['accumulated-distance'] = d + +class parameterContainer: + def __init__(self, xml_file): + self.paramDict = {} + self._xmlFile = xml_file + + self.tree = ET.parse(self._xmlFile) + self._parse() + + def _parse(self): + root = self.tree.getroot() + for param in root.iter('parameter'): + #print param.attrib['name'], param.attrib['value'] + self.paramDict[param.attrib['name']] = float(param.attrib['value']) + + def getParam(self, name): + if name in self.paramDict: + return self.paramDict[name] + else: + return None + + def getParams(self): + return self.paramDict + + def writeToFile(self): + for parName in self.paramDict: + self._writeToTree(parName, self.paramDict[parName]) + + self.tree.write(self._xmlFile) + + def writeParam(self, parName, value): + if parName in self.paramDict: + self.paramDict[parName] = value + + def _writeToTree(self, parName, value): + """update parameter in xml-tree""" + root = self.tree.getroot() + + for param in root.iter('parameter'): + if param.attrib['name'] == parName: + param.attrib['value'] = str(round(value, 2)) + break + + +class LubeControl: + def __init__(self, lube_on_time, accumulated_distance, distance_threshold, number_of_lubings): + self.lubeOnTime = lube_on_time # [sec] + self.total_distance = accumulated_distance # [mm] + self.distance_threshold = distance_threshold # [mm] + self.numberOfLubings = number_of_lubings + + self.state = 'OFF' + self.lubeLevelOkOut = True + self._lubeLevelOkIn = True + self.prev_time = time.time() + + def calc_dist_from_vel(self, v_x, v_y, v_z): + current_time = time.time() + time_delta = current_time - self.prev_time + + self.total_distance += abs(v_x) * time_delta + self.total_distance += abs(v_y) * time_delta + self.total_distance += abs(v_z) * time_delta + + self.prev_time = current_time + + + def runStateMachine(self, ext_req): + currentTime = time.time() + if self.total_distance >= self.distance_threshold or ext_req == True: + self.state = 'ON' + self.timeout = self.lubeOnTime + currentTime + self.total_distance = 0 + self.numberOfLubings += 1 + + if self.state == 'ON': + if currentTime > self.timeout: + #check lube pressure sensor + self.lubeLevelOkOut = self._lubeLevelOkIn + + self.state = 'OFF' + + + def setLubeLevelOK(self, levelOk): + self._lubeLevelOkIn = levelOk + + def reset(self): + self.total_distance = 0 + self.state = 'OFF' + self.lubeLevelOkOut = True + +def _usage(): + """ print command line options """ + print "usage luber.py -h -c in_file.xml\n"\ + "in_file # input xml-file describing what knobs and/or button are on the pendant\n"\ + "-c # name of component in HAL. 'my-luber' default\n"\ + "-h # Help test" + +def main(): + xmlFile = 'luber.xml' + #xmlFile = '' + name = 'my-luber' # default name of component in HAL + + try: + opts, args = getopt.getopt(sys.argv[1:], "hc:", ["input="]) + except getopt.GetoptError as err: + # print help information and exit: + print(err) # will print something like "option -a not recognized" + sys.exit(2) + + for o, a in opts: + if o == "-h": + _usage() + sys.exit() + elif o == "-c": + name = a + elif o == "--input": + xmlFile = a + else: + print o + assert False, "unhandled option" + + if xmlFile == '': + if len(sys.argv) < 2: + _usage() + sys.exit(2) + else: + xmlFile = sys.argv[-1] + + p = parameterContainer(xmlFile) + + h = HalAdapter(name) + + totalDistance = p.getParam('totalDistance') + distanceThreshold = p.getParam('distanceThreshold') + lubeOnTime = p.getParam('lubePulseTime') + nbrOfLubings = p.getParam('numberOfLubings') + + lubeCtrl = LubeControl(lubeOnTime, totalDistance, distanceThreshold, nbrOfLubings) + + try: + while 1: + if h.is_reset(): + lubeCtrl.reset() + + lubeCtrl.setLubeLevelOK(h.is_lube_level_ok()) + v = h.get_velocities() + lubing_external_request = h.is_lube_ext_req() + lubeCtrl.calc_dist_from_vel(v.x, v.y, v.z) + + lubeCtrl.runStateMachine(lubing_external_request) + + h.set_lube_on(lubeCtrl.state == 'ON') + h.set_lube_level_alarm(lubeCtrl.lubeLevelOkOut) + h.set_accumulated_distance(lubeCtrl.total_distance) + + time.sleep(0.1) + + except KeyboardInterrupt: + p.writeParam('totalDistance', lubeCtrl.total_distance) + p.writeParam('numberOfLubings', lubeCtrl.numberOfLubings) + p.writeToFile() + raise SystemExit + +if __name__ == '__main__': + main() diff --git a/my-emco/luber.xml b/my-emco/luber.xml new file mode 100644 index 0000000..e4d8adf --- /dev/null +++ b/my-emco/luber.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/my-emco/mpg.xml b/my-emco/mpg.xml new file mode 100644 index 0000000..fdbaae0 --- /dev/null +++ b/my-emco/mpg.xml @@ -0,0 +1,8 @@ + + "jog-counts"jogs32 + "axis-selector"selas32 + "scale-selector"selss32 + "func-btn"funcbit + "prog-run-btn"runbit + "estop-btn"estbit + diff --git a/my-emco/my-emco.hal b/my-emco/my-emco.hal new file mode 100644 index 0000000..40a76d5 --- /dev/null +++ b/my-emco/my-emco.hal @@ -0,0 +1,110 @@ +# Generated by stepconf 1.1 at Mon Nov 4 14:01:20 2019 +# If you make changes to this file, they will be +# overwritten when you run stepconf again +loadrt [KINS]KINEMATICS +#autoconverted trivkins +loadrt [EMCMOT]EMCMOT base_period_nsec=[EMCMOT]BASE_PERIOD servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS +loadrt hal_parport cfg="0 out" +setp parport.0.reset-time 5000 +loadrt stepgen step_type=0,0,0 +loadrt pwmgen output_type=0 + +addf parport.0.read base-thread +addf stepgen.make-pulses base-thread +addf pwmgen.make-pulses base-thread +addf parport.0.write base-thread +addf parport.0.reset base-thread + +addf stepgen.capture-position servo-thread +addf motion-command-handler servo-thread +addf motion-controller servo-thread +addf stepgen.update-freq servo-thread +addf pwmgen.update servo-thread + +net spindle-cmd-rpm <= spindle.0.speed-out +#net spindle-cmd-rpm pwmgen.0.value <= spindle.0.speed-out +net spindle-on <= spindle.0.on => pwmgen.0.enable +net spindle-pwm <= pwmgen.0.pwm + +setp pwmgen.0.pwm-freq 100.0 +setp pwmgen.0.scale 4500 #duty_cycle = (value/scale) + offset, with 1.0 meaning 100% +#setp pwmgen.0.offset 0.02 + +setp pwmgen.0.dither-pwm true + +net spindle-cmd-rpm-abs <= spindle.0.speed-out-abs +net spindle-cmd-rps <= spindle.0.speed-out-rps +net spindle-cmd-rps-abs <= spindle.0.speed-out-rps-abs +net spindle-cw <= spindle.0.forward + +net spindle-pwm => parport.0.pin-16-out +net xstep => parport.0.pin-02-out +setp parport.0.pin-02-out-reset 1 +setp parport.0.pin-03-out-invert 1 +net xdir => parport.0.pin-03-out +net ystep => parport.0.pin-04-out +setp parport.0.pin-04-out-reset 1 +net ydir => parport.0.pin-05-out +net zstep => parport.0.pin-06-out +setp parport.0.pin-06-out-reset 1 +setp parport.0.pin-07-out-invert 1 +net zdir => parport.0.pin-07-out +net spindle-cw => parport.0.pin-08-out +net home-z <= parport.0.pin-10-in-not +net home-y <= parport.0.pin-11-in-not +net home-x <= parport.0.pin-12-in-not +#net max-home-y <= parport.0.pin-11-in-not +#net max-home-x <= parport.0.pin-12-in-not + +setp stepgen.0.position-scale [JOINT_0]SCALE +setp stepgen.0.steplen 1 +setp stepgen.0.stepspace 0 +setp stepgen.0.dirhold 35000 +setp stepgen.0.dirsetup 35000 +setp stepgen.0.maxaccel [JOINT_0]STEPGEN_MAXACCEL +net xpos-cmd joint.0.motor-pos-cmd => stepgen.0.position-cmd +net xpos-fb stepgen.0.position-fb => joint.0.motor-pos-fb +net xstep <= stepgen.0.step +net xdir <= stepgen.0.dir +net xenable joint.0.amp-enable-out => stepgen.0.enable +#net max-home-x => joint.0.home-sw-in +#net max-home-x => joint.0.pos-lim-sw-in +net home-x => joint.0.home-sw-in + +setp stepgen.1.position-scale [JOINT_1]SCALE +setp stepgen.1.steplen 1 +setp stepgen.1.stepspace 0 +setp stepgen.1.dirhold 35000 +setp stepgen.1.dirsetup 35000 +setp stepgen.1.maxaccel [JOINT_1]STEPGEN_MAXACCEL +net ypos-cmd joint.1.motor-pos-cmd => stepgen.1.position-cmd +net ypos-fb stepgen.1.position-fb => joint.1.motor-pos-fb +net ystep <= stepgen.1.step +net ydir <= stepgen.1.dir +net yenable joint.1.amp-enable-out => stepgen.1.enable +#net max-home-y => joint.1.home-sw-in +#net max-home-y => joint.1.pos-lim-sw-in +net home-y => joint.1.home-sw-in + +setp stepgen.2.position-scale [JOINT_2]SCALE +setp stepgen.2.steplen 1 +setp stepgen.2.stepspace 0 +setp stepgen.2.dirhold 35000 +setp stepgen.2.dirsetup 35000 +setp stepgen.2.maxaccel [JOINT_2]STEPGEN_MAXACCEL +net zpos-cmd joint.2.motor-pos-cmd => stepgen.2.position-cmd +net zpos-fb stepgen.2.position-fb => joint.2.motor-pos-fb +net zstep <= stepgen.2.step +net zdir <= stepgen.2.dir +net zenable joint.2.amp-enable-out => stepgen.2.enable +net home-z => joint.2.home-sw-in + +# moved to custom.hal +#net estop-out <= iocontrol.0.user-enable-out +#net estop-out => iocontrol.0.emc-enable-in + +loadusr -W hal_manualtoolchange +net tool-change iocontrol.0.tool-change => hal_manualtoolchange.change +net tool-changed iocontrol.0.tool-changed <= hal_manualtoolchange.changed +net tool-number iocontrol.0.tool-prep-number => hal_manualtoolchange.number +net tool-prepare-loopback iocontrol.0.tool-prepare => iocontrol.0.tool-prepared diff --git a/my-emco/my-emco.ini b/my-emco/my-emco.ini new file mode 100644 index 0000000..ee1a850 --- /dev/null +++ b/my-emco/my-emco.ini @@ -0,0 +1,169 @@ +# This config file was created 2021-03-08 20:17:44.282043 by the update_ini script +# The original config files may be found in the /home/johan/linuxcnc/configs/my-emco/my-emco.old directory + +# Generated by stepconf 1.1 at Mon Nov 4 14:01:20 2019 +# If you make changes to this file, they will be +# overwritten when you run stepconf again + +[EMC] +# The version string for this INI file. +VERSION = 1.1 + +MACHINE = my-emco +DEBUG = 0 + +[DISPLAY] +DISPLAY = axis +EDITOR = gedit +POSITION_OFFSET = RELATIVE +POSITION_FEEDBACK = ACTUAL +ARCDIVISION = 64 +GRIDS = 10mm 20mm 50mm 100mm +MAX_FEED_OVERRIDE = 1.2 +MIN_SPINDLE_OVERRIDE = 0.5 +MAX_SPINDLE_OVERRIDE = 1.2 +DEFAULT_LINEAR_VELOCITY = 25.0 +MIN_LINEAR_VELOCITY = 0 +MAX_LINEAR_VELOCITY = 25.00 +INTRO_GRAPHIC = linuxcnc.gif +INTRO_TIME = 5 +PROGRAM_PREFIX = /home/johan/linuxcnc/nc_files +INCREMENTS = 5mm 1mm .5mm .1mm .05mm .01mm .005mm +GLADEVCP= -u ./gladevcp-handler.py ./my-emco.ui + +[FILTER] +PROGRAM_EXTENSION = .png,.gif,.jpg Greyscale Depth Image +PROGRAM_EXTENSION = .py Python Script +png = image-to-gcode +gif = image-to-gcode +jpg = image-to-gcode +py = python + +[RS274NGC] +PARAMETER_FILE=linuxcnc.var +SUBROUTINE_PATH=custom-m-codes +USER_M_PATH=custom-m-codes +REMAP=M6 modalgroup=6 ngc=mychange + +[EMCMOT] +EMCMOT = motmod +COMM_TIMEOUT = 1.0 +BASE_PERIOD = 35000 +SERVO_PERIOD = 1000000 + +[TASK] +TASK = milltask +CYCLE_TIME = 0.010 + +[HAL] +HALUI = halui +HALFILE = my-emco.hal +HALFILE = custom.hal +HALFILE = luber.hal +POSTGUI_HALFILE = custom_postgui.hal + +[HALUI] +# add halui MDI commands here (max 64) +MDI_COMMAND = G53 G0 X0 Y0 Z0 ;cmd 0 "return to home" used by MPG +MDI_COMMAND = G53 G0 Z88 ;cmd 1 "tool change position" +MDI_COMMAND = M61 Q#4999 G43 ;cmd 2 called after homing to set tool saved at last tool change +MDI_COMMAND = M68 E0 Q#4999 ;cmd 3 called after homing to output current tool on analog signal + +[TRAJ] +COORDINATES = XYZ +LINEAR_UNITS = mm +ANGULAR_UNITS = degree +DEFAULT_LINEAR_VELOCITY = 25.0 +MAX_LINEAR_VELOCITY = 25.00 +DEFAULT_LINEAR_ACCELERATION = 150.0 +MAX_LINEAR_ACCELERATION = 250.0 +JOINTS = 3 +HOME = 0 0 0 + +[EMCIO] +EMCIO = io +CYCLE_TIME = 0.100 +TOOL_TABLE = tool.tbl + +[KINS] +KINEMATICS = trivkins coordinates=XYZ +#This is a best-guess at the number of joints, it should be checked +JOINTS = 3 + +[AXIS_X] +MIN_LIMIT = -85.0 +MAX_LIMIT = 92.0 +MAX_VELOCITY = 23.75 +MAX_ACCELERATION = 250.0 +# vi testar att sänka MAX_ACCELERATION = 750.0 + +[JOINT_0] +TYPE = LINEAR +HOME = 0.0 +MAX_VELOCITY = 23.75 +MAX_ACCELERATION = 250.0 +STEPGEN_MAXACCEL = 300 +#MAX_ACCELERATION = 750.0 +#STEPGEN_MAXACCEL = 937.5 +SCALE = 1000.0 +FERROR = 1 +MIN_FERROR = .25 +MIN_LIMIT = -85.0 +MAX_LIMIT = 92.0 +HOME_OFFSET = 93.000000 +HOME_SEARCH_VEL = 5.500000 +HOME_LATCH_VEL = 0.500000 +HOME_IGNORE_LIMITS = YES +HOME_SEQUENCE = 1 + +[AXIS_Y] +#tog bort givarna som ändlägesstopp, används bara vid homing +#kunde lägga till lite på pos-y... +#MIN_LIMIT = -45.0 +#MAX_LIMIT = 45.0 +MIN_LIMIT = -43.0 +MAX_LIMIT = 61.0 +MAX_VELOCITY = 23.75 +#MAX_ACCELERATION = 750.0 +MAX_ACCELERATION = 250.0 + +[JOINT_1] +TYPE = LINEAR +HOME = 0.0 +MAX_VELOCITY = 23.75 +MAX_ACCELERATION = 250.0 +STEPGEN_MAXACCEL = 300 +#MAX_ACCELERATION = 750.0 +#STEPGEN_MAXACCEL = 937.5 +SCALE = 1000.0 +FERROR = 1 +MIN_FERROR = 0.25 +MIN_LIMIT = -43.0 +MAX_LIMIT = 61.0 +HOME_OFFSET = 46.000000 +HOME_SEARCH_VEL = 5.500000 +HOME_LATCH_VEL = 0.500000 +HOME_IGNORE_LIMITS = YES +HOME_SEQUENCE = 1 + +[AXIS_Z] +MIN_LIMIT = -112.0 +MAX_LIMIT = 88.0 +MAX_VELOCITY = 5.0 +MAX_ACCELERATION = 25.0 + +[JOINT_2] +TYPE = LINEAR +HOME = 0.0 +MAX_VELOCITY = 5.0 +MAX_ACCELERATION = 25.0 +STEPGEN_MAXACCEL = 31.25 +SCALE = 1000.0 +FERROR = 1 +MIN_FERROR = .25 +MIN_LIMIT = -112.0 +MAX_LIMIT = 88.0 +HOME_OFFSET = 0.0 +HOME_SEARCH_VEL = 2.500000 +HOME_LATCH_VEL = 0.500000 +HOME_SEQUENCE = 0 diff --git a/my-emco/my-emco.ui b/my-emco/my-emco.ui new file mode 100644 index 0000000..14a7883 --- /dev/null +++ b/my-emco/my-emco.ui @@ -0,0 +1,508 @@ + + + + + + + Goto Machine Zero + G53 G0 X0 Y0 Z0 + + + G53 G0 Z88 + + + False + + + True + False + 2 + + + True + False + 0 + + + True + False + 5 + 5 + 12 + 12 + + + True + False + 5 + True + + + True + False + #bebebebebebe + 6000 + 1 + 0.019999999552965164 + 0.69999998807907104 + #0000ffff0000 + 0.89999997615814209 + #ffffffff0000 + #ffff00000000 + + + False + True + 0 + + + + + True + False + 3 + 2 + + + True + False + Spindle cmd: + + + + + True + False + label + 1 + %.1f rpm + + + 1 + 2 + + + + + True + False + Spindle actual: + + + 1 + 2 + + + + + True + False + label + 1 + %.1f rpm + + + 1 + 2 + 1 + 2 + + + + + True + False + Spindle at Speed: + + + 2 + 3 + + + + + True + False + 0 + #ffff00000000 + #0000ffff0000 + + + 1 + 2 + 2 + 3 + + + + + True + True + 1 + + + + + Spindle Feedback Watchdog Enable + True + True + False + True + True + + + True + False + 2 + + + + + + + + + True + False + <b>Spindle</b> + True + + + + + False + False + 0 + + + + + True + False + 0 + + + True + False + 5 + 5 + 12 + 12 + + + True + False + 2 + 2 + + + True + False + Current Tool: + + + + + True + False + label + + + 1 + 2 + + + + + True + False + Set Tool: + + + 1 + 2 + + + + + True + False + 0 + on + 0 + + + + + 1 + + + + + 1 + 2 + 1 + 2 + + + + + + + + + True + False + <b>Tool</b> + True + + + + + False + False + 1 + + + + + True + False + 0 + + + True + False + 5 + 5 + 12 + 12 + + + True + False + 20 + True + + + Goto Machine Zero + True + True + True + + + True + True + 0 + + + + + Goto Tool Change Position + True + True + True + + + True + True + 1 + + + + + + + + + True + False + <b>Commands</b> + True + + + + + False + False + 2 + + + + + True + False + 0 + + + True + False + 5 + 5 + 12 + 12 + + + True + False + 2 + + + True + False + Func Button: + + + + + True + False + 0 + 0 + + + + 1 + + + + + 1 + 2 + + + + + + + + + True + False + <b>MPG</b> + True + + + + + False + False + 3 + + + + + True + False + 0 + + + True + False + 5 + 5 + 12 + 12 + + + True + False + 3 + 2 + 20 + True + + + True + False + Accumulated Distance: + + + + + True + False + Lube Pump: + + + 1 + 2 + + + + + Reset Saved Data + True + True + True + + + 2 + 3 + + + + + True + False + label + 1 + %.2f mm + + + 1 + 2 + + + + + True + False + 0 + #ffff00000000 + #0000ffff0000 + + + 1 + 2 + 1 + 2 + + + + + Lube + True + True + True + + + 1 + 2 + 2 + 3 + + + + + + + + + True + False + <b>Lube</b> + True + + + + + False + False + 4 + + + + + + diff --git a/my-emco/postgui_backup.hal b/my-emco/postgui_backup.hal new file mode 100644 index 0000000..bc69494 --- /dev/null +++ b/my-emco/postgui_backup.hal @@ -0,0 +1,4 @@ +# Include your customized HAL commands here +# The commands in this file are run after the AXIS GUI (including PyVCP panel) starts + +sets spindle-at-speed true diff --git a/my-emco/serialEventHandler.py b/my-emco/serialEventHandler.py new file mode 100644 index 0000000..e9916cb --- /dev/null +++ b/my-emco/serialEventHandler.py @@ -0,0 +1,281 @@ +#! /usr/bin/python +"""usage serialEventHanlder.py -h -c -d/--debug= -p/--port= in_file.xml +in_file - input xml-file describing what knobs and/or button are on the pendant +-c # name of component in HAL. 'my-mpg' default +-d/--debug= # debug level, default 0 +-p/--port= # serial port to use. '/dev/ttyUSB0' default +-h # Help +python serialEventHandler.py -w mpg_pendant/config/mpg.xml +""" + +### https://docs.python.org/2/library/xml.etree.elementtree.html + +import time +import getopt +import sys +import comms +import xml.etree.ElementTree as ET +import hal + +class Pin: + """ Representation of a Pin and it's data""" + def __init__(self, name, type): + self.name = name # HAL pin name + self.val = 0 # current value of pin, e.g. 1 - on, 0 - off + self.type = type # type (string read from xml) + + def __repr__(self): + return 'pin name: ' + self.name + '\tval: ' + str(self.val) + '\ttype: ' + self.type + +class ComponentWrapper: + def __init__(self, name): + self.pin_dict = {} # dictionary used to map event to pin + self.hal = hal.component(name) # instanciate the HAL-component + + def __repr__(self): + tmp_str = '' + for k in self.pin_dict: + tmp_str += 'event: ' + k + '\t' + str(self.pin_dict[k]) + '\n' + return tmp_str + + def __getitem__(self, name): + if name in self.pin_dict: + return self.pin_dict[name].val + + def __setitem__(self, name, val): + self.set_pin(name, val) + + def add_pin(self, name, hal_name, type): + self.pin_dict[name] = Pin(hal_name, type) + self._add_hal_pin(hal_name, type) + + def event_set_pin(self, event): + """ updates pin value with new data + input: pin name, set value' + output: nothing. """ + if event.name in self.pin_dict: + try: + self.pin_dict[event.name].val = self._type_saturate(self.pin_dict[event.name].type, int(event.data)) + except ValueError: + print 'bad event' + + + def set_pin(self, name, value): + """ updates pin value with new data + input: pin name, set value' + output: nothing. """ + if name in self.pin_dict: + try: + self.pin_dict[name].val = self._type_saturate(self.pin_dict[name].type, int(value)) + except ValueError: + print 'bad event' + + def setReady(self): + self.hal.ready() + + def update_hal(self): + for key in self.pin_dict: + self.hal[self.pin_dict[key].name] = self.pin_dict[key].val + + def _add_hal_pin(self, hal_name, type): + self.hal.newpin(hal_name, self._get_hal_type(type), hal.HAL_OUT) # create the user space HAL-pin + + def _type_saturate(self, type, val): + """ helper function to convert type read from xml to HAL-type """ + retVal = 0 + + if type == 'bit': + if val >= 1: + retVal = 1 + + if type == 'float': + retVal = val + + if type == 's32': + retVal = val + + if type == 'u32': + retVal = val + + return retVal + + def _get_hal_type(self, str): + """ helper function to convert type read from xml to HAL-type """ + retVal = '' + + if str == 'bit': + retVal = hal.HAL_BIT + + if str == 'float': + retVal = hal.HAL_FLOAT + + if str == 's32': + retVal = hal.HAL_S32 + + if str == 'u32': + retVal = hal.HAL_U32 + return retVal + +class OptParser: + def __init__(self, argv): + self.xml_file = '' # input xml-file describing what knobs and/or button are on the pendant + self.name = 'my-mpg' # default name of component in HAL + self.port = '/dev/ttyUSB0' # default serial port to use + self.watchdog_reset = False + + self._get_options(argv) + + def __repr__(self): + return 'xml_file: ' + self.xml_file + '\tname: ' + self.name + '\tport: ' + self.port + + def _get_options(self, argv): + try: + opts, args = getopt.getopt(argv, "hwp:c:", ["input=", "port="]) + except getopt.GetoptError as err: + # print help information and exit: + print(err) # will print something like "option -a not recognized" + sys.exit(2) + + ### parse input command line + for o, a in opts: + if o == "-h": + self._usage() + sys.exit() + if o == "-c": + self.name = a + elif o == "--input": + self.xml_file = a + elif o in ("-p", "--port"): + self.port = a + elif o == "-w": + self.watchdog_reset = True + else: + print o, a + assert False, "unhandled option" + + if self.xml_file == '': + if len(sys.argv) < 2: + self._usage() + sys.exit(2) + else: + self.xml_file = argv[-1] + + def get_name(self): + return self.name + + def get_port(self): + return self.port + + def get_XML_file(self): + return self.xml_file + + def get_watchdog_reset(self): + return self.watchdog_reset + + def _usage(self): + """ print command line options """ + print "usage serialEventHandler.py -h -c -d/--debug= -p/--port= in_file.xml\n"\ + "in_file - input xml-file describing what knobs and/or button are on the pendant\n"\ + "-c # name of component in HAL. 'mpg' default\n"\ + "-p/--port= # default serial port to use. '/dev/ttyS2' default\n"\ + "-w # start watchdog deamon" \ + "-h # Help test" + +class XmlParser: + def __init__(self, f): + self.tree = [] + self.pin_dict = {} + + self._parse_file(f) + + def __repr__(self): + tmp_str = '' + + for k in self.pin_dict: + tmp_str += 'event: ' + k + '\t' + str(self.pin_dict[k]) + '\n' + return tmp_str + + def get_parsed_data(self): + return self.pin_dict + + def _parse_file(self, f): + self.tree = ET.parse(f) + root = self.tree.getroot() + + for halpin in root.iter('halpin'): + type = halpin.find('type') + event = halpin.find('event') + + # create the LinuxCNC hal pin and create mapping dictionary binding incomming events with data and the hal pins + if type is not None and event is not None: + if self._check_supported_HAL_type(type.text) == True: + self.pin_dict[event.text] = Pin(halpin.text.strip('"'), type.text) + + def _check_supported_HAL_type(self, str): + """ helper function to check if type is supported """ + retVal = False + + if str == 'bit' or str == 'float' or str == 's32' or str == 'u32': + retVal = True + + return retVal + +class BrokeeContainer: + def __init__(self, handler, args): + self.handler = handler + self.args = args + +class EventBroker: + def __init__(self): + self.brokee_dict = {} + self.received_event = comms.Message('') + + def attach_handler(self, event_name, handler, args = ()): + self.brokee_dict[event_name] = BrokeeContainer(handler, args) + + def handle_event(self, event): + self.received_event.copy(event) + + if event.name in self.brokee_dict: + self.brokee_dict[event.name].handler(*self.brokee_dict[event.name].args) + +################################################ +def main(): + optParser = OptParser(sys.argv[1:]) + componentName = optParser.get_name() + portName = optParser.get_port() + xmlFile = optParser.get_XML_file() + print optParser + + xmlParser = XmlParser(xmlFile) + + c = ComponentWrapper(componentName) #HAL adaptor + eventBroker = EventBroker() #maps incomming events to the correct handler + serialEventGenerator = comms.instrument(portName, eventBroker.handle_event, True, 5, 1) #serial adaptor + + # add/create the HAL-pins from parsed xml and attach them to the adaptor event handler + parsedXmlDict = xmlParser.get_parsed_data() + for key in parsedXmlDict: + c.add_pin(key, parsedXmlDict[key].name, parsedXmlDict[key].type) + eventBroker.attach_handler(key, c.event_set_pin, args = (eventBroker.received_event,)) + # TODO eventBroker.attach_handler(key, c.set_pin, args = (eventBroker.received_event.name, eventBroker.received_event.data)) + + print c + + # ready signal to HAL, component and it's pins are ready created + c.setReady() + + time.sleep(0.5) + + try: + while 1: + serialEventGenerator.readMessages() #blocks until '\n' received or timeout + c.update_hal() + + time.sleep(0.1) + + except KeyboardInterrupt: + raise SystemExit + +if __name__ == '__main__': + main() diff --git a/my-emco/test_ladder.clp b/my-emco/test_ladder.clp new file mode 100644 index 0000000..85bb750 --- /dev/null +++ b/my-emco/test_ladder.clp @@ -0,0 +1,301 @@ +_FILES_CLASSICLADDER +_FILE-rung_1.csv +#VER=2.0 +#LABEL= +#COMMENT= +#PREVRUNG=2 +#NEXTRUNG=2 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 +99-0-0/0 , 99-0-0/0 , 20-0-0/2 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 50-0-60/0 +99-0-0/0 , 99-0-0/0 , 20-0-0/3 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 50-0-60/1 +99-0-0/0 , 99-0-0/0 , 20-0-0/4 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 50-0-60/2 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 +_/FILE-rung_1.csv +_FILE-sections.csv +#VER=1.0 +#NAME000=Prog1 +000,0,-1,1,2,0 +_/FILE-sections.csv +_FILE-counters.csv +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +_/FILE-counters.csv +_FILE-arithmetic_expressions.csv +#VER=2.0 +0000,@310/0@=100 +0002,@270/0@=2 +0003,@270/0@=1 +0004,@270/0@=0 +0005,@270/1@=1 +0006,@270/1@=2 +0007,@270/1@=3 +0008,@310/0@=100 +0009,@310/0@=10 +0010,@270/1@=0 +0011,@310/0@=1 +_/FILE-arithmetic_expressions.csv +_FILE-timers.csv +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +_/FILE-timers.csv +_FILE-monostables.csv +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +1,0 +_/FILE-monostables.csv +_FILE-ioconf.csv +#VER=1.0 +_/FILE-ioconf.csv +_FILE-modbusioconf.csv +#VER=1.0 +_/FILE-modbusioconf.csv +_FILE-sequential.csv +#VER=1.0 +_/FILE-sequential.csv +_FILE-symbols.csv +#VER=1.0 +%I0,%func-btn,no signal connected +%I1,%f-disabl, +%I2,%f-enable, +%I3,%f-move1, +%I4,%f-move2, +%I5,%I5, +%I6,%I6, +%I7,%I7, +%I8,%I8, +%I9,%I9, +%I10,%I10, +%I11,%I11, +%I12,%I12, +%I13,%I13, +%I14,%I14, +%Q0,%x-axis, +%Q1,%y-axis, +%Q2,%z-axis, +%Q3,%vel-mode, +%Q4,%Q4, +%Q5,%Q5, +%Q6,%Q6, +%Q7,%Q7, +%Q8,%Q8, +%Q9,%Q9, +%Q10,%Q10, +%Q11,%Q11, +%Q12,%Q12, +%Q13,%Q13, +%Q14,%Q14, +%B0,%B0, +%B1,%B1, +%B2,%B2, +%B3,%B3, +%B4,%B4, +%B5,%B5, +%B6,%B6, +%B7,%B7, +%B8,%B8, +%B9,%B9, +%B10,%B10, +%B11,%B11, +%B12,%B12, +%B13,%B13, +%B14,%B14, +%B15,%B15, +%B16,%B16, +%B17,%B17, +%B18,%B18, +%B19,%B19, +%W0,%W0, +%W1,%W1, +%W2,%W2, +%W3,%W3, +%W4,%W4, +%W5,%W5, +%W6,%W6, +%W7,%W7, +%W8,%W8, +%W9,%W9, +%W10,%W10, +%W11,%W11, +%W12,%W12, +%W13,%W13, +%W14,%W14, +%W15,%W15, +%W16,%W16, +%W17,%W17, +%W18,%W18, +%W19,%W19, +%IW0,%sel-a, +%IW1,%sel-s, +%IW2,%IW2, +%IW3,%IW3, +%IW4,%IW4, +%IW5,%IW5, +%IW6,%IW6, +%IW7,%IW7, +%IW8,%IW8, +%IW9,%IW9, +%QW0,%QW0, +%QW1,%QW1, +%QW2,%QW2, +%QW3,%QW3, +%QW4,%QW4, +%QW5,%QW5, +%QW6,%QW6, +%QW7,%QW7, +%QW8,%QW8, +%QW9,%QW9, +%IF0,%IF0, +%IF1,%IF1, +%IF2,%IF2, +%IF3,%IF3, +%IF4,%IF4, +%IF5,%IF5, +%IF6,%IF6, +%IF7,%IF7, +%IF8,%IF8, +%IF9,%IF9, +%QF0,%scale, +%QF1,%QF1, +%QF2,%QF2, +%QF3,%QF3, +%QF4,%QF4, +%QF5,%QF5, +%QF6,%QF6, +%QF7,%QF7, +%QF8,%QF8, +%QF9,%QF9, +%T0,%T0,Old Timer +%T1,%T1,Old Timer +%T2,%T2,Old Timer +%T3,%T3,Old Timer +%T4,%T4,Old Timer +%T5,%T5,Old Timer +%T6,%T6,Old Timer +%T7,%T7,Old Timer +%T8,%T8,Old Timer +%T9,%T9,Old Timer +%TM0,%TM0,New Timer +%TM1,%TM1,New Timer +%TM2,%TM2,New Timer +%TM3,%TM3,New Timer +%TM4,%TM4,New Timer +%TM5,%TM5,New Timer +%TM6,%TM6,New Timer +%TM7,%TM7,New Timer +%TM8,%TM8,New Timer +%TM9,%TM9,New Timer +%M0,%M0,One-shot +%M1,%M1,One-shot +%M2,%M2,One-shot +%M3,%M3,One-shot +%M4,%M4,One-shot +%M5,%M5,One-shot +%M6,%M6,One-shot +%M7,%M7,One-shot +%M8,%M8,One-shot +%M9,%M9,One-shot +%C0,%C0,Counter +%C1,%C1,Counter +%C2,%C2,Counter +%C3,%C3,Counter +%C4,%C4,Counter +%C5,%C5,Counter +%C6,%C6,Counter +%C7,%C7,Counter +%C8,%C8,Counter +%C9,%C9,Counter +%E0,%E0,Error Flag Bit +%E1,%E1,Error Flag Bit +%E2,%E2,Error Flag Bit +%E3,%E3,Error Flag Bit +%E4,%E4,Error Flag Bit +%E5,%E5,Error Flag Bit +%E6,%E6,Error Flag Bit +%E7,%E7,Error Flag Bit +%E8,%E8,Error Flag Bit +%E9,%E9,Error Flag Bit +_/FILE-symbols.csv +_FILE-general.txt +PERIODIC_REFRESH=1 +SIZE_NBR_RUNGS=100 +SIZE_NBR_BITS=20 +SIZE_NBR_WORDS=20 +SIZE_NBR_TIMERS=10 +SIZE_NBR_MONOSTABLES=10 +SIZE_NBR_COUNTERS=10 +SIZE_NBR_TIMERS_IEC=10 +SIZE_NBR_PHYS_INPUTS=15 +SIZE_NBR_PHYS_OUTPUTS=15 +SIZE_NBR_ARITHM_EXPR=100 +SIZE_NBR_SECTIONS=10 +SIZE_NBR_SYMBOLS=160 +_/FILE-general.txt +_FILE-com_params.txt +MODBUS_MASTER_SERIAL_PORT= +MODBUS_MASTER_SERIAL_SPEED=9600 +MODBUS_MASTER_SERIAL_DATABITS=8 +MODBUS_MASTER_SERIAL_STOPBITS=1 +MODBUS_MASTER_SERIAL_PARITY=0 +MODBUS_ELEMENT_OFFSET=0 +MODBUS_MASTER_SERIAL_USE_RTS_TO_SEND=0 +MODBUS_MASTER_TIME_INTER_FRAME=100 +MODBUS_MASTER_TIME_OUT_RECEIPT=500 +MODBUS_MASTER_TIME_AFTER_TRANSMIT=0 +MODBUS_DEBUG_LEVEL=0 +MODBUS_MAP_COIL_READ=0 +MODBUS_MAP_COIL_WRITE=0 +MODBUS_MAP_INPUT=0 +MODBUS_MAP_HOLDING=0 +MODBUS_MAP_REGISTER_READ=0 +MODBUS_MAP_REGISTER_WRITE=0 +_/FILE-com_params.txt +_FILE-rung_2.csv +#VER=2.0 +#LABEL= +#COMMENT= +#PREVRUNG=1 +#NEXTRUNG=-1 +99-0-0/0 , 99-0-0/0 , 20-0-0/5 , 9-0-0/5 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 99-0-0/0 , 99-0-0/0 , 60-0-0/8 +99-0-0/0 , 99-0-0/0 , 20-0-0/6 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 99-0-0/0 , 99-0-0/0 , 60-0-0/9 +99-0-0/0 , 99-0-0/0 , 20-0-0/7 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 99-0-0/0 , 99-0-0/0 , 60-0-0/11 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/10 +99-0-0/0 , 99-0-0/0 , 20-0-0/10 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 9-0-0/0 , 50-0-60/3 +0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 0-0-0/0 , 99-1-0/0 , 99-0-0/0 , 60-0-0/0 +_/FILE-rung_2.csv +_FILE-timers_iec.csv +1,0,0 +1,0,0 +1,0,0 +1,0,0 +1,0,0 +1,0,0 +1,0,0 +1,0,0 +1,0,0 +1,0,0 +_/FILE-timers_iec.csv +_/FILES_CLASSICLADDER diff --git a/my-emco/tool.tbl b/my-emco/tool.tbl new file mode 100644 index 0000000..52a72bc --- /dev/null +++ b/my-emco/tool.tbl @@ -0,0 +1,9 @@ +T1 P1 Z-8.28607 D12 ;12mm end mill, 4 flutes +T2 P2 Z-13.3224 D6 ;6mm end mill alu, 3 flutes +T3 P3 Z-18.0309 D2.5 ;2.5mm dubhole drill +T4 P4 Z-4.83769 D1 ;1mm end mill +T5 P5 Z-11.5912 D2 ;2mm end mill, 3 flutes +T6 P6 Z0 D10 ;10mm end mill alu, 3 flutes +T8 P8 Z0 D3 ;engraver +T10 P10 Z0 D10 ;10 mm 4 flutes 20230820 +T7 P7 Z0 D4 ;4mm alu diff --git a/my-emco/watchdog.py b/my-emco/watchdog.py new file mode 100644 index 0000000..2b4148e --- /dev/null +++ b/my-emco/watchdog.py @@ -0,0 +1,94 @@ +#! /usr/bin/python +import time +import threading +import sys + +class WatchDog(): + def __init__(self, timeout): + self.timeout = timeout + self.last_ping_time = time.time() + + def ping(self): + self.last_ping_time = time.time() + + def check(self): + if time.time() - self.last_ping_time > self.timeout: + self.last_ping_time = time.time() #reset tick time + return True + else: + return False + + def insideMargin(self): + if time.time() - self.last_ping_time <= self.timeout: + return True + else: + self.last_ping_time = time.time() #reset tick time + return False + + +class WatchDogDaemon(threading.Thread): + def __init__(self, timeout, periodicity, enable = True): + self.wd = WatchDog(timeout) + self.periodicity = periodicity + self.enabled = enable + self._start() + + def _start(self): + threading.Thread.__init__(self) + self.daemon = True + self.start() + + def ping(self): + self.wd.ping() + + def run(self): + print "Starting watchdog deamon..." + while(self.enabled): + time.sleep(self.periodicity) + + if not self.wd.insideMargin(): + print "Watchdog outside margin" + self.reset() + + print "stopping watchdog deamon..." + + def setEnabled(self, enabled): + if self.enabled == False and enabled == True: + self.enabled = True + self.wd.ping() # reset tick time + self._start() + + if enabled == False: + self.enabled = False + + def reset(self): + """to be overriden by client""" + pass + + +def reset(): + print 'reset' + +def main(): + i = 0 + wdd = WatchDogDaemon(2, 0.5, False) + wdd.reset = reset + try: + while 1: + time.sleep(1) + print 'main_' + str(i) + print wdd.is_alive() + i = i+1 + + if i == 5 or i == 15: + wdd.setEnabled(True) + if i == 10: + wdd.setEnabled(False) + + wdd.ping() + + except KeyboardInterrupt: + raise SystemExit + +if __name__ == '__main__': + main() \ No newline at end of file