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: Electronics Workbench

Electrical & Electronic gadgets

  • Improving Avahi Startup Speed

    At least on my network, disabling the IPv6 functions makes Avahi start up faster. You do that by tweaking the obvious IPV6 line in /etc/avahi/avahi-daemon.conf:

    use-ipv4=yes
    use-ipv6=no
    

    It’s also useful to disable power management in the USB WiFi dongle by adding /etc/modprobe.d/8192cu.conf:

    # Disable power saving
    options 8192cu rtw_power_mgnt=0 rtw_enusbss=0
    
  • UDEV Rules for Cheap Numeric Keypads

    I’m thinking of numeric keypads as control panels for Raspberry Pi projects like a simpleminded streaming radio player, so:

    Numeric keypads
    Numeric keypads

    Sorta like wedding pictures: you can expose for the groom-in-black or the bride-in-white, but not both at the same time.

    The wireless keypad does not have a slot for the USB radio: put ’em in a bag to keep ’em together when not in use.

    The general idea is to create a standard name (/dev/input/keypad) for either keypad when it gets plugged in, so the program need not figure out the device name from first principles. This being an embedded system, I can ensure only one keypad will be plugged in at any one time.

    The wired keypad has an odd name that makes a certain perverse sense:

    cat /proc/bus/input/devices
    ... snippage ...
    I: Bus=0003 Vendor=13ba Product=0001 Version=0110
    N: Name="HID 13ba:0001"
    P: Phys=usb-20980000.usb-1.4/input0
    S: Sysfs=/devices/platform/soc/20980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/0003:13BA:0001.0007/input/input6
    U: Uniq=
    H: Handlers=sysrq kbd event0
    B: PROP=0
    B: EV=120013
    B: KEY=10000 7 ff800000 7ff febeffdf f3cfffff ffffffff fffffffe
    B: MSC=10
    B: LED=7
    

    It’s a single-function device, so this rule in /etc/udev/rules.d/KeyPad.rules suffices:

    ATTRS{name}=="HID 13ba:0001", SYMLINK+="input/keypad"
    

    Using the Vendor and Device ID strings (13ba:0001) might make more sense.

    The wireless keypad isn’t nearly that easy, because it reports for duty as both a keyboard and a mouse:

    cat /proc/bus/input/devices
    ... snippage ...
    I: Bus=0003 Vendor=062a Product=4101 Version=0110
    N: Name="MOSART Semi. 2.4G Keyboard Mouse"
    P: Phys=usb-20980000.usb-1.4/input0
    S: Sysfs=/devices/platform/soc/20980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/0003:062A:4101.0008/input/input7
    U: Uniq=
    H: Handlers=sysrq kbd event0
    B: PROP=0
    B: EV=120013
    B: KEY=10000 7 ff9f207a c14057ff febeffdf ffefffff ffffffff fffffffe
    B: MSC=10
    B: LED=7
    
    I: Bus=0003 Vendor=062a Product=4101 Version=0110
    N: Name="MOSART Semi. 2.4G Keyboard Mouse"
    P: Phys=usb-20980000.usb-1.4/input1
    S: Sysfs=/devices/platform/soc/20980000.usb/usb1/1-1/1-1.4/1-1.4:1.1/0003:062A:4101.0009/input/input8
    U: Uniq=
    H: Handlers=kbd mouse0 event2
    B: PROP=0
    B: EV=1f
    B: KEY=3f 3007f 0 0 0 0 483ffff 17aff32d bf544446 0 0 1f0001 130f93 8b17c000 677bfa d941dfed 9ed680 4400 0 10000002
    B: REL=1c3
    B: ABS=1f01 0
    B: MSC=10
    

    That may be because the 0x06a2 Vendor ID was cloned (that’s pronounced “ripped-off”) from Creative Labs. My guess is they ripped the entire chipset, because the 0x4101 device ID came from a Creative Labs wireless keyboard + mouse:

    lsusb
    ... snippage ...
    Bus 001 Device 011: ID 062a:4101 Creative Labs
    ... snippage ...
    

    Because it’s a dual-mode wireless device, we need more information to create the corresponding udev rule. The keyboard part appears (on this boot) as event0, which we find thusly:

    ll /dev/input/by-id
    total 0
    lrwxrwxrwx 1 root root 9 Feb  5 17:39 usb-Burr-Brown_from_TI_USB_Audio_CODEC-event-if03 -> ../event1
    lrwxrwxrwx 1 root root 9 Feb  5 17:39 usb-MOSART_Semi._2.4G_Keyboard_Mouse-event-kbd -> ../event0
    lrwxrwxrwx 1 root root 9 Feb  5 17:39 usb-MOSART_Semi._2.4G_Keyboard_Mouse-if01-event-mouse -> ../event2
    lrwxrwxrwx 1 root root 9 Feb  5 17:39 usb-MOSART_Semi._2.4G_Keyboard_Mouse-if01-mouse -> ../mouse0
    

    Some spelunking suggests using the environment variables set up by the default udev rules, which we find thusly:

    udevadm test /sys/class/input/event0
    ... vast snippage ...
    .INPUT_CLASS=kbd
    ACTION=add
    DEVLINKS=/dev/input/by-id/usb-MOSART_Semi._2.4G_Keyboard_Mouse-event-kbd /dev/input/by-path/platform-20980000.usb-usb-0:1.4:1.0-event-kbd
    DEVNAME=/dev/input/event0
    DEVPATH=/devices/platform/soc/20980000.usb/usb1/1-1/1-1.4/1-1.4:1.0/0003:062A:4101.0012/input/input17/event0
    ID_BUS=usb
    ID_INPUT=1
    ID_INPUT_KEY=1
    ID_INPUT_KEYBOARD=1
    ID_MODEL=2.4G_Keyboard_Mouse
    ID_MODEL_ENC=2.4G\x20Keyboard\x20Mouse
    ID_MODEL_ID=4101
    ID_PATH=platform-20980000.usb-usb-0:1.4:1.0
    ID_PATH_TAG=platform-20980000_usb-usb-0_1_4_1_0
    ID_REVISION=0108
    ID_SERIAL=MOSART_Semi._2.4G_Keyboard_Mouse
    ID_TYPE=hid
    ID_USB_DRIVER=usbhid
    ID_USB_INTERFACES=:030101:030102:
    ID_USB_INTERFACE_NUM=00
    ID_VENDOR=MOSART_Semi.
    ID_VENDOR_ENC=MOSART\x20Semi.
    ID_VENDOR_ID=062a
    MAJOR=13
    MINOR=64
    SUBSYSTEM=input
    ... more snippage ...
    

    So when that vendor and device appear with ID_INPUT_KEYBOARD set, we can create a useful symlink using this rule in /etc/udev/rules.d/KeyPad.rules:

    ATTRS{idVendor}=="062a", ATTRS{idProduct}=="4101", ENV{ID_INPUT_KEYBOARD}=="1", SYMLINK+="input/keypad"
    

    Because only one keypad will be plugged in at any one time, the /etc/udev/rules.d/KeyPad.rules file can contain both rules:

    ATTRS{name}=="HID 13ba:0001", SYMLINK+="input/keypad"
    ATTRS{idVendor}=="062a", ATTRS{idProduct}=="4101", ENV{ID_INPUT_KEYBOARD}=="1", SYMLINK+="input/keypad"
    

    Reload the rules and fire them off:

    sudo udevadm control --reload
    sudo udevadm trigger
    

    And then It Just Works:

    ll /dev/input/by-id
    total 0
    lrwxrwxrwx 1 root root 9 Feb  5 17:39 usb-Burr-Brown_from_TI_USB_Audio_CODEC-event-if03 -> ../event1
    lrwxrwxrwx 1 root root 9 Feb  5 19:03 usb-MOSART_Semi._2.4G_Keyboard_Mouse-event-kbd -> ../event0
    lrwxrwxrwx 1 root root 9 Feb  5 19:03 usb-MOSART_Semi._2.4G_Keyboard_Mouse-if01-event-mouse -> ../event2
    lrwxrwxrwx 1 root root 9 Feb  5 19:03 usb-MOSART_Semi._2.4G_Keyboard_Mouse-if01-mouse -> ../mouse0ll /dev/input
    
    ll /dev/input
    total 0
    drwxr-xr-x 2 root root     120 Feb  5 19:03 by-id
    drwxr-xr-x 2 root root     120 Feb  5 19:03 by-path
    crw-rw---- 1 root input 13, 64 Feb  5 19:03 event0
    crw-rw---- 1 root input 13, 65 Feb  5 17:39 event1
    crw-rw---- 1 root input 13, 66 Feb  5 19:03 event2
    lrwxrwxrwx 1 root root       6 Feb  5 19:03 keypad -> event0
    crw-rw---- 1 root input 13, 63 Feb  5 17:39 mice
    crw-rw---- 1 root input 13, 32 Feb  5 19:03 mouse0
    

    My configuration hand is strong

    Note: Once again, I manually restored the source code after the WordPress “improved” editor shredded it by replacing all the double-quote and greater-than symbols inside the “protected” sourcecode blocks with their HTML-escaped equivalents. Some breakage may remain and, as always, WP can shred sourcecode blocks even if I don’t edit the post. They’ve (apparently) banned me from contacting Support, because of an intemperate rant based on years of having them ignore this (and other) problems. I didn’t expect any real help, so this isn’t much of a step backwards in terms of actual support …

  • Vacuum Tube LEDs: Ersatz Heat Sink Plate Cap

    I wanted a slightly larger “plate cap” to fit a big incandescent bulb and it seemed a fake heatsink might add gravitas to the proceedings:

    Vacuum Tube LEDs - large incandescent bulb
    Vacuum Tube LEDs – large incandescent bulb

    Yeah, that antique ceramic socket holds the bulb at a rakish angle. Worse, even though I painstakingly laid out the position of the heatsink atop the bulb, it’s visibly off-center. Which wouldn’t be so bad, had I not epoxied the damn thing in place.

    After reaming out the M2’s filament drive, the entire blue base printed without incident.

    A closer look at the cap:

    Vacuum Tube LEDs - ersatz heatsink plate cap
    Vacuum Tube LEDs – ersatz heatsink plate cap

    Memo to Self: Next time, line it up with the vertical glass support inside the bulb and ignore the external evidence.

    The boss has a hole for the braid-enclosed cable to the knockoff Neopixel:

    Vacuum Tube Lights - finned cap - Slic3r preview
    Vacuum Tube Lights – finned cap – Slic3r preview

    The cupped surface perfectly fits the bulb’s 3.75 inch diameter. While you wouldn’t mill out a real heatsink, it definitely looks better this way and (alas) gives the epoxy more footprint for a better grip.

    I built the fins with a 1/8 inch cutter in mind, so the fin root radius allows for a G3/G3 arc without gouging. I doubt machining a fake heatsink from aluminum makes any sense, but the cheap extruded heatsinks on eBay don’t look very good. Plus, they sport completely unnecessary tapped holes for LED mounts and suchlike.

    A cross-section shows the wiring channel and cable entry:

    Vacuum Tube Lights - fin cap solid model - section
    Vacuum Tube Lights – fin cap solid model – section

    I epoxied the Neopixel in place, applied double-sided carpet tape to the whole thing, then painstakingly trimmed around the fins with an Xacto knife:

    Vacuum Tube LEDs - Ersatz Heatsink plate cap - tape
    Vacuum Tube LEDs – Ersatz Heatsink plate cap – tape

    That looked better from the top side (where it was completely hidden) and came heartbreakingly close to working, but after about a day the cable + braid put enough torque on the cap to peel it off the bulb. Obviously, the tape holds much less enthusiastically after that.

    Part of the problem came from the cable’s rather sharp angle just outside the cap:

    Vacuum Tube LEDs - Ersatz Heatink plate cap - detail
    Vacuum Tube LEDs – Ersatz Heatink plate cap – detail

    Rakish angle, indeed. Two of ’em, in fact.

    Unlike the smaller cap on the halogen bulb, this time I didn’t bother with a brass tube ferrule, mostly to see how it looks. I think it came out OK and the black braid looks striking in person. Conversely, a touch of brass never detracts from the appearance.

    Obviously, the cable wasn’t long enough, either. Part of that problem came from underestimating the braid length: it shortens dramatically when slipped over the cable, even when you expect shortening. Somehow I managed to overlook that, despite cutting the cable quite long enough, thankyouverymuch. There’s a tradeoff between gentle angles and having the cable stick out too far for comfort.

    Memo to Self: Use a cable at least four inches longer than necessary, measure the combined cable + braid assembly after screwing the bulb in the socket, and don’t epoxy anything before all the parts are ready for assembly.

    That’s why it’s a prototype made out of blue PETG…

    Protip: running old ceramic sockets through the dishwasher greatly simplifies their subsequent cleanup.

    All in all, I like it.

    The OpenSCAD source code as a GitHub gist:

    // Vacuum Tube LED Lights
    // Ed Nisley KE4ZNU January 2016
    Layout = "FinCap"; // Cap LampBase USBPort Socket(s) (Build)FinCap
    Section = true; // cross-section the object
    Support = true;
    //- 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
    // https://en.wikipedia.org/wiki/Tube_socket#Summary_of_Base_Details
    T_NAME = 0; // common name
    T_NUMPINS = 1; // total, with no allowance for keying
    T_PINBCD = 2; // tube pin circle diameter
    T_PINOD = 3; // … diameter
    T_PINLEN = 4; // … length (overestimate)
    T_HOLEOD = 5; // nominal panel hole from various sources
    T_PUNCHOD = 6; // panel hole optimized for inch-size Greenlee punches
    T_TUBEOD = 7; // envelope or base diameter
    T_PIPEOD = 8; // light pipe from LED to tube base
    T_SCREWOC = 9; // mounting screw holes
    // Name pins BCD dia length hole punch env pipe screw
    TubeData = [
    ["Mini7", 8, 9.53, 1.016, 7.0, 16.0, 11/16 * inch, 18.0, 5.0, 22.5],
    ["Octal", 8, 17.45, 2.36, 10.0, 36.2, (8 + 1)/8 * inch, 32.0, 11.5, 39.0],
    ["Noval", 10, 11.89, 1.1016, 7.0, 22.0, 7/8 * inch, 21.0, 5.0, 28.0],
    ["Duodecar", 13, 19.10, 1.05, 9.0, 32.0, 1.25 * inch, 38.0, 12.5, 39.0],
    ];
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Pixel = [7.0,10.0,3.0]; // ID = contact patch, OD = PCB dia, LENGTH = overall thickness
    Nut = [3.5,8.0,3.0]; // socket mounting nut recess
    BaseShim = 2*ThreadThick; // between pin holes and pixel top
    SocketFlange = 2.0; // rim around socket below punchout
    PanelThick = 2.0; // socket extension through punchout
    //———————-
    // 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);
    }
    //———————-
    // Tube cap
    CapTube = [4.0,3/16 * inch,10.0]; // brass tube for flying lead to cap LED
    CapSize = [Pixel[ID],(Pixel[OD] + 3.0),(CapTube[OD] + 2*Pixel[LENGTH])];
    CapSides = 6*4;
    module Cap() {
    difference() {
    union() {
    cylinder(d=CapSize[OD],h=(CapSize[LENGTH]),$fn=CapSides); // main cap body
    translate([0,0,CapSize[LENGTH]]) // rounded top
    scale([1.0,1.0,0.65])
    sphere(d=CapSize[OD]/cos(180/CapSides),$fn=CapSides); // cos() fixes slight undersize vs cylinder
    cylinder(d1=(CapSize[OD] + 2*3*ThreadWidth),d2=CapSize[OD],h=1.5*Pixel[LENGTH],$fn=CapSides); // skirt
    }
    translate([0,0,-Protrusion]) // bore for wiring to LED
    PolyCyl(CapSize[ID],(CapSize[LENGTH] + 3*ThreadThick + Protrusion),CapSides);
    translate([0,0,-Protrusion]) // PCB recess with clearance for tube dome
    PolyCyl(Pixel[OD],(1.5*Pixel[LENGTH] + Protrusion),CapSides);
    translate([0,0,(1.5*Pixel[LENGTH] – Protrusion)]) // small step + cone to retain PCB
    cylinder(d1=(Pixel[OD]/cos(180/CapSides)),d2=Pixel[ID],h=(Pixel[LENGTH] + Protrusion),$fn=CapSides);
    translate([0,0,(CapSize[LENGTH] – CapTube[OD]/(2*cos(180/8)))]) // hole for brass tube holding wire loom
    rotate([90,0,0]) rotate(180/8)
    PolyCyl(CapTube[OD],CapSize[OD],8);
    }
    }
    //———————-
    // Heatsink tube cap
    CableOD = 3.5; // cable + braid diameter
    BulbOD = 3.75 * inch; // bulb OD; use 10 inches for flat
    FinCutterOD = 1/8 * inch;
    echo(str("Fin Cutter: ",FinCutterOD));
    FinSides = 2*4;
    FinCapSize = [(Pixel[OD] + 2*FinCutterOD),30.0,(10.0 + 2*Pixel[LENGTH])];
    BulbRadius = BulbOD / 2;
    BulbDepth = BulbRadius – sqrt(pow(BulbRadius,2) – pow(FinCapSize[OD],2)/4);
    echo(str("Bulb OD: ",BulbOD," recess: ",BulbDepth));
    module FinCap() {
    NumFins = floor(PI*FinCapSize[ID] / (2*FinCutterOD));
    FinAngle = 360 / NumFins;
    echo(str("NumFins: ",NumFins," angle: ",FinAngle," deg"));
    difference() {
    union() {
    cylinder(d=FinCapSize[ID],h=FinCapSize[LENGTH],$fn=2*NumFins); // main body
    for (i = [0:NumFins – 1]) // fins
    rotate(i * FinAngle)
    hull() {
    translate([FinCapSize[ID]/2,0,0])
    rotate(180/FinSides)
    cylinder(d=FinCutterOD,h=FinCapSize[LENGTH],$fn=FinSides);
    translate([(FinCapSize[OD] – FinCutterOD)/2,0,0])
    rotate(180/FinSides)
    cylinder(d=FinCutterOD,h=FinCapSize[LENGTH],$fn=FinSides);
    }
    rotate(FinAngle/2) // cable entry boss
    translate([FinCapSize[ID]/2,0,FinCapSize[LENGTH]/2])
    cube([FinCapSize[OD]/4,FinCapSize[OD]/4,FinCapSize[LENGTH]],center=true);
    }
    for (i = [1:NumFins – 1]) // fin inner gullets, omit cable entry side
    rotate(i * FinAngle + FinAngle/2) // joint isn't quite perfect, but OK
    translate([FinCapSize[ID]/2,0,-Protrusion])
    rotate(0*180/FinSides)
    cylinder(d=FinCutterOD/cos(180/FinSides),h=(FinCapSize[LENGTH] + 2*Protrusion),$fn=FinSides);
    translate([0,0,-Protrusion]) // PCB recess
    PolyCyl(Pixel[OD],(1.5*Pixel[LENGTH] + Protrusion),FinSides);
    PolyCyl(Pixel[ID],(FinCapSize[LENGTH] – 3*ThreadThick),FinSides); // bore for LED wiring
    translate([0,0,(FinCapSize[LENGTH] – 3*ThreadThick – 2*CableOD/(2*cos(180/8)))]) // cable inlet
    rotate(FinAngle/2) rotate([0,90,0]) rotate(180/8)
    PolyCyl(CableOD,FinCapSize[OD],8);
    if (BulbOD <= 10.0 * inch) // curve for top of bulb
    translate([0,0,-(BulbRadius – BulbDepth + 2*ThreadThick)]) // … slightly flatten tips
    sphere(d=BulbOD,$fn=16*FinSides);
    }
    }
    //———————-
    // Aperture for USB-to-serial adapter snout
    // These are all magic numbers, of course
    module USBPort() {
    translate([0,28.0])
    rotate([90,0,0])
    linear_extrude(height=28.0)
    polygon(points=[
    [0,0],
    [8.0,0],
    [8.0,4.0],
    // [4.0,4.0],
    [4.0,6.5],
    [-4.0,6.5],
    // [-4.0,4.0],
    [-8.0,4.0],
    [-8.0,0],
    ]);
    }
    //———————-
    // Box for Leviton ceramic lamp base
    module LampBase() {
    Bottom = 3.0;
    Base = [4.0*inch,4.5*inch,20.0 + Bottom];
    Sides = 12*4;
    Retainer = [3.5,11.0,1.0]; // flat fiber washer holding lamp base screws in place
    StudSides = 8;
    StudOC = 3.5 * inch;
    Stud = [0.107 * inch, // 6-32 mounting screws
    min(15.0,1.5*(Base[ID] – StudOC)/cos(180/StudSides)), // OD = big enough to merge with walls
    (Base[LENGTH] – Retainer[LENGTH])]; // leave room for retainer
    union() {
    difference() {
    rotate(180/Sides)
    cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
    rotate(180/Sides)
    translate([0,0,Bottom])
    cylinder(d=Base[ID],h=Base[LENGTH],$fn=Sides);
    translate([0,-Base[OD]/2,Bottom + 1.2]) // mount on double-sided foam tape
    rotate(0)
    USBPort();
    }
    for (i = [-1,1])
    translate([i*StudOC/2,0,0])
    rotate(180/StudSides)
    difference() {
    # cylinder(d=Stud[OD],h=Stud[LENGTH],$fn=StudSides);
    translate([0,0,Bottom])
    PolyCyl(Stud[ID],(Stud[LENGTH] – (Bottom – Protrusion)),6);
    }
    }
    }
    //———————-
    // Tube Socket
    module Socket(Name = "Mini7") {
    NumSides = 6*4;
    Tube = search([Name],TubeData,1,0)[0];
    echo(str("Building ",TubeData[Tube][0]," socket"));
    echo(str(" Punch: ",TubeData[ID][T_PUNCHOD]," mm = ",TubeData[ID][T_PUNCHOD]/inch," inch"));
    echo(str(" Screws: ",TubeData[ID][T_SCREWOC]," mm =",TubeData[ID][T_SCREWOC]/inch," inch OC"));
    OAH = Pixel[LENGTH] + BaseShim + TubeData[Tube][T_PINLEN];
    BaseHeight = OAH – PanelThick;
    difference() {
    union() {
    linear_extrude(height=BaseHeight)
    hull() {
    circle(d=(TubeData[Tube][T_PUNCHOD] + 2*SocketFlange),$fn=NumSides);
    for (i=[-1,1])
    translate([i*TubeData[Tube][T_SCREWOC]/2,0])
    circle(d=2*Nut[OD],$fn=NumSides);
    }
    cylinder(d=TubeData[Tube][T_PUNCHOD],h=OAH,$fn=NumSides);
    }
    for (i=[0:(TubeData[Tube][T_NUMPINS] – 1)]) // tube pins
    rotate(i*360/TubeData[Tube][T_NUMPINS])
    translate([TubeData[Tube][T_PINBCD]/2,0,(OAH – TubeData[Tube][T_PINLEN])])
    rotate(180/4)
    PolyCyl(TubeData[Tube][T_PINOD],(TubeData[Tube][T_PINLEN] + Protrusion),4);
    for (i=[-1,1]) // mounting screw holes & nut traps
    translate([i*TubeData[Tube][T_SCREWOC]/2,0,-Protrusion]) {
    PolyCyl(Nut[OD],(Nut[LENGTH] + Protrusion),6);
    PolyCyl(Nut[ID],(OAH + 2*Protrusion),6);
    }
    translate([0,0,-Protrusion]) { // LED recess
    PolyCyl(Pixel[OD],(Pixel[LENGTH] + Protrusion),8);
    }
    translate([0,0,(Pixel[LENGTH] – Protrusion)]) { // light pipe
    rotate(180/TubeData[Tube][T_NUMPINS])
    PolyCyl(TubeData[Tube][T_PIPEOD],(OAH + 2*Protrusion),TubeData[Tube][T_NUMPINS]);
    }
    }
    // Totally ad-hoc support structures …
    if (Support) {
    color("Yellow") {
    for (i=[-1,1]) // nut traps
    translate([i*TubeData[Tube][T_SCREWOC]/2,0,(Nut[LENGTH] – ThreadThick)/2])
    for (a=[0:5])
    rotate(a*30 + 15)
    cube([2*ThreadWidth,0.9*Nut[OD],(Nut[LENGTH] – ThreadThick)],center=true);
    if (Pixel[OD] > TubeData[Tube][T_PIPEOD]) // support pipe only if needed
    translate([0,0,(Pixel[LENGTH] – ThreadThick)/2])
    for (a=[0:7])
    rotate(a*22.5)
    cube([2*ThreadWidth,0.9*Pixel[OD],(Pixel[LENGTH] – ThreadThick)],center=true);
    }
    }
    }
    //———————-
    // Build it
    if (Layout == "Cap") {
    if (Section)
    difference() {
    Cap();
    translate([-CapSize[OD],0,CapSize[LENGTH]])
    cube([2*CapSize[OD],2*CapSize[OD],3*CapSize[LENGTH]],center=true);
    }
    else
    Cap();
    }
    if (Layout == "FinCap") {
    if (Section) render(convexity=5)
    difference() {
    FinCap();
    // translate([0,-FinCapSize[OD],FinCapSize[LENGTH]])
    // cube([2*FinCapSize[OD],2*FinCapSize[OD],3*FinCapSize[LENGTH]],center=true);
    translate([-FinCapSize[OD],0,FinCapSize[LENGTH]])
    cube([2*FinCapSize[OD],2*FinCapSize[OD],3*FinCapSize[LENGTH]],center=true);
    }
    else
    FinCap();
    }
    if (Layout == "BuildFinCap")
    translate([0,0,FinCapSize[LENGTH]])
    rotate([180,0,0])
    FinCap();
    if (Layout == "LampBase")
    LampBase();
    if (Layout == "USBPort")
    USBPort();
    if (Layout == "Socket")
    if (Section) {
    difference() {
    Socket();
    translate([-100/2,0,-Protrusion])
    cube([100,50,50],center=false);
    }
    }
    else
    Socket();
    if (Layout == "Sockets") {
    translate([0,50,0])
    Socket("Mini7");
    translate([0,20,0])
    Socket("Octal");
    translate([0,-15,0])
    Socket("Duodecar");
    translate([0,-50,0])
    Socket("Noval");
    }
  • Vacuum Tube LEDs: First Light!

    A test lashup to see how it all works, with an ersatz plate cap atop the IBM 21HB5A Beam Power tube on the far right end:

    Vacuum Tube LEDs - test lashup
    Vacuum Tube LEDs – test lashup

    Those sockets must mount in a chassis, not flop around loose on the cable.

    I hacked the code out of the Hard Drive Platter Mood Light; there’s a lot to not like about what’s left and I must rethink the overall structure. The colors now run an order of magnitude faster than the Platter Mood Light, with a 90° phase angle between successive Neopixels.

    The mica spacers in the 12AT7 Dual Triode tube (second in the sequence, Noval socket) look cool & crystalline:

    Vacuum Tube LEDs - Noval tube - blue phase
    Vacuum Tube LEDs – Noval tube – blue phase

    When the red phase comes around, it becomes a firebottle:

    Vacuum Tube LEDs - Noval tube - red phase
    Vacuum Tube LEDs – Noval tube – red phase

    With a touch of fire in its hole, the IBM 21HB5A Beam Power tube looks just flat-out gorgeous, despite that translucent blue plate cap:

    Vacuum Tube LEDs - IBM 21HB5A Beam Power Tube - violet amber phase
    Vacuum Tube LEDs – IBM 21HB5A Beam Power Tube – violet amber phase

    Cool green works pretty well:

    Vacuum Tube LEDs - IBM 21HB5A Beam Power Tube - green violet phase
    Vacuum Tube LEDs – IBM 21HB5A Beam Power Tube – green violet phase

    If you wait long enough, it’ll probably turn True IBM Blue.

    This worked out even better than I expected!

    The Arduino source code as a GitHub gist:

    // Neopixel lighting for multiple vacuum tubes
    // Ed Nisley – KE4ANU – January 2015
    #include <Adafruit_NeoPixel.h>
    //———-
    // Pin assignments
    const byte PIN_NEO = A3; // DO – data out to first Neopixel
    const byte PIN_HEARTBEAT = 13; // DO – Arduino LED
    //———-
    // Constants
    #define UPDATEINTERVAL 25ul
    const unsigned long UpdateMS = UPDATEINTERVAL – 1ul; // update LEDs only this many ms apart minus loop() overhead
    // number of steps per cycle, before applying prime factors
    #define RESOLUTION 100
    // phase difference between tubes for slowest color
    #define BASEPHASE (PI/4.0)
    // number of LED strips around each tube
    #define LEDSTRIPCOUNT 1
    // number of LEDs per strip
    #define LEDSTRINGCOUNT 5
    // want to randomize the startup a little?
    #define RANDOMIZE true
    //———-
    // Globals
    // instantiate the Neopixel buffer array
    Adafruit_NeoPixel strip = Adafruit_NeoPixel(LEDSTRIPCOUNT * LEDSTRINGCOUNT, PIN_NEO, NEO_GRB + NEO_KHZ800);
    uint32_t FullWhite = strip.Color(255,255,255);
    uint32_t FullOff = strip.Color(0,0,0);
    struct pixcolor_t {
    byte Prime;
    unsigned int NumSteps;
    unsigned int Step;
    float StepSize;
    float TubePhase;
    byte MaxPWM;
    };
    // colors in each LED
    enum pixcolors {RED, GREEN, BLUE, PIXELSIZE};
    struct pixcolor_t Pixels[PIXELSIZE]; // all the data for each pixel color intensity
    byte Map[LEDSTRINGCOUNT][LEDSTRIPCOUNT] = {{0},{1},{2},{3},{4}}; // pixel IDs around each tube, bottom to top.
    unsigned long MillisNow;
    unsigned long MillisThen;
    //– Figure PWM based on current state
    byte StepColor(byte Color, float Phi) {
    byte Value;
    Value = (Pixels[Color].MaxPWM / 2.0) * (1.0 + sin(Pixels[Color].Step * Pixels[Color].StepSize + Phi));
    // Value = (Value) ? Value : Pixels[Color].MaxPWM; // flash at dimmest points
    // printf("C: %d Phi: %d Value: %d\r\n",Color,(int)(Phi*180.0/PI),Value);
    return Value;
    }
    //– Helper routine for printf()
    int s_putc(char c, FILE *t) {
    Serial.write(c);
    }
    //——————
    // Set the mood
    void setup() {
    pinMode(PIN_HEARTBEAT,OUTPUT);
    digitalWrite(PIN_HEARTBEAT,LOW); // show we arrived
    Serial.begin(57600);
    fdevopen(&s_putc,0); // set up serial output for printf()
    printf("Multiple Vacuum Tube Mood Light with Neopixels\r\nEd Nisley – KE4ZNU – January 2016\r\n");
    /// set up Neopixels
    strip.begin();
    strip.show();
    // lamp test: run a brilliant white dot along the length of the strip
    printf("Lamp test: walking white\r\n");
    strip.setPixelColor(0,FullWhite);
    strip.show();
    delay(500);
    for (int i=1; i<strip.numPixels(); i++) {
    digitalWrite(PIN_HEARTBEAT,HIGH);
    strip.setPixelColor(i-1,FullOff);
    strip.setPixelColor(i,FullWhite);
    strip.show();
    digitalWrite(PIN_HEARTBEAT,LOW);
    delay(500);
    }
    strip.setPixelColor(strip.numPixels() – 1,FullOff);
    strip.show();
    delay(500);
    // fill the layers
    printf(" … fill using Map array\r\n");
    for (int i=0; i < LEDSTRINGCOUNT; i++) { // for each layer
    digitalWrite(PIN_HEARTBEAT,HIGH);
    for (int j=0; j < LEDSTRIPCOUNT; j++) { // spread color around the layer
    strip.setPixelColor(Map[i][j],FullWhite);
    strip.show();
    delay(250);
    }
    digitalWrite(PIN_HEARTBEAT,LOW);
    }
    // clear to black
    printf(" … clear\r\n");
    for (int i=0; i < LEDSTRINGCOUNT; i++) { // for each layer
    digitalWrite(PIN_HEARTBEAT,HIGH);
    for (int j=0; j < LEDSTRIPCOUNT; j++) { // spread color around the layer
    strip.setPixelColor(Map[i][j],FullOff);
    strip.show();
    delay(250);
    }
    digitalWrite(PIN_HEARTBEAT,LOW);
    }
    delay(1000);
    // set up the color generators
    MillisNow = MillisThen = millis();
    if (RANDOMIZE)
    randomSeed(MillisNow + analogRead(7));
    else
    printf("Start not randomized\r\n");
    printf("First random number: %ld\r\n",random(10));
    Pixels[RED].Prime = 7;
    Pixels[GREEN].Prime = 5;
    Pixels[BLUE].Prime = 3;
    printf("Primes: (%d,%d,%d)\r\n",Pixels[RED].Prime,Pixels[GREEN].Prime,Pixels[BLUE].Prime);
    unsigned int TubeSteps = (unsigned int) ((BASEPHASE / TWO_PI) *
    RESOLUTION * (unsigned int) max(max(Pixels[RED].Prime,Pixels[GREEN].Prime),Pixels[BLUE].Prime));
    printf("Tube phase offset: %d deg = %d steps\r\n",(int)(BASEPHASE*(360.0/TWO_PI)),TubeSteps);
    Pixels[RED].MaxPWM = 255;
    Pixels[GREEN].MaxPWM = 128;
    Pixels[BLUE].MaxPWM = 255;
    for (byte c=0; c < PIXELSIZE; c++) {
    Pixels[c].NumSteps = RESOLUTION * (unsigned int) Pixels[c].Prime;
    Pixels[c].Step = (RANDOMIZE) ? random(Pixels[c].NumSteps) : (3*Pixels[c].NumSteps)/4;
    Pixels[c].StepSize = TWO_PI / Pixels[c].NumSteps; // in radians per step
    Pixels[c].TubePhase = TubeSteps * Pixels[c].StepSize; // radians per tube
    printf("c: %d Steps: %d Init: %d",c,Pixels[c].NumSteps,Pixels[c].Step);
    printf(" PWM: %d Phi %d deg\r\n",Pixels[c].MaxPWM,(int)(Pixels[c].TubePhase*(360.0/TWO_PI)));
    }
    }
    //——————
    // Run the mood
    void loop() {
    MillisNow = millis();
    if ((MillisNow – MillisThen) > UpdateMS) {
    digitalWrite(PIN_HEARTBEAT,HIGH);
    for (byte c=0; c < PIXELSIZE; c++) { // step to next increment in each color
    if (++Pixels[c].Step >= Pixels[c].NumSteps) {
    Pixels[c].Step = 0;
    printf("Cycle %d steps %d at %8ld delta %ld ms\r\n",c,Pixels[c].NumSteps,MillisNow,(MillisNow – MillisThen));
    }
    }
    for (int i=0; i < LEDSTRINGCOUNT; i++) { // for each layer
    byte Value[PIXELSIZE];
    for (byte c=0; c < PIXELSIZE; c++) { // … for each color
    Value[c] = StepColor(c,-i*Pixels[c].TubePhase); // figure new PWM value
    // Value[c] = (c == RED && Value[c] == 0) ? Pixels[c].MaxPWM : Value[c]; // flash highlight for tracking
    }
    uint32_t UniColor = strip.Color(Value[RED],Value[GREEN],Value[BLUE]);
    if (false && (i == 0))
    printf("L: %d C: %08lx\r\n",i,UniColor);
    for (int j=0; j < LEDSTRIPCOUNT; j++) { // fill layer with color
    strip.setPixelColor(Map[i][j],UniColor);
    }
    }
    strip.show();
    MillisThen = MillisNow;
    digitalWrite(PIN_HEARTBEAT,LOW);
    }
    }
    view raw MultiTube.ino hosted with ❤ by GitHub
  • Vacuum Tube LEDs: Ersatz Tube Sockets

    Even vacuum tubes destined to be decorations need sockets:

    Vacuum Tube Bases - solid models
    Vacuum Tube Bases – solid models

    They’re entirely plastic, of course, but they match the dimensions of “real” tube sockets pretty closely. The bosses around the pins have hard-inch dimensions, so you (well, I) can unleash Genuine Greenlee Radio Chassis Punches on sheet metal.

    All the key dimensions come from a table, so you can build whatever sockets you need. These four seem to cover the most common relics of the Hollow State Empire:

    T_NAME = 0;                                             // common name
    T_NUMPINS = 1;                                          // total, with no allowance for keying
    T_PINBCD = 2;                                           // tube pin circle diameter
    T_PINOD = 3;                                            //  ... diameter
    T_PINLEN = 4;                                           //  ... length (overestimate)
    T_HOLEOD = 5;                                           // nominal panel hole from various sources
    T_PUNCHOD = 6;                                          // panel hole optimized for inch-size Greenlee punches
    T_TUBEOD = 7;                                           // envelope or base diameter
    T_PIPEOD = 8;                                           // light pipe from LED to tube base
    T_SCREWOC = 9;                                          // mounting screw holes
    
    //    Name      pins     BCD   dia  length   hole            punch       env  pipe screw
    TubeData = [
        ["Mini7",     8,    9.53, 1.016,   7.0,  16.0,        11/16 * inch,  18.0,  5.0, 22.5],
        ["Octal",     8,   17.45, 2.36,   10.0,  36.2,    (8 + 1)/8 * inch,  32.0, 11.5, 39.0],
        ["Noval",    10,   11.89, 1.1016,  7.0,  22.0,          7/8 * inch,  21.0,  5.0, 28.0],
        ["Duodecar", 13,   19.10, 1.05,    9.0,  32.0,         1.25 * inch,  38.0, 12.5, 39.0],
    ];
    

    Given that the tubes lack electrical connections, I omitted the base keying: plug them in for best visual effect.

    The hole through the middle passes light from a knockoff Neopixel on a 10 mm OD PCB:

    Vacuum Tube LEDs - Octal base - top
    Vacuum Tube LEDs – Octal base – top

    Seen from the bottom, each base traps a pair of 6-32 nuts for chassis mounting and has a Neopixel press-fit in the middle:

    Vacuum Tube LEDs - Duodecar base - bottom
    Vacuum Tube LEDs – Duodecar base – bottom

    Those recesses require support structures:

    Vacuum Tube Bases - solid models - support
    Vacuum Tube Bases – solid models – support

    The Miniature 7-pin socket has the least space for the 10 mm OD Neopixel PCB and shows the thin layer between the bottom of the pin holes and the top of the openings.

    Vacuum Tube Base - Mini7 - solid model section
    Vacuum Tube Base – Mini7 – solid model section

    You see half of the eight holes in the “7 pin” socket, because it has the eighth hole where a standard socket has a gap between pins 1 and 7.

    Somewhat to my surprise, punching the support spiders out with a 6-32 stud (grabbed in the drill press) worked perfectly:

    Vacuum Tube Base - nut trap overhang - detail
    Vacuum Tube Base – nut trap overhang – detail

    They look like I intended to build tiny decorations:

    Vacuum Tube Base - support structure - detail
    Vacuum Tube Base – support structure – detail

    The cookies held on tenuously, then released with a loud bang! as I gradually increased the pressure. A PETG support structure in a blind recess wouldn’t pop out nearly so well.

    The OpenSCAD source code as a GitHub gist:

    // Vacuum Tube LED Lights
    // Ed Nisley KE4ZNU January 2016
    Layout = "Sockets"; // Cap LampBase USBPort Socket(s)
    Section = true; // cross-section the object
    Support = true;
    //- 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
    // https://en.wikipedia.org/wiki/Tube_socket#Summary_of_Base_Details
    T_NAME = 0; // common name
    T_NUMPINS = 1; // total, with no allowance for keying
    T_PINBCD = 2; // tube pin circle diameter
    T_PINOD = 3; // … diameter
    T_PINLEN = 4; // … length (overestimate)
    T_HOLEOD = 5; // nominal panel hole from various sources
    T_PUNCHOD = 6; // panel hole optimized for inch-size Greenlee punches
    T_TUBEOD = 7; // envelope or base diameter
    T_PIPEOD = 8; // light pipe from LED to tube base
    T_SCREWOC = 9; // mounting screw holes
    // Name pins BCD dia length hole punch env pipe screw
    TubeData = [
    ["Mini7", 8, 9.53, 1.016, 7.0, 16.0, 11/16 * inch, 18.0, 5.0, 22.5],
    ["Octal", 8, 17.45, 2.36, 10.0, 36.2, (8 + 1)/8 * inch, 32.0, 11.5, 39.0],
    ["Noval", 10, 11.89, 1.1016, 7.0, 22.0, 7/8 * inch, 21.0, 5.0, 28.0],
    ["Duodecar", 13, 19.10, 1.05, 9.0, 32.0, 1.25 * inch, 38.0, 12.5, 39.0],
    ];
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Pixel = [7.0,10.0,3.0]; // ID = contact patch, OD = PCB dia, LENGTH = overall thickness
    Nut = [3.5,8.0,3.0]; // socket mounting nut recess
    BaseShim = 2*ThreadThick; // between pin holes and pixel top
    SocketFlange = 2.0; // rim around socket below punchout
    PanelThick = 2.0; // socket extension through punchout
    //———————-
    // 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);
    }
    //———————-
    // Tube cap
    CapTube = [4.0,3/16 * inch,10.0]; // brass tube for flying lead to cap LED
    CapSize = [Pixel[ID],(Pixel[OD] + 3.0),(CapTube[OD] + 2*Pixel[LENGTH])];
    CapSides = 6*4;
    module Cap() {
    difference() {
    union() {
    cylinder(d=CapSize[OD],h=(CapSize[LENGTH]),$fn=CapSides); // main cap body
    translate([0,0,CapSize[LENGTH]]) // rounded top
    scale([1.0,1.0,0.65])
    sphere(d=CapSize[OD]/cos(180/CapSides),$fn=CapSides); // cos() fixes slight undersize vs cylinder
    cylinder(d1=(CapSize[OD] + 2*3*ThreadWidth),d2=CapSize[OD],h=1.5*Pixel[LENGTH],$fn=CapSides); // skirt
    }
    translate([0,0,-Protrusion]) // bore for wiring to LED
    PolyCyl(CapSize[ID],(CapSize[LENGTH] + 3*ThreadThick + Protrusion),CapSides);
    translate([0,0,-Protrusion]) // PCB recess with clearance for tube dome
    PolyCyl(Pixel[OD],(1.5*Pixel[LENGTH] + Protrusion),CapSides);
    translate([0,0,(1.5*Pixel[LENGTH] – Protrusion)]) // small step + cone to retain PCB
    cylinder(d1=(Pixel[OD]/cos(180/CapSides)),d2=Pixel[ID],h=(Pixel[LENGTH] + Protrusion),$fn=CapSides);
    translate([0,0,(CapSize[LENGTH] – CapTube[OD]/(2*cos(180/8)))]) // hole for brass tube holding wire loom
    rotate([90,0,0]) rotate(180/8)
    PolyCyl(CapTube[OD],CapSize[OD],8);
    }
    }
    //———————-
    // Aperture for USB-to-serial adapter snout
    // These are all magic numbers, of course
    module USBPort() {
    translate([0,28.0])
    rotate([90,0,0])
    linear_extrude(height=28.0)
    polygon(points=[
    [0,0],
    [8.0,0],
    [8.0,4.0],
    // [4.0,4.0],
    [4.0,6.5],
    [-4.0,6.5],
    // [-4.0,4.0],
    [-8.0,4.0],
    [-8.0,0],
    ]);
    }
    //———————-
    // Box for Leviton ceramic lamp base
    module LampBase() {
    Bottom = 3.0;
    Base = [4.0*inch,4.5*inch,20.0 + Bottom];
    Sides = 12*4;
    Retainer = [3.5,11.0,1.0]; // flat fiber washer holding lamp base screws in place
    StudSides = 8;
    StudOC = 3.5 * inch;
    Stud = [0.107 * inch, // 6-32 mounting screws
    min(15.0,1.5*(Base[ID] – StudOC)/cos(180/StudSides)), // OD = big enough to merge with walls
    (Base[LENGTH] – Retainer[LENGTH])]; // leave room for retainer
    union() {
    difference() {
    rotate(180/Sides)
    cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
    rotate(180/Sides)
    translate([0,0,Bottom])
    cylinder(d=Base[ID],h=Base[LENGTH],$fn=Sides);
    translate([0,-Base[OD]/2,Bottom + 1.2]) // mount on double-sided foam tape
    rotate(0)
    USBPort();
    }
    for (i = [-1,1])
    translate([i*StudOC/2,0,0])
    rotate(180/StudSides)
    difference() {
    # cylinder(d=Stud[OD],h=Stud[LENGTH],$fn=StudSides);
    translate([0,0,Bottom])
    PolyCyl(Stud[ID],(Stud[LENGTH] – (Bottom – Protrusion)),6);
    }
    }
    }
    //———————-
    // Tube Socket
    module Socket(Name = "Mini7") {
    NumSides = 6*4;
    Tube = search([Name],TubeData,1,0)[0];
    echo(str("Building ",TubeData[Tube][0]," socket"));
    echo(str(" Punch: ",TubeData[ID][T_PUNCHOD]," mm = ",TubeData[ID][T_PUNCHOD]/inch," inch"));
    echo(str(" Screws: ",TubeData[ID][T_SCREWOC]," mm =",TubeData[ID][T_SCREWOC]/inch," inch OC"));
    OAH = Pixel[LENGTH] + BaseShim + TubeData[Tube][T_PINLEN];
    BaseHeight = OAH – PanelThick;
    difference() {
    union() {
    linear_extrude(height=BaseHeight)
    hull() {
    circle(d=(TubeData[Tube][T_PUNCHOD] + 2*SocketFlange),$fn=NumSides);
    for (i=[-1,1])
    translate([i*TubeData[Tube][T_SCREWOC]/2,0])
    circle(d=2*Nut[OD],$fn=NumSides);
    }
    cylinder(d=TubeData[Tube][T_PUNCHOD],h=OAH,$fn=NumSides);
    }
    for (i=[0:(TubeData[Tube][T_NUMPINS] – 1)]) // tube pins
    rotate(i*360/TubeData[Tube][T_NUMPINS])
    translate([TubeData[Tube][T_PINBCD]/2,0,(OAH – TubeData[Tube][T_PINLEN])])
    rotate(180/4)
    PolyCyl(TubeData[Tube][T_PINOD],(TubeData[Tube][T_PINLEN] + Protrusion),4);
    for (i=[-1,1]) // mounting screw holes & nut traps
    translate([i*TubeData[Tube][T_SCREWOC]/2,0,-Protrusion]) {
    PolyCyl(Nut[OD],(Nut[LENGTH] + Protrusion),6);
    PolyCyl(Nut[ID],(OAH + 2*Protrusion),6);
    }
    translate([0,0,-Protrusion]) { // LED recess
    PolyCyl(Pixel[OD],(Pixel[LENGTH] + Protrusion),8);
    }
    translate([0,0,(Pixel[LENGTH] – Protrusion)]) { // light pipe
    rotate(180/TubeData[Tube][T_NUMPINS])
    PolyCyl(TubeData[Tube][T_PIPEOD],(OAH + 2*Protrusion),TubeData[Tube][T_NUMPINS]);
    }
    }
    // Totally ad-hoc support structures …
    if (Support) {
    color("Yellow") {
    for (i=[-1,1]) // nut traps
    translate([i*TubeData[Tube][T_SCREWOC]/2,0,(Nut[LENGTH] – ThreadThick)/2])
    for (a=[0:5])
    rotate(a*30 + 15)
    cube([2*ThreadWidth,0.9*Nut[OD],(Nut[LENGTH] – ThreadThick)],center=true);
    if (Pixel[OD] > TubeData[Tube][T_PIPEOD]) // support pipe only if needed
    translate([0,0,(Pixel[LENGTH] – ThreadThick)/2])
    for (a=[0:7])
    rotate(a*22.5)
    cube([2*ThreadWidth,0.9*Pixel[OD],(Pixel[LENGTH] – ThreadThick)],center=true);
    }
    }
    }
    //———————-
    // Build it
    if (Layout == "Cap") {
    if (Section)
    difference() {
    Cap();
    translate([-CapSize[OD],0,CapSize[LENGTH]])
    cube([2*CapSize[OD],2*CapSize[OD],3*CapSize[LENGTH]],center=true);
    }
    else
    Cap();
    }
    if (Layout == "LampBase")
    LampBase();
    if (Layout == "USBPort")
    USBPort();
    if (Layout == "Socket")
    if (Section) {
    difference() {
    Socket();
    translate([-100/2,0,-Protrusion])
    cube([100,50,50],center=false);
    }
    }
    else
    Socket();
    if (Layout == "Sockets") {
    translate([0,50,0])
    Socket("Mini7");
    translate([0,20,0])
    Socket("Octal");
    translate([0,-15,0])
    Socket("Duodecar");
    translate([0,-50,0])
    Socket("Noval");
    }

     

  • Raspberry Pi Model B+ Reset Connector

    Turns out Raspberry Pi boards have provision for a Reset switch, but you gotta dig for it. On the Model B+, it’s labeled RUN:

    Raspberry Pi BPlus - RUN header
    Raspberry Pi BPlus – RUN header

    Soldering in that 2-pin header and plugging a pushbutton switch on a short cable will suffice until I get around to thinking of / scrounging a suitable case.

    Poking the button forces a power-on reset, which you shouldn’t do with the RPi running, lest you trash the filesystem. After shutting down with sudo halt, however, the switch does exactly what’s needed: restarts the CPU from scratch.

    The RPi draws little enough power that there’s no point in actually pulling the plug; stressing that Micro-B connector is definitely a Bad Idea.

  • Hollow State Electronics: Desk Decorations

    I did a lightning talk / show-n-tell last Tuesday at the MHV LUG meeting and covered one end of a table with the Neopixel-lit bulbs & vacuum tubes & hard drive platters I’ve been playing with:

    MHVLUG – Hollow State Decorations – Lightning Talk

    Some of the posts won’t go live for a week, but here’s a peek into the future:

    Vacuum Tube LEDs - IBM 21HB5A Beam Power Tube - violet amber phase
    Vacuum Tube LEDs – IBM 21HB5A Beam Power Tube – violet amber phase

    Dang, that came out well…