We spotted this upgrade on a recent trip to a Powerhouse Theater production:

Compared with the older version, I’d say it’s a great improvement:

Who says things never get better?
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.
Making the world a better place, one piece at a time
We spotted this upgrade on a recent trip to a Powerhouse Theater production:

Compared with the older version, I’d say it’s a great improvement:

Who says things never get better?
A discussion over the Squidwrench Operating Table about injecting entropy into VMs before / during their boot sequence reminded me that I wanted to try the Entropy library with my 8×8 RGB LED matrix:

The original version trundled along with random numbers produced by timing Geiger counter ticks. The second version, digitizing the amplified noise from a reverse-biased PN junction, ran much faster.
What’s new & different: the Entropy library measures the jitter between the ATmega328 watchdog timer’s RC oscillator and the ceramic resonator (on Pro Mini boards) driving the CPU. It cranks out four bytes of uncorrelated bits every half-second, which isn’t quite fast enough for a sparkly display, but re-seeding the Arduino PRNG whenever enough entropy arrives works well enough.
One could, of course, re-seed the PRNG with Geiger bits or junction noise to the same effect. The key advantage of the Entropy library: no external hardware required. The downside: no external hardware required, so, minus those techie transistors / resistors / op amps, it will look like Just Another Arduino Project.

Le sigh.
In any event, the Entropy library has excellent documentation and works perfectly.
The Arduino PRNG can produce results fast enough for wonderfully twinkly output that’s visually indistinguishable from the “true” random numbers from the Geiger counter or PN junction. I dialed it back to one update every 5 ms, because letting it free-run turned the display into an unattractive blur.
The top trace shows the update actually happens every 6 ms:

