Tube Turning Adapters

Finishing the PVC tubes reinforcing the vacuum cleaner adapters required fixtures on each end:

Dirt Devil adapter - pipe turning
Dirt Devil adapter – pipe turning

Because the tubes get epoxied into the adapters, there’s no particular need for a smooth surface finish and, in fact, some surface roughness makes for a good epoxy bond. The interior of a 3D printed adapter is nothing if not rough; the epoxy in between will be perfectly happy.

Turning the tubes started by just grabbing the conduit in the chuck and peeling the end that stuck out down to the finished diameter, because the conduit was thick-walled enough to let that work.

The remaining wall was so thin that the chuck would crunch it into a three-lobed shape, so the white ring in the chuck is a scrap of PVC pipe turned to fit the tube ID and provide enough reinforcement to keep the tube round.

The conduit ID isn’t a controlled dimension and was, in point of fact, not particularly round. It was, however, smooth, which counts for more than anything inside a tube carrying airborne fuzzy debris; polishing the interior of a lathe-bored pipe simply wasn’t going to happen.

The fixture on the other end started as a scrap of polycarbonate bandsawed into a disk with a hole center-drilled in the middle:

Pipe end lathe fixture - center drilling
Pipe end lathe fixture – center drilling

Stick it onto a disk turning fixture and sissy-cut the OD down a little smaller than the eventual tube OD:

Pipe end lathe fixture - turning OD
Pipe end lathe fixture – turning OD

Turn the end down to fit the tube ID, flip it around to center-drill the other side, stick it into the tube, and finally finish the job:

Dirt Devil adapter - pipe fixture
Dirt Devil adapter – pipe fixture

The nice layering effect along the tube probably comes from molding the conduit from recycled PVC with no particular concern for color matching.

A family portrait of the fixtures with a finished adapter:

Dirt Devil adapter - fixtures
Dirt Devil adapter – fixtures

A fine chunk of Quality Shop Time: solid modeling, 3D printing, mini-lathe turning, and even some coordinate drilling on the Sherline.

Bafang BBS02: Terry Symmetry Shift Sensor & Cable Guides

The Bafang BBS02 came with (because I added it to the order) what looks like a genuine shift (“gear”) sensor made by the original company in the Czech Republic:

Terry Bafang - shift sensor - installed
Terry Bafang – shift sensor – installed

On a typical bike, it mounts against a cable stop with the cable housing holding it in place against its other end:

Tour Easy Bafang BBS02 - shift sensor - installed
Tour Easy Bafang BBS02 – shift sensor – installed

The Terry Symmetry has only two lengths of housing: in front of the adjuster on the downtube and behind the stop brazed to the chainstay. In either position, the sensor would move as the shift cable flexed and (IMO) put unreasonable stress on the electrical cable running to the motor.

Yes, the Tour Easy has those same two lengths of housing, but the forward one joins a sheaf of wires & cables that barely moves.

Fortunately, the sensor fits neatly between stations 1 and 2 along the downtube, with a snippet of PTFE lIned housing holding it firmly in place, with the 3D printed battery mounting blocks including paths for both cables:

Terry - Bafang battery - all stations - solid model
Terry – Bafang battery – all stations – solid model

The shift cable originally ran from the adjuster in the front to the guide under the bottom bracket along a slightly diagonal path I could not possibly match. Instead, the path is now parallel to the downtube from the front adjuster:

Terry Bafang - OEM shift stop
Terry Bafang – OEM shift stop

.. to the rear block, where it angles downward over the motor to the bottom bracket:

Terry Bafang - shift cable clearance
Terry Bafang – shift cable clearance

The front block at station 1 has a Delrin / acetal bushing to align the cable with the rest of the blocks:

Terry shift guide - acetal installed
Terry shift guide – acetal installed

Yes, it’s a round peg jammed in a hexagonal hole:

Terry shift guide - acetal hole
Terry shift guide – acetal hole

Turning it from stock is well within the capabilities of Tiny Lathe™:

Terry shift guide - acetal cutoff
Terry shift guide – acetal cutoff

For great slippery, a similar UHMW PE bushing supports the cable bend at the rear of the station 4 block:

Terry shift guide - UHMWPE installed
Terry shift guide – UHMWPE installed

