Astable Multivibrator: Dressed-up LED Spider

Adding a bit of trim to the bottom of the LED spider makes it look better and helps keep the strut wires in place:

Astable Multivibrator - Alkaline - Radome trim
Astable Multivibrator – Alkaline – Radome trim

It’s obviously impossible to build like that, so it’s split across the middle of the strut:

Astable Multivibrator - Alkaline - Radome trim
Astable Multivibrator – Alkaline – Radome trim

Glue it together with black adhesive and a couple of clamps:

LED Spider - glue clamping
LED Spider – glue clamping

The aluminum fixtures (jigs?) are epoxied around snippets of strut wire aligning the spider parts:

LED Spider - gluing fixture
LED Spider – gluing fixture

Those grossly oversized holes came pre-drilled in an otherwise suitable aluminum rod from the Little Tray o’ Cutoffs. I faced off the ends, chopped the rod in two, recessed the new ends, and declared victory. Might need better ones at some point, but they’ll do for now.

Next step: wire up an astable with a yellow LED to go with the green and blue boosted LEDs.

USB Memory: Premature Deaths

After about a year of streaming music, the music died over the course of a month, producing progressively bizarre symptoms on all the local Icecast stations. Killing the streaming server and yanking all the USB memory sticks produced this tableau:

USB Memory - streamer failures
USB Memory – streamer failures

The USB 2.0 32 GB SanDisk Cruzer Fit (tiny, black, upper left) holds images from various network cameras and is not involved with music. It’s nigh onto seven years old and, apparently, still going strong.

The USB 2.0 Centron (gray-and-retroreflective, upper right) was forgotten from the last time I set up a drive for our Forester’s player. There’s another one just like it in the car; they’re impossibly old, as you’d expect from their minuscule size.

The USB 3.0 64 GB Samsung Fit (small, white, lower left) is totally dead, to the extent it doesn’t even announce its presence when plugged into a USB socket. It’s 2.5 years into a five year warranty, but their new USB 3.1 version is twelve bucks; Samsung wins. It formerly contained an extensive selection of public-domain music.

The 64 GB Sandisk Cruzer (huge, black, lower right) suffered some serious damage:

sudo mount -o ro /dev/sdg1 /mnt/part
ll /mnt/part
 ls: cannot access '/mnt/part/PILZ': Input/output error
 total 384K
 drwxr-xr-x   6 ed   users 4.0K Nov 28  2019 ./
 drwxr-xr-x  17 root root  4.0K Jun  7  2019 ../
 -rw-r--r--   1 ed   ed    215K Mar  9  2019 CDClassical.m3u
 drwxrwxr-x  56 ed   ed    4.0K Mar  9  2019 Classical/
 drwx------   2 root root   16K Mar  9  2019 lost+found/
 d?????????   ? ?    ?        ?            ? PILZ/
 drwxrwxr-x 116 ed   ed     12K Mar  9  2019 Pop/
 -rw-r--r--   1 ed   ed    117K Nov 28  2019 Pop.m3u

It still contains a fair amount of music ripped from the CDs we’ve collected over the decades, but it’s obviously unusable. Just for fun, I tried reformatting and copying some files to it, but it eventually hard-crashed with I/O errors:

