Jeff Epler's blog

18 August 2021, 19:03 UTC

Using Adafruit Macropad as LinuxCNC Control Pendant



CircuitPython recently gained the power to have custom USB descriptors. With these, we can define a USB HID device that will work with LinuxCNC's hal_input.

For instance, the Adafruit Macropad has a (very coarse, just 20 detents/revolution) encoder, 12 keyswitch positions, and an OLED screen.

The two pieces of software below, when placed in the CIRCUITPY drive as boot.py and code.py configure it for use with hal_input, using a halcmd line similar to loadusr -W hal_input Macropad. I haven't actually done the work of hooking it all the way up to Touchy yet, but it causes all the buttons & the encoder to appear in halcmd show pin.

This is just the device I picked first; there's nothing to prevent you from hooking up more exotic things like voltage/temperature monitors through added CircuitPython code. Addition of output reports for status indicators is left for the motivated reader.

read more…

9 August 2021, 23:44 UTC

OBS "insert current timestamp" hotkey


In the CircuitPython weekly meetings, one of the things the host does is record the timestamps of parts of the meeting into the notes document.

To facilitate this, I've created an OBS script that can be configured with a global hotkey. Then, just press the hotkey to insert OBS's idea of the current timestamp into any program.

It uses the program "xte" (debian package name: xautomation) on Linux to fake keypresses, and there's a spot where you would customize the script if you use another operating system-- Windows and Mac both have their own packages for making fake keypresses or using any other method that may be available to insert the text. (for instance, putting the timestamp in the operating system paste buffer and then synthesizing a press of the "paste" hotkey)

[permalink]

5 August 2021, 15:41 UTC

Collatz-bolge: A non-universal language, unless the Collatz Conjecture is false


Speculation on an esolang

I was reading recently about Malbolge, an esoteric language that was "designed to be as difficult to program in as possible". The language just has 8 operations. Those operations are "turing complete", (up to the limitation of memory size) but several complications are added for difficulty of creating working programs. In fact, while the language was specified in 1998, it took until 2021 for the announcement of a working LISP interpreter on a related language, Malbolge Unshackled.

Here I'm concerned with the "encypher operation": After each instruction is executed, the memory position indicated by the code pointer is "encyphered" according to a fixed permutation. Part of the proof of Malbolge's turing completeness was finding cycles under the encypher operation that would predictably alternate an instruction between a useful operation and a NOP.

In Collatz-bolge, the fixed permutation is replaced with the Collatz iteration function, n' = n * 2 + 1 (n odd), n' = n / 2 (n even), and the restriction that instructions be in the range 33-126 is relaxed to requiring instructions be 33 or above. It's widely believed that (as formalized in the Collatz Conjecture) every positive integer starting value eventually falls into the repeating sequence 4-2-1. If the Collatz Conjecture is true, every Collatz-bolge program's instructions would eventually decay into non-executable instructions, making the machine halt in a finite number of steps.

On the other hand, if there turn out to be other nontrivial cycles of the Collatz iteration function, it might be possible to prove the language was universal, by using those cycles to construct a translator from Normalized Malbolge to Collatz-bolge.

(Collatz-bolge would also need Malbolge Unshackled features like unbounded register sizes; as arbitrarily large numbers can probably be constructed by extremely long ASCII programs in Collatz-bolge we could also allow a training-wheels version where immense numbers could be directly specified)

[permalink]

13 July 2021, 21:05 UTC

Hybrid Keyboard Descriptor supports Boot & NKRO


This information is slightly out of date! The format of boot.py was modified in 7.0.0-rc.0 and newer

It has been suggested that it's possible to have both N-key rollover (NKRO) and be compatible with computer early boot screens (BIOS/UEFI/etc) by ensuring that the first 8 bytes of the report are compatible with the standard keyboard protocol.

On the heels of my earlier experience with how easy CircuitPython 7 made it to try a custom descriptor for NKRO, I tried implementing this too. And it works! On a sample of one Dell notebook computer, the hybrid descriptor can navigate in the boot menu but also enjoys full NKRO once Linux has booted.

