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.

Category: Software

General-purpose computers doing something specific

  • Icecast and Ezstream Configuration

    Plugging a 64 GB USB stick with directories full of MP3 / OGG files into an always-on Raspberry Pi running Pi-Hole, one can use Icecast to stream them for clients on the LAN, so as to avoid over-the-Intertubes streaming issues.

    The only changes in the /etc/icecast2/icecast.xml file cover passwords, the number of source streams, and the hostname. It’s that simple, really.

    Given a directory of files, generate a file-per-line playlist:

    find /mnt/music/goodmusic/ -name \*mp3 | sort > /mnt/music/goodmusic/playlist.m3u
    

    Then set up a corresponding Ezstream XML file, perhaps imaginatively named goodmusic.xml:

    <ezstream>
        <url>http://localhost:8000/goodmusic</url>
        <sourcepassword>make-up-your-own</sourcepassword>
        <format>MP3</format>
        <filename>/mnt/music/goodmusic/playlist.m3u</filename>
        <shuffle>1</shuffle>
        <stream_once>0</stream_once>
        <svrinfoname>Good Music</svrinfoname>
        <svrinfourl>pihole.local</svrinfourl>
        <svrinfogenre>Good Music Streaming 24x7</svrinfogenre>
        <svrinfodescription>Techno Dub</svrinfodescription>
        <svrinfobitrate>128</svrinfobitrate>
        <svrinfochannels>2</svrinfochannels>
        <svrinfosamplerate>44100</svrinfosamplerate>
        <svrinfopublic>1</svrinfopublic>
    </ezstream>
    

    Fire off the source stream in /etc/rc.local:

    ezstream -c /home/pi/Icecast/goodmusic.xml &
    

    The ampersand tells Bash to fire-and-forget the process, so it runs all the time. One could, I suppose, put it in crontab to start after each boot or puzzle out the corresponding systemd incantation, but …

    Add the station to your streaming media player:

             'KEY_KP5'   : ['Good Music',False,['mplayer','-playlist','http://192.168.1.2:8000/goodmusic.m3u']],
    

    And then It Just Works™.

  • Xubuntu Startup Delay vs. xsetwacom

    Over the years, various xsetwacom incantations have confined the tablet stylus to the left-hand landscape monitor on my desk. Updating to Xubuntu 18.04 once again changed the monitors names (from HEAD-0 back to DP-1), but xsetwacom stopped working.

    My startup.sh script runs from Xubuntu’s “Application Autostart” list, so X is already running and xsetwacom should do the right thing. Alas, even with $XAUTHORITY and $DISPLAY set correctly (automagically by X), xsetwacom still didn’t corral the cursor.

    Some rummaging around the Intertubes suggested a delay would allow X to get up to speed and, indeed, sleeping for two seconds solved the problem:

    logger "startup.sh - copying Xauthority values"
    whoami > /tmp/who
    cp /home/ed/.Xauthority /tmp/Xauthority.txt
    echo $XAUTHORITY > /tmp/XAUTHORITY.txt
    cp $XAUTHORITY /tmp/xauth.cp
    echo $DISPLAY > /tmp/DISPLAY.txt
    # xsetwacom needs an additional delay after $XAUTHORITY and $DISPLAY become correct
    logger "startup.sh - waiting aimlessly"
    sleep 2s
    logger "startup.sh - doing wacom setup"
    xsetwacom --verbose set "Wacom Graphire3 6x8 Pen stylus" MapToOutput "DP-1"
    xsetwacom --verbose set "Wacom Graphire3 6x8 Pen eraser" MapToOutput "DP-1"
    

    Sheesh & similar remarks.

    The complete Bash script as a GitHub Gist:

    logger "startup.sh – starting"
    logger "startup.sh – setting configurations"
    #setxkbmap -option terminate:ctrl_alt_bksp
    #pactl set-default-sink alsa_output.pci-0000_00_1b.0.hdmi-stereo
    amixer -D pulse sset Master 41%
    amixer -D pulse sset Master 1%-
    gsettings set org.gnome.Evince page-cache-size 500
    # enable Kinesis volume and calculator keys
    #/home/ed/bin/Kinesis/kfreestyle2d /dev/hidraw6 &
    logger "startup.sh – starting applications"
    /usr/bin/thunar &
    /usr/bin/gimp &
    /usr/bin/chromium-browser &
    /usr/bin/digikam &
    /usr/bin/firefox &
    /usr/bin/thunderbird &
    /usr/bin/xfce4-terminal &
    logger "startup.sh – copying Xauthority values"
    # similar lines in .xprofile happen before X grabs the display
    whoami > /tmp/who
    cp /home/ed/.Xauthority /tmp/Xauthority.txt
    echo $XAUTHORITY > /tmp/XAUTHORITY.txt
    cp $XAUTHORITY /tmp/xauth.cp
    echo $DISPLAY > /tmp/DISPLAY.txt
    # xsetwacom needs an additional delay after $XAUTHORITY and $DISPLAY become correct
    logger "startup.sh – waiting aimlessly"
    sleep 2s
    logger "startup.sh – doing wacom setup"
    xsetwacom –verbose set "Wacom Graphire3 6×8 Pen stylus" MapToOutput "DP-1"
    #xsetwacom –verbose set "Wacom Graphire3 6×8 Pen stylus" MapToOutput "HEAD-0"
    xsetwacom –verbose set "Wacom Graphire3 6×8 Pen eraser" MapToOutput "DP-1"
    #xsetwacom –verbose set "Wacom Graphire3 6×8 Pen eraser" MapToOutput "HEAD-0"
    logger "startup.sh — done"
    view raw startup.sh hosted with ❤ by GitHub

    The cruft in there reminds me of previous fixes / workarounds / haxx, so it’s not entirely wasted space.

  • Kindle Fire Picture Frame: Side Block

    A steel frame that Came With The House™ emerged from a hidden corner and, instants before tossing it in the recycle heap, I realized it had excellent upcycling potential:

    Kindle Fire Picture Frame - Test Run
    Kindle Fire Picture Frame – Test Run

    Stipulated: I need better pictures for not-so-techie audiences.

    Anyhow, my long-disused Kindle Fire fits perfectly into the welded-on clips, with just enough room for a right-angle USB cable, and Photo Frame Slideshow Premium does exactly what’s necessary to show pictures from internal storage with no network connection.

    All I needed was a small block holding the Kindle against the far side of the frame:

    Kindle Frame - side blocks
    Kindle Frame – side blocks

    A strip of double-stick carpet tape holds the block onto the frame. To extract the Kindle, should the need arise, slide it upward to clear the bottom clips, rotate it rearward, and out it comes.

    Getting a good block required three tries, because the basement has cooled off enough to trigger Marlin’s Thermal Runaway protection for the M2’s platform heater. A test fit after the first failure showed the long leg was 1 mm too wide and, after the second failure, I reduced the fan threshold to 15 s and the minimum layer time to 5 s, producing the third block without incident.

    The platform heater runs at 40 V and I considered bumping it to 43 V for a 15% power boost, but it has no trouble keeping up when the fan isn’t blowing chilly basement air across its surface.

    The OpenSCAD source code, such as it is, doesn’t deserve its own GitHub Gist:

    // Block to hold Kindle in a picture frame mount
    // Ed Nisley - KE4ZNU
    // November 2018
    
    Protrusion = 0.1;
    
    difference() {
    
      cube([18,44,10]);
      translate([-Protrusion,-Protrusion,-Protrusion])
        cube([18-4 + Protrusion,44-10 + Protrusion,10 + 2*Protrusion]);
    
    }
    
  • Monthly Image: AMP Plug Board

    Around 1960, somebody my father knew at the Harrisburg AMP factory gave me a chunk of plugboard bandsawed from a scrapped computer or industrial controller, because he knew I’d enjoy it:

    AMP Plug Board
    AMP Plug Board

    He was right.

    I spent months rearranging those little cubes (some with cryptic legends!) into meaningful (to me) patterns, plugging cables between vital spots, and imagining how the whole thing worked:

    AMP Plug Board - detail
    AMP Plug Board – detail

    Long springs ran through the notches under the top of the blocks to connect the plug shells to circuit ground. The ends of the steel rails (still!) have raw bandsaw cuts, some of the blocks were sliced in two, the tip contact array behind the panel wasn’t included, and none of that mattered in the least.

    Only a fraction of the original treasure trove survives. It was absolutely my favorite “toy” ever.

    Quite some years ago, our Larval Engineer assembled the pattern you see; the hardware still had some attraction.

    I’ve asked Mary to toss it in the hole with whatever’s left of me, when that day arrives …

  • MPCNC: Guilloche Engraving First Light

    A diamond point drag engraving bit in the MPCNC scratched a suitable Guilloché pattern into a scrap hard drive platter much much better than I had any reason to expect:

    MPCNC - Guilloche 835242896 - HD plattter - 0.1mm
    MPCNC – Guilloche 835242896 – HD plattter – 0.1mm

    That’s with a 0.1 mm cut depth, sidelit with an LED flashlight.

    Feeding those nine digits into the Guilloché pattern generator script should get you the same pattern; set the paper size to 109 mm and use Pen=0 to suppress the legend.

    The same pattern at 0.3 mm cut depth looks about the same:

    MPCNC - Guilloche 835242896 - HD plattter - 0.3mm
    MPCNC – Guilloche 835242896 – HD plattter – 0.3mm

    It’s slightly more prominent in real life, but not by enough to make a big difference. I should try a graduated series of tests, of course, which will require harvesting a few more platters from dead drives.

    Either side will look great under a 21HB5A tube, although the disks are fingerprint and dust magnets beyond compare.

  • Let the Dead Past Bury Its Dead

    My old Gen 1 Kindle Fire hasn’t seen much action lately, so I figured maybe it could end its days by becoming a slide show / picture frame. While fiddling around, I tried to fire up the Amazon Shopping app:

    Amazon App Unsupported on Amazon Gen 1 Kindle
    Amazon App Unsupported on Amazon Gen 1 Kindle

    Clicking the big orange button fired up Amazon’s Silk browser, which promptly crashed.

    Some days, the punch line writes itself …

  • Kenmore Progressive Vacuum Tool Adapters: Third Failure

    The adapter for an old Electrolux crevice tool (not the dust brush) snapped at the usual stress concentration after about three years:

    Crevice tool adapter - broken vs PVC pipe
    Crevice tool adapter – broken vs PVC pipe

    The lower adapter is the new version, made from a length of 1 inch PVC pipe (that’s the ID, kinda-sorta) epoxied into a revised Kenmore adapter fitting.

    The original OpenSCAD model provided the taper dimensions:

    Electrolux Crevice Tool Adapter - PVC taper doodles
    Electrolux Crevice Tool Adapter – PVC taper doodles

    The taper isn’t quite as critical as it seems, because the crevice tool is an ancient molded plastic part, but a smidge over half a degree seemed like a good target.

    Start by boring out the pipe ID until it’s Big Enough (or, equally, the walls aren’t Scary Thin) at 28 mm:

    Crevice tool adapter - boring PVC
    Crevice tool adapter – boring PVC

    Alas, the mini-lathe’s craptastic compound has 2° graduations:

    Minilathe compound angle scale
    Minilathe compound angle scale

    So I set the angle using a somewhat less craptastic protractor and angle gauge:

    Crevice tool adapter - compound angle
    Crevice tool adapter – compound angle

    The little wedge of daylight near the gauge pivot is the difference between the normal perpendicular-to-the-spindle axis setting and half-a-degree-ish.

    Turning PVC produces remarkably tenacious swarf:

    Crevice tool adapter - PVC swarf
    Crevice tool adapter – PVC swarf

    The gash along the top comes from a utility knife; just pulling the swarf off didn’t work well at all.

    The column of figures down the right side of the doodles shows successive approximations to the target angle, mostly achieved by percussive adjustment, eventually converging to about the right taper with the proper dimensions.

    Cutting off the finished product with the (newly angled) cutoff bit:

    Crevice tool adapter - cutoff
    Crevice tool adapter – cutoff

    And then It Just Worked™.

    The OpenSCAD source code for all the adapters as a GitHub Gist:

    // Kenmore vacuum cleaner nozzle adapters
    // Ed Nisley KE4ZNU November 2015 and ongoing
    // Layout options
    Layout = "CrevicePipe"; // MaleFitting CoilWand FloorBrush
    // CreviceTool Crevice Pipe ScrubbyTool LuxBrush DustBrush
    //- Extrusion parameters must match reality!
    // Print with +1 shells and 3 solid layers
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    Protrusion = 0.1; // make holes end cleanly
    //———————-
    // Dimensions
    ID1 = 0; // for tapered tubes
    ID2 = 1;
    OD1 = 2;
    OD2 = 3;
    LENGTH = 4;
    OEMTube = [35.0,35.0,41.7,40.5,30.0]; // main fitting tube
    EndStop = [OEMTube[ID1],OEMTube[ID2],47.5,47.5,6.5]; // flange at end of main tube
    FittingOAL = OEMTube[LENGTH] + EndStop[LENGTH];
    $fn = 12*4;
    //———————-
    // 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(r=(FixDia + HoleWindage)/2,h=Height,$fn=Sides);
    }
    //——————-
    // Male fitting on end of Kenmore tools
    // This slides into the end of the handle or wand and latches firmly in place
    module MaleFitting() {
    Latch = [40,11.5,5.0]; // rectangle latch opening
    EntryAngle = 45; // latch entry ramp
    EntrySides = 16;
    EntryHeight = 15.0; // lower edge on *inside* of fitting
    KeyRadius = 1.0;
    translate([0,0,6.5])
    difference() {
    union() {
    cylinder(d1=OEMTube[OD1],d2=OEMTube[OD2],h=OEMTube[LENGTH]); // main tube
    hull() // insertion guide
    for (i=[-(6.0/2 – KeyRadius),(6.0/2 – KeyRadius)],
    j=[-(28.0/2 – KeyRadius),(28.0/2 – KeyRadius)],
    k=[-(26.0/2 – KeyRadius),(26.0/2 – KeyRadius)])
    translate([(i – (OEMTube[ID1]/2 + OEMTube[OD1]/2)/2 + 6.0/2),j,(k + 26.0/2 – 1.0)])
    sphere(r=KeyRadius,$fn=8);
    translate([0,0,-EndStop[LENGTH]]) // wand tube butts against this
    cylinder(d=EndStop[OD1],h=EndStop[LENGTH] + Protrusion);
    }
    translate([0,0,-OEMTube[LENGTH]]) // main bore
    cylinder(d=OEMTube[ID1],h=2*OEMTube[LENGTH] + 2*Protrusion);
    translate([0,-11.5/2,23.0 – 5.0]) // latch opening
    cube(Latch);
    translate([OEMTube[ID1]/2 + EntryHeight/tan(90-EntryAngle),0,0]) // latch ramp
    translate([(Latch[1]/cos(180/EntrySides))*cos(EntryAngle)/2,0,(Latch[1]/cos(180/EntrySides))*sin(EntryAngle)/2])
    rotate([0,-EntryAngle,0])
    intersection() {
    rotate(180/EntrySides)
    PolyCyl(Latch[1],Latch[0],EntrySides);
    translate([-(2*Latch[0])/2,0,-Protrusion])
    cube(2*Latch[0],center=true);
    }
    }
    }
    //——————-
    // Refrigerator evaporator coil wand
    module CoilWand() {
    union() {
    translate([0,0,50.0])
    rotate([180,0,0])
    difference() {
    cylinder(d1=EndStop[OD1],d2=42.0,h=50.0);
    translate([0,0,-Protrusion])
    cylinder(d1=35.0,d2=35.8,h=100);
    }
    translate([0,0,50.0 – Protrusion])
    MaleFitting();
    }
    }
    //——————-
    // Samsung floor brush
    module FloorBrush() {
    union() {
    translate([0,0,60.0])
    rotate([180,0,0])
    difference() {
    union() {
    cylinder(d1=EndStop[OD1],d2=32.4,h=10.0);
    translate([0,0,10.0 – Protrusion])
    cylinder(d1=32.4,d2=30.7,h=50.0 + Protrusion);
    }
    translate([0,0,-Protrusion])
    cylinder(d1=28.0,d2=24.0,h=100);
    }
    translate([0,0,60.0 – Protrusion])
    MaleFitting();
    }
    }
    //——————-
    // Crevice tool
    module CreviceTool() {
    union() {
    translate([0,0,60.0])
    rotate([180,0,0])
    difference() {
    union() {
    cylinder(d1=EndStop[OD1],d2=32.0,h=10.0);
    translate([0,0,10.0 – Protrusion])
    cylinder(d1=32.0,d2=30.4,h=50.0 + Protrusion);
    }
    translate([0,0,-Protrusion])
    cylinder(d1=28.0,d2=24.0,h=100);
    }
    translate([0,0,60.0 – Protrusion])
    MaleFitting();
    }
    }
    //——————-
    // Crevice tool
    // Hacked for 1 inch Schedule 40 PVC pipe stiffening tube
    module CrevicePipe() {
    PipeOD = 33.5;
    union() {
    translate([0,0,10.0])
    rotate([180,0,0])
    difference() {
    cylinder(d1=EndStop[OD1],d2=PipeOD+2*8*ThreadWidth,h=10.0);
    translate([0,0,-Protrusion])
    cylinder(d=PipeOD,h=100);
    }
    translate([0,0,10.0])
    MaleFitting();
    }
    }
    //——————-
    // Mystery brush
    module ScrubbyTool() {
    union() {
    translate([0,0,60.0])
    rotate([180,0,0])
    difference() {
    union() {
    cylinder(d1=EndStop[OD1],d2=31.8,h=10.0);
    translate([0,0,10.0 – Protrusion])
    cylinder(d1=31.8,d2=31.0,h=50.0 + Protrusion);
    }
    translate([0,0,-Protrusion])
    cylinder(d1=26.0,d2=24.0,h=100);
    }
    translate([0,0,60.0 – Protrusion])
    MaleFitting();
    }
    }
    //——————-
    // eBay horsehair dusting brush
    // Hacked for 3/4" Schedule 40 PVC stiffening tube
    // eBay: 30.0 32.0 30.0
    // Shopvac: 30.3 31.0 25.0
    // Must build snout down with brim to avoid support
    module DustBrush() {
    PipeOD = 27.0; // stiffening pipe
    Snout = [0,0, 31.0, 30.3, 25.0];
    TaperLength = 10.0; // transition cone from fitting to snout
    union() {
    translate([0,0,Snout[LENGTH] + TaperLength])
    rotate([180,0,0])
    difference() {
    union() {
    cylinder(d1=EndStop[OD1],d2=Snout[OD1],h=TaperLength);
    translate([0,0,TaperLength – Protrusion])
    cylinder(d1=Snout[OD1],d2=Snout[OD2],h=Snout[LENGTH] + Protrusion);
    }
    translate([0,0,-Protrusion]) // 3/4 inch Sch 40 PVC
    PolyCyl(PipeOD,100);
    }
    translate([0,0,Snout[LENGTH] + TaperLength – Protrusion])
    MaleFitting();
    }
    }
    //——————-
    // Electrolux brush ball
    module LuxBrush() {
    union() {
    translate([0,0,30.0])
    rotate([180,0,0])
    difference() {
    union() {
    cylinder(d1=EndStop[OD1],d2=30.8,h=10.0);
    translate([0,0,10.0 – Protrusion])
    cylinder(d1=30.8,d2=30.0,h=20.0 + Protrusion);
    }
    translate([0,0,-Protrusion])
    cylinder(d1=25.0,d2=23.0,h=30 + 2*Protrusion);
    }
    translate([0,0,30.0 – Protrusion])
    MaleFitting();
    }
    }
    //———————-
    // Build it!
    if (Layout == "MaleFitting")
    MaleFitting();
    if (Layout == "CoilWand")
    CoilWand();
    if (Layout == "FloorBrush")
    FloorBrush();
    if (Layout == "CreviceTool")
    CreviceTool();
    if (Layout == "CrevicePipe")
    CrevicePipe();
    if (Layout == "DustBrush")
    DustBrush();
    if (Layout == "ScrubbyTool")
    ScrubbyTool();
    if (Layout == "LuxBrush")
    LuxBrush();