The Basement Laboratory Warehouse Wing disgorged an overly large rod taxing Tiny Lathe™ to its limit:

Terry shift guide - UHMWPE turning
Terry shift guide – UHMWPE turning

Memo to Self: next time, just saw off a stub and move on.

Bafang BBS02: Motor Reaction Spacer

The Terry Symmetry’s rear shift cable passes along the side of the downtube and through a plastic guide channel under the bottom bracket shell. The Bafang BBS02 motor must press against the bottom of the downtube, so the shift cable rubs against the top of the motor.

The solution is a small block shaped around the point of contact to cradle the downtube, the bottom bracket shell lug, and the motor case:

Terry - Bafang motor spacer - solid model
Terry – Bafang motor spacer – solid model

A strip of double-sided foam tape holds the block to the motor and the reaction force from the motor’s torque presses the block against the downtube:

Terry Bafang - motor reaction block
Terry Bafang – motor reaction block

Seen from the other side, looking parallel to the shift cable, you can see the tight clearance:

Terry Bafang - shift cable clearance
Terry Bafang – shift cable clearance

The block holds the motor 8 mm from the downtube, just enough to give the cable some breathing room.

The block is slightly taller on its front end, because the motor doesn’t meet the downtube at a right angle:

Terry - Bafang motor spacer - tube angle - solid model
Terry – Bafang motor spacer – tube angle – solid model

I determined the proper angle by taping waxed paper to the top of the motor, sticking a trial (non-angled) block to the downtube, coating its bottom surface with hot-melt glue, then squishing the motor against the block. The cooled glue was flush with the block on the rear and 1.8 mm thick on the front, a 5° angle over the 20 mm block.

Definitely easier than correctly figuring the geometry from first principles: tweak the model to include the measured thickness, compute the angle, tilt the tube, and print another block that fits like it grew there.

With the block in place and the motor held against the downtube, tighten the retaining nut against the “fixing plate” by giving it a few gentle whacks with a hammer, then tighten the jam nut.

The OpenSCAD source code snippet:

// Motor Reaction Block
// Holds motor away from downtube enough to miss rear shift wire

MotorOD = 111;              // motor frame dia
MotorMountRad = 85;         // BB spindle center to motor center
Space = 8.0;                // motor to frame space

Spacer = [20.0,DownTube[ID]/2,4*Space];
SpaceAngle = atan(1.8/Spacer.x);            // tilt due to non-right-angle meeting
echo(str("Spacer angle: ",SpaceAngle));

module MotorSpacer() {

