Raspberry Pi Streaming Radio Player: OLED Display

With the OLED wired up to the Raspberry Pi, the LUMA.OLED driver makes it surprisingly easy to slap text on the screen, at least after some obligatory fumbling around:

RPi OLED Display - Plenitude

RPi OLED Display – Plenitude

Connect the hardware, install the driver, then the setup goes like this:

import textwrap

from luma.oled.device import sh1106
from luma.core.serial import spi
from luma.core.render import canvas
from PIL import ImageFont

… snippage …

font1 = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf',14)
font2 = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',11)

wrapper = textwrap.TextWrapper(width=128//font2.getsize('n')[0])

StatLine = 0
DataLine = 17           # allow for weird ascenders and accent marks
LineSpace = 16

Contrast = 255          # OLED brightness setting

serial = spi(device=0,port=0)
device = sh1106(serial)

The Python Imaging Library below the LUMA driver supports Truetype fonts that look much better than the default fonts. For these tiny displays, DejaVu Sans comes close enough to being a single-stroke (“stick”) font and, being proportional, packs more text into a fixed horizontal space.

The textwrap library chops a string into lines of a specified length, which works great with a fixed-width font and not so well with a proportional font. I set the line length based on the width of a mid-size lowercase letter and hope for the best. In round numbers, each 128 pixel line can hold 20-ish characters of the size-11 (which might be the height in pixels) font.

It also understands hyphens and similar line-ending punctuation:

Felix Mendelssohn-
Bartholdy - Piano
Concerto No.01 in

It turns out whatever library routine blits characters into the bitmap has an off-by-one error that overwrites the leftmost column with the pixel columns that should be just off-screen on the right; it may also overwrite the topmost row with the bottommost row+1. I poked around a bit, couldn’t find the actual code amid the layers of inherited classes and methods and suchlike, and gave up: each line starts in pixel column 1, not 0. With textwrap generally leaving the rightmost character in each line blank, the picket-fence error (almost) always overwrites the first column with dark pixels.

Display coordinates start at (0,0) in the upper left corner, but apparently the character origin corresponds to the box around an uppercase letter, with ascenders and diacritical marks extending (at least) one pixel above that. The blue area in these displays starts at (0,16), but having the ascenders poke into the yellow section is really, really conspicuous, so DataLine Finagles the text down by one pixel. The value of Linespace avoids collisions between descenders and ascenders in successive lines that you (well, I) wouldn’t expect with a spacing equal to the font height.

The display has a variable brightness setting, called “contrast” by the datasheet and driver, that determines the overall LED current (perhaps according to an exponential relationship, because an α appears in the tables). I tweak the value in Contrast based on where the streamer lives, with 1 being perfectly suitable for a dark room and 255 for ordinary lighting.

The LUMA package includes a scrolling terminal emulator. With maybe four lines, tops, on that display (in a reasonable font, anyhow), what’s the point?

Instead, I homebrewed a panel with manual positioning:

def ShowStatus(L1=None,L2=None,L3='None'):
  with canvas(device) as screen:
    screen.text((127-(4*font1.getsize('M')[0] + 2),StatLine),'Mute' if Muted else ' ',

    screen.text((1,DataLine + 1*LineSpace),L2,
    screen.text((1,DataLine + 2*LineSpace),L3,

Yeah, those are global variables in the first line; feel free to object-orient it as you like.

The LUMA driver hands you a blank screen inside the with … as …: context, whereupon you may draw as you see fit and the driver squirts the bitmap to the display at the end of the context. There’s apparently a way to set up a permanent canvas and update it at will, but this works well enough for now.

That means you (well, I) must mange those three lines by hand:

ShowStatus('Startup in ' + Location,
           'Mixer: ' + MixerChannel + ' = ' + MixerVol,
           'Contrast: ' + str(Contrast))

Chopping the track info string into lines goes like this:

if TrackName:
  info = wrapper.wrap(TrackName)
             info[1] if len(info) > 1 else '',
             info[2] if len(info) > 2 else '')
  ShowStatus('No track info','','')

Something along the way ruins Unicode characters from the track info, converting them into unrelated (and generally accented) characters. They work fine when shipped through the logging interface, so it may be due to a font incompatibility or, more likely, my not bothering to work around Python 2’s string vs. byte stream weirdness. Using Python 3 would be a Good Idea, but I’m unsure all the various & sundry libraries are compatible and unwilling to find out using programming as an experimental science.

The Python source code as a GitHub Gist:




Raspberry Pi: OLED Display

These cute displays have barely enough dots for the job:

RPi OLED Display - Classical

RPi OLED Display – Classical

That’s a 0.96 inch = 24.4 mm OLED display, measured diagonally, with a breathtaking 8192 = 128×64 dots. It’s a binary display: on or off pixels, nothing in between. This is not a color display: what you see is what it does, with a two-pixel void between the yellow and blue sections.

The void is a physical separation that does not affect the display addressing: the yellow section has 16 rows, the blue section has 48. It’s your responsibility to keep things where they belong; a character descender from the yellow section will appear in the blue section.

They’re three bucks each, shipped halfway around the planet: search eBay / Amazon for oled 128x64 yellow. The all-blue and all-white versions do not have the two-pixel void. I have some white 1.3 inch versions on the way for those applications requiring 35% more visibility.

The SPI interface uses all seven wires, peeled from a premade 100 mm 40-pin cable with female pin connectors:

RPi OLED Display - Wiring

RPi OLED Display – Wiring

Other OLED versions have a four-wire I2C interface. The boards have option jumpers on the back, but the pin header along the edge will have 7 holes for SPI or 4 holes for I2C .

Caveat emptor for online buyers: the item picture(s) may not match the title or the description text. The low-end sellers carrying beach balls, cookware, MOSFETs, cheap consumer electronics, and OLEDs do not understand the tech on a small board that’s Just Another SKU among thousands.

For cables, search eBay or Amazon for ribbon dupont "female to female" 10cm. Amazon has sets of male-female, male-male, and female-female jumpers for ten bucks in various lengths. The insulation seems rather stiff and I may be forced to build better cables with fine wire inside PET braid.

The SPI interface soaks up a tidy block of pins on the RPi’s big header:

RPi OLED Display - RPi connector detail

RPi OLED Display – RPi connector detail

The LUMA-OLED Python driver doc gives a useful summary of those connections, herewith extracted for future reference:

  • 17 VCC – 3.3 V works for sure, 5 V might not
  • 18 DC – Data/Command
  • 19 D1 (“dee one”) – Data to display = MOSI
  • 20 GND
  • 21 not used, that’s the pin in the midst of the block
  • 22 RST – Reset
  • 23 D0 (“dee zero”) – clock to display = SCLK
  • 24 CS – Chip Select = CE0 (“cee ee zero”)

Pin 1 is in front on the left end of that picture, closest to the MicroSD card slot, and proceeds 1-2, 3-4, and so forth along the length of the connector: odds toward the CPU, evens toward the PCB edge.

The LUMA-OLD maintainter must have OLED boards with a slightly different SPI pinout than mine: VCC and GND are interchanged. Caveat emptor!

Obviously, it’s desperately in need of a cute little case, which is in the nature of fine tuning.




Monthly Image: Turkey Vulture Sunning

This must feel soooo good:

Turkey Vulture atop utility pole - alert

Turkey Vulture atop utility pole – alert

Just close your eyes and soak up the warmth of the sun:

Turkey Vulture atop utility pole - snoozing

Turkey Vulture atop utility pole – snoozing

Turkey vultures look imposing, even with all that flight hardware tucked away:

Turkey Vulture on branch

Turkey Vulture on branch

However, I think this is a low-status bird, because those splashes on the left wing look a lot like bird crap…

Taken with the DSC-H5, zoomed all the way tight with the 1.7× teleadapter, handheld on a lovely sunny day.

Update: Because I write these posts a few days in advance of their appearance, I didn’t know yesterday’s weather would look like this:

Driveway clearing - 2017-03-14

Driveway clearing – 2017-03-14

That’s a screenshot from a Raspberry Pi streaming camera I set up so a friend in North Carolina could gloat.

I suppose the vultures huddle in a tree, as do the turkeys, and await better flying conditions.

Enjoy the sun while it shines!


SK2812 RGBW LED: Test Fixture

[Edit: The SK2812 in the title and elsewhere should be SK6812. If I change the title, then all the other links break. So it goes.]

An envelope of RGBW LEDs, allegedly with SK6812 controllers, arrived from halfway around the planet:

SK2812RGBW LEDs - as received

SK2812RGBW LEDs – as received

The yellow phosphor sauce poured atop the blue LED on the left that makes it glow white leaves the upper loop of two wire bonds sticking out, but I can’t fault ’em for that. The overall build quality looks better than the ill-fated WS2812 LEDs, although it’s hard to tell by looking.

I conjured a test stand from the vasty digital deep by tweaking the WS2812 mount:

SK6812 LED Array Test Fixture - Slic3r preview

SK6812 LED Array Test Fixture – Slic3r preview

Wiring up a 5×5 panel went as before:

SK2812RGBW test fixture - rear

SK2812RGBW test fixture – rear

The array test code adds another pixel channel and runs another raised sine wave with another random period, accomplished without much hackage.

With the warm-white LED at full throttle (MaxPWM = 255), the panel tends toward the pallid end of HSV space:

SK2812RGBW test fixture - front - W PWM255

SK2812RGBW test fixture – front – W PWM255

Dialing the white MaxPWM back to 32 crisps things a bit:

SK2812RGBW test fixture - front - W PWM32

SK2812RGBW test fixture – front – W PWM32

Of course, the RGBW data stream isn’t compatible with the RGB data stream, so vacuum tubes with SK6812 chips require a slightly different driver and I can’t mix the two chips on a single tube.

The Arduino source code as a GitHub Gist:

, ,


Kenmore Electric Clothes Dryer Rebuild

Our ancient Kenmore clothes dryer (Model 110.96282100 for maximal SEO goodness) developed symptoms suggesting the heater and overtemperature cutouts were in fine shape: it continued to turn and heat, but didn’t completely dry the clothes. In addition, it emitted a horrible whine that sounded like a bad bearing.

The wiring diagram pasted on the back panel shows how it works (clicky for more dots):

Kenmore clothes dryer 110.96282100 - wiring diagram

Kenmore clothes dryer 110.96282100 – wiring diagram

Obviously, it’s not a firmware problem…

The motor ran just fine, so Thermal Fuse 2 had never blown at 196 °F.

The Operating Thermostat (along the bottom edge of the diagram) switches the 240 VAC heater off when the clothes temperature (actually, the drum exhaust temperature) exceeds 155 °F. It’s in series with the non-resettable 350 °F thermal cutoff and the resettable 250 °F high limit thermostat, both of which were intact, as shown by the fact that the heater still worked.

We generally run the dryer in Auto mode, with the Temperature Selector in the middle position. The Selector varies the resistance in series with the Operating Thermostat heater (near the middle of the diagram), controlled by Timer Switch 1: increasing resistance reduces the heater current and requires hotter clothes before the Thermostat trips. For the first part of the cycle, the BK-BU contact closes to allow the Selector to affect the current. The BK-V contact also closes during the last part of the cycle, cutting out the Selector and letting the Thermostat hold the clothes at 155 °F by cycling the drum heater.

So I installed a new Operating Thermostat (plus the accompanying thermal fuse I didn’t need):

Kenmore clothes dryer - operating thermostat

Kenmore clothes dryer – operating thermostat

You can do that from the back of the dryer without dismantling it, by removing the rear cover.

For whatever it’s worth, the replacement Operating Thermostat heater has a 74 kΩ resistance, not the 5.6 to 8.4 kΩ range shown on the wiring diagram. Preliminary testing suggests it does what it’s supposed to, so maybe they’ve improved (and, surely, cheapnified) its guts to work with 1% of the original power. More likely, the Temperature Selector now doesn’t do anything, as its (minimum) 10 kΩ resistance on the High setting doesn’t amount to squat compared with the new thermostat heater, but we don’t have enough experience to say anything definite.

In an attempt to fix the whine, I took the whole thing apart to replace the idler wheels supporting the drum, the drum drive belt, and the belt tensioner pulley. The interior of the dryer is filled with sharp edges and hatred, so expect some bloodshed.

Removing and installing the triangular wheel retainers requires a small flat-blade screwdriver and considerable muttering. Here’s the old wheel to the left of the motor, before replacement:

Kenmore clothes dryer - tub support wheel

Kenmore clothes dryer – tub support wheel

After reassembling the dryer, the heater worked fine.

The whine also worked fine, much to my dismay.

So I took it all apart again, removed the plate covering the duct from the drum exhaust port to the blower wheel on the motor, removed a generous handful of lint from the middle of the blower wheel, extracted a pile of debris from the bottom of the duct below the wheel, vacuumed everything in sight, reassembled the dryer, and it now sounds great.

Along the way, a small square brass (?) rod fell out of the debris, sporting one shiny end, well-worn to a diagonal slope. I think the rod got trapped between the duct and the back of the blower wheel, where it would produce the whine only when the motor got up to speed (thus, sounding OK while hand-turning the motor). The accumulated debris & lint held it in place, so flipping the dryer on its face and rotating the motor in both directions had no effect: turning the dryer upright simply let it fall back into the same position.

No pictures, alas. We did the second teardown in a white-hot frenzy to Get It Done and swept the brass rod away with all the other debris.



Credit Union Email: Phishing or Not?

The Credit Union recommends we practice “Safe Computing” with this helpful advice (clicky for more dots):

HVFCU - Safe Computing - sketchy URL

HVFCU – Safe Computing – sketchy URL

The link leading to that page was on their website, but the page is on trabian.com, whoever they are. Should I trust the links on that page to return me to the credit union site or not?

Here’s their definition of “phishing”:

HVFCU - Phishing description

HVFCU – Phishing description

Having just switched to “paperless statements” at the Credit Union, a recent email prompted me to look at my statement. Let’s start by seeing where the email came from:

HVFCU - Statement email - From address

HVFCU – Statement email – From address


It claims to be from the credit union, but does its actual address (insofar as anything concerning email can be actual) of statement2web.com sound a little phishy to you, too?

Well, let’s look at the full headers, which I can do because, yo, 1337 H4X0R. Here’s a snippet from the bottom of the stack:

HVFCU - Email detail header

HVFCU – Email detail header


So the email started from statement2web.com and bankshotted off kbmla.com. Further up, the headers show it rattled through pobox.com and eventually arrived in my inbox. As far as I can tell, it never touched its alleged starting point of hvfcu.org at any point in its journey.

Quick: phish or no phish?

Of course, it’s a perfectly innocent message from the credit union, but it contains every single warning sign we’re supposed to notice in spam or phishing emails, complete with a clicky link!

[heavy sigh]


Cheap WS2812 LEDs: Another Failure

A few days after epoxying a replacement WS2812 RGB LED into the base of the 21HB5A and, en passant, soldering a 3.5 mm plug-and-jack into the plate lead for EZ removal, the top LED failed.

21HB5A - Audio plug cable

21HB5A – Audio plug cable

In this case, it also failed the Josh Sharpie test with bad encapsulation sealing:

WS2812 LED failure - ink test patterns

WS2812 LED failure – ink test patterns

Here’s a view from another angle, with a warm-white desk lamp for a bit of color:

WS2812 LED failure - ink test patterns - 2

WS2812 LED failure – ink test patterns – 2

Those patterns took a few days to appear and also showed up in some, but not all, of the previous failing LEDs.

Although I have no idea what’s going on, it’s certainly distinctive!

An envelope of RGBW LEDs, allegedly with SK2812 controllers, has arrived from a different eBay supplier, so it’s time for an upgrade.