alt_neokey.alt_neokey1x4

Alternative CircuitPython API for NeoKey 1x4 i2c Keypad

  • Author(s): Greg Paris

Implementation Notes

Hardware:

Software and Dependencies:

class alt_neokey.alt_neokey1x4.NeoKey1x4(i2c_bus, addr=48, *, brightness=0.2, auto_color=None, auto_action=None, blink=False)

Alternative API for Adafruit’s i2c keypad with RGB LEDs.

Parameters
  • i2c_bus (i2c) – bus the NeoKey 1x4 is connected to

  • addr (int) – i2c address (or list of addresses) of NeoKey 1x4 module(s)

  • brightness (float) – RGB LED intensity

  • auto_color (function) – set colors when keys pressed/released

  • auto_action (function) – run when keys pressed/released

  • blink (bool) – blink all keys when they are not pressed

Raises
  • RuntimeError – if unsupported features are used

  • ValueError – for incorrect i2c addresses

  • TypeError – if auto_color or auto_action is not a function

The intent of this alternative API is to reduce the amount of user code necessary to manage key colors and respond to key-press events. It also simplifies the task of managing more than one NeoKey 1x4 module simultaneously. The cost is that this library uses more memory than the standard does. Comparing simpletest examples from the two libraries, this alternative uses about 8 KB more memory. The all-bells-and-whistles blinktest example uses another 3 KB.

Basic usage is one NeoKey 1x4 module. In that case, supply its i2c address as the addr argument.

To use multiple modules together, supply a list or tuple of i2c addresses as the addr argument. Sixteen modules can be supported by selectively solder-bridging the four address selectors to give each board a unique address, 0x30 through 0x3F. Key numbers will be assigned to the keys in the order of board addresses in the list, first 0-3, second 4-7, …, sixteenth 60-63. (If you accidentally assemble your project with the modules out of ascending address order, no worries! Just order them the same way in the addr list; the keys will be numbered the way you want.)

Keys may be referenced by indexing the NeoKey1x4 instance. Each key is represented by a NeoKeyKey instance.

To dynamically manipulate key colors without coding it into your main loop, define a function that returns a color (24-bit RGB) and pass it to the NeoKey1x4 constructor using the auto_color parameter. The function will be called for each key press and key release event, as detected by the read() method. That method will call the function with a single argument, a NeoKeyEvent.

Similarly, to have read() run arbitrary code whenever a key is pressed, use the NeoKey1x4 constructor’s auto_action parameter. Any return value from the function will be ignored. As with auto_color, it will be passed a single NeoKeyEvent argument when invoked.

The blink parameter is provided to initially enable all keys to blink while not being pressed, as sensed by read(). Keys may be set individually to blink or not blink using their blink property, regardless of the blink value passed to the NeoKey1x4 constructor. The blink feature requires time.monotonic_ns(), which is not available on some boards. In that case, the feature is disabled and attempting to use it will raise a RuntimeError exception.

Note

The auto_color function is used to initialize key colors whenever it is set or changed (except when set to None). It is used with blink mode to establish the ‘on’ color in the on/off cycle. In contrast, the auto_action function can be relied upon to be called only on key press and key release events.

Any time spent doing anything other than reading keys can detract from the responsiveness of the keys. It is probably a good idea to have keys change color when they are pressed, so that the user gets immediate feedback that the key press has been registered.

The following example code has the keys light white when pressed.

import board
from alt_neokey.alt_neokey1x4 import NeoKey1x4
i2c = board.i2c()
neokey = NeoKey1x4(
    i2c,
    auto_color=lambda e: 0xFFFFFF if e.pressed else 0
)
while True:
    neokey.read()
classmethod all(i2c_bus, **kargs)

Find all NeoKey 1x4 devices on the bus and create a NeoKey1x4 instance using all of them, in ascending i2c address order. Returns the new instance.

Parameters
  • i2c_bus (i2c) – bus connecting the NeoKey 1x4 module(s)

  • base (int) – lowest i2c address (default 0x30)

  • last (int) – highest i2c address (default 0x3F)

Return type

NeoKey1x4

Raises

RuntimeError – if no NeoKey 1x4 modules found.

Any other named parameters are passed onto the NeoKey1x4 constructor.

property auto_action

Automatic action function. Function is invoked on key press or release and is passed a single NeoKeyEvent as argument. The return value of this function is ignored. Use None to remove a previously set function.

property auto_color

Automatic color management function. Function is invoked on key press or release and is passed a single NeoKeyEvent as argument. The function must return a 24-bit RGB color integer. Use None to remove a previously set auto_color function. All keys are immediately set to their ‘released’ color whenever this parameter is set to a value other than None.

The code snippet below, taken from one of the example programs, shows key 0 being used to toggle between two key-color modes.

while True:
    for event in neokey.read():
        if event.pressed and event.key_num == 0:
            if neokey.auto_color is normal_mode:
                neokey.auto_color = special_mode
            else:
                neokey.auto_color = normal_mode
property brightness

Float value of brightness shared by all NeoKey 1x4 LEDs.

fill(color)

Set all keys to the specified color. Useful when setting key colors other than with auto_color.

Parameters

color (int) – 24-bit color integer

read()

At the most basic level, read() queries all keys via the i2c bus.

Return type

list(NeoKeyEvent)