[37787.872410] usb 2-1: new high-speed USB device number 2 using xhci_hcd
 [37788.013027] usb 2-1: New USB device found, idVendor=0781, idProduct=5530, bcdDevice= 1.00
 [37788.013030] usb 2-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
 [37788.013032] usb 2-1: Product: Cruzer
 [37788.013034] usb 2-1: Manufacturer: SanDisk
 [37788.013036] usb 2-1: SerialNumber: 4C530001151215101233
 [37788.013604] usb-storage 2-1:1.0: USB Mass Storage device detected
 [37788.014778] scsi host9: usb-storage 2-1:1.0
 [37789.033409] scsi 9:0:0:0: Direct-Access     SanDisk  Cruzer           1.00 PQ: 0 ANSI: 6
 [37789.034569] sd 9:0:0:0: [sdf] 120225792 512-byte logical blocks: (61.6 GB/57.3 GiB)
 [37789.035820] sd 9:0:0:0: [sdf] Write Protect is off
 [37789.035825] sd 9:0:0:0: [sdf] Mode Sense: 43 00 00 00
 [37789.036137] sd 9:0:0:0: [sdf] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA
 [37789.086533]  sdf: sdf1
 [37789.089418] sd 9:0:0:0: [sdf] Attached SCSI removable disk
 [38035.071013] EXT4-fs (sdf1): mounting ext3 file system using the ext4 subsystem
 [38035.183172] EXT4-fs (sdf1): mounted filesystem with ordered data mode. Opts: (null)
 [38485.302549] usb 2-1: reset high-speed USB device number 2 using xhci_hcd
 [38490.622285] usb 2-1: device descriptor read/64, error -110
 [38506.195617] usb 2-1: device descriptor read/64, error -110
 [38506.425616] usb 2-1: reset high-speed USB device number 2 using xhci_hcd
 [38511.742339] usb 2-1: device descriptor read/64, error -110
 <<< snippage >>>
 [38548.845743] usb 2-1: USB disconnect, device number 2
 [38548.858925] blk_update_request: I/O error, dev sdf, sector 99556320 op 0x1:(WRITE) flags 0x4800 phys_seg 30 prio class 0
 [38548.858933] EXT4-fs warning (device sdf1): ext4_end_bio:309: I/O error 10 writing to inode 1531939 (offset 0 size 0 starting block 12444541
 )
 [38548.858937] Buffer I/O error on device sdf1, logical block 12444284
 [38548.858944] EXT4-fs warning (device sdf1): ext4_end_bio:309: I/O error 10 writing to inode 1531939 (offset 0 size 0 starting block 12444542
 )
 <<< snippage >>>
 [38548.858984] Buffer I/O error on device sdf1, logical block 12444293
 [38548.859034] blk_update_request: I/O error, dev sdf, sector 99017520 op 0x1:(WRITE) flags 0x4000 phys_seg 3 prio class 0
 [38548.859158] blk_update_request: I/O error, dev sdf, sector 99556560 op 0x1:(WRITE) flags 0x4800 phys_seg 30 prio class 0
 [38548.859224] blk_update_request: I/O error, dev sdf, sector 99017760 op 0x1:(WRITE) flags 0x4000 phys_seg 2 prio class 0
 [38548.859237] blk_update_request: I/O error, dev sdf, sector 99018000 op 0x1:(WRITE) flags 0x4000 phys_seg 2 prio class 0
 >>
 [38549.230765] JBD2: Detected IO errors while flushing file data on sdf1-8
 [38549.230920] Aborting journal on device sdf1-8.
 [38549.231008] Buffer I/O error on dev sdf1, logical block 1545, lost sync page write
 [38549.231011] JBD2: Error -5 detected when updating journal superblock for sdf1-8.
 [38549.231325] Buffer I/O error on dev sdf1, logical block 0, lost sync page write
 [38549.231332] EXT4-fs (sdf1): I/O error while writing superblock
 [38549.231333] EXT4-fs error (device sdf1): ext4_journal_check_start:61: Detected aborted journal
 [38549.231334] EXT4-fs (sdf1): Remounting filesystem read-only
 <<< and so forth and so on >>>

So, yeah, it’s dead, Jim.

The Icecast streaming server reads data continuously from the USB sticks and, given that I set up half a dozen “stations”, there’s plenty of reading going on. The drives are formatted as ext3 and mounted with the noatime option, so there shouldn’t be any writing going on, but it seems a year of constant reading can kill a USB drive.

Fortunately, the original data lives elsewhere, with scripts to copy the appropriate files to the right places, so rebuilding the drives on a pair of new USB sticks wasn’t a big deal.

Manjaro 20.1: CUPS Setup

Tweaking a new Manjaro Linux 20.1 installation to share printers and allow remote administration, done while replacing an aging Optiplex desktop box that’s been running unattended for far too long.

Start by installing Manjaro’s printer support package:

pamac install manjaro-printer

In the general section of the default /etc/cups/cupsd.conf file, up near the top:

Listen *:631       # listen on all interfaces
DefaultShared Yes  # share the local printers
BrowseWebIF Yes    # turn on the Web interface

Allow remote admin:

<Location />
  Allow all
  Order allow,deny
