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.

Author: Ed

  • KeyboardIO Atreus: LED Diffuser

    KeyboardIO Atreus: LED Diffuser

    After staring at the RGB LED I installed in my Atreus keyboard for a while, I converted the stub of a ¼-20 nylon screw into a light diffuser:

    Atreus keyboard - LED diffuser
    Atreus keyboard – LED diffuser

    It stands slightly proud of the surface plate so I can extract it without dismantling the whole keyboard again:

    Atreus keyboard - LED diffuser installed
    Atreus keyboard – LED diffuser installed

    I’ll eventually make a better-looking diffuser from a recently arrived translucent acrylic rod, but this will reduce the accumulation of fuzz inside the keyboard until the matching Round Tuit arrives.

  • Floor Lamp Rebasing

    Floor Lamp Rebasing

    The torchiere floor lamp in the sewing room suffered a catastrophic failure:

    Floor lamp - failed plastic base shell
    Floor lamp – failed plastic base shell

    Contrary to what you might think from seeing the shattered plastic base, we didn’t use the lamp as a club or battering ram. Apparently the designer expected the thin plastic surrounding the hole to withstand all the torque produced by the long pole against the cheap concrete / mortar / grout / whatever lump in the base. As we can recall, this lamp came to us from either a yard sale or a roadside debris harvest, so I suppose the hardware outlasted any reasonable expectation.

    The Basement Laboratory Warehouse disgorged the pole and base from a similar lamp, albeit sporting black paint and a smaller rod connecting its pole to its somewhat larger weight. Not being too fussy about decor, I embiggened the hole in the black base to fit the white lamp’s threaded rod:

    Floor lamp - enlarging replacement base
    Floor lamp – enlarging replacement base

    The dust on the base shows why you shouldn’t stand motionless in the Basement Laboratory for very long.

    The alert reader will have noted the cord passing through a strain relief grommet in the white base. Rather than dismantle the entire lamp, I just cut the cord, ran it through the new base weight, reinstalled the washer + nut, then crimped on a pair of solderless connectors:

    Floor lamp - cord splice
    Floor lamp – cord splice

    The new base doesn’t offer much in the way of attachment points, so I added a cable tie to keep the strain off the connectors:

    Floor lamp - cord strain relief
    Floor lamp – cord strain relief

    A strip of genuine 3M duct tape with double-thick adhesive now traps the cord inside that small channel and, given that the lamps spends most of its time standing quietly in a corner, the cord should be fine for long enough.

  • Off To A Good Start

    Off To A Good Start

    This could happen:

    Flying Pig
    Flying Pig

    It vanished a few hours after appearing at the end of a neighbor’s driveway: a pig must be flying somewhere!

  • Review Phreesia Authorization

    “Preregistering” for a medical appointment started by clicking a link in an email to reach a website with no obvious relation to the medical office, filling in a selection of my private bits, then being confronted by this wall of text:

    ———- Wall of text begins ———-

    Review Phreesia Authorization
    Please review the authorization below. A copy of this authorization form will be available at the front desk.
    Authorization for Uses and Disclosures of Protected Health Information
    Health-Related Materials

    I hereby authorize my healthcare provider to release to Phreesia’s Check-in system my health information entered during the automated Check-in process, or on file with my healthcare provider, to help determine the health-related materials I will receive as part of my use of Phreesia. The health-related materials may include information and advertisements related to treatments and therapies specific to my health status. The materials may be provided by my health insurance plan, a pharmaceutical manufacturer or another healthcare entity. Phreesia may receive a payment for making such information available to me through the Check-in System or Phreesia’s Patient Communication Services including items such as newsletters, patient reminders for visits, medication/treatment adherence and other practice-related services.

    If I am presented with an advertisement pursuant to this Authorization and I choose to request certain information and/or samples as described in the advertisement, then I further authorize Phreesia to disclose my protected health information to the advertiser as designated in the advertisement, such as my name, email address, mailing address, or phone number in order to receive such information and/or samples. Phreesia may receive a payment for releasing my personal information. The use and disclosure of my protected health information solely as set forth in this paragraph is valid only for purposes of when I choose to receive the information and/or samples, as described in the advertisement and until I receive such information and/or samples.

    My healthcare provider is using Phreesia’s secure platform to enhance the patient-provider experience and eliminate inefficiencies associated with Check-in.

    The following is the Authorization to provide me personalized educational health content and to allow Phreesia, on behalf of my healthcare provider, to conduct analytics using some of the information that I provide to gain insight into and support the effectiveness of this educational health content.

    Utilizing Federal guidelines and its corporate policy, Phreesia, on behalf of my healthcare provider, ensures that all patient-related health information is protected by administrative, technical, and physical safeguards.

    Phreesia will safeguard my personal information and will not use it for any purpose, other than to: provide health-related materials to me; anonymously analyze health outcomes in support of that educational health content, as well as to measure the effect of the health-related materials furnished to me on my communications with me or my family member’s healthcare provider (this analysis is computer-automated and involves no human review of my protected health information); and carry out any use or disclosure otherwise permitted by this Authorization.

    Although there is the potential for information disclosed pursuant to this Authorization to be subject to redisclosure by the recipient and no longer be protected by federal privacy rules, Phreesia maintains administrative, technical, and physical safeguards as required by the Federal Government’s Health Information Privacy Rule, or “HIPAA,” to protect each patient’s confidential information. Phreesia does not disclose personally identifiable information to anyone other than each patient’s healthcare provider without this Authorization or as governed, permitted or required by law.

    I do not have to grant this Authorization but, if I do not, I will not receive personalized health-related material or, as applicable, receive the materials as described in the advertisement. I understand that my healthcare provider will treat me regardless of whether I grant this Authorization.

    I have a right to receive a copy of this Authorization. I may change my mind and revoke (take back) this Authorization at any time, except to the extent that my healthcare provider or Phreesia has already acted based on this Authorization. To revoke this Authorization, I must contact my healthcare provider c/o Phreesia in writing (including my name, date of birth, gender, home address and healthcare provider’s name) at: Privacy Officer, Phreesia, Inc., 434 Fayetteville Street, Suite 1400, Raleigh, NC 27601; or PrivacyOfficer@Phreesia.com. This information will not be used for any purposes other than to verify my identity in order to revoke this Authorization.

    This Authorization is valid for the following time periods:

    • One year from the date on which I grant this Authorization – for use in delivering personalized health-related materials from my healthcare provider on the Phreesia platform;
    • When the Patient Communication Services Program concludes – for use in delivering Phreesia’s Patient Communication Services on behalf of my healthcare provider; and
    • When the Analytics conclude – for use in Phreesia’s analytics programs

    Phreesia is a business associate of my healthcare provider and is bound by federal law to protect and safeguard my privacy.

    Authorization signed by: The patient, [me]

    ———- Wall of text ends ———-

    I assume your eyes glazed over immediately upon seeing the text and it’s entirely reasonable to assume most folks simply select the “Agree” button (which doesn’t appear here), sign the form, and move on.

    Having actually read the damn thing, it turns out to be an agreement to let Phreesia (apparently, all the good names were used up) spam me with medical advertising vaguely related to my current malady.

    Look at that first paragraph again:

    I hereby authorize my healthcare provider to release to Phreesia’s Check-in system my health information entered during the automated Check-in process, or on file with my healthcare provider, to help determine the health-related materials I will receive as part of my use of Phreesia. The health-related materials may include information and advertisements related to treatments and therapies specific to my health status. The materials may be provided by my health insurance plan, a pharmaceutical manufacturer or another healthcare entity. Phreesia may receive a payment for making such information available to me through the Check-in System or Phreesia’s Patient Communication Services including items such as newsletters, patient reminders for visits, medication/treatment adherence and other practice-related services.

    “May receive a payment” indeed. I declined and haven’t died yet.

    This could happen:

    … there is the potential for information disclosed pursuant to this Authorization to be subject to redisclosure by the recipient and no longer be protected by federal privacy rules …

    Scum, the lot of them.

  • KeyboardIO Atreus: RGB LED Firmware

    KeyboardIO Atreus: RGB LED Firmware

    Having wired a WS2812 RGB LED into my KeyboardIO Atreus, lighting it up requires some QMK firmware configuration. It’s easiest to set up a “new” keymap based on the QMK Atreus files, as described in the QMK startup doc:

    qmk new-keymap -kb keyboardio/atreus -km ednisley

    Obviously, you’ll pick a different keymap name than I did. All the files mentioned below will reside in the new subdirectory, which starts out with only a keymap.c file copied from the default layout.

    The rules.mk file enables RGB Lighting, as well as Auto Shift and Tap Dance:

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

    If you had different hardware, you could specify the driver with a WS2812_DRIVER option.

    QMK can also control single-color LEDs with PWM (a.k.a. backlighting), and per-key RGB LEDs (a.k.a. RGB Matrix). These functions, their configuration / controls / data, and their documentation overlap and intermingle to the extent that I spent most of my time figuring out what not to include.

    Some configuration happens in the config.h file:

    #define RGB_DI_PIN B2
    #define RGBLED_NUM 1
    
    // https://github.com/qmk/qmk_firmware/blob/master/docs/ws2812_driver.md
    //#define WS2812_TRST_US 280
    //#define WS2812_BYTE_ORDER WS2812_BYTE_ORDER_GRB
    
    #define RGBLIGHT_LAYERS
    #define RGBLIGHT_EFFECT_RGB_TEST
    #define RGBLIGHT_LIMIT_VAL 63
    
    #define NO_DEBUG
    #define NO_PRINT
    

    The first two lines describe a single WS2812 RGB LED wired to pin B2 (a.k.a. MOSI) of the Atmel 32U4 microcontroller. The default Reset duration and Byte Order values work for the LED I used

    Protip: swapping the order from GRB to RGB is a quick way to discover if the firmware actually writes to the LED, even before you get anything else working: it’ll be red with the proper setting and green with the wrong one.

    Dialing the maximum intensity down works well with a bright LED shining directly at your face from a foot away.

    Turning on RGBLIGHT_LAYERS is what makes this whole thing happen. The RGBLIGHT_EFFECT_RGB_TEST option enables a simple test animation at the cost of a few hundred bytes of code space; remove that line after everything works.

    The last two lines remove the debugging facilities; as always with microcontroller projects, there’s enough room for either your code or the debugger required to get it running, but not both.

    With those files set up, the keymap.c file does the heavy lifting:

    // Modified from the KeyboardIO layout
    // Ed Nisley - KE4ZNU
    
    #include QMK_KEYBOARD_H
    
    enum layer_names {
        _BASE,
        _SHIFTS,
        _FUNCS,
        _NLAYERS
    };
    
    // Tap Dance
    
    enum {
        TD_SPC_ENT,
    };
    
    qk_tap_dance_action_t tap_dance_actions[] = {
        [TD_SPC_ENT] = ACTION_TAP_DANCE_DOUBLE(KC_SPC, KC_ENT),
    };
    
    
    // Layer lighting
    
    // Undefine this to enable simple test mode
    // Also put #define RGBLIGHT_EFFECT_RGB_TEST in config.h
    
    #define LED_LL
    
    #ifdef LED_LL
    
    const rgblight_segment_t PROGMEM ll_0[] = RGBLIGHT_LAYER_SEGMENTS( {0,1,HSV_WHITE} );
    const rgblight_segment_t PROGMEM ll_1[] = RGBLIGHT_LAYER_SEGMENTS( {0,1,HSV_MAGENTA} );
    const rgblight_segment_t PROGMEM ll_2[] = RGBLIGHT_LAYER_SEGMENTS( {0,1,HSV_CYAN} );
    const rgblight_segment_t PROGMEM ll_3[] = RGBLIGHT_LAYER_SEGMENTS( {0,1,HSV_BLUE} );
    const rgblight_segment_t PROGMEM ll_4[] = RGBLIGHT_LAYER_SEGMENTS( {0,1,HSV_GREEN} );
    const rgblight_segment_t PROGMEM ll_5[] = RGBLIGHT_LAYER_SEGMENTS( {0,1,HSV_RED} );
    const rgblight_segment_t PROGMEM ll_6[] = RGBLIGHT_LAYER_SEGMENTS( {0,1,HSV_YELLOW} );
    
    const rgblight_segment_t* const PROGMEM ll_layers[] = RGBLIGHT_LAYERS_LIST(
        ll_0,ll_1,ll_2,ll_3,ll_4,ll_5,ll_6
    );
    
    #endif
    
    void keyboard_post_init_user(void) {
    
    #ifdef LED_LL
        rgblight_layers = ll_layers;
        rgblight_set_layer_state(0, 1);
    #else
        rgblight_enable_noeeprom();
        rgblight_mode_noeeprom(RGBLIGHT_MODE_RGB_TEST);
    //    rgblight_mode_noeeprom(RGBLIGHT_MODE_BREATHING + 3);
    #endif
    
    }
    
    
    #ifdef LED_LL
    
    layer_state_t layer_state_set_user(layer_state_t state) {
        for (uint8_t i=0 ; i < _NLAYERS; i++)
            rgblight_set_layer_state(i, layer_state_cmp(state, i));
    
        return state;
    }
    #endif
    
    
    // Key maps
    
    const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
      [_BASE] = LAYOUT(                             // base layer for typing
        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(_FUNCS,KC_ESC), KC_TAB, KC_LGUI,  KC_BSPC, KC_LSFT,  KC_LCTL, KC_ENT , TD(TD_SPC_ENT),  MO(_SHIFTS), KC_MINS, KC_QUOT, KC_BSLS),
    
      [_SHIFTS] = LAYOUT(                           // shifted chars and numpad
        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 ),
    
      [_FUNCS] = LAYOUT(                            // function keys
        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(_BASE), KC_PSCR, KC_SLCK, KC_PAUS )
    };
    

    Undefine LED_LL to enable the test mode, compile, flash, and the LED should cycle red / green / blue forever; you also need the RGB_TEST option in the config.h file.

    Define LED_LL and layer lighting should then Just Work™, with the LED glowing:

    • White for the basic layer with all the letters
    • Magenta with the Fun key pressed
    • Cyan with the Esc key pressed

    The key map code defines colors for layers that don’t yet exist, but it should get you started.

    For convenience, I wadded all three QMK files into a GitHub Gist.

    The LED is kinda subtle:

    Atreus keyboard - LED installed
    Atreus keyboard – LED installed

    As you might expect, figuring all that out took much longer than for you to read about it, but now I have a chance of remembering what I did.

  • MTD Snowthrower Throttle Knob: Found!

    MTD Snowthrower Throttle Knob: Found!

    After the snow cleared and we ventured out again, the missing snowthrower throttle knob was sitting on the far reaches of the driveway:

    MTD Snowthrower Throttle Knob - crude repair
    MTD Snowthrower Throttle Knob – crude repair

    It’s a well-meaning, albeit totally ineffectual, expedient repair.

    I put a channel around the slot for the throttle shaft to mimic the original design:

    MTD Snowthrower Throttle Knob - fracture
    MTD Snowthrower Throttle Knob – fracture

    The fracture started at the end and worked its way back, loosening the knob’s grip on the shaft as it went:

    MTD Snowthrower Throttle Knob - pieces
    MTD Snowthrower Throttle Knob – pieces

    Should my replacement survive the next 13 years, it’ll likely outlive the rest of the snowthrower:

    Snowthrower throttle knob - installed
    Snowthrower throttle knob – installed

    If the original MTD choke knob didn’t have that fancy metal insert, I’d replace it just for pretty …

  • KeyboardIO Atreus: RGB LED Installation

    KeyboardIO Atreus: RGB LED Installation

    Having scouted out the territory inside the KeyboardIO Atreus, adding an LED requires taking it completely apart to drill a hole in the aluminum faceplate:

    Atreus keyboard - panel drilling
    Atreus keyboard – panel drilling

    Reattaching the plate to the PCB with only three screws allows marking the hole position on the PCB, which is much easier than pretending to derive the position from first principles:

    Atreus keyboard - LED marking
    Atreus keyboard – LED marking

    Despite appearances, I traced the hole with a mechanical pencil: black graphite turns shiny silvery gray against matte black soldermask. Also, the PCB trace is off-center, not the hole.

    Overlay the neighborhood with Kapton tape to protect the PCB from what comes next:

    Atreus keyboard - Kapton tape

    Snip a WS2812 RGB LED from a strip, stick it in place with eyeballometric alignment over the target, and wire it up:

    Atreus keyboard - LED wiring
    Atreus keyboard – LED wiring

    Despite the terrible reliability of WS2812 RGB LEDs mounted on PCB carriers, a different set on a meter of high-density flex tape have worked reasonably well when not thermally stressed, so I’ll assume this one arrived in good order.

    Aligning the LED directly under the hole required a few iterations:

    Atreus keyboard - LED positioning
    Atreus keyboard – LED positioning

    The iridescent green patch is a diffraction pattern from the controller chip’s internal circuitry.

    The data comes from MOSI, otherwise known as B2, down in the lower left corner:

    Atmel 32U4 - JTAG pins
    Atmel 32U4 – JTAG pins

    Actually lighting the LED now becomes a simple matter of software QMK firmware.