andyclymer / minikbd

119 stars 9 forks source link

Writing a string does not work with a single button press #8

Open Charlie22911 opened 5 years ago

Charlie22911 commented 5 years ago

Working on my first project involving any degree of code, and also my first github post; I'm looking to use this as way to insert strings with a single button press (for example, game server commands). Using "kbdLayout.write('This is a test!')" for example causes it to insert that string when the trinket boots, and subsequently locks up on the next button press.

Using Mu serial monitor I see:

main.py output: Traceback (most recent call last): File "main.py", line 44, in File "miniKbdButtons.py", line 53, in update File "main.py", line 28, in buttonDownCallback File "adafruit_hid/keyboard.py", line 100, in press File "adafruit_hid/keyboard.py", line 135, in _add_keycode_to_report File "adafruit_hid/keycode.py", line 311, in modifier_bit TypeError: unsupported types for le: 'int', 'NoneType'

I'm not really sure how to approach this, any help would be appreciated.

andyclymer commented 5 years ago

Hi @Charlie22911 !

First, I wonder if you're using any of the example code from this repo? It sounds like maybe you're using the "SixKeys-Template" code? Did you modify the main.py file? If you made any changes, could you paste it into a comment here so that I can have a closer look at what might be going on?

The error is pointing to line 28 in the main.py file, if you're using the "SixKeys-Template" this is the line that turns a buttonID number into a keycode command by checking the buttonIDtoKeycode dictionary, and then it tells the device to type that keycode with the kbd.press() function.

If you changed this line, or if you added more than just a keycode in the buttonIDtoKeycode dictionary (like, is this where you added your entire string?) then some other lines might need to change too. But let's start by having a look at your main.py script if you can paste it in here.

Andy

Charlie22911 commented 4 years ago

Apologies for the delay, I was preoccupied with the wonderful kit from Tindie. Yes, I am using the example code here; specifically the six keys code.

The specific change that breaks things is changing line 19 to: 1: kbdLayout.write('string'),

Here is main.py as it sits on my trinket now:

import board
from digitalio import DigitalInOut, Direction, Pull
import adafruit_dotstar as dotstar
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS

from miniKbdButtons import MiniKbdButtons

dot = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.5)
dot[0] = (0, 0, 0)

kbd = Keyboard()
kbdLayout = KeyboardLayoutUS(kbd)

# Customize these keycodes
# https://circuitpython.readthedocs.io/projects/hid/en/latest/api.html#adafruit-hid-keycode-keycode
buttonIDtoKeycode = {
    1: kbdLayout.write('string'),
    2: Keycode.TWO,
    3: Keycode.THREE,
    4: Keycode.FOUR,
    5: Keycode.FIVE,
    6: Keycode.SIX}

def buttonDownCallback(buttonID, othersDown):
    kbd.press(buttonIDtoKeycode[buttonID])
    print('Button ' + str(buttonID) + ' is pressed. ' + str(othersDown))

def buttonUpCallback(buttonID):
    kbd.release(buttonIDtoKeycode[buttonID])
    print('Button ' + str(buttonID) + ' is released.')

ButtonMatrix = MiniKbdButtons(
    keyDownCallback=buttonDownCallback,
    keyUpCallback=buttonUpCallback)

# Main Loop
while True:
    ButtonMatrix.update()

I've made other superficial changes to help get a grasp at whats going on, but I don't think they are responsible. miniKbdButtons.py is unchanged. I really appreciate the help Andy!

Charlie22911 commented 4 years ago

I got things working, but not in the way I expected. I cobbled together bits of arduino code from various sources online. I think I'd rather figure out how to make it work in circuit python since I haven't accomplished my goal by such "cheating". Here is the relevant code, I'm sure its a horror show (not that I'd know):

#include "Adafruit_DotStar.h"
#include "Adafruit_Keypad.h"
#include "Keyboard.h"
#include "SPI.h"

Adafruit_DotStar strip = Adafruit_DotStar(1, INTERNAL_DS_DATA, INTERNAL_DS_CLK, DOTSTAR_BGR);

const byte ROWS = 2;
const byte COLS = 3;
char keys[ROWS][COLS] = {
  {'1','2','3'},
  {'4','5','6'}
};
byte rowPins[ROWS] = {0, 1};
byte colPins[COLS] = {3, 4, 2};

Adafruit_Keypad customKeypad = Adafruit_Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS);

long lastDebounceTime = 0;
long debounceDelay = 50;

void setLedColor(const char color[]) {
  if (color == "red") {
    strip.setPixelColor(0, 64, 0, 0);
  } else if (color == "green") {
    strip.setPixelColor(0, 0, 64, 0);
  } else if (color == "blue") {
    strip.setPixelColor(0, 0, 0, 64);
  } else if (color == "yellow") {
    strip.setPixelColor(0, 64, 64, 0);
  } else if (color == "magenta") {
    strip.setPixelColor(0, 64, 00, 64);
  } else if (color == "cyan") {
    strip.setPixelColor(0, 0, 64, 64);
  } else if (color == "black") {
    strip.setPixelColor(0, 0, 0, 0);
  } else {
    strip.setPixelColor(0, 255, 255, 255);
  }
  strip.show();
}

void setup() {
  strip.begin();
  strip.show();
  customKeypad.begin();
}