</Location>
<Location /admin>
  Allow all
  Order allow,deny
</Location>

Restart the CUPS server:

sudo systemctl restart org.cups.cupsd

And then It Should Just Work.

Bicycling For The Fun of It All

Somewhere out there, you’ll find his video:

Photo Op - 2020-11-09 - 287
Photo Op – 2020-11-09 – 287

Everybody needs a reason to smile!

Bonus: enough vehicles to keep the signal at Burnett green.

In the unlikely event you were wondering, 287 is the frame number from the video-to-still conversion:

ffmpeg -ss 00:03:30 -i /mnt/video/AS30V/2020-11-09/MAH07624.mp4 -t 20 -f image2 -q 1 'Photo Op - 2020-11-09 - '%03d.jpg

All in all, a fine day for a ride …

XFCE vs. Screen Locking

For reasons not relevant here, I was asked to tweak an XFCE 20.04 installation to not ask for a password after the screen power-saver kicks in. There’s no need for a screensaver with an LCD panel, so this should be straightforward, as per the XFCE 18.04 setup:

XFCE Power Manager Light Locker settings 18.04
XFCE Power Manager Light Locker settings 18.04

Which had no effect.

For some reason, perhaps having to do with an upgrade from 18.04 to 20.04, Light Locker wasn’t actually handling the screen locking; some dedicated searching suggested this is a problem of long standing.

So tweak the Lock Screen settings of the screen saver that’s not in use:

XFCE Screensaver Lock Screen Preferences - 20.04
XFCE Screensaver Lock Screen Preferences – 20.04

If you’re doing this remotely, adding a stanza to ~/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-screensaver.xml should suffice:

<?xml version="1.0" encoding="UTF-8"?>

<channel name="xfce4-screensaver" version="1.0">
  <property name="saver" type="empty">
    <property name="mode" type="int" value="0"/>
  </property>
  <property name="lock" type="empty">
    <property name="enabled" type="bool" value="false"/>
  </property>
</channel>

The threat model for this particular installation is “minimal”.

Raspberry Pi Camera vs. RTSP Streaming

It Would Be Nice to turn the various Raspberry Pi camera boxen around here into more-or-less full-automatic IP streaming cameras, perhaps using RTSP, so as to avoid having to start everything manually, then restart the machinery after a trivial interruption. I naively thought video streaming was a solved problem, especially on an RPi, particularly with an Official RPi Camera, given the number of solutions found by casual searching with the obvious keywords.

As far as I can tell, however, all of the recommended setups fail in glorious / amusing / tragic ways. Some failures may be due to old configurations no longer applicable to new software, but I’m nowhere near expert experienced enough to figure out what’s broken and how to fix anything in particular.

Doing RTSP evidently requires the live555.com Streaming Media libraries & test suite. Compiling requires adding -DNO_SSL=1 to the COMPILE_OPTS line in the Makefile, then letting it bake it for a while.

The v4l2rtspserver code fetches & cleanly compiles its version of the live555 code, then emits various buffer overflow errors while streaming; the partial buffers clearly show how the compression works on small blocks in successive lines. Increasing various buffer sizes from 60 kB to 100 kB to 300 kB had little effect. This may have to do with the stream’s encoding / compression methods / bit rates, none of which seem amenable to random futzing.

Another straightforward configuration compiled fine, but VLC failed to actually show the stream, perhaps due to differences between the old version of Raspbian (“Stretch”) and the new version of Raspberry Pi OS (“Buster”).

Running the RPi camera through the Video4Linux2 interface to create a /dev/video0 device seems to work, but controlling the camera’s exposure (and suchlike) with v4l2_ctl behaves erratically. Obvious effects, like rotation & flipping, work fine, but not the fine details along the lines of auto exposure and color modes.

Attempting to fire raspivid through cvlc to produce an RTSP stream required installing VLC on a headless Raspberry Pi, plus enough co-requisite packages to outfit world+dog+kitchenSink. After all the huffing & puffing wound down, the recommended VLC parameters failed to produce an output stream. The VLC doc regarding streaming is, to me, impenetrable, so I have no idea how to improve the situation; I assume RTSP streaming is possible, just not by me.