read() compares the states of the keys to the previous time it was run. From that comparison, it generates an event list. Each event in that list corresponds to a key press or key release. This is not the same as the current state of a key, as a key that was neither pressed nor released since the last read() will not have an event in the list. (To get the current state of a key, use its pressed property.) Each event in the list is a NeoKeyEvent instance. The return value is this list.

The following code snippet, adapted from the simpletest example, demonstrates responding to the event list returned by read().

while True:
    for event in neokey.read():
        if event.pressed:
            print(f"key {event.key_num} pressed")
            do_something_useful()
        else:
            print(f"key {event.key_num} released")

The NeoKey 1x4 module has an RGB LED under each key. Many uses would have the keys change colors when keys are pressed and released. This can be achieved in your main program, but cluttering it up with key-color management will make it harder to see the code that’s there for the main purpose of your program.

Instead, define a function that returns a color based on (or ignoring) the single NeoKeyEvent argument that will be passed to the function when it is invoked. Then, either as an argument to the NeoKey1x4 constructor or by using its auto_color property, you inform the NeoKey1x4 to use this function to set key colors. It will invoke the function on key press and key release events, setting the color of the key in question.

This function in many cases is so simple that it can be expressed as a lambda, as in the code snippet below, which sets the key color to red when pressed and ‘off’ when released.

neokey.auto_color = lambda e: 0xFF0000 if e.pressed else 0

Another thing NeoKey1x4 can do for you is blink your keys. For example, to signal an alert condition associated with a key, the blink property of that key could be set to True. Each time read() is run, it checks to see whether the key should change from ‘off’ to ‘on’ or vice versa and takes care of it.

neokey[2].blink = True # sets key 2 blinking

Finally, similar to auto_color, read() can execute a function every time a key is pressed or released. This can be set in an argument to the NeoKey1x4 constructor or it can be specified using the auto_action property.

Although there is no limitation on uses for this function, a suggestion is to use it for other housekeeping functions, such as key-clicks, haptic feedback, key logging, etc. This would allow you to keep such code separate from the main thrust of your program. Any return value from the auto_action function will be ignored.

def sound_on_pressed(kev):
    if kev.pressed:
        if kev.key_num == 0:
            my_ring_bell()
        else:
            my_key_click()

neokey.auto_action = sound_on_pressed
read_event(*, timeout=0)

Similar to read() in its calling of auto_color and auto_action functions and managing blink, but uses a different approach to gathering and returning events.

Parameters

timeout (int) – yield None if no key activity in 10ths of a second

Return type

NeoKeyEvent or None

Raises

ValueError – for negative timeout

This method queries one NeoKey 1x4 module at a time. If there are events from that module, yields those events back to the caller one event at a time. On the next call, it starts where it left off. It continues to the next module, starting over with the first after the last, looping forever.

The principal advantage of read_event() over read() is latency fairness. With read(), the first NeoKey 1x4 module gets quicker response than the last because auto_color, auto_action and blink are processed in module and key order. (This effect also could be present in your main program, if it processes the event list in order.) With read_event(), latency is shared evenly because it always picks up from where it left off.

Note

A possibly critical disadvantage of using read_event() is that it does not return to the caller until it detects an event.

To overcome this behavior, use the timeout parameter to cause read_event() to return None whenever the specified time has elapsed without a key event. As with blink, timeout is supported only when the board supports time.monotonic_ns().

Depending on your application, latency differences might be insignificant or unimportant. In those cases, you probably should use read(), as it allows for more flexibility in the main program, given that the timeout feature is not universally supported.

Here is an example of using read_event() with a timeout of three tenths of a second.

# no need for "while True:"
for event in neokey.read_event(timeout=3):
    if event is not None:
        ... # handle key event here
    ... # do other tasks
class alt_neokey.alt_neokey1x4.NeoKeyEvent(key_num, pressed)

Event list element.

Parameters
  • key_num (int) – key number. 0-3 on first NeoKey, 4-7 on second, etc.

  • pressed (bool) – True for key press event; False for key release event.

property key_num

Alias for field number 0

property pressed

Alias for field number 1

class alt_neokey.alt_neokey1x4.NeoKeyKey(seesaw, pixel, key_num, *, blink=False)

A single key and pixel pairing.

Parameters
  • seesaw (seesaw) – NeoKey 1x4 Seesaw

  • pixel (neopixel) – NeoKey 1x4 NeoPixel

  • key_num (int) – key number assigned by NeoKey1x4

The constructor for this class is not intended to be invoked by anything other than NeoKey1x4. One :class`NeoKeyKey` instance is created by NeoKey1x4 for each key. Keys are numbered 0-3 on the first NeoKey 1x4 module. 4-7 on the second, etc.

These instances can be referenced by indexing a NeoKey1x4 object, as shown in the examples below.

neokey = NeoKey1x4(i2c, addr=[0x30, 0x31, 0x32])

neokey[0].color = 0xFF0000 # make red
neokey[11].blink = True # start blinking

key = neokey[0] # reference a NeoKeyKey instance
key.color = 0xFF0000 # same as above example

# key numbers of keys pressed now
pressed = [k for k in neokey if neokey[k].pressed]

Read-write boolean property, True when key is blinking.

property color

Read-write integer property representing the key’s pixel color. Reads and writes are done over the i2c bus.

property key_num

Integer key number assigned by NeoKey1x4. Read-only.

property pressed

Immediate read of this key’s state via the i2c bus. Read-only property is True if the key is being pressed. Does not invoke or affect auto_color or auto_action.