    difference() {
            rotate([0,90 + SpaceAngle,0]) rotate(180/FrameSides)
        translate([DownTube[LENGTH]/2,0,DownTube[ID]/2 - DownTube[LENGTH]*sin(SpaceAngle)/2])       // concentric with ID
            rotate([0,90 + SpaceAngle,0]) rotate(180/FrameSides)
        translate([0,0,-(MotorOD/2 + Space)])
            rotate([90,0,0]) rotate(180/48)


Mary’s Tour Easy didn’t need this block, because all the cables run elsewhere, but I did capture a piece of closed-cell foam between its vestigial downtube and the motor to prevent chafing.

Tour Easy: Bafang 48 V 11.6 A·h Battery Mount

Bafang BBS02 batteries should mount on the water bottle bosses along a more-or-less standard bicycle’s downtube, which a Tour Easy recumbent has only in vestigial form. The battery does, however, fit perfectly along the lower frame tubes:

Tour Easy Bafang mid-drive - battery
Tour Easy Bafang mid-drive – battery

You might be forgiven for thinking Gardner Martin (not to be confused with Martin Gardner of Scientific American fame) designed the Tour Easy frame specifically to hold that battery, but the design dates back to the 1970s and it’s just a convenient coincidence.

The battery slides into a flat baseplate and locks in place, although it’s definitely not a high-security design. Mostly, the lock suffices to keep honest people honest and prevent the battery from vibrating loose while riding:

Tour Easy Bafang battery mount - baseplate installed
Tour Easy Bafang battery mount – baseplate installed

The flat enclosure toward the rear was obviously designed for more complex circuitry than it now contains:

Tour Easy Bafang battery mount - interior
Tour Easy Bafang battery mount – interior

Those are all neatly drilled and tapped M3 machine screw holes. The cable has no strain relief, despite the presence of suitable holes at the rear opening. I tucked the spare cable inside, rather than cut it shorter, under the perhaps unwarranted assumption they did a good job crimping / soldering the wires to the terminals.

The red frame tubes are not parallel, so each of the four mounting blocks fits in only one location. They’re identified by the side-to-side tube measurement at their centerline and directional pointers:

Bafang Battery Mount - Show bottom
Bafang Battery Mount – Show bottom

The first three blocks have a hole for the mounting screw through the battery plate. The central slot fits around the plate’s feature for the recessed screw head. The two other slots clear the claws extending downward from the battery into the plate:

Bafang Battery Mount - Show view
Bafang Battery Mount – Show view

The rear block has a flat top and a recessed screw head, because the fancy metal enclosure doesn’t have a screw hole:

Tour Easy Bafang battery mount - top detail
Tour Easy Bafang battery mount – top detail

I thought of drilling a hole through the plate, but eventually put a layer of carpet tape atop the block to encourage it to not slap around, as the whole affair isn’t particularly bendy. We’ll see how well it works on the road.

I had intended to put an aluminum plate across the bottom to distribute the clamping force from the screw, but found a suitable scrap of the institutional-grade cafeteria tray we used as a garden cart seat:

Tour Easy Bafang battery mount - bottom detail
Tour Easy Bafang battery mount – bottom detail

I traced around the block, bandsawed pretty close to the line, then introduced it to Mr Disk Sander for final shaping.

The round cable runs from the rear wheel speed sensor through all four blocks to join the motor near the bottom bracket. Because a recumbent bike’s rear wheel is much further from its bottom bracket, what you see is actually an extension cable with a few extra inches doubled around its connection just ahead of the battery.

Each of the four blocks takes about an hour to print, so I did them individually while making continuous process improvements to the solid model:

Bafang Battery Mount - Build view
Bafang Battery Mount – Build view

The heavy battery cable runs along the outside of the left frame tube, with enough cable ties to keep it from flopping around:

Tour Easy Bafang battery mount - bottom view
Tour Easy Bafang battery mount – bottom view

I wanted to fit it between the tubes, but there just wasn’t enough room around the screw in the front block where the tubes converge. It’s still pretty well protected and should be fine.

The chainline worked out much better than I expected:

Tour Easy Bafang battery mount - chainline
Tour Easy Bafang battery mount – chainline

That’s with the chain on the lowest (most inboard) rear sprocket, so it’s as close to the battery as it gets. I’m sure the battery will accumulate oily chain grime, as does everything else on a bike.

Lithium batteries have a vastly higher power density than good old lead acid batteries, but seven pounds is still a lot of weight!

The OpenSCAD source code as a GitHub Gist:

// Tour Easy Bafang Battery Mount
// Ed Nisley KE4ZNU 2021-04
Layout = "Build"; // [Frame,Block,Show,Build,Bushing,Cateye]
FrameWidths = [60.8,62.0,63.4,66.7]; // last = rear overhang support block
Support = true;
//- Extrusion parameters must match reality!
/* [Hidden] */
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
Protrusion = 0.1; // make holes end cleanly
inch = 25.4;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
ID = 0;
OD = 1;
// Dimensions
// Bike frame lies along X axis, rear to +X
FrameTube = [350,22.6 + HoleWindage,22.6 + HoleWindage]; // X = longer than anything else
FrameAngle = atan((65.8 - 59.4)/300); // measured distances = included angle between tubes
TubeAngle = FrameAngle/2; // .. frame axis to tube
FrameSides = 24;
echo(str("Frame angle: ",FrameAngle));
SpeedOD = 3.5; // speed sensor cable along frame
PowerOD = 6.7; // power cable between frame tubes
BatteryBoss = [5.5,16.0,2.5]; // battery mount boss, center is round
BossSlotOAL = 32.0; // .. end bosses are elongated
BossOC = 65.0; // .. along length of mount
LatchWidth = 10.0; // battery latches to mount plate
LatchThick = 1.5;
LatchOC = 56.0;
WallThick = 5.0; // thinnest wall
Block = [25.0,78.0,FrameTube.z + 2*WallThick]; // must be larger than frame tube spacing
echo(str("Block: ",Block));
// M5 SHCS nyloc nut
Screw = [5.0,8.5,5.0]; // OD, LENGTH = head
Washer = [5.5,10.1,1.0];
Nut = [5.0,9.0,5.0];
// 10-32 Philips nyloc nut
Screw10 = [5.2,9.8,3.6]; // OD, LENGTH = head
Washer10 = [5.5,11.0,1.0];
Nut10 = [5.2,10.7,6.2];
Kerf = 1.0; // cut through middle to apply compression
CornerRadius = 5.0;
EmbossDepth = 2*ThreadThick; // lettering depth
// Useful routines
module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
FixDia = Dia / cos(180/Sides);
cylinder(d=(FixDia + HoleWindage),h=Height,$fn=Sides);
// clamp overall shape
module ClampBlock() {
difference() {
for (i=[-1,1], j=[-1,1])
translate([i*(Block.x/2 - CornerRadius),j*(Block.y/2 - CornerRadius),-Block.z/2])
translate([0,0,-(Block.z/2 + Protrusion)])
PolyCyl(Screw[ID],Block.z + 2*Protrusion,6);
translate([0,-(Block.y/2 - PowerOD + Protrusion/2),-PowerOD/2])
cube([2*Block.x,2*PowerOD + Protrusion,PowerOD],center=true);
// frame tube layout with measured side-to-side width
module Frame(Outer = FrameWidths[0],AdjustDia = 0.0) {
TubeOC = Outer - FrameTube.y/cos(TubeAngle); // increase dia for angle
for (i=[-1,1])
rotate([0,90,i*TubeAngle]) rotate(180/FrameSides)
cylinder(d=FrameTube.z + AdjustDia,h=FrameTube.x,center=true,$fn=FrameSides);
// complete clamp block
module Clamp(Outer = FrameWidths[0]) {
TubeOC = Outer - FrameTube.y/cos(TubeAngle); // increase dia for angle
difference() {
translate([0,(TubeOC/2 - FrameTube[OD]/2),-SpeedOD/2])
translate([0,15,Block.z/2 - EmbossDepth/2 + Protrusion])
translate([0,22,-Block.z/2 + EmbossDepth/2 - Protrusion])
if (Outer == FrameWidths[len(FrameWidths) - 1]) { // special rear block
translate([0,0,Block.z/2 - 2*Screw10[LENGTH]])
PolyCyl(Washer10[OD],2*Screw10[LENGTH] + Protrusion,6);
else { // other blocks have channels
translate([0,0,Block.z/2 - BatteryBoss[LENGTH]/2 + Protrusion])
cube([BossSlotOAL,BatteryBoss[OD],BatteryBoss[LENGTH] + Protrusion],center=true);
for (i=[-1,1])
translate([0,i*LatchOC/2,Block.z/2 - LatchThick/2 + Protrusion])
cube([BossSlotOAL,LatchWidth,LatchThick + Protrusion],center=true);
translate([0,15,Block.z/2 - EmbossDepth])
text(text="^",size=5,spacing=1.00,font="Bitstream Vera Sans:style=Bold",
rotate(-90) mirror([0,1,0])
text(text=str("^ ",Outer),size=4.5,spacing=1.00,font="Bitstream Vera Sans:style=Bold",
if (Support)
color("Yellow") {
NumRibs = 7;
RibOC = Block.x/(NumRibs - 1);
intersection() {
translate([0,0,Block.z/2 + Kerf/2])
union() for (j=[-1,1]) {
cube([1.1*Block.x,FrameTube.y - 2*ThreadThick,4*ThreadThick],center=true);
for (i=[-floor(NumRibs/2):floor(NumRibs/2)])
rotate([0,90,0]) rotate(180/FrameSides)
cylinder(d=FrameTube.z - 2*ThreadThick,h=2*ThreadWidth,$fn=FrameSides,center=true);
// Half clamp sections for printing
module HalfClamp(i = 0, Section = "Upper") {
intersection() {
if (Section == "Upper")
// Handlebar bushing for controller
BushingSize = [16.0,22.2,15.0];
module Bushing() {
difference() {
translate([0*(BushingSize[OD] - BushingSize[ID])/4,0,BushingSize[LENGTH]/2])
// Cateye cadence sensor bracket
module Cateye() {
Pivot = [3.0,10.0,8.0];
Slot = [4.2,14.0,14.0];
Clip = [8.0,Slot.y,Slot.z + Pivot[OD]/2];
difference() {
union() {
rotate([0,90,0]) rotate(180/6)
translate([0,0,-(Clip.z - Slot.z/2)])
cube(Slot + [0,Protrusion,Protrusion],center=true);
// Build them
if (Layout == "Frame")
if (Layout == "Block")
if (Layout == "Bushing")
if (Layout == "Cateye")
if (Layout == "Upper" || Layout == "Lower")
if (Layout == "Show") {
color("Red", 0.3)
if (Layout == "Build") {
n = len(FrameWidths);
gap = 1.2;
for (i=[0:n-1]) {
j = i - ceil((n-1)/2);

Tek CC Milled Cursor vs. Speed vs. Coolant

After getting the Sherline running with the Mesa 5I25, I could return to milling cursor hairlines for the Tek Circuit Computer:

Hairline V tool - fixture
Hairline V tool – fixture

That’s the fixture intended for Gyros circular saw blades, repurposed for V tool engraving. The V tool in the Sherline tool holder collet is one of the ten-pack from the CNC 3018, unused until this adventure.

The actual setup had a scrap cursor secured with a strip of Kapton tape:

Hairline V tool - 0.2 0.3 0.4 DOC 10K RPM - Kapton fixture
Hairline V tool – 0.2 0.3 0.4 DOC 10K RPM – Kapton fixture

Those are three passes at (nominal) depths of 0.2, 0.3, and 0.4 mm (bottom to top) with a pre-existing hairline visible just above the second pass. The spindle ran at the Sherline’s top speed of just under 10 kRPM with no coolant on the workpiece.

I touched off the 0.2 mm cut by lowering the tool 0.1 mm at a time until it just left a mark on the Kapton tape, after a coarse touch-off atop a 0.5 mm plastic card, and calling it zero.

Scribbling over the cuts with a red Industrial Sharpie looked downright gory:

Hairline V tool - 0.2 0.3 0.4 DOC - Kapton Sharpie
Hairline V tool – 0.2 0.3 0.4 DOC – Kapton Sharpie

Peeling the tape and applying a cloth moistened with denatured alcohol showed three gnarly hairlines:

Hairline V tool - 0.2 0.3 0.4 DOC 10K RPM - Kapton start
Hairline V tool – 0.2 0.3 0.4 DOC 10K RPM – Kapton start

The top hairline shows distinct signs of melted PET plastic along the trench, with poor color fill due to the Sharpie not sticking to / wiping off the smooth-ish trench bottom. The next one is the existing saw-cut hairline with the lead-in cut over on the left.

The 0.3 and 0.2 mm hairlines look much better, with less debris and more complete fill. Unfortunately, the right side of the Sherline’s tooling plate seems to be a few tenths of a millimeter lower than the left, causing the 0.2 mm hairline to … disappear … where the cutter skipped up onto the Kapton tape:

Hairline V tool - 0.2 0.3 0.4 DOC 10K RPM - Kapton mid
Hairline V tool – 0.2 0.3 0.4 DOC 10K RPM – Kapton mid

Now, in practical terms, this is the first time I’ve actually needed platform alignment to within a hundred microns in subtractive machining. As some folks discover to their astonishment, however, 3D printing does require that level of accuracy:

Thinwall Box - platform height
Thinwall Box – platform height

Engraving through a layer of tape isn’t the right way to do it and some coolant will definitely improve the results, so I ignored the alignment issue, remounted the same scrap cursor with the red hairlines on the bottom, pulled a strip of water + detergent along the tool path, cut the same hairlines, and colored the trenches with blue Industrial Sharpie:

Hairline V tool - 0.2 0.3 0.4 DOC 10K RPM - water cool start
Hairline V tool – 0.2 0.3 0.4 DOC 10K RPM – water cool start

The 0.2 mm hairline on the bottom becomes a line as the V bit begins sliding along the surface at 10 kRPM without cutting:

Hairline V tool - 0.2 0.3 0.4 DOC 10K RPM - water cool mid
Hairline V tool – 0.2 0.3 0.4 DOC 10K RPM – water cool mid

The 0.3 mm hairline looks pretty good and the 0.4 mm hairline remains too rugged by the end of the passes. I think the actual depth of cut is at least 0.05 mm less than at the start:

Hairline V tool - 0.2 0.3 0.4 DOC 10K RPM - water cool end
Hairline V tool – 0.2 0.3 0.4 DOC 10K RPM – water cool end

Obviously, neurotically precise touchoff carries a big reward, as will aligning the tooling plate to an absurd degree.

A real machinist simply flycuts the top of an offending part / fixture / tooling plate to align it with the machine’s spindle, but I have a sneaky suspicion the real problem is a speck (or ten) of swarf between the Sherline’s table and the tooling plate; better cleanliness and attention to detail may improve the situation.

Sherline CNC Driver Step Pulse Width Puzzle

Long long ago, as part of tidying up the power distribution inside the Sherline CNC controller PCB, I wrote a cleanroom reimplementation of its PIC firmware and settled on a 25 µs Step pulse width with a minimum 50 µs period:

ADDRESS = 0x378
RESET_TIME = 10000
STEPLEN = 25000
DIRSETUP = 50000
DIRHOLD = 50000

Even shorter values for the Direction signal worked with the initial pncconf setup for the Mesa 5I25 FPGA card:

DIRSETUP   = 25000
DIRHOLD    = 25000
STEPLEN    = 25000
STEPSPACE  = 25000

After thrashing through enough of the Kicad-to-HAL converter to get a HAL file sufficiently tasty to prevent LinuxCNC from spitting it out, the X and A axes moved with a gritty sound and the two other axes were pretty much inert.

After eliminating everything else, including having Tiny Scope™ confirm the pulses were exactly the right duration, I increased them by 10 µs:

DIRSETUP   = 35000
DIRHOLD    = 35000
STEPLEN    = 35000
STEPSPACE  = 35000

After which, all the axes suddenly worked perfectly.

At some point along the way, I (re)discovered that Sherline Step pulses are active-low, although in practical terms getting the pulse upside-down just delays the active edge by its width. Given that the Sherline’s top speed is 24 inch/min = 0.4 inch/s, the minimum step period is 156 µs and even a wrong-polarity step should work fine.

For the record, here’s a perfectly good Step pulse:

Mesa 5I25 35us active-low Step pulse
Mesa 5I25 35us active-low Step pulse

Gotta wipe off that screen more often …

Logitech Gamepad: HAL Startup Holdoff

The HAL configuration for the Logitech gamepad has only a few changes from the decade-ago (!) version.

The HAL driver will reverse the direction of the Y and Z axes by feeding -127.5 to the scale inputs:

Sherline HAL schematic - Logitech YZ invert
Sherline HAL schematic – Logitech YZ invert

The flat inputs make sure the joystick knob has a dead zone around the rest position; otherwise, the axes tend to crawl off without a hand on the controls.

The gamepad doesn’t emit any values until you poke a button or move a joystick, the driver starts up with all zeros in its counts registers, so the HAL axis position pins emit XYZA = -1 +1 +1 -1 just as if you had the joysticks jammed hard at their upper-left corners. The only way out is to squelch the joystick until a button gets pressed and the machine is turned on:

Sherline HAL schematic - gamepad valid - parameters
Sherline HAL schematic – gamepad valid – parameters

The Gamepad-Valid signal forcibly disables all four axes:

Gamepad Axis Priority - schematic sample
Gamepad Axis Priority – schematic sample

That’s from an earlier iteration of the axis priority logic, before I got the Kicad annotate-starting-at-100 code working.

I somewhat finessed the startup issue by dedicating two of the gamepad’s back-panel buttons to clearing the initial E-Stop and turning the machine on:

Sherline HAL schematic - gamepad EStop logic
Sherline HAL schematic – gamepad EStop logic

However, if you flip those two conditions with the LinuxCNC GUI, the joysticks will remain zeroed until you do poke a button or move a knob. So, without the Gamepad-Valid logic, the Sherline will take off for the far upper left corner at a pretty good clip, which is not what you want.

The gamepad outputs remain valid for the rest of the session, so there’s no need to reset the Gamepad-Valid flipflop. HAL does not take kindly to hotplugging USB devices, so (AFAICT) you can’t hard-reset the gamepad + driver to the initial no-data-yet condition.

Earlier versions of the top two schematics used CONSTANT symbols, but it turns out PARAMETER symbols work just as well and the resulting setp commands eliminate the need for nets and real time functions.