Pixel 3a Boot Failure

Attempting to rub a smudge off the phone’s screen while it was booting causes problems:

Pixel 3a boot fail
Pixel 3a boot fail

Rebooting that sucker cleared the problem, so it wasn’t permanently fatal.

Whew!

Huion H610Pro (V2) Tablet vs. USB 3.0

For reasons that surely made sense at the time, the Huion H610Pro (V2) tablet can recognize when it’s connected to an Android device’s USB port and enter a special mode where the stylus only responds in a phone-shaped portrait rectangle over on the left side:

Huion H610Pro (V2) Tablet - Android layout
Huion H610Pro (V2) Tablet – Android layout

There’s a Vulcan Nerve Pinch button push to force the tablet into Android mode if it doesn’t automagically get there on its own, but AFAICT there’s no way to force it out of Android mode.

It’s a USB 2.0 device, but I had plugged it into a USB 3.0 port on my desktop box, whereupon it would enter Android mode on pretty nearly every boot. The only way to coerce it back into normal mode was to unplug it, replug it, then manually run the xsetwacom incantation to restrict the coordinates to the portrait monitor.

I just discovered it works perfectly when plugged into one of the few USB 2.0 ports on the box.

Apparently, USB 3.0 ports keep the thing powered all the time, whereupon it doesn’t see the proper sequence of events (or, perhaps, sees the Android sequence) during the next boot. USB 2.0 ports don’t do that and it works fine all the time.

Much better!

Pixel 3a Screen Protector FTW!

Despite carrying a glass-fronted gadget in my pocket for most of the past two decades, this is the first time I’ve done this:

Pixel 3a screen protector - as broken
Pixel 3a screen protector – as broken

Turns out you can’t trust a rolling seat on a slightly unlevel surface, as shifting your weight can let the thing roll out from under you with no warning. If you’re taking a picture at the same time, the phone reaches the impact point before your hand: even a nice case with bumpers all around won’t be quite enough protection.

I was tempted to leave it un-fixed as a constant reminder to not do that again, but the broken glass was rough to the touch and interfered with Android’s swipe-upward gestures.

Fortunately, the tempered-glass screen protector absorbed the energy without damage to the actual screen:

Pixel 3a screen protector - sidelit
Pixel 3a screen protector – sidelit

A thin plastic layer holds the protector’s fragments together; I hadn’t known it was a two-layer structure.

Being that type of guy, I had a spare protector in a desk drawer and managed to apply it without trapping any bubbles or fuzz underneath.

Finding & Copying Only The New Music Files

Given a collection of music files in various subdirectories, find all the mp3 files that aren’t in the target directory and copy them. The only catch: don’t use rsync, because the target directory is on a Google Pixel phone filesystem which doesn’t support various attributes required by rsync.

The solution goes like this:

cd /mnt/music/Netlabel Mixes
sudo jmtpfs /mnt/pixel -o allow_other,fsname="Pixel"
find . -name \*mp3 -execdir test ! -e  /mnt/pixel/Internal\ shared\ storage/Music/Netlabel/\{\} \; -execdir cp -v -t /mnt/pixel/Internal\ shared\ storage/Music/Netlabel/ \{\} \;
sudo umount /mnt/pixel

The trick is remembering the second execdir operation in find happens only if the first succeeds, so the cp runs when the target file doesn’t exist.

All the backslash escaping gets tedious, but it’s the least awful way to get the job done when the directories contain blanks, which is true for the default directory structure inside the Pixel.

Your choice in music will surely be different …

Monthly Image: Moonrise

With some heavy weather on the way:

Moonrise in Red Oaks Mill - 2020-04-08
Moonrise in Red Oaks Mill – 2020-04-08

Bracing the Pixel 3a on the deck railing. Despite the star near the top, it decided to not invoke Astrophotography mode.

This was apparently a Pink Moon and a Supermoon and surely some other adjectives nobody cared about until Webbish media discovered they could generate ad revenue using clickbait headlines concerning a monthly event.

We just enjoy the sights out along the driveway, whatever they may be.

Schwab / Symantec VIP Access vs. Yubikey

