CNCjs handwheel pendant (detailed howto for nerds)

Hi folks,

Recently I was inspired by this post (thanks @npross!) and bought one of these pendants that have two multi-position switches, a handwheel, and an enable pushbutton


This post is intended to document how I interfaced it with the Shapeoko, in case anyone wants to make a similar setup. This is completely overkill if you just want to trig predefined keyboard shortcuts on your favorite G-code sender, a USB or wireless keyboard will be much simpler to use. But I wanted to have the ability to map the pendant controls to run arbitrary G-code snippets on the Shapeoko.

The pendant I bought came with a wiring sheet, which spared me the effort of figuring out which wire corresponded to which part of the pendant:

(the Aliexpress link to this pendant is in the original post, but there are many variants of this around)

I use CNCjs as my main G-code sender, and there happens to be a few examples here of code to implement CNCjs pendants (they use the “Node.js” framework). CNCjs is actually a web server, and the user interface itself is just a web page running in any browser, either locally on the same machine CNCjs runs on, or on a remote computer.

I decided to use a Raspberry Pi 4 mini-computer to install CNCjs as well as the pendant. Any Raspberry Pi model could work, but the oldest models may have trouble running CNCjs efficiently.

I installed a stock “Raspbian” image on the SD card, did initial configuration, and proceeded to install CNCjs using these instructions.

I connected the Shapeoko to a USB port of the Raspberry Pi, launched CNCjs from the command line:

cncjs &

By default, CNCjs listens for incoming connections on port 8000. I was then able to access the CNCjs user interface by connecting to

http://[IP address of the raspberry Pi]:8000

So far so good.
Next, I connected the pendant wires to the Pi like this:

Left column from top to bottom
pin 9 / GND
pin 11 / GPIO17 = X
pin 13 / GPIO27 = Y
pin 15 / GPIO22 = Z
pin 19 / GPIO10 = MOSI = 4
pin 21/ GPIO9 = MISO = X1
pin 23 / GPIO11 = SCLK =X10
pin 27 / GPIO0 = ID_SD = X100
pin 29 / GPIO5 = EStop

Right column from top to bottom
pin 2 / 5V = handwheel power supply
pin 8 / GPIO14 = TXD = A
pin 10 / GPIO15 = RXD = B
pin 12 / GPIO18 = PCM_CLK = LED+

A few notes on the pendant signals:

  • 5V is used as an input voltage for the internal handwheel mechanism.
  • the “A” and “B” signals are connected to the handwheel, and allow one to determine when the wheel is turning, and in which direction. They are supplied by the 5V input, but a Raspberry Pi has 3.3V inputs, so a simple voltage divider with two resistors is used to bring voltage down to around 3.3V before it enters the Pi (I used 1200 ohm and 2200ohm resistors for this).
  • the pendant has an LED, that can be turned on and off
  • the X/Y/Z/4 signals come from the left switch/selector.
  • the X1/X10/X100 signals come from the right switch/selector.
  • there is a signal for the E-stop pushbutton
  • the pendant has an enable pushbutton on the side, but no dedicated signal, it just enables/disables other signals.

I started from the cncjs-pendant-raspi-gpio boilerplate code, and adapted it to my needs. The resulting cncjs-pendant-raspi-jogdial code is available here.

A disclaimer for web developers out there: this is my first timing coding in Javascript, so I’m sure it violates half of the best practices. I commented it (a lot) so hopefully if someone reuses it he’ll be able to figure it out easily.

What the code does is connect to the CNCjs server, and send “write” operations to send specific G-code commands to the Shapeoko (CNCjs server acts as a gateway)

The code implements this logic:

  • the enable button needs to be pushed for anything to happen. The LED on the pendant is turned on when enable is activated.
  • if wheel movement is detected
    • a short turn will trig a single jog step (e.g. G91 G0 X5)
    • a continous rotation will trig a smooth jog (e.g. by sending $J=G91 G21 X10 F2000 repeatedly)
      • stopping the rotation will trig the sending of a jog cancel command (0x85)
  • the left switch is read to determine if the jog should happen on X,Y, or Z axis
  • the right switch (X1/X10/X100) is read to determine
    • the jog step when doing a single jog step (currently mapped to 0.1mm, 1mm, and 10mm)
    • the jogging speed when doing smooth/continuous jogging (currently mapped to 100mm/min, 1000mm/min, and 5000mm/min)

Note : I implemented it all in metric units (millimeters)

For the code to work, ¨some of the Raspberry Pi GPIOs used need to be configured to activate their internal pull-up resistors, I did this by editing the file /boot/config.txt and adding this line:


(followed by a reboot)

Note: input pins 13 and 19 for signal A / signal B do not need any specific configuration, as they have an internal pull-down by default, which is the right configuration for these 5V inputs.

The pendant is then installed by copying the cncjs-pendant-raspi-jogdial directory somewhere on the pi, entering the directory and typing:

npm install

Then it’s executed using

node bin/cncjs-pendant-raspi-jogdial

and then selecting the USB port on which the Shapeoko is connected to the Raspberry Pi (/dev/ttyACM0 in my case)

And voilà:

EDIT: here are a copy of the instructions to make cncjs and the pendant code start automatically when the raspberry pi is turned on, using the “pm2” utility:

First install PM2:

