USB Pendant for Universal Gcode Sender (UGS) for CNC Machining

15,776

255

18

Introduction: USB Pendant for Universal Gcode Sender (UGS) for CNC Machining

I have a New Carve 1000 from CNC4Newbie and I use Universal Gcode Sender (UGS) to send my g-code to my controller. UGS has the ability to map keys to various actions, and this gives rise to the ability to have a wired or wireless "pendant" that I can use to move the machine around, run macros, etc. without having to use the main keyboard or mouse.

Originally, I found the Instructable by mr.incredible here: Wireless-UGS-Pendant. It was a great start, but I had a couple issues with the Jelly Comb numeric keypad that he used. Specifically, the program that maps keys from the keypad to the keystrokes to send to UGS had trouble reconnecting to the keypad after it went to sleep. And because it was wireless, I couldn't figure out how to keep it from going to sleep. So I switched to a wired USB keyboard that would always be on. Also, I use my machine a bit differently, so I updated the keys to follow my use case more closely. Regardless, it was a wonderful start.

Here, I'll detail how I made my pendant, including the hardware, software, keymapping for UGS, and artwork. It should be everything you need to get up and running with minimal effort.

Supplies

Step 1: Interpreting and Mapping Keys From the USB Keypad

You can do this with any keypad, but the images that I've created and the scripts are specific to the keypad in the Supplies section. Assuming you have this keypad, hook it up to your computer.

So theoretically you could just hook up the USB keypad and then map the keys in UGS to whatever functions you wanted. But the keys are common keys, like '5', that you may hit all the time, when you actually want to type a '5' and not move your y-axis, for example.

So what I did was use the program LuaMacros to intercept the keys from the USB keypad and map them to CTRL+ALT+{some letter}, which are likely not assigned to anything else and unlikely that you're going to accidentally hit them. With this program, you can capture keystrokes from a particular device like the USB keypad and ignore all keys from other devices, like your main keyboard. You could even hook up several keypads and have separate macros for each. The program will understand whether the numlock on the keypad is on, whether the fn key is being pressed, etc. It gives you a great deal of control.

Unfortunately, you need to write Lua script. What the...? Yeah, go figure. Lua script. Everybody knows Lua, right?!

Don't worry, I've done most of the heavy lifting already, and you can download my scripts in the sections below.

Step 2: Installation

To install LuaMacros just download the zip file from the Binary download section of the page and extract it. There is no install; it runs from wherever you put it.

When you run it, you'll see a window like shown above. The top section is where the Lua script goes, and the bottom section is the output.

Step 3: Finding Your USB Keypad ID

First, copy and paste the following code into the top window and press the play button (the blue triangle).

lmc_print_devices()

You'll see some output like shown in the image above.

In the output text, look for the code between the first and second ampersands. These are your keyboard identifiers.

In my case, my USB keypad is the top line:

<unassigned> : \\?\HID#VID_0603&PID_00F7&MI_00#7&D37C125&0&000#{884B96C3-56EF-11D1-BC8C-00A0C91405DD} [25494243]: keyboard

And my keyboard ID is PID_00F7.

Save these codes so you can try each of them until you get the right one. I'll show you how to do that, next.

Step 4: The Lua Script

Copy the code below into LuaMacros. (Or download the file from here.)

-- UGS Pendant
-- Todd Fjield, 09-27-2020
--lmc_print_devices()

local kbID='PID_00F7'

local key18 = 0 -- This is the Alt key
local key96 = 0
local key97 = 0
local key98 = 0
local key99 = 0
local key100 = 0
local key102 = 0
local key144set = 0


lmc_device_set_name('MACROS',kbID)

clear()

lmc.autoReload = true
lmc.minimizeToTray = true
--lmc_minimize()

