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: Software

General-purpose computers doing something specific

  • KeyboardIO Atreus Keymapping

    KeyboardIO Atreus Keymapping

    Having a customizable keyboard like the KeyboardIO Atreus means one must customize it. As it turns out, I wanted to use some features of the underlying QMK Kaleidoscope firmware that aren’t exposed by Chrysalis, KeyboardIO’s otherwise competent keymap configuration utility, so what you see below runs on hard mode.

    Start by installing QMK, compiling the default Atreus layout, and flashing the keyboard just to confirm all the steps work:

    Atreus keyboard - overview
    Atreus keyboard – overview

    With all that working, add (or create) two lines to the rules.mk file in the keymap directory you’re tweaking:

    AUTO_SHIFT_ENABLE = yes			# allow automagic shifting
    TAP_DANCE_ENABLE = yes			# allow multi-tap keys

    Enabling Auto-Shift lets you generate shifted characters (like Z) by briefly holding down the unshifted key (like z). This requires unlearning an entire lifetime of touch typing practice, but is definitely worthwhile; if a thumb still reaches for the shift key, there’s no harm done. There are, of course, a myriad options, all of which I left unchanged.

    Complex passwords suffer, as you must blind-type carefully while tapping each key rapidly.

    Enabling Tap Dance lets a key generate one character when tapped and another when double-tapped; you can go crazy with more taps. An enum{} in the keymap.c file generates indexes for the keys and an array holds the action definitions:

    enum {
        TD_SPC_ENT,
        TD_BS_DEL,
    };
    
    qk_tap_dance_action_t tap_dance_actions[] = {
        [TD_SPC_ENT] = ACTION_TAP_DANCE_DOUBLE(KC_SPC, KC_ENT),
        [TD_BS_DEL] = ACTION_TAP_DANCE_DOUBLE(KC_BSPC, KC_DEL),
    };

    Then each key uses a TD() macro in the keymap.c file:

    … TD(TD_BS_DEL), … TD(TD_SPC_ENT), …

    In contrast, layer shifting uses straightforward built-in macros. The Fun key produces a momentary shift to Layer 1 (known as _RS) when held down:

    … MO(_RS), …

    The ESC key in the lower left corner emits the expected Escape key code when tapped and switches to Layer 2 (a.k.a. _LW) when held:

    LT(_LW,KC_ESC), …

    For reference, the current state of the keymap.c file:

    // Modified from the KeyboardIO layout
    // Ed Nisley - KE4ZNU
    // Enable Auto Shift and Tap Dance in rules.mk
    
    #include QMK_KEYBOARD_H
    
    enum layer_names {
        _QW,
        _RS,
        _LW,
    };
    
    enum {
        TD_SPC_ENT,
        TD_BS_DEL,
    };
    
    qk_tap_dance_action_t tap_dance_actions[] = {
        [TD_SPC_ENT] = ACTION_TAP_DANCE_DOUBLE(KC_SPC, KC_ENT),
        [TD_BS_DEL] = ACTION_TAP_DANCE_DOUBLE(KC_BSPC, KC_DEL),
    };
    
    const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
      [_QW] = LAYOUT( /* Qwerty */
        KC_Q,    KC_W,    KC_E,    KC_R,    KC_T,                      KC_Y,    KC_U,    KC_I,    KC_O,    KC_P    ,
        KC_A,    KC_S,    KC_D,    KC_F,    KC_G,                      KC_H,    KC_J,    KC_K,    KC_L,    KC_SCLN ,
        KC_Z,    KC_X,    KC_C,    KC_V,    KC_B,    KC_GRV,  KC_LALT, KC_N,    KC_M,    KC_COMM, KC_DOT,  KC_SLSH ,
        LT(_LW,KC_ESC), KC_TAB, KC_LGUI,  TD(TD_BS_DEL), KC_LSFT,  KC_LCTL, KC_ENT , TD(TD_SPC_ENT),  MO(_RS), KC_MINS, KC_QUOT, KC_BSLS),
    
      [_RS] = LAYOUT( /* [> RAISE <] */
        KC_EXLM, KC_AT,   KC_UP,   KC_DLR,  KC_PERC,                  KC_PGUP, KC_7,    KC_8,   KC_9, KC_HOME,
        KC_LPRN, KC_LEFT, KC_DOWN, KC_RGHT, KC_RPRN,                  KC_PGDN, KC_4,    KC_5,   KC_6, KC_END,
        KC_LBRC, KC_RBRC, KC_HASH, KC_LCBR, KC_RCBR, KC_CIRC, KC_AMPR,KC_ASTR, KC_1,    KC_2,   KC_3, KC_PLUS,
        KC_NO  , KC_INS,  KC_LGUI, KC_DEL , KC_BSPC, KC_LCTL, KC_LALT,KC_SPC,  KC_TRNS, KC_DOT, KC_0, KC_EQL ),
    
      [_LW] = LAYOUT( /* [> LOWER <] */
        KC_INS,  KC_HOME, KC_UP,   KC_END,  KC_PGUP,                   KC_UP,   KC_F7,   KC_F8,   KC_F9,   KC_F10  ,
        KC_DEL,  KC_LEFT, KC_DOWN, KC_RGHT, KC_PGDN,                   KC_DOWN, KC_F4,   KC_F5,   KC_F6,   KC_F11  ,
        KC_NO,   KC_VOLU, KC_NO,   KC_NO,   RESET,   _______, _______, KC_NO,   KC_F1,   KC_F2,   KC_F3,   KC_F12  ,
        KC_NO,   KC_VOLD, KC_LGUI, KC_LSFT, KC_BSPC, KC_LCTL, KC_LALT, KC_SPC,  TO(_QW), KC_PSCR, KC_SLCK, KC_PAUS )
    };
    

    With all that set up, It Just Works and I can contemplate grafting a status LED into the thing.

  • MTD Snowthrower: Replacement Throttle Knob

    MTD Snowthrower: Replacement Throttle Knob

    The throttle knob on our MTD snowthrower (a.k.a. snowblower) cracked apart around its metal shaft when I pulled it upward. A temporary fix involving duct tape and cable ties sufficed to start the engine, although the usual intense vibration shook the knob loose somewhere along the driveway during the next hour.

    Update: Found it!

    Although I have no photographic evidence, I did make a few quick measurements:

    Throttle Knob Dimension Doodles
    Throttle Knob Dimension Doodles

    It fits an MTD model E6A4E, but I suspect nearly all their engines have identical throttle shafts:

    Snowthrower Throttle Knob - stem end - solid model
    Snowthrower Throttle Knob – stem end – solid model

    The only practical way to build the thing has it standing on the shaft end, surrounded by a brim to improve adhesion, so I added (actually, subtracted) a pair of holes for music-wire reinforcements:

    Snowthrower throttle knob - reinforcing wires
    Snowthrower throttle knob – reinforcing wires

    It definitely has a stylin’ look, next to the original choke control knob:

    Snowthrower throttle knob - installed
    Snowthrower throttle knob – installed

    I omitted the finger grip grooves for obvious reasons.

    The slot-and-hole came out slightly smaller than the metal shaft and, rather than wait for epoxy to cure, I deployed a 230 W soldering gun (not a piddly temperature-controlled iron suitable for electronics) on the shaft and melted it into the knob.

    More snow may arrive this week and I printed another knob just in case …

    The OpenSCAD source code as a GitHub Gist:

    // MTD Snowthrower Throttle Knob
    // Ed Nisley KE4ZNU 2020-12-18
    /* [Options] */
    Layout = "Show"; // [Build, Show]
    // Extrusion parameters
    /* [Hidden] */
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    Protrusion = 0.1; // make holes end cleanly
    inch = 25.4;
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    //———————-
    // Dimensions
    Throttle = [17.0,1.85,6.5]; // blade insertion, thickness, width
    PaddleSize = [25,30,9];
    PaddleRound = 4.0;
    PaddleThick = 8.5;
    StemDia = 13.0;
    StemLength = 20.0;
    PinDia = 1.6;
    PinLength = PaddleSize.x + StemLength/2;
    echo(str("Pin: ",PinLength," x ",PinDia," mm"));
    //———————-
    // 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);
    }
    //———————-
    // Pieces
    module Paddle() {
    difference() {
    hull() {
    translate([PaddleSize.x/2,0,0]) {
    for (i=[-1,1], j=[-1,1])
    translate([i*(PaddleSize.x – PaddleRound)/2,j*(PaddleSize.y – PaddleRound)/2,0])
    sphere(d=PaddleRound,$fn=12);
    rotate([0,90,0]) rotate(180/12)
    cylinder(d=PaddleThick,h=PaddleSize.x,,center=true,$fn=12);
    }
    translate([-StemLength,0,0])
    rotate([0,90,0]) rotate(180/12)
    cylinder(d=StemDia,h=Throttle.x,center=false,$fn=12);
    }
    translate([-StemLength,0,0])
    cube([2*Throttle.x,Throttle.y,Throttle.z],center=true);
    translate([-(StemLength + Protrusion),0,0])
    rotate([0,90,0]) rotate(0*180/6)
    PolyCyl(2*Throttle.y,Throttle.x,6);
    for (j=[-1,1])
    translate([-StemLength/2,j*PaddleSize.y/6,0])
    rotate([0,90,0]) rotate(180/4)
    PolyCyl(PinDia,PinLength,4);
    }
    }
    //———————-
    // Build it
    if (Layout == "Show")
    Paddle();
    if (Layout == "Build") {
    translate([0,0,StemLength])
    rotate([0,-90,0])
    Paddle();
    }

  • Astable Multivibrator: Dressed-up LED Spider

    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

    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

    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

    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”.