The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Tag: Improvements

Making the world a better place, one piece at a time

  • Xubuntu 16.04 LTS Bringup

    Notes on updating my desktop Optiplex 980 from Xubuntu 14.04 LTS to 16.04 LTS, after being unable to compile OpenSCAD, Slic3r, and GNU Radio from source. Blowing three days on reconfiguration & tweakage after a clean install is the price one pays to get rid of a few years of cruft; the old version remains bootable and lootable on another partition, Just In Case.

    Install:

    • nfs-common
    • xfce4-goodies
    • xubuntu-restricted-extras
    • hp2xx
    • xsane, xscanimage
    • kate (which hauls in a big hunk of wayland, alas)
    • digikam
    • chromium-browser
    • devilspie2
    • dropbox (from site)
    • remmina

    Apply:

    The hacks required to ensure NFS mounts happen before signing in seem to be obsolete. Given that systemd now controls everything, I have NFI how to proceed if that’s not the case.

    The Xubuntu load progress display looks like it’s scaled up from 640×480.

    There seems no way to disable on-screen notifications without deep hackery; some of the collateral damage involves the death of the volume control applet in the indicator panel thingy, so I didn’t try very hard.

    The aforementioned volume control now fires up the Pulseaudio control dialog. That display explained why the audio came out of the 980’s crappy internal speaker, but switching it to the HDMI output produced only silence. After blowing away ~/.config/pulse and rebooting that sucker, it’s all good: Linux audio remains a slow-motion train wreck.

    The initial sign-on dialog appears on the portrait monitor, minus rotation, because the dialog box follows the mouse pointer: the initial mouse position sits one pixel beyond the landscape monitor. Blind-type the password, whack Enter, and it’s all good.

    FWIW, I’ve installed the XFCE flavor of Mint Linux on the laptops, but that’s basically Xubuntu with (some of) the ugly sanded off and really doesn’t buy much for somebody who pays no attention to eyecandy.

  • Raspberry Pi Streaming Radio Player: Ignoring a Missing Volume Knob

    The Dell AC511 USB SoundBars have volume control knobs, which this udev rule turns into the /dev/input/volume device:

    ATTRS{name}=="Dell Dell AC511 USB SoundBar", SYMLINK+="input/volume"
    

    I recently wanted to use an ordinary USB “sound card” that did not, of course, have a volume knob:

    Sabrent USB Audio Adapter
    Sabrent USB Audio Adapter

    This hack skips the configuration that makes the knob’s events visible to the Python program:

    import os.path
    
    ... snippage ...
    
    # if volume control knob exists, then set up its events
    
    VolumeDevice = '/dev/input/volume'
    
    vp = select.poll()
    if os.path.exists(VolumeDevice):
      v = InputDevice(VolumeDevice)
      v.grab()
      vp.register(v.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
    

    It turns out that if you never register a device with the event polling interface, then the interface never reports any events and the rest of the code remains blissfully undisturbed: the non-existent knob doesn’t do anything, while the volume control buttons on the keypad continue to function as usual.

    The end result of this fiddling puts a Raspberry Pi 2 Model B to work as a streaming player on my Electronics Workbench, untethering the laptop from those powered speakers:

    RPi 2 Streaming Player - USB sound gadget
    RPi 2 Streaming Player – USB sound gadget

    It’s a shame that USB audio gadget is so big, because it crowds out standard USB plugs to the side.

    The most satisfactory LED configuration for a translucent case with an external WiFi adapter seems to be:

    dtparam=pwr_led_trigger=cpu0
    dtparam=act_led_trigger=mmc0
    

    The rest of the code remains unchanged as shown in that GitHub Gist.

    Bomb the bass!

  • Loop Antenna Splice Reinforcement

    Those solder joints and finicky little wires seem much too fragile on their own:

    LF Loop Antenna - complete joint
    LF Loop Antenna – complete joint

    This should help:

    Loop Antenna Splice - assembled
    Loop Antenna Splice – assembled

    Foam blocks hold the ribbon cable in place and provide a bit of strain relief around the hard plastic edge:

    Loop Antenna Splice - hardware
    Loop Antenna Splice – hardware

    The brass inserts in the bottom block (on the left) got epoxied in place, because they must provide quite a bit of force to clamp the foam. Their larger knurled end sits flush with the outside surface and the smaller end has one thread thickness of clearance below the inner surface.

    A last look at the wiring:

    Loop Antenna Splice - wiring
    Loop Antenna Splice – wiring

    I think the preamp must sit at some distance from the antenna to prevent feedback, but that remains to be seen.

    The M2’s nozzle accumulated a huge blob of PETG that turned into a giant smear:

    Loop Antenna Splice - PETG booger
    Loop Antenna Splice – PETG booger

    Fortunately, it’s on the inside where nobody will ever see it. If you know where to look, it’s barely visible from the outside.

    The solid model shows off the structure a bit better:

    Loop Antenna Splice - show view
    Loop Antenna Splice – show view

    The inside view:

    Loop Antenna Splice - bottom
    Loop Antenna Splice – bottom

    The OpenSCAD source code as a GitHub Gist:

    // Ribbon cable loop antenna splice
    // Ed Nisley KE4ZNU December 2016
    Layout = "Text";
    //- Extrusion parameters must match reality!
    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);
    //———-
    // Dimensions
    Cable = [200,48.0,1.5]; // X = longer than anything else
    Splice = [15.0,53.0,5.0]; // epoxy blob around joints
    Foam = [15.0,Splice[1],2.0];
    CornerRadius = 5.0;
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Insert = [3.9,4.6 – 0.1,5.8]; // 4-40 knurled brass insert
    Screw = [2.7,5.5,2.0]; // OD = head LENGTH = head thickness
    Washer = [3.0,8.0,0.8];
    BlockOA = [60.0, // convenient length
    Splice[1] + 4*Washer[OD], // clearance around washer on top
    2*(Insert[LENGTH] + 2*ThreadThick)]; // insert sets both thicknesses
    NumScrews = 2; // screws along each side of cable
    ScrewOC = [BlockOA[0] / NumScrews,
    BlockOA[1] – 2*Washer[OD],
    2*BlockOA[2] // ensure complete holes
    ];
    TextThick = 3*ThreadThick; // depth of text into surface
    TextFit = HoleWindage/2; // clearance around text polygons
    //———————-
    // 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);
    }
    //—–
    // Blocky model of cable + splice + wire tap for subtraction
    module Antenna() {
    union() {
    cube(Cable,center=true);
    cube(Splice,center=true);
    for (i=[-1,1])
    translate([0,-Splice[1]/2,0])
    cube([Splice[0]/2,Splice[1],2*Foam[2]],center=true);
    }
    }
    // Outside shape of splice Block, less screw clearance
    module SpliceBlock() {
    difference() {
    hull()
    for (i=[-1,1], j=[-1,1])
    translate([i*(BlockOA[0]/2 – CornerRadius),j*(BlockOA[1]/2 – CornerRadius),-BlockOA[2]/2])
    cylinder(r=CornerRadius,h=BlockOA[2],$fn=4*8);
    for (i = [0:NumScrews – 1], j=[-1,1])
    translate([-BlockOA[0]/2 + ScrewOC[0]/2 + i*ScrewOC[0],j*ScrewOC[1]/2,-(BlockOA[2]/2 + Protrusion)])
    PolyCyl(Screw[ID],BlockOA[2] + 2*Protrusion,6);
    }
    }
    // Splice block less cable
    module ShapedBlock() {
    difference() {
    SpliceBlock();
    Antenna();
    }
    }
    // Bottom
    module BottomPlate() {
    difference() {
    ShapedBlock();
    translate([0,0,BlockOA[2]/2])
    cube(BlockOA + 2*[Protrusion,Protrusion,0],center=true);
    Antenna(Splice);
    for (i = [0:NumScrews – 1], j=[-1,1])
    translate([-BlockOA[0]/2 + ScrewOC[0]/2 + i*ScrewOC[0],j*ScrewOC[1]/2,-(BlockOA[2]/2 + Protrusion)])
    PolyCyl(Insert[OD],2*Insert[LENGTH],6);
    for (i=[-1,1])
    translate([i*((BlockOA[0] – Foam[0] + Protrusion)/2),0,(BlockOA[2]/2 – Cable[2]/2 – Foam[2])])
    cube([Foam[0] + Protrusion,Foam[1],BlockOA[2]],center=true);
    }
    }
    // Top
    module TopPlate() {
    difference() {
    ShapedBlock();
    translate([0,0,-BlockOA[2]/2])
    cube(BlockOA + 2*[Protrusion,Protrusion,0],center=true);
    Antenna(Splice);
    for (i=[-1,1])
    translate([i*((BlockOA[0] – Foam[0] + Protrusion)/2),0,-(BlockOA[2]/2 – Cable[2]/2 – Foam[2])])
    cube([Foam[0] + Protrusion,Foam[1],BlockOA[2]],center=true);
    rotate(90) {
    translate([0,6,BlockOA[2]/2 – TextThick])
    TextHack("KE4ZNU",8,0.0,1.15,TextThick + Protrusion);
    translate([0,-6,BlockOA[2]/2 – TextThick])
    TextHack("2016·12",6,0.0,1.20,TextThick + Protrusion);
    }
    }
    }
    module TextHack(Text="sample",Size=10,Offset=0.0,Space=1.0,Thick=ThreadThick) {
    linear_extrude(height=Thick,convexity=10)
    offset(r=Offset)
    text(Text,font=":bold",size=Size,spacing=Space,halign="center",valign="center");
    }
    //———-
    // Build them
    if (Layout == "Antenna")
    Antenna();
    if (Layout == "SpliceBlock")
    SpliceBlock();
    if (Layout == "ShapedBlock")
    ShapedBlock();
    if (Layout == "Bottom")
    BottomPlate();
    if (Layout == "Top")
    TopPlate();
    if (Layout == "Text") {
    translate([0,6,0])
    TextHack("KE4ZNU",8,-TextFit,1.15,TextThick);
    translate([0,-6,0])
    TextHack("2016·12",6,-TextFit,1.20,TextThick);
    }
    if (Layout == "Show") {
    translate([0,0,5])
    TopPlate();
    translate([0,0,-5])
    BottomPlate();
    color("Orange",0.2)
    Antenna();
    }
    if (Layout == "Build") {
    translate([0,-0.6*BlockOA[1],BlockOA[2]/2])
    rotate([180,0,0])
    TopPlate();
    translate([0,0.6*BlockOA[1],BlockOA[2]/2])
    BottomPlate();
    }
  • Raspberry Pi: Forcing VNC Display Resolution

    You can use VNC with a headless Raspberry Pi, but, absent a display with which to negotiate the screen resolution, X defaults something uselessly small: 720×480. To force a more reasonable resolution, edit /boot/config.txt and set the framebuffer size:

    framebuffer_width=1920
    framebuffer_height=1280
    

    You can use a nonstandard resolutions, as with the 1920×1280 that fits neatly on my 2560×1440 landscape monitor, but getting too weird will surely bring its own reward. When you plug in a display, X will ought to negotiate as usual for the highest resolution the display can handle.

    The System Configuration dialog has a “Resolution” button offering standard resolutions:

    RPi display resolution configuration
    RPi display resolution configuration

    The shiny RPi Pixel UI bakes the RealVNC server directly into whatever handles the startup process these days, rendering all previous recommendations about forcing VNC resolutions inoperative. I found the trick of editing the config file on StackExchange after the usual flailing around.

    Memo to Self: Remmina (the VNC client I use in XFCE on my desktop PC) doesn’t respond well to having the VNC server shut down while it’s connected. Fire up a command prompt, enter this:

    sleep 10 ; sudo reboot
    

    Then, quick like a bunny, disconnect the VNC session.

  • Under-cabinet Lamp Brackets: Close-fit Power Plug

    Adding a little tab to the angled brackets prevents them from pivoting while you’re tightening the mounting screw into the brass insert:

    Kitchen Light Bracket - angled lip - Slic3r preview
    Kitchen Light Bracket – angled lip – Slic3r preview

    The trick with those tabs is to chop ’em off halfway to the tip, because there’s no point trying to print a wedge that ends with a sharp edge:

    Kitchen Light Bracket - angled - tab cutoff - solid model
    Kitchen Light Bracket – angled – tab cutoff – solid model

    Generating & positioning that block goes a little something like this:

    translate([0,
               2*MountBlock[1] - LEDEndBlock[2]*sin(StripAngle),
               MountBlock[2]/2 + MountHeight - 0.5*LEDEndBlock[2]*cos(StripAngle)])
        cube(2*MountBlock,center=true);
    
    

    As a rule of thumb, there’s no point in fussing with smaller shapes when a big one will suffice…

    This LED strip fits under the cabinet over the butcher block countertop next to the stove, which turns out to be Just Barely longer than the strip itself:

    Under-cabinet light - cramped power plug
    Under-cabinet light – cramped power plug

    The OEM straight-on coaxial plug (near the bottom of the picture) attached to the wall wart cable obviously wouldn’t fit in the available space, so I gimmicked up a right-angle adapter by the simple expedient of shortening the solder lugs of a plug from the heap (which, admittedly, doesn’t quite fully seat in the socket), bending them sideways, soldering a pair of wires, heatshrinking appropriately, then coating wires + plug with JB Kwik epoxy. The other end of the wires gets a coaxial jack that miraculously fits the OEM plug, styled up with more heatshrink tubing. Not pretty, but nobody will ever see it.

    Unlike the LED strip under the other cabinet, this IR proximity sensor doesn’t mind having a wood edge next to it and, thus, didn’t need a strip of tape to keep it happy.

  • Under-cabinet Lamp Brackets: Angled Edition

    The LED strip lights have a reasonably diffuse pattern with an on-axis bright area that puts more light on the rear of the counter than seems strictly necessary. Revising the original brackets to tilt the strips moves the bright patch half a foot forward:

    Kitchen Light Bracket - angled - solid model
    Kitchen Light Bracket – angled – solid model

    For lack of anything smarter, the angle puts the diagonal of the LED strip on the level:

    Kitchen Light Bracket - angled - Slic3r preview
    Kitchen Light Bracket – angled – Slic3r preview

    The translucent block represents the strip (double-thick and double-wide), with a peg punching a hole for the threaded brass insert.

    Although the source code has an option to splice the middle blocks together, it can also build them separately:

    Kitchen Light Bracket - angled - LED block
    Kitchen Light Bracket – angled – LED block

    Turns out they’re easier to assemble that way; screw ’em to the strips, then screw the strips to the cabinet.

    I moved the deck screw holes to the other end of the block, thus putting the strips against the inside of the cabinet face. It turns out the IR sensor responds to the DC level of the reflected light, not short-term changes, which meant the reflection from the adjacent wood blinded it to anything waved below. Adding a strip of black electrical tape killed enough of the reflected light to solve that problem:

     

    Under-cabinet light - IR sensor shield
    Under-cabinet light – IR sensor shield

    The tape isn’t quite as far off-center as it looks, but I’m glad nobody will ever see it …

    The before-and-after light patterns, as viewed on B-size metric graph paper centered on the left-hand strip and aligned with the belly side of the countertop:

    Under-cabinet light - straight vs angled patterns
    Under-cabinet light – straight vs angled patterns

    Those look pretty much the same, don’t they? So much for photography as evidence for anything.

    The OpenSCAD source code as a GitHub Gist:


    // Mounting brackets for eShine under-counter LED lights
    // Ed Nisley KE4ZNU December 2016
    Layout = "Build";
    //- Extrusion parameters must match reality!
    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);
    //———-
    // Dimensions
    MountHeight = (1 + 0*3/16) * inch; // distance from cabinet bottom
    THREADOD = 0;
    HEADOD = 1;
    LENGTH = 2;
    WoodScrew = [4.0,8.3,41]; // 8×1-5/8 Deck screw
    WoodScrewRecess = 3.0;
    WoodScrewMargin = 1.5 * WoodScrew[HEADOD]; // head OD + flat ring
    Insert = [3.9,4.6,5.8 + 2.0]; // 4-40 knurled brass insert
    JoinerLength = 19.0; // joiner between strips
    LEDEndBlock = [11.0,28.8,9.5]; // LED plastic end block
    LEDScrewOffset = [1.0,8.2,0]; // hole offset from end block center point
    StripAngle = atan2(LEDEndBlock[2],LEDEndBlock[1]);
    echo(str("Strip angle: ",StripAngle));
    MountBlock = [WoodScrewMargin,(WoodScrewMargin + LEDEndBlock[1]*cos(StripAngle)),MountHeight];
    //———————-
    // 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);
    }
    //—–
    // LED end block with positive insert for subtraction
    // returned with mounting hole end of strip along X axis
    // ready for positioning & subtraction
    module EndBlock(Side = "L") {
    LSO = [((Side == "L") ? 1 : -1)*LEDScrewOffset[0],LEDScrewOffset[1],LEDScrewOffset[2]];
    rotate([-StripAngle,0,0])
    translate([0,LEDEndBlock[1]/2,LEDEndBlock[2]])
    union() {
    cube(LEDEndBlock + [LEDEndBlock[0],0,LEDEndBlock[2]],center=true);
    translate(LSO + [0,0,-(LEDEndBlock[2] + Insert[2])])
    rotate(180/6)
    PolyCyl(Insert[1],2*Insert[2],6);
    }
    }
    //—–
    // End mounting block with proper hole offsets
    module EndMount(Side = "L") {
    translate([0,0,MountBlock[2]/2])
    difference() {
    translate([0,MountBlock[1]/2,0])
    cube(MountBlock,center=true);
    translate([0,WoodScrewMargin,MountBlock[2]/2])
    EndBlock(Side);
    translate([0,WoodScrewMargin/2,-MountBlock[2]])
    rotate(180/6)
    PolyCyl(WoodScrew[THREADOD],2*MountBlock[2],6);
    translate([0,WoodScrewMargin/2,(MountBlock[2]/2 – WoodScrewRecess)])
    rotate(180/6)
    PolyCyl(WoodScrew[HEADOD],WoodScrewRecess + Protrusion,6);
    translate([((Side == "L") ? 1 : -1)*MountBlock[0]/2,3*MountBlock[1]/4,-MountBlock[2]/4])
    rotate([90,0,((Side == "L") ? 1 : -1)*90])
    translate([0,0,-2*ThreadThick])
    linear_extrude(height=4*ThreadThick,convexity=3)
    text(Side,font=":style=bold",valign="center",halign="center");
    }
    }
    module MidMount() {
    XOffset = (JoinerLength + MountBlock[0])/2;
    BridgeThick = 5.0;
    union() {
    translate([XOffset,0,0])
    EndMount("L");
    translate([0,MountBlock[1]/2,BridgeThick/2])
    cube([JoinerLength,MountBlock[1],BridgeThick] + [2*Protrusion,0,0],center=true);
    translate([-XOffset,0,0])
    EndMount("R");
    }
    }
    //———-
    // Build them
    if (Layout == "EndBlock")
    EndBlock("L");
    if (Layout == "EndMount")
    EndMount("R");
    if (Layout == "MidMount")
    MidMount();
    if (Layout == "BuildJoined") {
    translate([-(JoinerLength + 2*MountBlock[0]),0,0])
    EndMount("L");
    MidMount();
    translate([(JoinerLength + 2*MountBlock[0]),0,0])
    EndMount("R");
    }
    if (Layout == "Build") {
    translate([-MountBlock[0],0,0])
    EndMount("L");
    translate([MountBlock[0],0,0])
    EndMount("R");
    }

  • Respooling Stainless Steel Thread

    For various reasons, I needed a smaller quantity of that stainless steel thread / yarn, so I mooched an empty spool from Mary, ran a bolt through it with washers + nut on the far end, chucked the bolt in the lathe, and ran the spindle backwards at the slowest speed:

    Stainless steel thread - smaller spool
    Stainless steel thread – smaller spool

    I started by letting the big spool unroll from the side, but that produced horrible twists in the slack thread. Remembering the lesson from our previous thread spool adventure, I put it on the floor and let the thread pull from the top:

    Stainless steel thread - unwinding spool
    Stainless steel thread – unwinding spool

    It still accumulated a huge twist between the two spools, even while guiding it hand-over-hand onto the rotating spool. Either the factory lays the thread on the large spool with a built-in twist or, more likely, a multi-strand steel thread behaves like a spring, no matter what anybody wants, and comes off the spool with a nasty case of inherent vice.

    Memo to Self: don’t let stainless steel thread slide through your hands under power, because some of the fuzz visible in the top picture will stay with you.