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: PC Tweakage

Remembering which tweaks worked

  • 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 …

  • Removing Old Kernels

    Mostly, I don’t worry about the accumulation of old kernels building up in /boot and sudo apt-get autoremove may scrub most of them, but sometimes it doesn’t when I’m doing something else and I must wade through the accumulation of old packages in Synaptic. Removing all those packages by hand gets tedious, but I’m reluctant to unleash a rarely used script on the clutter for fear of creating a worse problem.

    The iterator in this burst of Bash line noise:

    for f in $(ls /boot | grep vmlinuz | cut -d\- -f2,3 | sort | head -n -1) ; do dpkg -l | grep "^ii\ \ linux-" | grep $f | cut -d" " -f 3 >> /tmp/pkgs.txt ; done
    

    … parses the list of kernels in /boot into version numbers, finds the corresponding installed packages, sorts them in ascending order, discards the last entry so as to not uninstall the most recent kernel, and passes each line of the resulting list into the loop.

    N.B: The grep argument has two spaces after the ii that WordPress would destroy without the escaping backslashes. You can try "^ii linux-", but if the loop puts nothing in the file, that’s why.

    Given each kernel version number, the loop extracts the package names from the installed kernel packages and glues the result onto a file that looks like this:

    cat /tmp/pkgs.txt
    linux-headers-3.13.0-73
    linux-headers-3.13.0-73-generic
    linux-image-3.13.0-73-generic
    linux-image-extra-3.13.0-73-generic
    linux-headers-3.13.0-74
    linux-headers-3.13.0-74-generic
    linux-image-3.13.0-74-generic
    linux-image-extra-3.13.0-74-generic
    linux-headers-3.13.0-76
    linux-headers-3.13.0-76-generic
    linux-image-3.13.0-76-generic
    linux-image-extra-3.13.0-76-generic
    

    Convert that file into a one-line string of package names and verify what would happen:

    paste -s -d " " /tmp/pkgs.txt | xargs sudo apt-get --dry-run purge
    

    If everything looks good, change --dry-run to --yes and blow ’em away.

    No, I can’t possibly remember or type that gibberish by hand, but I do know where to find it…

  • Raspberry Pi: Jessie Lite Setup for Streaming Audio

    As a first pass at a featureless box that simply streams music from various sources, I set up a Raspberry Pi with a Jessie Lite Raspbian image. I’m mildly astonished that they use dd to transfer the image to the MicroSD card, but it certainly cuts out a whole bunch of felgercarb that comes with a more user-friendly interface.

    I used dcfldd (for progress reports while copying) and verify the copied image:

    sudo dcfldd statusinterval=10 bs=4M if=/mnt/diskimages/ISOs/Raspberry\ Pi/2015-11-21-raspbian-jessie-lite.img of=/dev/sdb
    sudo dcfldd statusinterval=10 bs=4M if=/dev/sdb of=/tmp/rpi.img count=350
    truncate --reference /mnt/diskimages/ISOs/Raspberry\ Pi/2015-11-21-raspbian-jessie-lite.img /tmp/rpi.img
    diff -s /tmp/rpi.img /mnt/diskimages/ISOs/Raspberry\ Pi/2015-11-21-raspbian-jessie-lite.img
    

    That fits neatly on a minuscule 2 GB MicroSD card:

    df -h
    Filesystem      Size  Used Avail Use% Mounted on
    /dev/root       1.8G  1.1G  549M  67% /
    devtmpfs        214M     0  214M   0% /dev
    tmpfs           218M     0  218M   0% /dev/shm
    tmpfs           218M  4.5M  213M   3% /run
    tmpfs           5.0M  4.0K  5.0M   1% /run/lock
    tmpfs           218M     0  218M   0% /sys/fs/cgroup
    /dev/mmcblk0p1   60M   20M   41M  34% /boot
    

    Set the name of the Raspberry Pi to something memorable, perhaps streamer1.

    Disable IPV6, because nothing around here supports it, by tweaking /etc/modprobe.d/ipv6.conf:

    alias ipv6 off
    

    Enable the USB WiFi dongle by adding network credentials to /etc/wpa_supplicant/wpa_supplicant.conf:

    ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
    update_config=1
    
    network={
     ssid="your network SSID goes here"
     psk="pick your own"
    }
    

    Nowadays, there’s no need for a fixed IP address, because after adding your public key to the (empty) list in ~/.ssh/authorized_keys, you can sign in using a magic alias:

    ssh -p12345 pi@streamer1.local
    

    I have absolutely no idea how that works, nor how to find out. If it ever stops working, I’m doomed.

    The Raspberry Pi Model B+ has “improved” audio that, to Mary’s ears, comes across as pure crap; even my deflicted ears can hear low-level hissing and bad distortion at moderate volumes. An old Creative Labs Sound Blaster USB box sidesteps that problem, but requires a tweak in /etc/asound.conf to route the audio to the proper destination:

    # Make USB sound gadget the default output
    
    pcm.!default {
     type hw card 1
    }
    ctl.!default {
     type hw card 1
    }
    

    ALSA then seems to default to the wrong channel (or something), although this tweak in the middle of /usr/share/alsa/alsa.conf may not be needed:

    #pcm.front cards.pcm.front
    pcm.front cards.pcm.default
    

    Good old mplayer seems to handle everything involved in streaming audio from the Interwebs.

    Set up blank /etc/mplayer/input.conf and ~/.mplayer/input.conf files to eliminate kvetching:

    # Dummy file to quiet the "not found" error message
    

    Set up ~/.mplayer/config thusly:

    prefer-ipv4=true
    novideo=true
    #ao=alsa:device=hw=1.0
    ao=alsa
    format=s16le
    #mixer-channel=Master
    softvol=true
    volume=25
    quiet=true
    

    The commented-out ao option will force the output to the USB gadget if you want to route the default audio to the built-in headphone jack or HDMI output.

    Telling mplayer to use its own software volume control eliminates a whole bunch of screwing around with the ALSA mixer configuration.

    The quiet option silences the buffer progress display, while still showing the station ID and track information.

    With that in hand, the Public Domain Project has a classical music stream that is strictly from noncommercial:

    mplayer -playlist http://relay.publicdomainproject.org/classical.aac.m3u
    

    Send them a sack of money if you like them as much as we do.

    By contrast, the local NPR station comes across as talk radio:

    mplayer http://live.str3am.com:2070/wmht1
    

    You can’t feed nested playlists into mplayer, but fetching the contents of the stream playlists produces a one-station-per-line playlist file that one might call RadioList.txt:

    http://relay.publicdomainproject.org:80/classical.aac
    http://relay.publicdomainproject.org:80/jazz_swing.aac
    http://live.str3am.com:2070/wmht1
    

    So far, I’ve been manually starting mplayer just to get a feel for reliability and suchlike, but the setup really needs an autostart option with some user-friendly way to select various streams, plus a way to cleanly halt the system. A USB numeric keypad may be in order, rather than dinking around with discrete buttons and similar nonsense.

    There exists a horrible hack to transfer the stream metadata from mplayer onto an LCD, but I’m flat-out not using PHP or Perl. Perhaps the Python subprocess management module will suffice to auto-start a Python program that:

    • starts mplayer with the default playlist
    • parses mplayer’s piped output
    • updates the LCD accordingly
    • reads / translates keypad input

    This being a Pi, not an Arduino, one could actually use a touchscreen LCD without plumbing the depths of absurdity, but that starts looking like a lot of work…

  • Linux Mint Login: HTML vs. GDM

    Linux Mint uses an HTM-based login screen that displays an assortment of lush images. That would be fine, except that on the Lenovo Q150, the rendering engine (or whatever you call it) drives one core at full throttle whenever the login screen is up. That turns out to be all the time when I’m signed in through ssh and, for that box, the HTML engine is just a sucking chest wound.

    To fix that:

    System → Login → Theme tab

    Mint Linux - Login Theme Selection
    Mint Linux – Login Theme Selection

    Then pick any theme using GDM rather than HTML; they’re marked to the right of the theme name.

    Being that type of guy, I picked SimpleGreeter, which presents a dead-centered field in a blank screen:

    Mint Linux - SimpleGreeter Login
    Mint Linux – SimpleGreeter Login

    Set it to auto-select the previous user (in the Options tab) and you’re good to go.

    Burns zero CPU and works for me, anyhow.

  • Lenovo Q150: Setup for HP 7475A Plotter Demo

    Starting with a blank 120 GB SSD, I had to disable the “Plug-n-Play OS” BIOS option to get the Lenovo Q150 to boot from a System Rescue CD USB stick. While the hood was up, I told the BIOS to ignore keyboard errors so it can boot headless.

    Partitioning:

    • 50 GB ext4 partition for Mint
    • 8 GB swap
    • The remainder unallocated

    Booting & installing Mint Linux 17.2 XFCE from another USB stick went smoothly, after which it inhaled the usual gazillion updates. Rather than wait for the auto-updater to wake up and smell the repositories, I generally get it done thusly:

    sudo apt-get update
    sudo apt-get upgrade
    apt-get dist-upgrade
    apt-get autoremove
    

    Add my user to the dialout group, so I have access to the USB serial converter on /dev/ttyUSB0 that will drive the plotter.

    Configure a static IP address that appears in the appropriate /etc/hosts files.

    Install some useful packages:

    • nfs-common
    • openssh-server
    • htop and iotop

    Set up ssh for public key authentication, rather than passwords, on an unusual port, so everything else can happen from the Comfy Chair upstairs.

    Install packages that Chiplotle will need:

    • build-essential
    • python-setuptools
    • python-dev
    • python-numpy
    • python-serial
    • hp2xx

    I think some of those would be auto-installed as dependencies by the next step, but now I can remember what they are for the next time around this action loop:

    sudo easy_install -U chiplotle
    ... blank line to show underscore above ...
    

    Plug the old hard drive into a USB-SATA adapter to copy:

    Then chuck up some paper and pens to let it grind out art:

    HP 7475A - demo plot
    HP 7475A – demo plot

    It’s good clean fun…

  • Lenovo Q150: Opening the Case For an SSD

    A pre-Christmas sale brought a cheap SSD that rendered my oath not to install one in the Lenovo Q150 inoperative, so I had to figure out how to open the case. Removing the visible screws didn’t release the cover, but some exploratory prying eventually popped the internal snap latches. Knowing the latch & screw locations will simplify harvesting the SSD when that time comes…

    Front (with USB & SPDIF jacks):

    Lenovo Q150 - case latches - front
    Lenovo Q150 – case latches – front

    Rear (with all the other jacks):

    Lenovo Q150 - case screws - rear
    Lenovo Q150 – case screws – rear

    Top (with the heatsink outlet):

    Lenovo Q150 - case latches - top
    Lenovo Q150 – case latches – top

    Bottom (with the mounting boss):

    Lenovo Q150 - case latch screws - bottom
    Lenovo Q150 – case latch screws – bottom

    With the cover off, the inside looks like this:

    Lenovo Q150 - interior overview
    Lenovo Q150 – interior overview

    The two rubber blocks glued to the hard drive bracket (carrier / sled / whatever) conceal the screws holding that side to the chassis. However, removing the blocks and the screws didn’t release the bracket, because it had what looked like a black adhesive layer below the screw flanges:

    Lenovo Q150 - hidden drive bracket screw
    Lenovo Q150 – hidden drive bracket screw

    Gentle prying from the edge of the bracket eventually released it, showing that the black plastic was just an insulating layer. Below that, two thin foam strips had firmly affixed themselves to the PCB, despite not having any adhesive on that side:

    Lenovo Q150 - drive bracket - foam strips
    Lenovo Q150 – drive bracket – foam strips

    With the bracket on the bench, installing the SSD went exactly as you’d expect and reinstalling the cover was, quite literally, a snap.

  • Forcing UDEV to Not Rename Network Adapters

    I use System Rescue CD to repartition / backup / restore hard drive partitions, but a not-very-recent change to udev caused the familiar eth0 name to come up as something like enp0s26u1u2 on the Lenovo Q150. Which would be OK, but feeding either that or eth0 into net-setup causes it to fall over dead.

    Avoiding that mess requires an incantation in the kernel boot parameters: select a main boot option, hit Tab, type net.ifnames=0 (with a leading space), and whack Enter to boot. Then good old eth0 appears where it should and everything works.

    It’s annoying, but not quite enough to create a specialized SysRescCD image with that incantation preloaded.