-- Define callback for whole device
lmc_set_handler('MACROS',function(button,direction,ts,flags,makecode)
	if (button == 18) then key18 = direction end
	if (button == 96) then key96 = direction end
	if (button == 97) then key97 = direction end
	if (button == 98) then key98 = direction end
	if (button == 99) then key99 = direction end
	if (button == 100) then key100 = direction end
	if (button == 102) then key102 = direction end

	if (button == 144 and direction == 1) then
		if (key144set == 0) then 
			key144set = 1
		else 
			key144set = 0 
		end
	end

	if (direction == 1) then
		if     (button == 102 and key18 == 1 and key99 == 1) then withAlt(function() withCtrl(function() press('A') end) end) print('

Near the top, find this line and replace PID_00F7 with the ID of your USB keypad:

local kbID='PID_00F7'

Then press the save button (the little image of a floppy disk at the top left) and save the file as "UGS Pendant.lua". I save it in the same directory as LuaScript.exe.

) elseif (button == 96 and key18 == 1 and key100 == 1) then withAlt(function() withCtrl(function() press('B') end) end) print('(') elseif (button == 104 and key18 == 1 and key96 == 1 and key97 == 1 and key98 == 1) then withAlt(function() withCtrl(function() press('C') end) end) print('euro') elseif (button == 97 and key18 == 1 and key100 == 1) then withAlt(function() withCtrl(function() press('D') end) end) print(')') elseif (button == 101 and key18 == 1 and key96 == 1 and key97 == 1 and key102 == 1) then withAlt(function() withCtrl(function() press('E') end) end) print('yen') elseif (button == 46 and key144set == 0) then withAlt(function() withCtrl(function() press('F') end) end) print('delete') elseif (button == 27) then withAlt(function() withCtrl(function() press('G') end) end) print('esc') elseif (button == 111) then if(key144set == 1) then withAlt(function() withCtrl(function() press('H') end) end) print('/') else withAlt(function() withCtrl(function() press('I') end) end) print('numlock /') end elseif (button == 106) then if(key144set == 1) then withAlt(function() withCtrl(function() press('J') end) end) print('*') else withAlt(function() withCtrl(function() press('K') end) end) print('numlock *') end elseif (button == 109) then if(key144set == 1) then withAlt(function() withCtrl(function() press('L') end) end) print('-') else withAlt(function() withCtrl(function() press('M') end) end) print('numlock -') end elseif (button == 97 and key18 == 1 and key102 == 1) then withAlt(function() withCtrl(function() press('N') end) end) print('=') elseif (button == 36 and key144set == 1) then withAlt(function() withCtrl(function() press('O') end) end) print('home') elseif (button == 103) then withAlt(function() withCtrl(function() press('P') end) end) print('7') elseif (button == 38 and key144set == 1) then withAlt(function() withCtrl(function() press('Q') end) end) print('up') elseif (button == 104) then withAlt(function() withCtrl(function() press('R') end) end) print('8') elseif (button == 33 and key144set == 1) then withAlt(function() withCtrl(function() press('S') end) end) print('pgup') elseif (button == 105) then withAlt(function() withCtrl(function() press('T') end) end) print('9') elseif (button == 107) then -- Sending both keys so that we can perform two simultaneous functions. --if(key144set == 1) then withAlt(function() withCtrl(function() press('U') end) end) print('+') --else withAlt(function() withCtrl(function() press('V') end) end) print('numlock +') --end elseif (button == 9) then withAlt(function() withCtrl(function() press('W') end) end) print('tab') elseif (button == 37 and key144set == 1) then withAlt(function() withCtrl(function() press('X') end) end) print('left') elseif (button == 100 and key18 == 0) then withAlt(function() withCtrl(function() press('Y') end) end) print('4') elseif (button == 12 and key144set == 1) then withAlt(function() withCtrl(function() press('Z') end) end) print('no numlock 5') elseif (button == 101) then withAlt(function() withCtrl(function() withShift(function () press('A') end) end) end) print('5') elseif (button == 39 and key144set == 1) then withAlt(function() withCtrl(function() withShift(function () press('B') end) end) end) print('right') elseif (button == 102 and key18 == 0) then withAlt(function() withCtrl(function() withShift(function () press('C') end) end) end) print('6') elseif (button == 8) then withAlt(function() withCtrl(function() withShift(function () press('D') end) end) end) print('backspace') elseif (button == 35 and key144set == 1) then withAlt(function() withCtrl(function() withShift(function () press('E') end) end) end) print('end') elseif (button == 97 and key18 == 0) then withAlt(function() withCtrl(function() withShift(function () press('F') end) end) end) print('1') elseif (button == 40 and key144set == 1) then withAlt(function() withCtrl(function() withShift(function () press('G') end) end) end) print('down') elseif (button == 98 and key18 == 0) then withAlt(function() withCtrl(function() withShift(function () press('H') end) end) end) print('2') elseif (button == 34 and key144set == 1) then withAlt(function() withCtrl(function() withShift(function () press('I') end) end) end) print('pgdn') elseif (button == 99 and key18 == 0) then withAlt(function() withCtrl(function() withShift(function () press('J') end) end) end) print('3') elseif (button == 13) then -- Sending both keys so that we can perform two simultaneous functions. --if(key144set == 1) then withAlt(function() withCtrl(function() withShift(function () press('K') end) end) end) print('enter') --else withAlt(function() withCtrl(function() withShift(function () press('L') end) end) end) print('numlock enter') --end elseif (button == 45 and key144set == 1) then withAlt(function() withCtrl(function() withShift(function () press('M') end) end) end) print('insert') elseif (button == 96 and key18 == 0) then withAlt(function() withCtrl(function() withShift(function () press('N') end) end) end) print('0') elseif (button == 46 and key144set == 1) then withAlt(function() withCtrl(function() withShift(function () press('O') end) end) end) print('delete') elseif (button == 110) then withAlt(function() withCtrl(function() withShift(function () press('P') end) end) end) print('.') -- All keys above ALT+CTRL+SHIFT+P are available for sending multiple commands with a single keystroke.

end end

--print('CALLBACK :: Button ' .. button .. ', direction '..direction..', makecode: ' ..makecode.. ', flags '..flags)

if (direction == 0) then return end -- If any button is released, capture it. (Don't let ups pass through.) end)

-- Send any key (letter) as keystroke function press(key) lmc_send_input(string.byte(key), 0, 0) -- press lmc_send_input(string.byte(key), 0, 2) -- release end

-- Wraps function call with shift press function withShift(callback) lmc_send_input(16, 0, 0) -- press callback() lmc_send_input(16, 0, 2) -- release end

-- Wraps function call with alt press function withAlt(callback) lmc_send_input(18, 0, 0) -- press callback() lmc_send_input(18, 0, 2) -- release end

-- Wraps function call with alt press function withCtrl(callback) lmc_send_input(17, 0, 0) -- press callback() lmc_send_input(17, 0, 2) -- release end

Near the top, find this line and replace PID_00F7 with the ID of your USB keypad:

local kbID='PID_00F7'

Then press the save button (the little image of a floppy disk at the top left) and save the file as "UGS Pendant.lua". I save it in the same directory as LuaScript.exe.

Step 5: Testing the Script

Now, if you press the play button the script will run.

If you selected the right keyboard ID, you'll be able to press keys on the USB keypad and LuaMacros will output your keypress. (I did this for debugging purposes.) If when you hit keys on the USB keypad you don't see anything in the bottom window, replace the keyboard ID with the other keyboard and press play again to test it.

Once you're seeing a response in the bottom window, you've got LuaMacros configured properly!

Don't forget to save the file.

Step 6: Understanding the Key Mappings

The Qisan keypad is shown above, with the captured keystrokes overlayed on top of it. It's a bit small, but if you zoom in you should be able to see all the keystrokes. These keystrokes are all captured and interpreted in the Lua script to determine which keys are being pressed.

The three buttons on the top left have different functions if you hold down the fn key, and most of the other keys have different functions depending on whether or not the numlock key is pressed. Interestingly, when a single key is pressed there are often several keystrokes received. For example, when numlock is not on, pressing the 8 / up arrow key sends ascii key 144 down, 144 up, 38 down, 38 up, 144 down, 144 up. When numlock is on, the same key sends 104 down, 104 up.

I mapped each of these individual key outputs to CTRL+ALT+{some letter}, starting with a lowercase A in the top left corner (CTRL+ALT+A) all the way down to capital P on the lower right of the keypad (CTRL+ALT+SHIFT+P).

You don't actually need to know what keystroke each key is, because when you're setting up your key mappings in UGS, when you hit that key on the keypad UGS will tell you what key combination it receives. But if you want to modify the Lua script, the above mapping should help.

Step 7: Pendant Layout

I designed my pendant as follows, from top left to bottom right:

  • Calculator: Opens the calculator (this can't be overridden)
  • "G54" / $ = fn+Calculator: Calls the macro "G54" that puts the system into the G54 WCS
  • "rpm--" / '(': During sending, overrides the spindle rpm (reduces)
  • "G55" / € = fn+'(': Calls the macro "G55" that puts the system into the G55 WCS
  • "rpm++" / ')': During sending, overrides the spindle rpm (increases)
  • "G56" / ¥ = fn+')': Calls the macro "G56" that puts the system into the G56 WCS
  • fn: Function key, used to modify keys above
  • Recycle / delete: Soft reset
  • Numlock: Toggles the numlock. Various keys below have different functions if numlock is on.
  • "feed --" / esc: During sending, overrides the feed rate (reduces)
  • "feed ++" / '/' with or without numlock: During sending, overrides the feed rate (increases)
  • Spindle Toggle / '*' with or without numlock: During sending, when the send is paused, toggles the spindle on and off. Make sure to turn the spindle back on and give it time to spin back up before resuming the cut.
  • Home / numlock + '_': Homes the machine
  • Unlock / '_': Sends CTRL-X to GRBL
  • Tool Change / '=': Executes the macro "Tool Change" to move the machine to a convenient location
  • "X0" / '7': Zeroes the x-axis.
  • "G28" / home: Executes the macro "G28" that goes to the G28 position.
  • "Y0" / '8': Zeroes the y-axis.
  • "G28.1" / up arrow: Executes the macro "G28.1" to set the G28 position.
  • "Z0" / '9': Zeroes the z-axis.
  • "safe" / pgup: Executes the macro "Go To Safe" which moves the spindle back and to the left.
  • Outwards Arrows / '+': Multiplies the XY step size by 10, AND multiplies the Z step size by 10.
  • Probe / tab: Executes the macro "Full Probe Z" which executes a touch probe routine.
  • X right / '4' or left arrow: Moves the x-axis in the positive direction.
  • Y up / '5' with or without numlock: Moves the y-axis in the positive direction.
  • Z up / '6' or right arrow: Moves the z-axis in the positive direction.
  • "Stop" / back: Issues a stop command
  • X left / '1' or end: Moves the x-axis in the negative direction.
  • Y down / '2' or down arrow: Moves the y-axis in the negative direction.
  • Z down / '3' or pgdn: Moves the z-axis in the negative direction
  • Inwards Arrows / enter: Divides the XY step size by 10, AND divides the Z step size by 10.
  • Pause / '0' or insert: Pauses the send
  • Play / '.' or delete: Initiates send

There are a couple of things to consider:

  1. For some keys, like the X right (either '4' or left arrow) different codes are sent depending on whether numlock is on, but I wanted the same functionality, regardless. To get around this I mapped both keystroke (with numlock off and with numlock on) in UGS (as alternative shortcuts).
  2. Because I wanted a single keystroke to have the same effect as pressing the "Larger" or "Smaller" buttons on UGS, the '+' (Outwards Arrows) and the 'enter' (Inwards Arrows) send two keystrokes instead of just one (I send both the with and without numlock on keystrokes; you can see the comments in the Lua script) and I assign one of them to the XY and the other one to the Z for each. (There is no single keystroke in UGS to mimic hitting the "Larger" or "Smaller" buttons; it takes two to duplicate that functionality. I'm guessing that's an oversight.)

The way I use this is that I have numlock off most of the time. I only turn it on when I want to execute the rarer commands (like zeroing x, y, or z, or homing) that I don't want to hit accidentally when I'm jogging around, and then turn it off again right away. As an extra precaution, for example, I could remove the shortcut for play with numlock off, so that I would have to turn numlock on before the play button would do anything.

With the G54 / G55 / G56 commands, I tied these to the fn key so that I wouldn't accidentally change my WCS.

Step 8: Artwork

The artwork for the pendant can be downloaded below or here. The vinyl labels that I linked to above work very well and are printable with a standard inkjet printer. Then you can just cut them out with scissors or a paper shear and stick them on the keypad.

Step 9: UGS Macros

As mentioned in a previous section, several of the keystrokes are mapped to custom macros that I have written. These macros are as follows:

"G54"

G54;

"G55"

G55;

"G56"

G56;

"Change Tool"

G21;G90G53G0Z-20;G53G0X-743Y-805;G90;

"Go To G28"

G21;G91G28Z0;G28X0Y0;G90;

"Set G28"

G28.1;

"Go To Safe"

G21;G53G0Z-40;G91G0X-80Y260;G90;

"Full Probe Z"

G21;G90G53G0Z-40;G53G0X-743Y-805;G53G1Z-110F1000;G91G38.2Z-40F250;G0Z1;G38.2Z-2F50;G10L20P0Z9.06;G90G0Z20;G90;

Unfortunately, it doesn't seem like I can just export these macros for you to import, so you'll have to manually create them yourself if you want to use them.

Note that if you don't enter these macros before you import the key maps into UGS, some of the keys won't map and have any functionality. So you might consider at least creating blank macros before going any further, just to get the linkages, and then update them later.

Be careful with these macros! They are set up for specific locations on my machine that make sense for me, and they might not make sense for you.

Step 10: Mapping the Keys in UGS

Some of the keymaps for UGS are shown in the image above. You can see that some actions have multiple keystrokes assigned to them. This is so that the key works whether num lock is set or not.

You can individually map the keys yourself, or save yourself the trouble and download my keymaps here that you can directly import into UGS.

To use the file I provided, just save the zip file to disk, and then in the UGS settings click on the "Import..." button in the lower left of the settings dialog and point it to the file.

For the keymaps to set to the macros defined in the previous section, I've found that you need to define the macros and then restart UGS. Once you've restarted, you can import the Keymaps.zip file and it will map correctly to the macros.

Step 11: Test and Celebrate!

At this point, when UGS is running and LuaMacros is running the script, you should be able to press the keys on the USB keypad and UGS will execute the actions.

You'll notice in the script there is a line like this:

--lmc_minimize()

If you remove the "--" at the beginning of the line and save your script, then it will automatically minimize to the tray when it starts.

One final thing that you might consider is to create a shortcut to LuaMacros to automatically start your script. Just create a shortcut on your desktop and enter something like this for your target:

"C:\Install\LuaMacros\LuaMacros.exe" "C:\Install\LuaMacros\UGS Pendant.lua" -r

Update the paths as necessary. The "-r" flag automatically runs your script. You can put this shortcut in your startup folder and it will always be running silently in the background.

Hopefully the above has saved you some time and got you up and running with a USB pendant.

Good luck, and enjoy!

Be the First to Share

    Recommendations

    • Audio Challenge

      Audio Challenge
    • Made with Math Contest

      Made with Math Contest
    • Sewing Challenge

      Sewing Challenge

    18 Comments

    0
    mr.incredible
    mr.incredible

    6 months ago

    Very nice job. Better instructable than mine. You keys are cleaner too. The vinyl is a better choice. I would still clearcoat them they will rub off over time. I get past the sleep thing by hitting the numloc twice to wake it up. Still running on the original batteries.

    0
    Todd Fjield
    Todd Fjield

    Reply 6 months ago

    Thank you! The problem that I had with the sleeping was that HIDMacros and LuaMacros would lose the connection to the keypad when it went to sleep, so when it woke back up it wouldn't map the keys anymore. And I really don't remember why I switched to LuaMacros. Surprisingly, the vinyl is still going great, but I'll add the clearcoat if they start to fade. And thank you for your Instructible! I probably wouldn't have done this if I hadn't followed yours first.

    0
    kdosbun
    kdosbun

    8 months ago

    This is a great project and I have used several parts of your description to work on my own. I really liked the idea of using one key to increase both Z and the XY steps. I do not know Lua script so looking through your code example I am not able to determine how you are doing it. Is there a good recommendation for where to see other, simpler examples of how to do this? This instructable has really helped me with the project..

    0
    mr.incredible
    mr.incredible

    Reply 6 months ago

    There is a settng in UGS that makes Z have same value as XY

    0
    Todd Fjield
    Todd Fjield

    Reply 6 months ago

    Yes, you're right, and that's a good option of you want them to be the same. I always want my Z to be 1/10 of XY so by sending the two keystrokes I can keep that ratio.

    0
    Todd Fjield
    Todd Fjield

    Reply 8 months ago

    Thank you! I'm glad it helped!

    The portion of the code that increases both Z and XY steps at the same time is this:

    elseif (button == 107) then
    -- Sending both keys so that we can perform two simultaneous functions.

    --if(key144set == 1) then

    withAlt(function() withCtrl(function() press('U') end) end) print('+')
    --else
    withAlt(function() withCtrl(function() press('V') end) end) print('numlock +')
    --end

    The '--' comment out the line, so instead of looking at key144set to determine whether or not numlock is sent, I'm sending both the CTRL-ALT-U and CTRL-ALT-V, regardless of the state of the numlock.

    There's another section below starting with "elseif (button == 13) then" that does the same thing for the enter button to decrease the steps.

    I hope this helps!

    0
    kdosbun
    kdosbun

    Reply 8 months ago

    Got it.. thanks again.. I appreciate your help...

    0
    Max Siebenschläfer

    Can I use this thing also for Octoprint aka 3D printer?

    0
    mr.incredible
    mr.incredible

    Reply 6 months ago

    If your CNC control program handles keyboard input you can modify this. It is just a separate keyboard that has macros assigned to the keys. Anything that you can use as a computer input device can be used. I've seen the rotary jog mouse used as well.

    0
    Todd Fjield
    Todd Fjield

    Reply 1 year ago

    Sorry, I'm not sure; I'm not familiar with the Octoprint. If the software that sends the files has programmable hotkeys, then I would think you would be able to do something at least similar.

    0
    10ryanlucas
    10ryanlucas

    7 months ago on Step 11

    Has anyone been able to integrate this with CNCJS yet? https://github.com/cncjs/cncjs-pendant-keyboard looks promising - just having sunk my teeth into yet. I have just finished printing and cutting the labels - so I'm well on my way - now to get the software to work.

    let me know if anyone has tackled this part yet. cheers!

    0
    mr.incredible
    mr.incredible

    Reply 6 months ago

    Too many buttons. It is hard enough dealing with the 22 on a key pad.

    0
    ItsGraGra
    ItsGraGra

    1 year ago

    Interesting project, I did similar with mach4 on my machine. Never used it as it didn't have any feedback.. It was dangerous!
    But UGS platform already has wireless pendant functionality, as an App on your phone.

    0
    Todd Fjield
    Todd Fjield

    Reply 1 year ago

    Yeah, that's a nice Instructable you put together! It's a shame that with all that work you don't use it.

    I tried the pendant functionality of UGS, but I don't like interfacing with a tiny touchscreen. A tablet would make it better, but I prefer the physical buttons since I can feel the keys and I don't have to keep looking at the keyboard. (And I didn't want to put all the work to get a rotary pulse generator like yours to work!)

    My monitor is mounted near my machine such that I can always see it, and I think that's a must for this type of implementation.

    0
    spiritburner
    spiritburner

    1 year ago

    Very nice piece of work, sadly I dont use UGS, I have Open Builds control but would love to see something similar for that. Well done this is a great asset to anyone using UGS on their CNC. I had a jog pendant on my Mach 4 machine originally and that was great to have. thanks for posting this.

    0
    Todd Fjield
    Todd Fjield

    Reply 1 year ago

    Thank you!

    I believe you can use everything above almost as is with OpenBuilds Control, as well. You might need to uncomment a couple lines in the script so that multiple keystrokes aren't sent to increase and decrease step size, because Control doesn't have different step sizes for XY and Z. Also, since Control only allows a single key assignment per action you would probably want to modify the script so that certain button presses send the same keystrokes regardless of whether numlock is pressed. For example, the home/7 button (jog X left) sends Ctrl+Alt+O without numlock and Ctrl+Alt+P with numlock. Just change the script to send Ctrl_Alt+O in both cases, and you're all set.

    Once you have LuaMacros running, under Wizards & Tools in OpenBuilds Control select Customize Shortcut Key Assignments and you can assign several of the keys to the built-in actions, like jogging, homing, run, stop, etc.

    For the custom items (changing WCS, probing, etc.), you can create a macro for each, and in the macro editor you can assign the key to it.

    I've included a couple images for example. If you do implement this, let me know if you need any help.

    Keyboard.pngMacro.png
    0
    Todd Fjield
    Todd Fjield

    Reply 1 year ago

    Thank you, Penolopy!