You have to configure the descriptor in boot.py and then use the HybridKeyboard class in your code. It is compatible with the adafruit_hid.keyboard.Keyboard class, except that if you ask it to press more than 6 keys in a row, it doesn't throw an exception!

The original NKRO descriptor is also in this boot.py, in case you want to try both and compare how they work for you; code.py is intended to auto-select the correct interface class. Remember that you need to fully restart the Feather when modifying boot.py!

The first file below is boot.py and the second is code.py, and the code is designed for an Adafruit Feather RP2040 with custom 4x5 keypad, detailed in a Learning System guide.


[permalink]

10 July 2021, 19:12 UTC

CircuitPython NKRO Keyboard


This information is slightly out of date! The format of boot.py was modified in 7.0.0-rc.0 and newer

This is not a boot-compatible keyboard, but it does allow full NRKO. Based on my light testing, it works on Linux (Debian 11 & Android). Presumably it does not work as a boot keyboard device.

You'll need CircuitPython 7 (including the latest alpha release).

The descriptor was designed based on one from the qmk documentation.

In boot.py, set up a custom keyboard descriptor. After putting boot.py on CIRCUITPY, reset the device and make sure that boot-out.txt contains the line "enabled HID with custom keyboard device".

import usb_hid

BITMAP_KEYBOARD_DESCRIPTOR_REPORT_ID = 7
REPORT_BYTES = 16
bitmap_keyboard_descriptor = bytes((
        0x05, 0x01,                     # Usage Page (Generic Desktop),
        0x09, 0x06,                     # Usage (Keyboard),
        0xA1, 0x01,                     # Collection (Application),
        0x85, 0xFF,                     #   6,7 Report ID  [SET AT RUNTIME]
        # bitmap of modifiers
        0x75, 0x01,                     #   Report Size (1),
        0x95, 0x08,                     #   Report Count (8),
        0x05, 0x07,                     #   Usage Page (Key Codes),
        0x19, 0xE0,                     #   Usage Minimum (224),
        0x29, 0xE7,                     #   Usage Maximum (231),
        0x15, 0x00,                     #   Logical Minimum (0),
        0x25, 0x01,                     #   Logical Maximum (1),
        0x81, 0x02,                     #   Input (Data, Variable, Absolute), ;Modifier byte
        # LED output report
        0x95, 0x05,                     #   Report Count (5),
        0x75, 0x01,                     #   Report Size (1),
        0x05, 0x08,                     #   Usage Page (LEDs),
        0x19, 0x01,                     #   Usage Minimum (1),
        0x29, 0x05,                     #   Usage Maximum (5),
        0x91, 0x02,                     #   Output (Data, Variable, Absolute),
        0x95, 0x01,                     #   Report Count (1),
        0x75, 0x03,                     #   Report Size (3),
        0x91, 0x03,                     #   Output (Constant),
        # bitmap of keys
        0x95, (REPORT_BYTES-1)*8,       #   Report Count (),
        0x75, 0x01,                     #   Report Size (1),
        0x15, 0x00,                     #   Logical Minimum (0),
        0x25, 0x01,                     #   Logical Maximum(1),
        0x05, 0x07,                     #   Usage Page (Key Codes),
        0x19, 0x00,                     #   Usage Minimum (0),
        0x29, (REPORT_BYTES-1)*8-1,     #   Usage Maximum (),
        0x81, 0x02,                     #   Input (Data, Variable, Absolute),
        0xc0                            # End Collection
))

bitmap_keyboard = usb_hid.Device(
    report_descriptor = bitmap_keyboard_descriptor,
    usage_page = 0x1,
    usage = 0x6,
    in_report_length = 16,
    out_report_length = 1,
    report_id_index = BITMAP_KEYBOARD_DESCRIPTOR_REPORT_ID,
)

print(bitmap_keyboard)
devices = [
    bitmap_keyboard
    usb_hid.Device.CONSUMER_CONTROL,
]
usb_hid.enable(devices)
print("enabled HID with custom keyboard device") 

In your code.py, use this subclass of adafruit_hid.keyboard.Keyboard:

class BitmapKeyboard(Keyboard):
    def __init__(self, devices):
        for device in devices:
            if device.usage == 6 and device.usage_page == 1:
                try:
                    device.send_report(b'\0' * 16)
                except ValueError:
                    print("found device but could not send report")
                    continue
                self._keyboard_device = device
                break
        else:
            raise IOError("Could not find an HID keyboard device.")

        # report[0] modifiers
        # report[1:16] regular key presses bitmask
        self.report = bytearray(16)

        self.report_modifier = memoryview(self.report)[0:1]
        self.report_keys = memoryview(self.report)[1:]

    def _add_keycode_to_report(self, keycode):
        modifier = Keycode.modifier_bit(keycode)
        print (f"{keycode:02x} {modifier:02x}")
        if modifier:
            # Set bit for this modifier.
            self.report_modifier[0] |= modifier
        else:
            self.report_keys[keycode >> 3] |= 1 << (keycode & 0x7)

    def _remove_keycode_from_report(self, keycode):
        modifier = Keycode.modifier_bit(keycode)
        if modifier:
            # Set bit for this modifier.
            self.report_modifier[0] &= ~modifier
        else:
            self.report_keys[keycode >> 3] &= ~(1 << (keycode & 0x7))

    def release_all(self):
        for i in range(len(self.report)):
            self.report[i] = 0
        self._keyboard_device.send_report(self.report)

[permalink]

24 June 2021, 1:43 UTC

Quick CircuitPython Driver for ES100 WWVB Receiver


I picked up an ES100 WWVB receiver kit and wrote a quick & dirty library to interact with it in CircuitPython.

I'm not super thrilled with how the chip works; I imagined that the date & time registers would act like an RTC after a successful reception, but instead they just mark the second when reception & decoding completed and are cleared to zero as soon as a new reception attempt is kicked off.

Still, I'll have to figure out a clock to put it inside. I am still thinking of doing an edge-lit display version of the Roman Solar Clock, so maybe that's where it'll go.

The library is jepler_es100.py and the example is code_es100.py (rename to code.py). I ran it on a Feather nRF52840 Expess with CircuitPython 6.3, but it should work on a range of boards.

Because the ES100 just locks up the I2C bus if you "repeated-start" it, I had to use my custom rolled register library instead of adafruit_register. I did build it on top of adafruit_bus_device.

Files currently attached to this page:

code_es100.py1.1kB
jepler_es100.py3.3kB

[permalink]

15 June 2021, 14:24 UTC

wwvb uploaded to pypi


As part of learning about modern packaging with Python, I've uploaded my wwvb package (also known as wwvbgen and wwvbpy) to pypi!
wwvbpy generates WWVB timecodes for any desired time. These timecodes may be useful in testing WWVB decoder software.

[permalink]

13 June 2021, 1:15 UTC

7 1/2 years of Prius fuel economy


Year Miles MPG Gal (est.)
2014 7546.8 44.4 170
2015 7333.6 43.9 167
2016 9368.8 44.8 209
2017 9936.2 45.1 220
2018 8853.6 42.0 210
2019 7188.6 40.1 179
2020 2290.9 42.7 54
2021 (partial) 2069.5 44.x 45

In June 2021, the Prius was hit while parked, and totaled. The final mileage was reported as 54588, from which I estimated the 2021 mileage. This still seems high, however, as we didn't manage to go on our recent trip to Boulder before the car was totaled and haven't been driving around town on a daily basis; certainly not an average of over 10 miles/day, but I don't have any more accurate information at hand.

As we are both work-from-home, we plan to be a 1-car family for awhile, as our lower fuel economy Subaru is still running well enough and of course the current supply chain issues have caused car prices to rise. The choice of what car to buy, in 2022 or 2023, will be interesting.

[permalink]

26 May 2021, 17:54 UTC

Semi-revived: Novelwriting

18 April 2021, 14:45 UTC

More Fibonacci?

3 February 2021, 14:20 UTC

Heat-staking in 3D prints

All older entries
Website Copyright © 2004-2021 Jeff Epler