Whenever any of those lashups produced any video whatsoever, the images suffered from tens-of-seconds latency, dropped frames, out-of-order video updates, and generally poor behavior. Some maladies certainly came from the aforementioned inappropriate encoding / compression methods / bit rates.

The least horrible alternative seems to be some variation on the original theme of using raspivid to directly create a tcp stream or firing raspivid into netcat to the same effect, then re-encoding it on a beefier PC as needed. I’m sure systemd can automagically restart raspivid (or, surely, a script with all the parameters) after it shuts down.

So far, this has been an … unsatisfactory … experience, but now I can close a dozen browser tabs.

Mini-Lathe Ball Drilling Fixture

Despite successfully drilling holes in a few plastic balls, I wanted a somewhat less terrifying setup than this:

Micromark Ball Vise - lathe ball hack
Micromark Ball Vise – lathe ball hack

The stiffness of the bike helmet mirror mount suggested a similar clamp would have enough griptivity to immobilize the ball while cutting it in the lathe:

Helmet Mirror Mount - 10 mm ball
Helmet Mirror Mount – 10 mm ball

Building the clamp around the lathe’s three-jaw lathe chuck eliminates the need for screws / washers / inserts:

Lathe Ball Fixture - 19 mm - Show
Lathe Ball Fixture – 19 mm – Show

The Ah-ha! moment came when I realized the fixture can expose half of the ball’s diameter for drilling while clamping 87% of its diameter, because 0.5 = sin 30° and 0.87 = cos 30°:

Lathe Ball Fixture - 19 mm - Show - front orthogonal
Lathe Ball Fixture – 19 mm – Show – front orthogonal

That’s an orthogonal view showing 13% of the ball radius sticking out of the fixture; it’s 6% of the diameter.

Which looks like this in real life:

Lathe Ball Fixture - 19 mm - sections with ball
Lathe Ball Fixture – 19 mm – sections with ball

The socket is offset toward the tailstock end of the clamp (on the right in the picture) to expose half its diameter flush with the surface perpendicular to the lathe axis. The other side necks down into a cylinder of the same diameter to clear the drill bit.

This works nicely until the ball diameter equals the chuck jaw’s 20 mm length, whereupon larger balls protrude into the chuck body’s spindle opening. Although I haven’t yet built one, the 25 mm balls in my Box o’ Bearings should fit, with exceedingly sissy cuts required for large holes.

The fixture doesn’t require support material, because the axial holes eliminate the worst of the overhang. Putting the tailstock side flat on the platform gives it the best-looking surface:

Lathe Ball Fixture - 19 mm - Slic3r - equator
Lathe Ball Fixture – 19 mm – Slic3r – equator

The kerf between the segments ensures the jaws can apply pressure to the ball, whereupon the usual crappy serrated 3D printed surface firmly grabs it.

The fixture is a slip fit on the chuck jaws:

Lathe Ball Fixture - 19 mm - installed
Lathe Ball Fixture – 19 mm – installed

Tightening the jaws shoves them all the way into the fixture’s slots and clamps the ball:

Lathe Ball Fixture - 19 mm - center drill
Lathe Ball Fixture – 19 mm – center drill

Overtightening the chuck will (probably) compress the ball around the drill, which will (best case) give you slightly oversize holes or (worst case) cause the ball to seize / melt around the drill bit, so sleaze up to the correct hole diameter maybe half a millimeter at a time:

Lathe Ball Fixture - 19 mm - 6 mm drill
Lathe Ball Fixture – 19 mm – 6 mm drill

That fixture exposes 9.5 mm = 19/2 of the ball. The drill makes a 6 mm hole to fit the telescoping shaft seen above.

Obviously, you must build a custom fixture for every ball diameter in your inventory, which is no big deal when you have a hands-off manufacturing process. Embossing the diameter into the fixture helps match them, although the scribbled Sharpie isn’t particularly elegant.

The OpenSCAD source code as a GitHub Gist:

// Lathe Ball Drilling Fixture
// Ed Nisley KE4ZNU 2020-11
/* [Layout options] */
Layout = "Build"; // [Build, Show, Body, Jaws]
BallDia = 10.0; // [5.0:0.5:25.0]
/* [Extrusion parameters] */
/* [Hidden] */
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
function IntegerLessMultiple(Size,Unit) = Unit * floor(Size / Unit);
Protrusion = 0.1; // make holes end cleanly
inch = 25.4;
ID = 0;
OD = 1;
LENGTH = 2;
//* [Basic dimensions] */
Chuck = [21.0,100.0,20.0]; // chuck bore, OD, jaw length
Jaw = [Chuck[LENGTH],15.0,12.0]; // jaw free length, base width, first step radius
JawInclAngle = 112; // < 120 degrees for clearance!
JawAngle = JawInclAngle/2; // angle from radius
WallThick = 5.0; // min wall thickness
Kerf = 0.75; // space between clamp blocks
ClampSides = 8*(2*3);
ClampBore = BallDia/2; // clear bore through clamp
ClampAngle = asin(ClampBore/BallDia); // angle from lathe axis to clamp front
Plate = [ClampBore,
BallDia + 2*WallThick + 2*Jaw.z,
Jaw.x];
LegendDepth = 1*ThreadWidth;
ShaftOD = 3.6; // sample shaft
ShowGap = 1.5;
//----------------------
// Chuck jaws
// Real jaws have a concave radiused tip we simply ignore
module ChuckJaws(l=Jaw.x,r=10) {
for (a=[0:120:240])
rotate(a)
linear_extrude(height=l)
translate([r,0])
difference() {
translate([Chuck[OD]/4,0])
square([Chuck[OD]/2,Jaw.y],center=true);
for (i=[-1,1])
rotate(i*(90 - JawAngle))
translate([-Jaw.z/2,0])
square([Jaw.z,2*Jaw.y],center=true);
}
}
//----------------------
// Clamp body
module ClampBlocks() {
difference() {
cylinder(d=Plate[OD],h=Plate[LENGTH],$fn=ClampSides); // main disk
translate([0,0,-Protrusion]) // central bore
cylinder(d=ClampBore,h=2*Plate[LENGTH],$fn=ClampSides);
for (a=[0:120:240]) // kerf slits
rotate(60 + a)
translate([Plate[OD]/2,0,Protrusion])
cube([Plate[OD],Kerf,2*Plate[LENGTH]],center=true);
translate([0,0,BallDia/2 * cos(ClampAngle)]) // ball socket
sphere(d=BallDia,$fn=ClampSides);
for (a=[0:120:240]) { // legend
rotate(4.5*360/ClampSides + a)
translate([Plate[OD]/2 - LegendDepth,0,Plate[LENGTH]/2])
rotate([0,90,0])
linear_extrude(height=LegendDepth + Protrusion,convexity=10)
mirror([0,0,0])
text(text=str(BallDia," mm"),size=2.5,spacing=1.20,font="Arial:style:Bold",halign="center",valign="center");
rotate(-4.5*360/ClampSides + a)
translate([Plate[OD]/2 - LegendDepth,0,Plate[LENGTH]/2])
rotate([0,90,0])
linear_extrude(height=LegendDepth + Protrusion,convexity=10)
mirror([0,0,0])
text(text="KE4ZNU",size=2.5,spacing=1.20,font="Arial:style:Bold",halign="center",valign="center");
}
}
}
//----------------------
// Clamp with jaw cutouts
module ClampBody() {
difference() {
ClampBlocks();
translate([0,0,-Protrusion])
ChuckJaws(l=Jaw.x + 2*Protrusion,r=BallDia/2 + WallThick);
}
}
//----------------------
// Lash it together
if (Layout == "Body") {
ClampBlocks();
}
if (Layout == "Jaws") {
ChuckJaws();
}
if (Layout == "Build") {
ClampBody();
}
if (Layout == "Show") {
ClampBody();
color("ivory",0.2)
ChuckJaws(r=BallDia/2 + WallThick + ShowGap); // move out for E-Z viewing
color("red",0.4)
translate([0,0,-Jaw.x/2])
cylinder(d=ShaftOD,h=2*Jaw.x,$fn=ClampSides,center=false);
color("white",0.5)
translate([0,0,BallDia/2 * cos(ClampAngle)]) // ball socket
sphere(d=BallDia,$fn=ClampSides);
}

The dimension doodles, including some notions that didn’t work:

Lathe Ball Clamp - dimension doodles
Lathe Ball Clamp – dimension doodles