The lower trace shows that each matrix row refresh takes about a millisecond. Refreshes occur on every main loop iteration and interfere with the update, not that that makes any difference. Should it matter, subtract one from the update period and it’ll be all good.
The Arduino source code as a GitHub Gist:
| // Random LED Dots | |
| // Based on Entropy library using watchdog timer jitter | |
| // https://sites.google.com/site/astudyofentropy/project-definition/timer-jitter-entropy-sources/entropy-library | |
| // Ed Nisley – KE4ANU – August 2016 | |
| #include <Entropy.h> | |
| //———- | |
| // Pin assignments | |
| const byte PIN_HEARTBEAT = 8; // DO – heartbeat LED | |
| const byte PIN_SYNC = A3; // DO – scope sync | |
| const byte PIN_LATCH = 4; // DO – shift register latch clock | |
| const byte PIN_DIMMING = 9; // AO – LED dimming control | |
| // These are *hardware* SPI pins | |
| const byte PIN_MOSI = 11; // DO – data to shift reg | |
| const byte PIN_MISO = 12; // DI – data from shift reg (unused) | |
| const byte PIN_SCK = 13; // DO – shift clock to shift reg (also Arduino LED) | |
| const byte PIN_SS = 10; // DO – -slave select (must be positive for SPI output) | |
| //———- | |
| // Constants | |
| #define UPDATE_MS 5 | |
| //———- | |
| // Globals | |
| // LED selects are high-active bits and low-active signals: flipped in UpdateLEDs() | |
| // *exactly* one row select must be active in each element | |
| typedef struct { | |
| const byte Row; | |
| byte ColR; | |
| byte ColG; | |
| byte ColB; | |
| } LED_BYTES; | |
| // altering the number of rows & columns will require substantial code changes… | |
| #define NUMROWS 8 | |
| #define NUMCOLS 8 | |
| LED_BYTES LEDs[NUMROWS] = { | |
| {0x80,0,0,0}, | |
| {0x40,0,0,0}, | |
| {0x20,0,0,0}, | |
| {0x10,0,0,0}, | |
| {0x08,0,0,0}, | |
| {0x04,0,0,0}, | |
| {0x02,0,0,0}, | |
| {0x01,0,0,0}, | |
| }; | |
| byte RowIndex; | |
| #define LEDS_ON 0 | |
| #define LEDS_OFF 255 | |
| unsigned long MillisNow; | |
| unsigned long MillisThen; | |
| //– Helper routine for printf() | |
| int s_putc(char c, FILE *t) { | |
| Serial.write(c); | |
| } | |
| //– Useful stuff | |
| // Free RAM space monitor | |
| // From http://playground.arduino.cc/Code/AvailableMemory | |
| uint8_t * heapptr, * stackptr; | |
| void check_mem() { | |
| stackptr = (uint8_t *)malloc(4); // use stackptr temporarily | |
| heapptr = stackptr; // save value of heap pointer | |
| free(stackptr); // free up the memory again (sets stackptr to 0) | |
| stackptr = (uint8_t *)(SP); // save value of stack pointer | |
| } | |
| void TogglePin(char bitpin) { | |
| digitalWrite(bitpin,!digitalRead(bitpin)); // toggle the bit based on previous output | |
| } | |
| void PulsePin(char bitpin) { | |
| TogglePin(bitpin); | |
| TogglePin(bitpin); | |
| } | |
| //——— | |
| //– SPI utilities | |
| void EnableSPI(void) { | |
| digitalWrite(PIN_SS,HIGH); // make sure this is high! | |
| SPCR |= 1 << SPE; | |
| } | |
| void DisableSPI(void) { | |
| SPCR &= ~(1 << SPE); | |
| } | |
| void WaitSPIF(void) { | |
| while (! (SPSR & (1 << SPIF))) { | |
| // TogglePin(PIN_HEARTBEAT); | |
| continue; | |
| } | |
| } | |
| byte SendRecSPI(byte Dbyte) { // send one byte, get another in exchange | |
| SPDR = Dbyte; | |
| WaitSPIF(); | |
| return SPDR; // SPIF will be cleared | |
| } | |
| void UpdateLEDs(byte i) { | |
| SendRecSPI(~LEDs[i].ColB); // low-active outputs | |
| SendRecSPI(~LEDs[i].ColG); | |
| SendRecSPI(~LEDs[i].ColR); | |
| SendRecSPI(~LEDs[i].Row); | |
| analogWrite(PIN_DIMMING,LEDS_OFF); // turn off LED to quench current | |
| PulsePin(PIN_LATCH); // make new shift reg contents visible | |
| analogWrite(PIN_DIMMING,LEDS_ON); | |
| } | |
| //————— | |
| // Set LED from integer | |
| // On average, this leaves the LED unchanged for 1/8 of the calls… | |
| void SetLED(unsigned long Value) { | |
| byte Row = Value & 0x07; | |
| byte Col = (Value >> 3) & 0x07; | |
| byte Color = (Value >> 6) & 0x07; | |
| byte BitMask = (0x80 >> Col); | |
| // printf("%u %u %u %u\r\n",Row,Col,Color,BitMask); | |
| LEDs[Row].ColR &= ~BitMask; | |
| LEDs[Row].ColR |= (Color & 0x04) ? BitMask : 0; | |
| LEDs[Row].ColG &= ~BitMask; | |
| LEDs[Row].ColG |= (Color & 0x02) ? BitMask : 0; | |
| LEDs[Row].ColB &= ~BitMask; | |
| LEDs[Row].ColB |= (Color & 0x01) ? BitMask : 0; | |
| } | |
| //—————— | |
| // Set things up | |
| void setup() { | |
| pinMode(PIN_HEARTBEAT,OUTPUT); | |
| digitalWrite(PIN_HEARTBEAT,HIGH); // show we arrived | |
| pinMode(PIN_SYNC,OUTPUT); | |
| digitalWrite(PIN_SYNC,LOW); | |
| pinMode(PIN_MOSI,OUTPUT); // SPI-as-output is not strictly necessary | |
| digitalWrite(PIN_MOSI,LOW); | |
| pinMode(PIN_SCK,OUTPUT); | |
| digitalWrite(PIN_SCK,LOW); | |
| pinMode(PIN_SS,OUTPUT); | |
| digitalWrite(PIN_SS,HIGH); // OUTPUT + HIGH is required to make SPI output work | |
| pinMode(PIN_LATCH,OUTPUT); | |
| digitalWrite(PIN_LATCH,LOW); | |
| Serial.begin(57600); | |
| fdevopen(&s_putc,0); // set up serial output for printf() | |
| printf("Random LED Dots – Watchdog Entropy\r\nEd Nisley – KE4ZNU – August 2016\r\n"); | |
| Entropy.initialize(); // start up entropy collector | |
| //– Set up SPI hardware | |
| SPCR = B01110001; // Auto SPI: no int, enable, LSB first, master, + edge, leading, f/16 | |
| SPSR = B00000000; // not double data rate | |
| EnableSPI(); // turn on the SPI hardware | |
| SendRecSPI(0); // set valid data in shift registers: select Row 0, all LEDs off | |
| //– Dimming pin must use fast PWM to avoid beat flicker with LED refresh rate | |
| // Timer 1: PWM 9 PWM 10 | |
| analogWrite(PIN_DIMMING,LEDS_OFF); // disable column drive (hardware pulled it low before startup) | |
| TCCR1A = B10000001; // Mode 5 = fast 8-bit PWM with TOP=FF | |
| TCCR1B = B00001001; // … WGM, 1:1 clock scale -> 64 kHz | |
| //– lamp test: send a white flash through all LEDs | |
| printf("Lamp test begins: white flash each LED…"); | |
| digitalWrite(PIN_HEARTBEAT,LOW); // turn off while panel blinks | |
| analogWrite(PIN_DIMMING,LEDS_ON); // enable column drive | |
| for (byte i=0; i<NUMROWS; i++) { | |
| for (byte j=0; j<NUMCOLS; j++) { | |
| LEDs[i].ColR = LEDs[i].ColG = LEDs[i].ColB = 0x80 >> j; | |
| for (byte k=0; k<NUMROWS; k++) { | |
| UpdateLEDs(k); | |
| delay(25); | |
| } | |
| LEDs[i].ColR = LEDs[i].ColG = LEDs[i].ColB = 0; | |
| } | |
| } | |
| UpdateLEDs(NUMROWS-1); // clear the last LED | |
| printf(" done!\r\n"); | |
| //– Preload LEDs with random values | |
| digitalWrite(PIN_HEARTBEAT,LOW); | |
| uint32_t rn = Entropy.random(); | |
| printf("Preloading LED array with seed: %08lx\r\n",rn); | |
| randomSeed(rn); | |
| for (byte Row=0; Row<NUMROWS; Row++) { | |
| for (byte Col=0; Col<NUMCOLS; Col++) { // Col runs backwards, but we don't care | |
| LEDs[Row].ColR |= random(2) << Col; // random(2) returns 0 or 1 | |
| LEDs[Row].ColG |= random(2) << Col; | |
| LEDs[Row].ColB |= random(2) << Col; | |
| } | |
| UpdateLEDs(Row); | |
| } | |
| check_mem(); | |
| printf("SP: %u HP: %u Free RAM: %u\r\n",stackptr,heapptr,stackptr – heapptr); | |
| printf("Running…\r\n"); | |
| MillisThen = millis(); | |
| } | |
| //—————— | |
| // Run the test loop | |
| void loop() { | |
| unsigned long Hash; | |
| uint32_t rn; | |
| MillisNow = millis(); | |
| // Re-seed the generator whenever we get enough entropy | |
| if (Entropy.available()) { | |
| digitalWrite(PIN_HEARTBEAT,HIGH); | |
| rn = Entropy.random(); | |
| // printf("Random: %08lx ",rn); | |
| randomSeed(rn); | |
| digitalWrite(PIN_HEARTBEAT,LOW); | |
| } | |
| // If it's time for a change, whack a random LED | |
| if ((MillisNow – MillisThen) > UPDATE_MS) { | |
| MillisThen = MillisNow; | |
| SetLED(random()); | |
| } | |
| // Refresh LED array to maintain the illusion of constant light | |
| UpdateLEDs(RowIndex++); | |
| if (RowIndex >= NUMROWS) { | |
| RowIndex = 0; | |
| PulsePin(PIN_SYNC); | |
| } | |
| } | |
I picked up a horsehair dust brush from eBay as a lightweight substitute for the Electrolux aluminum ball, discovered that an adapter I’d already made fit perfectly, did the happy dance, and printed one for the brush. That worked perfectly for half a year, whereupon:

It broke about where I expected, along the layer lines at the cross section where the snout joins the fitting. You can see the three perimeter shells I hoped would strengthen the part:

That has the usual 15% 3D Honeycomb infill, although there’s not a lot area for infill.
There’s obviously a stress concentration there and making the wall somewhat thicker (to get more plastic-to-plastic area) might suffice. I’m not convinced the layer bonding would be good enough, even with more wall area, to resist the stress; that’s pretty much a textbook example of how & where 3D printed parts fail.
That cross section should look like this:

Anyhow, I buttered the snout’s broken end with JB Kwik epoxy, aligned the parts, and clamped them overnight:

The source code now has a separate solid model for the dust brush featuring a slightly shorter snout; if when the epoxy fails, we’ll see how that changes the results. I could add ribs and suchlike along the outside, none of which seem worth the effort right now. Fairing the joint between those two straight sections would achieve the same end, with even more effort, because OpenSCAD.
The OpenSCAD source code as a GitHub Gist:
| // Kenmore vacuum cleaner nozzle adapters | |
| // Ed Nisley KE4ZNU August 2016 | |
| // Layout options | |
| Layout = "DustBrush"; // MaleFitting CoilWand FloorBrush CreviceTool 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(); | |
| } | |
| } | |
| //——————- | |
| // 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 | |
| module DustBrush() { | |
| union() { | |
| translate([0,0,40.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.6,d2=31.8,h=30.0 + Protrusion); | |
| } | |
| translate([0,0,-Protrusion]) | |
| cylinder(d1=26.0,d2=24.0,h=100); | |
| } | |
| translate([0,0,40.0 – 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 == "DustBrush") | |
| DustBrush(); | |
| if (Layout == "ScrubbyTool") | |
| ScrubbyTool(); | |
| if (Layout == "LuxBrush") | |
| LuxBrush(); |
The first pass at retrofitting SMD LEDs to light the needle area in Mary’s Model 158 sewing machine worked well enough:

However, she wanted more light on the right side of the needle, so now she has it:

That’s without any LEDs along the front and back of the arm, hence the dark pool beyond the sewing machine’s base.
Those are the same 5050 warm white LEDs I used on the other side:

Seen without the glare:

They’re mounted on a 32 mil brass strip from the shimstock stash, carefully hand-bent and twisted to match the curvature of the arm, and held in place with JB Kwik steel-filled epoxy for good heat conduction to the aluminum arm. One can argue with the epoxy oozing out from under the brass, but it’s invisible from above.
No construction photos, alas, because I made this in a white-hot frenzy one afternoon and managed to not take any pix during the entire session. Call it working in the flow, OK?
All four SMD LEDs sit in epoxy blobs that isolate them from the brass strip, with 26 AWG solid wire “bus bars” soldered to the top of their terminals and a length of that lovely PTFE-insulated miniature coax leading off into the endcap. More epoxy encloses all the wiring & connections to provide a surprisingly smooth surface that shouldn’t snag the fabric.
The power supply uses an 18 W 120 VAC to 12 VDC brick intended for small LED installations:

The AC comes from the same zip cord that formerly supplied the original 15 W incandescent bulb in the endcap, so the new lights behave the same way: push the power button to turn on the machine and the LEDs pop on just like they should. I put quick-disconnect terminals in the AC line to make it removable, although those need some sort of insulated plug to cover the exposed blades inside their housing.
Inside the black box, a small boost supply steps the voltage up to just under the nominal operating level of 21 VDC:

You can just see the adjusting screw hole in front of the AC brick in the overall view.
The DC output exits in the middle of the far side, through a coax jack epoxied to the base.
As before, all six LEDs run in parallel at (for now) 18.5 VDC and maybe 50 mA each, for a total of 300 mA, and seem fearsomely bright even at that. We can now tune for best light as needed.
This is a major major major improvement over the previous tangle of wires stuck on the outside of the machine, with all the wiring internal to the arm and the power supply out of sight under the sewing table.
After an hour, the arm above the four LEDs runs 13 °C above ambient and the endcap over the two LED heatsink is 6 °C over ambient. The AC supply runs at 104 °C and its plastic case offers no provision for heatsinking. All in all, things are warm and not hazardous.
I haven’t retrofit this machine with LED strips along the front & back of the arm, as those may not be needed with the intense needle lighting; the NisLite desk lamp may suffice for area illumination.
This turned out surprisingly well:

In the harsh light of the Electronics Workbench, you can see there’s less than meets the eye: a single knockoff Neopixel taped to the back side of the bulb just below the equator and a knockoff Arduino Pro Mini taped to the Mogul lamp socket:

The electrical box serves as a base and the cord doesn’t do anything in this incarnation.
The 5050 SMD LED package (inside an ugly 3D printed plate cap) looks enough like a point source to shadow the filament & support structure against the frosted bulb. The blurry upper part of the filament is closer to the LED, which isn’t really a point source and must fight its way through the frosting.
The Pro Mini runs the same firmware as the Bowl o’ Fire floodlamp, of course, dialed back for slow fades.
It lights up the room something wonderful …
I stuck some old 12 V 7 A·h batteries in my homebrew power supply for the HP 3801A GPS Time / Frequency Standard, fired it up, put the antenna where it could see a good chunk of the sky, gave it a day to warm up / settle out, and it’s perfectly happy:
------------------------------- Receiver Status -------------------------------
SYNCHRONIZATION ............................................. [ Outputs Valid ]
SmartClock Mode ___________________________ Reference Outputs _______________
>> Locked to GPS TFOM 3 FFOM 0
Recovery 1PPS TI -38.3 ns relative to GPS
Holdover HOLD THR 1.000 us
Power-up Holdover Uncertainty ____________
Predict 366.2 us/initial 24 hrs
ACQUISITION ............................................ [ GPS 1PPS CLK Valid ]
Satellite Status __________________________ Time _____ +1 leap second pending
Tracking: 4 Not Tracking: 6 UTC 18:22:19 22 Jul 2016
PRN El Az SS PRN El Az 1PPS CLK Synchronized to UTC
3 34 104 48 * 1 36 48 ANT DLY 0 ns
17 62 308 103 6 27 220 Position ________________________
19 39 281 50 11 21 58 MODE Hold
28 80 133 64 *22 Acq .
24 12 319 LAT N 41:39:32.328
30 15 191 LON W 73:52:26.733
ELEV MASK 10 deg *attempting to track HGT +82.87 m (MSL)
HEALTH MONITOR ......................................................... [ OK ]
Self Test: OK Int Pwr: OK Oven Pwr: OK OCXO: OK EFC: OK GPS Rcv: OK
scpi >
The FFOM 0 entry says the Frequency Figure Of Merit is “within specifications” of 10-9, averaged over one day. That means the actual frequency should be within 0.010 Hz of 10 MHz.
Feeding the 10 MHz frequency reference into the (equally warmed up) HP 8591E spectrum analyzer and selecting an absurdly narrow span produces a comforting sight:

Given the horizontal resolution, that’s dead on 10 MHz.
So, yeah, that signal at 57-ish kHz really isn’t at 60.000 kHz:

Which is good to know …
Within the space of four days, we had three rear-tire flats:
Basically, erosion from the (last remaining, I think) liner in the rear tire of Mary’s bike caused the first flat; I patched the tube and didn’t notice the gash. After the blowout, I patched the tube again, booted the gash (with a snippet from a roll of PET bottle plastic I carry around for exactly that purpose), stuck an ordinary patch atop the boot to cover its edges, and the whole mess has held air just fine for the last week. I’m reluctant to mess with success.
Not having a tire liner caused the third flat, this time on my bike. The wound looked like a nail or glass shard punched directly through the Kevlar armor behind the tread. Fortunately, it happened (or, more exactly, I realized I had a flat) half a mile from home, so I fired a CO2 cartridge into the tube and pedaled like crazy, which got me halfway to the goal and I rolled the rest of the way on a dead-flat tire.
Ya can’t win.
So I picked up a pair of Michelin Protek Max tubes, the weirdest things I’ve ever stuffed into a bike tire:

The bumps along the tread surface are much larger and uglier than shown in that picture:

The rubber forming the protrusions has the same thickness as the rest of the tube, so you’re looking at soft, flexible shapes, rather than thick bumps.
The “liquid” inside must be a thin film over the inner surface. I’ve never been a big fan of tire sealants, mostly because they’re reputed to ooze to the bottom of the tire into off-balance puddles.
For future reference, the Official Quasi-Instruction Manual / Blurb (clicky for more dots):

We’ll see how well these work…