sudo npm install -g pm2
pm2 startup
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u pi --hp /home/pi

Start cncjs and the pendant:

pm2 start $(which cncjs)-- --port 8000
pm2 start "node /home/pi/cncjs-pendant-raspi-jogdial/bin/cncjs-pendant-raspi-jogdial --port /dev/ttyACM0"

(you will need to adapt the /home/pi/… path to match your setup, and the serial port may not be named /dev/ttyACM0 in your case either)

Check everything is up and running, and save this configuration such that it starts automatically upon next boot:

pm2 save

I’ll work on a more permanent wiring scheme, and edit this post later accordingly.


Wow this is awesome, thanks for the detailed write up @Julien.

Is there a way to run it through a normal pc or do you need the Rpi GPIOs? A plug and play option for the masses would be cool too, maybe through an arduino?

Do you run your SO3 off the Rpi full time? If so, do you remote access the Rpi or just hook up monitor and keyboard etc and use It that way?

Thanks again for sharing, I’m sure a lot of people have wanted to look into this but weren’t sure of the best way to do it

In this prototype I need to use the Rpi GPIOs, which does make it a niche solution. In the original thread, @npross implemented a version that uses an Arduino, and emulates a keyboard, so you can then plug his version on any regular computer, which is great. However, this limits the functionality to using existing keyboard shortcuts in CNCjs (or any other sender), while I wanted to be able to send arbitrary G-code commands, and for this one needs to have a network connection to CNCjs server. Compromises…
While experimenting, I started a version running on an ESP8266 board, basically an Arduino with Wifi network connectivity, but stopped along the way since it was a pain in the neck to get the connection to CNCjs server to work. But that is definitely doable.

Up until very recently, I was running CNCjs standalone app on a laptop. I have now decided to use a dedicated Rpi to run CNCjs server, which will boot when I turn on the machine. And then connect remotely from a browser on my laptop. It’s not really useful right now, but I intend to leverage the Rpi GPIOs to control more things (feed hold, lights, camera stream…) and redo my control box around it.


mini-update: while I was pondering the most expedient way to go from the prototype breadboard setup to something usable, I stumbled upon this on Amazon:

It plugs onto the Raspberry Pi GPIO connector, has all GPIO signals available as screw terminals, and a small area of plated holes to install the few resistors for the voltage dividers.

Less soldering, less custom PCBs to create = happier me !

Now I just need to 3D print and enclosure for the Pi, this breakout board, the various incoming cables, then mount that on the side on my Shapeoko and I’ll call this done.


Absolutely brilliant @Julien and thanks for the write up😊. Just ordered a pendant so looking forward to try this.

1 Like

I am sure @Julien has already come across this to make enclosures for boards, but just in case and for all others who may have a need, here you go:


Making progress, one evening at a time.

First, I picked a random raspberry pi4 case on thingiverse, and butchered it to make this ugly-[donkey] version with enough room to accomodate the GPIO breakout board and all my wiring:

Printed it with whatever filament spool was loaded at the time :

The screw terminals made my life so much easier, I just had to solder 4 resistors on the side

(yes, RIP dupont cable I sacrified in the process)

And there you have it, the raspberry pi running CNCjs and the pendant software, wired to the pendant, and ready to be attached to the side of my enclosure.

Still need to configure the Pi for wifi, tune the jogging speeds for X1/X10/X100, and I’ll call this done.


Ooh thats tidy - That breakout board is a great find.

It’d be awesome if Carbide3D incorporated something like that into a control box that came with the Shapeoko. Just plug a keyboard, mouse and monitor into a and screen in and away you go

1 Like

I think they were doing something like that. Just connect to a network and control from whatever.

Care to share your Pi case? I just ordered the jog wheel and screw breakout board.

1 Like

Sure, here it is

obligatory disclaimer: it’s very hackish and the Fusion360 project is a mess, it’s probably only good for printing the case as is and hoping for the best :slight_smile:

1 Like

Wow that was fast!. I feel like I’m missing a step but I think i need to clone the modified code into /bin right?

into the /bin of what ?

You can clone the pendant code anywhere, go in any directory you like and do the git clone thing, it will create a cncjs-pendant-raspi-jogdial subdir, and then to launch the pendant (assuming CNCjs is already running), go into that subdir and type:

node bin/cncjs-pendant-raspi-jogdial

I wanted to pass along my thanks for this writeup. I now have a working pendant! The only issue i have is that I am still able to jog with the estop activated. Is that a feature or a bug?

1 Like

Sweet! The e-stop button is just not managed at all in the current implementation, because I had no real use for it (since I have another e-stop on my power control box already). If you think of a useful CNCjs action that you’d like mapped to the e-stop button on the pendant, let me know and I’ll add it.

Could it be used as a feedhold ?

Yes, and that’s probably the most sensible use. I’ll update the code over the week-end to support this, I had to do an update it anyway because I changed my GRBL params to greater max speeds, and need to adjust the smooth jog message timings anyway.

I’ll need instructions on how to update also please :wink:

would this work with a PC running CNCjs? :slight_smile:

the remote keypad pendant for CNCjs ? definitely, on a linux PC, possibly on a Windows PC.
I just ordered one RII remote keypad on Amazon, it was only 15 euros and I figured I would find some use for it. It’s coming in the mail tomorrow, right on time for some week-end coding.