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

  • 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();
  • Everybody Wants to be a Star

    The Wzye Pan camera overlooking the bird feeders attracted the attention of a Downy Woodpecker:

     

    Screenshot_20181029-112307 - Downy Woodpecker at the Pan
    Screenshot_20181029-112307 – Downy Woodpecker at the Pan

    The camera sits on a “guest” branch of the house network, fenced off from the rest of the devices, because Pi-Hole showed it relentlessly nattering with its Chinese servers:

    Blocked Domains - Wyze iotcplatform
    Blocked Domains – Wyze iotcplatform

    In round numbers, the Pan camera tried to reach those (blocked) iotcplatform domains every 30 seconds around the clock, using a (permitted) google.com lookup to check Internet connectivity. Pi-Hole supplied the latter from its cache and squelched the former, but enough is enough.

    I haven’t tested for traffic to hardcoded dotted-quad IP addresses not requiring DNS lookups through the Pi-Hole. Scuttlebutt suggests the camera firmware includes binary blobs from the baseline Xaiomi/Dafang cameras, so there’s no telling what’s going on in there.

    The Xiaomi-Dafang Hacks firmware doesn’t phone home to anybody, but requires router port forwarding and a compatible RTSP client on the remote end. Isolating it from the rest of the LAN must suffice until I can work out that mess; I assume the camera has already made my WiFi passwords public knowledge.

  • MPCNC: Drag Knife Holder

    My attempt to use a HP 7475A plotter as a vinyl cutter failed due to its 19 g pen load limit:

    HP 7475A knife stabilizer - big nut weight
    HP 7475A knife stabilizer – big nut weight

    The MPCNC, however, can apply plenty of downforce, so I tinkered up a quick-and-dirty adapter to put the drag knife “pen” body into the MPCNC’s standard DW660 router holder:

    MPCNC - DW660 adapter drag knife holder - fixed position
    MPCNC – DW660 adapter drag knife holder – fixed position

    That’s using the DW660 adapter upside-down to get the business end of the knife closer to the platform. The solid model descends from the linear-bearing Sakura pen holder by ruthless pruning.

    It didn’t work well at all, because you really need a spring for some vertical compliance and control over the downforce pressure.

    Back to the Comfy Chair:

    Drag Knife Holder - DW660 Mount - solid model
    Drag Knife Holder – DW660 Mount – solid model

    A trio of the lightest springs from a 200 piece assortment (in the front left compartment) pushes the upper plate downward against the drag knife’s flange:

    MPCNC - DW660 adapter drag knife holder - spring loaded
    MPCNC – DW660 adapter drag knife holder – spring loaded

    There’s a bit more going on than may be obvious at first glance.

    The screws slide in brass tubing press-fit into the upper plate, because otherwise their threads hang up on the usual 3D printed layers inside the (drilled-out) holes. Smaller free-floating brass tubing snippets inside the springs keep them away from the screw threads; the gap between the top of the tubing and the screw head limits the vertical compliance to 3 mm. The screws thread into brass inserts epoxied into the bottom disk, with a dab of low-strength Loctite for stay-put adjustment.

    I bored the orange PETG disk to a nice slip fit around the knife body:

    DW660 drag knife holder - boring body
    DW660 drag knife holder – boring body

    The upper plate also required fitting:

    DW660 drag knife holder - boring plate
    DW660 drag knife holder – boring plate

    A few iterations produced reasonably smooth motion over a few millimeters, but it’s definitely not a low-friction / low-stiction drag knife holder. It ought to be good for some proof-of-concept vinyl cutting, though.

    The OpenSCAD source code as a GitHub Gist:

    // Drag Knife Holder for DW660 Mount
    // Ed Nisley KE4ZNU – 2018-09-26
    Layout = "Show"; // Build, Show, Puck, Mount, Plate
    /* [Extrusion] */
    ThreadThick = 0.25; // [0.20, 0.25]
    ThreadWidth = 0.40; // [0.40]
    /* [Hidden] */
    Protrusion = 0.1; // [0.01, 0.1]
    HoleWindage = 0.2;
    inch = 25.4;
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    ID = 0;
    OD = 1;
    LENGTH = 2;
    //- Adjust hole diameter to make the size come out right
    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);
    }
    //- Dimensions
    KnifeBody = [12.0,16.0,2.0]; // body flange — resembles HP plotter pen
    WallThick = 3.0; // minimum thickness / width
    Screw = [4.0,8.5,8.0]; // holding it all together, OD = washer
    Insert = [4.0,6.0,10.0]; // brass insert
    Plate = [KnifeBody[ID],KnifeBody[OD] + 3*Screw[OD],4.0]; // spring reaction plate
    PlateGuide = [4.0,4.8,Plate[LENGTH]]; // … guide tubes
    NumScrews = 3;
    ScrewBCD = 2*(KnifeBody[OD]/2 + Screw[OD]/2 + 0.5);
    NumSides = 9*4; // cylinder facets (multiple of 3 for lathe trimming)
    // Basic shape of DW660 snout fitting into the holder
    // Lip goes upward to lock into MPCNC mount
    Snout = [44.6,50.0,9.6]; // LENGTH = ID height
    Lip = 4.0; // height of lip at end of snout
    PuckOAL = Snout[LENGTH] + Lip; // total height
    Key = [Snout[ID],25.7,PuckOAL]; // rectangular key
    module DW660Puck() {
    translate([0,0,PuckOAL])
    rotate([180,0,0]) {
    cylinder(d=Snout[OD],h=Lip/2,$fn=NumSides);
    translate([0,0,Lip/2])
    cylinder(d1=Snout[OD],d2=Snout[ID],h=Lip/2,$fn=NumSides);
    cylinder(d=Snout[ID],h=PuckOAL,$fn=NumSides);
    intersection() {
    translate([0,0,0*Lip + Key.z/2])
    cube(Key,center=true);
    cylinder(d=Snout[OD],h=Lip + Key.z,$fn=NumSides);
    }
    }
    }
    module MountBase() {
    difference() {
    DW660Puck();
    translate([0,0,-Protrusion]) // knife holder body
    PolyCyl(KnifeBody[ID],2*PuckOAL,NumSides);
    translate([0,0,PuckOAL – KnifeBody[LENGTH]/2]) // … half of flange, loose fit
    PolyCyl(KnifeBody[OD] + 2*HoleWindage,KnifeBody[LENGTH],NumSides);
    for (i=[0:NumScrews – 1]) // clamp screws
    rotate(i*360/NumScrews)
    translate([ScrewBCD/2,0,-Protrusion])
    rotate(180/8)
    PolyCyl(Insert[OD],2*PuckOAL,8);
    }
    }
    module SpringPlate() {
    difference() {
    cylinder(d=Plate[OD],h=Plate[LENGTH],$fn=NumSides);
    translate([0,0,-Protrusion]) // knife holder body
    PolyCyl(KnifeBody[ID],2*PuckOAL,NumSides);
    translate([0,0,Plate[LENGTH] – KnifeBody[LENGTH]/2]) // … half of flange, snug fit
    PolyCyl(KnifeBody[OD],KnifeBody[LENGTH],NumSides);
    for (i=[0:NumScrews – 1]) // clamp screws
    rotate(i*360/NumScrews)
    translate([ScrewBCD/2,0,-Protrusion])
    rotate(180/8)
    PolyCyl(PlateGuide[OD],2*PuckOAL,8);
    }
    }
    //—–
    // Build it
    if (Layout == "Puck")
    DW660Puck();
    if (Layout == "Plate")
    SpringPlate();
    if (Layout == "Mount")
    MountBase();
    if (Layout == "Show") {
    MountBase();
    translate([0,0,2*PuckOAL])
    rotate([180,0,0])
    SpringPlate();
    }
    if (Layout == "Build") {
    translate([0,Snout[OD]/2,0])
    MountBase();
    translate([0,-Snout[OD]/2,0])
    SpringPlate();
    }