void loop() {
  customKeypad.tick();

  if ( (millis() - lastDebounceTime) > debounceDelay) {

    while(customKeypad.available()){
      keypadEvent e = customKeypad.read();

      if ((e.bit.KEY == '1') && (e.bit.EVENT == KEY_JUST_PRESSED)){
        Keyboard.println("This key is TOP LEFT");
        setLedColor("red");
        lastDebounceTime = millis();
      }

      else if ((e.bit.KEY == '2') && (e.bit.EVENT == KEY_JUST_PRESSED)){
        Keyboard.println("This key is TOP MIDDLE");
        setLedColor("green");
        lastDebounceTime = millis();
      }

      else if ((e.bit.KEY == '3') && (e.bit.EVENT == KEY_JUST_PRESSED)){
        Keyboard.println("This key is TOP RIGHT");
        setLedColor("blue");
        lastDebounceTime = millis();
      }

      else if ((e.bit.KEY == '4') && (e.bit.EVENT == KEY_JUST_PRESSED)){
        Keyboard.println("This key is BOTTOM LEFT");
        setLedColor("yellow");
        lastDebounceTime = millis();
      }

      else if ((e.bit.KEY == '5') && (e.bit.EVENT == KEY_JUST_PRESSED)){
        Keyboard.println("This key is BOTTOM MIDDLE");
        setLedColor("magenta");
        lastDebounceTime = millis();
      }

      else if ((e.bit.KEY == '6') && (e.bit.EVENT == KEY_JUST_PRESSED)){
        Keyboard.println("This key is BOTTOM RIGHT");
        setLedColor("cyan");
        lastDebounceTime = millis();
      }

      else {
        setLedColor("black");
      }
    }
  }
}
andyclymer commented 4 years ago

HI @Charlie22911 ,

Thanks for posting your code, let me walk you through what's happening. From your Python traceback, the error can be found on line 28 in your main.py:

kbd.press(buttonIDtoKeycode[buttonID])

The kbd object has a method called press() which is expecting a single keycode. In one line, it takes the buttonID from the key, uses it as a keyword in teh buttonIDtoKeycode dictionary to fetch the keycode assigned to this key, and then passes the result as an argument in the kbd.press() method.

For this to work, all of the values in the dictionary should only be single keycodes. But I see on line 19 you changed the entry for ID 1 to kbdLayout.write('string') and this is the problem.

If you want to assign actions to the keys as a dictionary of strings instead of a dictionary of keycodes, then let me point you in the right direction. First, change all of the values in the buttonIDtoKeycode dictionary into strings, but without the kbdLayout.write() part:

buttonIDtoKeycode = {
    1: 'string',
    2: 'hello',
    3: 'asdf',
    4: 'another string',
    5: 'etc',
    6: 'etc'}

...and then change the callback on line 28 to write the string that it fetches from the dictionary instead of pressing a single key. This is where you'll use the kbdLayout.write()

def buttonDownCallback(buttonID, othersDown):
    kbdLayout.write(buttonIDtoKeycode[buttonID])

This is untested but it should work, I hope it makes sense what's going on. But if not let me know and I can go into a little bit more depth!

Charlie22911 commented 4 years ago

Thank you for the quick reply, making the suggested changed results in the the string typing out correctly, but it crashes immediately after the string completes:

main.py output:
Button 5 is pressed. [5]
Traceback (most recent call last):
  File "main.py", line 42, in <module>
  File "miniKbdButtons.py", line 51, in update
  File "main.py", line 32, in buttonUpCallback
  File "adafruit_hid/keyboard.py", line 116, in release
  File "adafruit_hid/keyboard.py", line 156, in _remove_keycode_from_report
  File "adafruit_hid/keycode.py", line 311, in modifier_bit
TypeError: unsupported types for __le__: 'int', 'str'

Could this have something to do with the function that releases the keys after being pressed, being that a string is not a key? main.py below:

import board
from digitalio import DigitalInOut, Direction, Pull
import adafruit_dotstar as dotstar
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS

from miniKbdButtons import MiniKbdButtons

dot = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2)
dot[0] = (0, 0, 0)

kbd = Keyboard()
kbdLayout = KeyboardLayoutUS(kbd)

# Customize these keycodes
# https://circuitpython.readthedocs.io/projects/hid/en/latest/api.html#adafruit-hid-keycode-keycode
buttonIDtoKeycode = {
    1: 'This is a test',
    2: 'This is a test',
    3: 'This is a test',
    4: 'This is a test',
    5: 'This is a test',
    6: 'This is a test'}

def buttonDownCallback(buttonID, othersDown):
    kbdLayout.write(buttonIDtoKeycode[buttonID])
    print('Button ' + str(buttonID) + ' is pressed. ' + str(othersDown))

def buttonUpCallback(buttonID):
    kbd.release(buttonIDtoKeycode[buttonID])
    print('Button ' + str(buttonID) + ' is released.')

ButtonMatrix = MiniKbdButtons(
    keyDownCallback=buttonDownCallback, 
    keyUpCallback=buttonUpCallback)

# Main Loop
while True:
    ButtonMatrix.update()
andyclymer commented 4 years ago

Ah yes! I knew I should have tested my reply before sending it but I didn't have a MiniKbd on hand at the moment.

In CircuitPython, the keyboard can work in two ways — you can press and hold keys and then release them, or you can have it write a string to type it out character by character. Since you've switched to writing strings, you can remove line 32, the line with the kbd.release() function call.

Adafruit's demo code for the HID Keyboard module has an interesting solution where it uses a condition between lines 57 and 61 where it chooses to write if the variable k is a string (which in your case would be what's returned from the buttonIDtoKeycode[buttonID] dictionary call and not the variable k), otherwise it will assume that it was a keycode and it will press and then release the keys. This is something you might be able to try if you still want to mix strings and keycodes in the buttonIDtoKeycode dictionary.