A Yubikey 5 NFC turns out to be perfectly compatible with any website using Symantec’s (no longer available) hardware key and VIP Access (definitely a misnomer) app to generate TOTP access codes, because the sites use bog-standard TOTP. The only difficulty comes from Symantec’s proprietary protocol creating the token linking an ID with a secret value to generate the TOTP codes, which is how they monetize an open standard.

Fortunately, Cyrozap reverse-engineered the Symantec protocol, dlenski mechanized it with a Python script, and it works perfectly:

python3 -m venv symkey-env
source symkey-env/bin/activate
pip3 install https://github.com/dlenski/python-vipaccess/archive/HEAD.zip
vipaccess provision -t SYMC
deactivate

That spits out a file containing the ID and secret, from which you create a QR code for the Yubikey Authenticator app:

qrencode -t UTF8 'otpauth://totp/VIP%20Access:SYMCidnumbers?secret=longsecretgibberish&issuer=Symantec&algorithm=SHA1&digits=6'

Fire up the app, wave the Yubikey behind the phone, scan the QR code, wave the Yubikey again to store it, sign in to the Schwab site, turn on 2FA, enter the ID & current TOTP value from the Yubikey Authenticator, and It Just Works™.

Of course, you can kiss Schwab’s tech support goodbye, because you’re on your own. If you ever lose the Yubikey, make sure you know the answers to your allegedly secret questions.

Equally of course, you’re downloading and running random shit from the Intertubes, but …

Now, if only all my financial institutions would get with the program.

Google Pixel 3a Microscope Adapter

Hand-holding my Google Pixel 3a phone over the microscope eyepiece worked well enough to justify building Yet Another Camera Adapter:

Pixel 3a Microscope Adapter - in action
Pixel 3a Microscope Adapter – in action

The solid model looks about like you’d expect:

Google Pixel 3a Zoom Microscope Mount - solid model - top
Google Pixel 3a Zoom Microscope Mount – solid model – top

The “camera” actually has the outside dimensions of a Spigen case, rather than the bare phone, because dropping a bare phone is never a good idea.

The base plate pretty much fills the M2’s platform:

Pixel 3a Microscope Adapter - M2 platform
Pixel 3a Microscope Adapter – M2 platform

I originally arranged the four corners around the plate to print everything in one go, but an estimated six hours of print time suggested doing the corners separately would maximize local happiness. Which it did, whew, even if the plate ran for a bit over 4-1/2 hours.

The snout is a loose fit around the 5× widefield microscope eyepiece, with the difference made up in a wrap of black tape; it’s much easier to adjust the fit upward than to bore out the snout. An overwrap of tape secures the snout to the eyepiece, which I’ve dedicated to the cause; the scope normally rocks 10× widefield glass.

The tapered hole exposes the phone’s fingerprint reader to simplify unlocking, should it shut down while I’m fiddling with something else.

The microscope doesn’t fully illuminate the camera’s entrance pupil at minimum zoom, with 4.5× filling the screen and (mostly) eliminating the vignette. The corner blocks have oversize holes to allow aligning the camera lens axis over the microscope optical axis. The solid model incorporates Lessons Learned from the version you see here, because you (well, I) can’t measure the camera axis with respect to the outside dimensions accurately enough:

Pixel 3a Microscope Adapter - installed - front
Pixel 3a Microscope Adapter – installed – front

Although it’s less unsteady than it looks, microscopy requires a gentle touch at the best of times. The adapter doesn’t add much wobble to the outcome:

Pixel 3a Microscope Adapter - installed - side
Pixel 3a Microscope Adapter – installed – side

The field is about 14×19 mm with the camera at 4.5× and the microscope at minimum zoom:

Pixel 3a Microscope Adapter - test image - min mag
Pixel 3a Microscope Adapter – test image – min mag

You can see a little darkening on the upper and lower right corners, so the phone’s still minutely leftward.

The field is about 1.5×2 mm at full throttle:

Pixel 3a Microscope Adapter - test image - max mag
Pixel 3a Microscope Adapter – test image – max mag

