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

  • Square Chain Mail Armor: Back From The Abyss

    After a Slic3r commit fixed the bridging regression, I ran off chain mail patches to celebrate:

    Square Chain Mail Armor - 3.3 3.5 4.0 thread bars
    Square Chain Mail Armor – 3.3 3.5 4.0 thread bars

    Two more Scli3r improvements calculate thin-wall and gap infill based on the available space, then vary the extrusion width to make the answers come out right for a given nozzle diameter. As a result, infill between close-set perimeter walls works much better than before; some of my long-held assumptions became invalid.

    The only differences between the sheets: tweaking the BarWidth and SheetSize parameters. The links recalculate themselves around those values.

    The OpenSCAD source code as a GitHub gist:

    // Chain Mail Armor Buttons
    // Ed Nisley KE4ZNU – December 2014
    Layout = "Build"; // Link Button LB Joiner Joiners Build PillarMod
    //——-
    //- Extrusion parameters must match reality!
    // Print with 1 shell and 2+2 solid layers
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    Protrusion = 0.1; // make holes end cleanly
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    //——-
    // Dimensions
    //- Set maximum sheet size
    SheetSizeX = 125; // 170 for full sheet on M2
    SheetSizeY = 125; // 230 …
    //- Diamond or rectangular sheet?
    Diamond = false; // true = rotate 45 degrees, false = 0 degrees for square
    BendAround = "X"; // X or Y = maximum flexibility *around* designated axis
    Cap = true; // true = build bridge layers over links
    CapThick = 4 * ThreadThick; // flat cap on link: >= 3 layers for solid bridging
    Armor = true && Cap; // true = build armor button atop (required) cap
    ArmorThick = IntegerMultiple(2.0,ThreadThick); // height above cap surface
    ArmorSides = 4;
    ArmorAngle = true ? 180/ArmorSides : 0; // true -> rotate half a side for best alignment
    //- Link bar sizes
    BarThick = 3 * ThreadThick;
    BarWidth = 3.3 * ThreadWidth;
    BarClearance = 3 * ThreadThick; // vertical clearance above & below bars
    VertexHack = false; // true to slightly reduce openings to avoid coincident vertices
    //- Compute link sizes from those values
    //- Absolute minimum base link: bar width + corner angle + build clearance around bars
    // rounded up to multiple of thread width to ensure clean filling
    BaseSide = IntegerMultiple((4*BarWidth + 2*BarWidth/sqrt(2) + 3*(2*ThreadWidth)),ThreadWidth);
    BaseHeight = 2*BarThick + BarClearance; // both bars + clearance
    echo(str("BaseSide: ",BaseSide," BaseHeight: ",BaseHeight));
    //echo(str(" Base elements: ",4*BarWidth,", ",2*BarWidth/sqrt(2),", ",3*(2*ThreadWidth)));
    //echo(str(" total: ",(4*BarWidth + 2*BarWidth/sqrt(2) + 3*(2*ThreadWidth))));
    BaseOutDiagonal = BaseSide*sqrt(2) – BarWidth;
    BaseInDiagonal = BaseSide*sqrt(2) – 2*(BarWidth/2 + BarWidth*sqrt(2));
    echo(str("Outside diagonal: ",BaseOutDiagonal));
    //- On-center distance measured along coordinate axis
    // the links are interlaced, so this is half of what you think it should be…
    LinkOC = BaseSide/2 + ThreadWidth;
    LinkSpacing = Diamond ? (sqrt(2)*LinkOC) : LinkOC;
    echo(str("Base spacing: ",LinkSpacing));
    //- Compute how many links fit in sheet
    MinLinksX = ceil((SheetSizeX – (Diamond ? BaseOutDiagonal : BaseSide)) / LinkSpacing);
    MinLinksY = ceil((SheetSizeY – (Diamond ? BaseOutDiagonal : BaseSide)) / LinkSpacing);
    echo(str("MinLinks X: ",MinLinksX," Y: ",MinLinksY));
    NumLinksX = ((0 == (MinLinksX % 2)) && !Diamond) ? MinLinksX + 1 : MinLinksX;
    NumLinksY = ((0 == (MinLinksY % 2) && !Diamond)) ? MinLinksY + 1 : MinLinksY;
    echo(str("Links X: ",NumLinksX," Y: ",NumLinksY));
    //- Armor button base
    ButtonHeight = BaseHeight + BarClearance + CapThick;
    echo(str("ButtonHeight: ",ButtonHeight));
    //- Armor ornament size & shape
    // Fine-tune OD & ID to suit the number of sides…
    TotalHeight = ButtonHeight + ArmorThick;
    echo(str("Overall Armor Height: ",TotalHeight));
    ArmorOD = 1.0 * BaseSide; // tune for best base fit
    ArmorID = 10 * ThreadWidth; // make the tip blunt & strong
    //——-
    module ShowPegGrid(Space = 10.0,Size = 1.0) {
    RangeX = floor(95 / Space);
    RangeY = floor(125 / Space);
    for (x=[-RangeX:RangeX])
    for (y=[-RangeY:RangeY])
    translate([x*Space,y*Space,Size/2])
    %cube(Size,center=true);
    }
    //——-
    // Create link with armor button as needed
    module Link(Topping = false) {
    LinkHeight = (Topping && Cap) ? ButtonHeight : BaseHeight;
    render(convexity=3)
    rotate((BendAround == "X") ? 90 : 0)
    rotate(Diamond ? 45 : 0)
    union() {
    difference() {
    translate([0,0,LinkHeight/2]) // outside shape
    intersection() {
    cube([BaseSide,BaseSide,LinkHeight],center=true);
    rotate(45)
    cube([BaseOutDiagonal,BaseOutDiagonal,(LinkHeight + 2*Protrusion)],center=true);
    }
    translate([0,0,(BaseHeight + BarClearance + 0*ThreadThick – Protrusion)/2])
    intersection() { // inside shape
    cube([(BaseSide – 2*BarWidth),
    (BaseSide – 2*BarWidth),
    (BaseHeight + BarClearance + 0*ThreadThick + (VertexHack ? Protrusion/2 : 0))],
    center=true);
    rotate(45)
    cube([BaseInDiagonal,
    BaseInDiagonal,
    (BaseHeight + BarClearance + 0*ThreadThick + (VertexHack ? Protrusion/2 : 0))],
    center=true);
    }
    translate([0,0,((BarThick + 2*BarClearance)/2 + BarThick)]) // openings for bars
    cube([(BaseSide – 2*BarWidth – 2*BarWidth/sqrt(2) – (VertexHack ? Protrusion/2 : 0)),
    (2*BaseSide),
    BarThick + 2*BarClearance – Protrusion],
    center=true);
    translate([0,0,(BaseHeight/2 – BarThick)])
    cube([(2*BaseSide),
    (BaseSide – 2*BarWidth – 2*BarWidth/sqrt(2) – (VertexHack ? Protrusion/2 : 0)),
    BaseHeight],
    center=true);
    }
    if (Topping && Armor)
    translate([0,0,(ButtonHeight – Protrusion)]) // sink slightly into the cap
    rotate(ArmorAngle)
    cylinder(d1=ArmorOD,d2=ArmorID,h=(ArmorThick + Protrusion), $fn=ArmorSides);
    }
    }
    //——-
    // Create split buttons to join sheets
    module Joiner() {
    translate([-LinkSpacing,0,0])
    difference() {
    Link(false);
    translate([0,0,BarThick + BarClearance + TotalHeight/2 – Protrusion])
    cube([2*LinkSpacing,2*LinkSpacing,TotalHeight],center=true);
    }
    translate([LinkSpacing,0,0])
    intersection() {
    translate([0,0,-(BarThick + BarClearance)])
    Link(true);
    translate([0,0,TotalHeight/2])
    cube([2*LinkSpacing,2*LinkSpacing,TotalHeight],center=true);
    }
    }
    //——-
    // Build it!
    //ShowPegGrid();
    if (Layout == "Link") {
    Link(false);
    }
    if (Layout == "Button") {
    Link(true);
    }
    if (Layout == "LB") {
    color("Brown") Link(true);
    translate([LinkSpacing,LinkSpacing,0])
    color("Orange") Link(false);
    }
    if (Layout == "Build")
    for (ix = [0:(NumLinksX – 1)],
    iy = [0:(NumLinksY – 1)]) {
    x = (ix – (NumLinksX – 1)/2)*LinkSpacing;
    y = (iy – (NumLinksY – 1)/2)*LinkSpacing;
    translate([x,y,0])
    color([(ix/(NumLinksX – 1)),(iy/(NumLinksY – 1)),1.0])
    if (Diamond)
    Link((ix + iy) % 2); // armor at odd,odd & even,even points
    else
    if ((iy % 2) && (ix % 2)) // armor at odd,odd points
    Link(true);
    else if (!(iy % 2) && !(ix % 2)) // connectors at even,even points
    Link(false);
    }
    if (Layout == "Joiner")
    Joiner();
    if (Layout == "Joiners") {
    NumJoiners = max(MinLinksX,MinLinksY)/2;
    for (iy = [0:(NumJoiners – 1)]) {
    y = (iy – (NumJoiners – 1)/2)*2*LinkSpacing + LinkSpacing/2;
    translate([0,y,0])
    color([0.5,(iy/(NumJoiners – 1)),1.0])
    Joiner();
    }
    }
    if (Layout == "PillarMod") // Slic3r modification volume to eliminate pillar infill
    translate([0,0,(BaseHeight + BarClearance)/2])
    cube([1.5*SheetSizeX,1.5*SheetSizeY,BaseHeight + BarClearance],center=true);
  • Raspberry Pi Power Heartbeat LED

    While looking for something else, I found a reference to the /boot/overlays/README file, wherein it is written:

            act_led_trigger         Choose which activity the LED tracks.
                                    Use "heartbeat" for a nice load indicator.
                                    (default "mmc")
    
            act_led_activelow       Set to "on" to invert the sense of the LED
                                    (default "off")
    
            act_led_gpio            Set which GPIO to use for the activity LED
                                    (in case you want to connect it to an external
                                    device)
                                    (default "16" on a non-Plus board, "47" on a
                                    Plus or Pi 2)
    
    ... snippage ...
    
            pwr_led_trigger
            pwr_led_activelow
            pwr_led_gpio
                                    As for act_led_*, but using the PWR LED.
                                    Not available on Model A/B boards.
    

    Although the power LED isn’t (easily) visible through the Canakit cases I’m using (it’s under the barely visible hole in front of the small hole near the hacked RUN connector), turning it into a heartbeat pulse distinguishes the CPU’s “running” and “halted” states; whether it will also distinguish “crashed” is up for grabs.

    It’s not at all clear what other choices you have.

    To enable heartbeating, add this to /boot/config.txt:

    # turn power LED into heartbeat
    dtparam=pwr_led_trigger=heartbeat
    #
    

    I expected a simple 50% duty cycle heartbeat, but it’s an annoying double blink: long off / on / off / on / long off. Fortunately, it still isn’t (easily) visible …

    While you have that file open, reduce the GPU memory to the absolute minimum for headless operation:

    # minimal GPU memory for headless operation
    gpu_mem=16
    #
    

    Some further ideas, including a way to turn off the HDMI interface.

  • LED Bulbs: Train Station Chandelier

    The three big chandeliers in the Poughkeepsie Train Station now sport LED bulbs:

    Poughkeepsie Train Station - LED bulbs
    Poughkeepsie Train Station – LED bulbs

    All three had 36 working bulbs and, with a bit of good QC, should continue that way for a long, long time.

    LED bulbs don’t have the intense point-source brilliance of clear tungsten bulbs and even the warm-white ones tend toward the cool end of the spectrum, but they’re Good Enough …

  • Streaming Player: NFS Program Distribution

    With three identical Raspberry Pi streaming players tootling around the house, it finally dawned on me that they should fetch their Python program directly from The Definitive Source, rather than a local copy.

    Tweak the auto-startup in /etc/rc.local:

    mount -o ro mollusk:/mnt/bulkdata/Project\ Files/Streaming\ Media\ Player/Firmware/ /mnt/part
    sudo -u pi python /mnt/part/Streamer.py &
    

    There’s probably a way to redirect all of the stdout and stderr results to a file for debugging, but the obvious method doesn’t work:

    sudo -u pi sh -c "python /mnt/part/Streamer.py 2>&1 > /tmp/st.log" &
    

    That redirects stdout from the subprocess call to set up the mixer, but doesn’t catch Python’s print output.

    Using the Python logging library would get most of the way to the goal, although stdout from things like the mixer would still vanish.

    Continuing with the network theme, one could netboot the RPi players, but that requires more sysadmin hackery than I’m willing to do, what with the good being the enemy of the best.

  • Panasonic Eneloop Pro NiMH Cells: First Charge

    The Sony DSC-H5 expects a much higher voltage from its pair of NiMH AA cells than the tired Sanyo Eneloops from 2010 can provide these days.

    Eneloop - 8-cell pack - 2010 - 2016 - merged
    Eneloop – 8-cell pack – 2010 – 2016 – merged

    The two upper curves show the first two charges for those eight cells back in 2010.

    The lower curve(s) started out with the wrong endpoint voltage (purple part of the middle curve), so I restarted the test (green curve) and edited the graph image to splice the two curves together into the purple/red curve.

    Although the capacity measured in mA·h isn’t much lower, the voltage depression reduces the available energy and trips the “low battery” alarm much earlier. In round numbers, the old cells were good for a few pictures, even hot off the charger, and didn’t have much energy left without being recharged before use.

    A quartet of Panasonic Eneloop Pro cells just arrived from BatterySpace, a nominally reputable supplier, all sporting a 14-05 date code suggesting they’re just shy of two years old. The packaging claims 85% charge retention after a year, so they should have a bit more than half of their rated 2.45 A·h “minimum” (or 2.55 mA·h “typical”, depending on whether you trust the label on the cell or the big print on the package) capacity remaining (although we don’t know the original state of charge, done from “solar power”). The lower curves say they arrived with 1 A·h remaining:

    Panasonic Eneloop - First Charge
    Panasonic Eneloop – First Charge

    However, the terminal voltage on those bottom curves would have any reasonable device reporting them as dead flat almost instantly, so you really can’t store Eneloops for two years: no surprise there.

    One pass through the 400 mA Sony charger produced the upper curves, with the dotted red curve from Cell A lagging in the middle. After that test, another pass through the charger brought Cell A back (upper solid red line) with the others, so I’ll assume it took a while to wake up.

    A pair of these in the camera will produce 2.2 V through 2.2 A·h, far better than the aged-out Sanyo Eneloops.

    Charging them at 400 mA = C/6 certainly counts as a slow charge. I’ve been charging the Sanyo cells in slow chargers in the hope that they’ll remain happier over the long term.

    The Panasonic Eneloops perform much better than some other cells you’ve seen around here, which may be due to the fact that I paid $5 each for them…

     

  • Sony NP-FS11 Lithium Battery Rebuilds: 2016

    It seems that two years is about as long as the NP-FS11 batteries last, as shown by the two lower curves from the ones I rebuilt in December 2013 with cells from 2011:

    Sony NP-FS11 2011-2016 Packs
    Sony NP-FS11 2011-2016 Packs

    The two middle curves with those same colors show the “back then” performance of those batteries: they’re shot in both total capacity and terminal voltage.

    I bought enough cells back in 2011 to leave two cells unused until now, which I built into a pack and charged. The green curve in the middle shows the result: those cells haven’t lost anything over the last five (!) years, as their performance still matches the other two batteries when they were new.

    The red curves come from a pair of batteries made with fresh new cells from batteryspace.com. They’re nominally 650 mA·h cells, so the NP-FS11 configuration (two parallel cells) should produce 1300 mA·h; surprisingly, they show 1500 mA·h with a nice voltage curve.

    So, although the 2011 cells work as well as their (now defunct) siblings, that pack can’t deliver the same capacity as the new cells. I expect I’ll rebuild it with 2016 cells in about a year.

    For whatever it’s worth, rebuilding these batteries goes much faster when I don’t have to saw them open. The Kapton tape wrapped around the case halves secures them well enough; there’s no need for fancy gluing.

    NP-FS11 Battery Rebuilds - 2016-03
    NP-FS11 Battery Rebuilds – 2016-03

    Yeah, I should make better labels. It’s hard to form a deep emotional attachment to the poor things, though.

    Here’s a case where something performs better than expected; I don’t always buy cheap junk from the usual eBay vendor…

     

     

     

     

  • OttLite LED Conversion

    Although Mary liked the illumination from her OttLite (an old 13 W fluorescent Folding Task Lamp), neither of us liked its tiny base and tippy nature. It recently fell / was dropped / jumped to its doom, smashing the CFL tube and wreaking havoc on the tiny plastic studs holding its large cast-iron weight and steel base in position. Given that the CFL ballast had started humming a while ago, I took it apart to see whether I could salvage anything from the rubble.

    Remove:

    • Four screws under the fuzzy felt feet
    • One screw under the label on the back
    • A final screw that becomes visible only after disemboweling the hinge assembly by unscrewing the obvious endcaps:
    OttLite LED Conversion - hinge screw
    OttLite LED Conversion – hinge screw

    Pull the hinge end of the white inside panel away from the outer stand at enough of an angle to disengage all three latches holding it to the base, then remove it just enough to let you start cutting wires around the ballast…

    I rebuilt the thing with a pair of 24 V 150 mA warm-white LED panels (good industrial surplus, not the usual cheap eBay crap) powered by a 19 V laptop adapter (from IBM, no less) through a (cheap eBay) boost converter sticky-foam-taped where the fluorescent ballast used to live:

    OttLite LED Conversion - boost supply wiring
    OttLite LED Conversion – boost supply wiring

    The power supply had only two conductors, the central wire surrounded by twisted shielding, and didn’t require a fussy interface. Hooray for simple bulk power supplies; I lopped off the connector and soldered the wires directly to the boost converter.

    The original lamp wiring has a 120 VAC switch inside the hinge that turned the lamp on as you raise the arm holding the CFL tube: exactly what I need for its new use. That eliminated figuring out how to crack the arm apart to rewire it.

    I harvested the base from a(nother) defunct CFL bulb:

    OttLite LED Conversion - harvested CFL base
    OttLite LED Conversion – harvested CFL base

    By soldering wires directly into the pins, I could reuse the existing CFL socket in the lamp arm, the existing wiring, and the switch.

    The LED panels dissipate 3-ish W each:

    OttLite LED Conversion - LED panel layout
    OttLite LED Conversion – LED panel layout

    They’re mounted on a 0.1 inch aluminum sheet from the heap that required exactly one saw cut to fit into the space available, so I defined it to be perfect. The 4-40 screws holding the panels in place continue through the plate and 3/8 inch aluminum standoffs into a quartet of knurled inserts epoxied into eyeballometrically match-drilled holes in the lamp arm:

    OttLite LED Conversion - epoxied threaded inserts
    OttLite LED Conversion – epoxied threaded inserts

    The faint yellowish discoloration from the CFL tube’s heat and UV is much more visible in real life, but nobody will ever see it again. The scrawled blue (+) and (-) marks give the socket polarity; it’s not mechanically polarized and a bit of care is in order. The black rectangle is actually a shiny metal sheet intended to reflect heat from the CFL tube’s base away from the plastic arm.

    I set the boost converter to 23.5 V, at which point the LED panels draw about 100 mA each and get just over uncomfortably warm after an hour or two:

    OttLite LED Conversion - in action
    OttLite LED Conversion – in action

    The panels run 120 °F = 50 °C and the SMD LEDs probably exceed 150 °F = 65 °C. The scant surplus doc touted “No heatsink required” and the single-sided FR4 PCB insulates the LEDs from the aluminum sheet, but I still smeared some heatsink compound behind the panels in the hopes of spreading the heat out a bit.

    I glued the shattered base studs back in place with IPS #3, surrounded them with generous epoxy fillets, plunked the cast iron weight in place atop some waxed paper to mold the epoxy to fit (and let me remove it again, if needs be), screwed everything together, and stuck a foam sheet over the steel base plate. It’s as tippy as before, but at least the LEDs won’t shatter if when it falls. It really needs a larger base; a polycarbonate plate might work, if only I could figure out how to attach it.

    All in all, the lamp looks good and the warm-white LEDs with DC drive don’t produce that horrible fluorescent flicker.

    The lamp now sports a label identifying it as a NisLite; because P-Touch labeler.