Color balance with the cold white LED ring isn’t the best, but it’s survivable. Mad props to OpenCamera for exposing All. The. Controls. you might possibly need.

The OpenSCAD source code as a GitHub Gist:

// Google Pixel 3a mount for stereo zoom microscope
// Ed Nisley - KE4ZNU - 2019-12
Layout = "Show"; // [Show,BuildAll,BuildBumpers,BuildPlate,DrillGuide,Phone,Plate,Bumper]
/* [Hidden] */
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
Protrusion = 0.1; // make holes end cleanly
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
ID = 0;
OD = 1;
LENGTH = 2;
inch = 25.4;
//----------------------
// Dimensions
Phone = [74.5,156.0,12.0]; // inside Spigen case
PhoneRadii = [10.0,10.0,3.0]; // corner rounding, likewise
LensOffset = [-17.0,-18.5,0]; // looking at phone screen, (-) sign = from right/top edge
PrintReader = [0,Phone.y/2 - 44.0,0]; // fingerprint reader from center
PrintReaderDia = [20.0,30.0,0]; // ... hole for access
Eyepiece = [11.5,28.0 + 0.50,27.0]; // ID = lens, OD includes clearance
Insert = [3.0,4.5,4.0]; // M3 threaded brass insert
Screw = [3.0,7.0,3.5]; // OD = washer, LENGTH = washer + head height
WallThick = 3.0; // minimum wall thickness
Bumper = [2*Screw[OD],20.0,Phone.z]; // bumper edge piece
BumperOAL = Bumper.y + Bumper.x; // outside length for corner piece
BumperRadius = 2.0;
MinMargin = 1.2*Bumper.x; // at least this much extra plate for bumpers
echo(str("MinMargin: ",MinMargin));
Plate = [IntegerMultiple(Phone.x + 2*MinMargin,5.0),
IntegerMultiple(Phone.y + 2*MinMargin,5.0),
false ? 3*ThreadThick : max(Insert[LENGTH] + 2*ThreadThick,WallThick)];
PlateRadius = 5.0;
echo(str("Plate: ",Plate," radius: ",PlateRadius));
EmbossDepth = 2*ThreadThick + Protrusion;
DebossHeight = EmbossDepth;
ScrewOffset = Bumper.x/2;
ScrewAdjust = 1.5*Screw[ID];
NumSides = 2*3*4;
Gap = 2.0; // between build layout parts
//----------------------
// 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);
}
// Basic shapes
// Overall phone outline
module Phone() {
hull()
for (i=[-1,1], j=[-1,1], k=[-1,1])
translate([i*(Phone.x/2 - PhoneRadii.x),j*(Phone.y/2 - PhoneRadii.y),k*(Phone.z/2 - PhoneRadii.z)])
resize(2*PhoneRadii)
sphere(r=1,$fn=NumSides);
}
module Plate() {
union() {
difference() {
union() {
hull()
for (i=[-1,1], j=[-1,1])
translate([i*(Plate.x/2 - PlateRadius),j*(Plate.y/2 - PlateRadius),0])
cylinder(r=PlateRadius,h=Plate.z,center=true,$fn=NumSides);
translate([Phone.x/2,Phone.y/2,-Eyepiece[LENGTH]/3 + Plate.z/2] + LensOffset)
cylinder(d=Eyepiece[OD] + 2*WallThick,h=Eyepiece[LENGTH]/3,
center=false,$fn=NumSides);
translate([Phone.x/2,Phone.y/2,-2*Eyepiece[LENGTH]/3 + Plate.z/2 + Protrusion] + LensOffset)
cylinder(d1=Eyepiece[OD] + 10*ThreadThick,
d2=Eyepiece[OD] + 2*WallThick,
h=Eyepiece[LENGTH]/3,
center=false,$fn=NumSides);
}
translate([Phone.x/2,Phone.y/2,-2*Eyepiece[LENGTH] + Plate.z/2 + Protrusion] + LensOffset)
PolyCyl(Eyepiece[OD],2*Eyepiece[LENGTH],NumSides);
translate(PrintReader + [0,0,-Plate.z/2 - Protrusion])
cylinder(d1=PrintReaderDia[OD],d2=PrintReaderDia[ID],h=Plate.z + 2*Protrusion,$fn=NumSides);
for (i=[-1,1], j=[-1,1])
translate([i*(Phone.x/2 + Bumper.x/2),j*(Phone.y/2 - Bumper.y/2),-Plate.z])
PolyCyl(Insert[OD],2*Plate.z,8);
for (i=[-1,1], j=[-1,1])
translate([i*(Phone.x/2 - Bumper.y/2),j*(Phone.y/2 + Bumper.x/2),-Plate.z])
PolyCyl(Insert[OD],2*Plate.z,8);
translate([0,-12,Plate.z/2]) // recess for legend
cube([55,40,EmbossDepth],center=true);
}
translate([0,0,Plate.z/2 - EmbossDepth])
linear_extrude(height=DebossHeight,convexity=20)
text(text="Pixel 3a",size=6,spacing=1.20,
font="Arial:style:Bold",halign="center",valign="center");
translate([0,-15,Plate.z/2 - EmbossDepth])
linear_extrude(height=DebossHeight,convexity=20)
text(text="Ed Nisley",size=6,spacing=1.20,
font="Arial:style:Bold",halign="center",valign="center");
translate([0,-25,Plate.z/2 - EmbossDepth])
linear_extrude(height=DebossHeight,convexity=20)
text(text="softsolder.com",size=4,spacing=1.20,
font="Arial:style:Bold",halign="center",valign="center");
}
}
module BumperPiece() {
difference() {
translate([0,-BumperOAL/2 + Bumper.x,0])
hull()
for (i=[-1,1], j=[-1,1])
translate([i*(Bumper.x/2 - BumperRadius),j*(BumperOAL/2 - BumperRadius),0])
cylinder(r=BumperRadius,h=Bumper.z,center=true,$fn=NumSides);
translate([0,-Bumper.y/2,-Bumper.z])
PolyCyl(ScrewAdjust,2*Bumper.z,8);
}
}
// Side bumpers, XY origin at inner corner
module BumperCorner() {
union() {
translate([Bumper.x/2,0,0])
BumperPiece();
translate([0,Bumper.x/2,0])
rotate(-90)
BumperPiece();
}
}
//- Build things
if (Layout == "Phone")
Phone();
if (Layout == "Plate")
Plate();
if (Layout == "Bumper")
BumperCorner();
if (Layout == "Show") {
color("LightBlue") Plate();
for (i=[-1,1], j=[-1,1]) {
a =
i > 0 && j > 0 ? 0 :
i < 0 && j > 0 ? 90 :
i > 0 && j < 0 ? -90 :
180
;
translate([i*Phone.x/2,j*Phone.y/2,Plate.z/2 + Bumper.z/2])
rotate(a)
color("LightGreen") BumperCorner();
translate([0,0,Phone.z/2 + Plate.z/2 + Protrusion])
color("DarkGray",0.5) Phone();
}
}
if (Layout == "BuildAll") {
translate([0,0,Plate.z/2])
rotate([0,180,0])
Plate();
for (i=[-1,1], j=[-1,1]) {
a =
i > 0 && j > 0 ? 0 :
i < 0 && j > 0 ? 90 :
i > 0 && j < 0 ? -90 :
180
;
translate([i*(Plate.x/2 + Gap),j*(Plate.y/2 + Gap),Bumper.z/2])
rotate(a)
BumperCorner();
}
}
if (Layout == "BuildPlate") {
translate([0,0,Plate.z/2])
rotate([0,180,0])
Plate();
}
if (Layout == "BuildBumpers") {
for (i=[-1,1], j=[-1,1]) {
a =
i > 0 && j > 0 ? 180 :
i < 0 && j > 0 ? -90 :
i > 0 && j < 0 ? 90 :
0
;
translate([i*(Bumper.x + Gap),j*(Bumper.x + Gap),Bumper.z/2])
rotate(a)
BumperCorner();
}
}
if (Layout == "DrillGuide") {
projection(cut=true)
Plate();
}