Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions fw/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ As of May 2021, it's only been tested on various RP2040 dev boards.
Board-agnostic code lives in the [core](./core) directory.

Board-specific code - for things like driving indicator LEDs, GPIO mappings - are in other folders.
Word libraries are also in these individual directories for now, as certain dev boards have less flash space.
Those boards have smaller default libraries.
Currently supported boards:
* [Pimoroni Tiny2040](./tiny2040)
* [Raspberry Pi Pico](./pico)
Expand All @@ -23,12 +21,13 @@ tested natively on desktop. *For key press detection on Mac, you'll need to give
2. Find the directory with your board's name in the `bundle` directory. Copy all files/subdirectories onto your CircuitPython device.
3. If your board requires third-party libraries to run this code (most do), you may have to manually copy them. Each board's `bundle` directory will contain a README listing all of the required dependencies. Official CircuitPython libraries available [here](https://circuitpython.org/libraries). As of May 2021, I have been using Bundle version 6.x.
4. If you're using one of the desktop versions, execute `python3 code.py` from the `bundle/[macos/win32]` directory. You'll have to `pip3 install pynput` as well.
5. There is now a `test` target that should run on any machine. This is just a script that simulates the typing of a single word. Execute `python3 code.py` from the `bundle/test` directory to run.

## Customizing the keypad layout

You can customize which letters get attached to each key by changing the `keypad_dict` dictionary in `code.py`. The default is:
You can customize which letters get attached to each key by changing the `key_map` dictionary in `key_map.py`. The default for English is something like:
```python
keypad_dict = {
key_map = {
'1' : ['1'],
'2' : ['a', 'b', 'c'],
'3' : ['d', 'e', 'f'],
Expand All @@ -44,7 +43,7 @@ keypad_dict = {
```
But as [Ben Torvaney discovered](https://torvaney.github.io/projects/t9-optimised.html), you can optimize your typing even further by using something like:
```python
keypad_dict = {
key_map = {
'1' : ['1'],
'2' : ['a', 'm', 'r'],
'3' : ['c', 'd', 'f', 'p', 'u'],
Expand Down
Binary file removed fw/ada-macropad/library.t9l
Binary file not shown.
1 change: 1 addition & 0 deletions fw/core/character_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
character_map = {"a" : 0,"b" : 1,"c" : 2,"d" : 3,"e" : 4,"f" : 5,"g" : 6,"h" : 7,"i" : 8,"j" : 9,"k" : 10,"l" : 11,"m" : 12,"n" : 13,"o" : 14,"p" : 15,"q" : 16,"r" : 17,"s" : 18,"t" : 19,"u" : 20,"v" : 21,"w" : 22,"x" : 23,"y" : 24,"z" : 25,"\'" : 26,"-" : 27,"\u00DF" : 28,"\u00E0" : 29,"\u00E1" : 30,"\u00E2" : 31,"\u00E3" : 32,"\u00E4" : 33,"\u00E5" : 34,"\u00E6" : 35,"\u00E7" : 36,"\u00E8" : 37,"\u00E9" : 38,"\u00EA" : 39,"\u00EB" : 40,"\u00EC" : 41,"\u00ED" : 42,"\u00EE" : 43,"\u00EF" : 44,"\u00F0" : 45,"\u00F1" : 46,"\u00F2" : 47,"\u00F3" : 48,"\u00F4" : 49,"\u00F5" : 50,"\u00F6" : 51,"\u00F8" : 52,"\u00F9" : 53,"\u00FA" : 54,"\u00FC" : 55,"\u00FE" : 56,"\u0161" : 57}
60 changes: 32 additions & 28 deletions fw/core/code.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import time
from led import Led
from t9_keypad import Keypad
from keyboard import Keyboard
from keyboard import Keycode
from t9_keyboard import Keyboard
from t9_keyboard import Keycode
from t9_display import Display
from key_map import key_map
from character_map import character_map

NO_WORD = 0
PARTIAL_WORD = 1
WORD = 2
NODE_HEADER_LEN = 3

CACHE_SIZE = 8000
# Used to store Trie node locations
Expand Down Expand Up @@ -92,28 +95,14 @@ def __init__(self, keys, words, pres):
def __str__(self):
return f'keys: {self.keys}, words: {self.words}, pres: {self.pres}'

keypad_dict = {
'1' : ['1'],
'2' : ['a', 'b', 'c'],
'3' : ['d', 'e', 'f'],
'4' : ['g', 'h', 'i'],
'5' : ['j', 'k', 'l'],
'6' : ['m', 'n', 'o'],
'7' : ['p', 'q', 'r', 's'],
'8' : ['t', 'u', 'v'],
'9' : ['w', 'x', 'y', 'z'],
'0' : [' ', '0', '\n'],
'#' : ['.', ',', '?', '!']
}

def error_mode():
print("ERROR!")
while True:
time.sleep(0.01)
pass

reverse_key_dict = { }
for k, l in keypad_dict.items():
for k, l in key_map.items():
for c in l:
reverse_key_dict[c] = k

Expand Down Expand Up @@ -146,13 +135,17 @@ def error_mode():
# Flag to indicate to our main loop that we want to start a new word
force_break_word = False

def read_int(file, offset, len):
file.seek(offset)
return int.from_bytes(file.read(len), 'big')

# given a file and location in that file, read a 24bit unsigned int
def read_int(file, offset):
def read_address(file, offset):
if (offset < CACHE_SIZE):
cached = file_cache[offset]
if cached >= 0:
return cached
file.seek(offset * 3)
file.seek(offset)
x = int.from_bytes(file.read(3), 'big')
if (offset < CACHE_SIZE):
file_cache[offset] = x
Expand All @@ -162,11 +155,22 @@ def read_int(file, offset):
def search(file, offset, s: str):
poll_keys()
if len(s) == 0:
file_val = read_int(file, offset)
return WORD if file_val == 1 else PARTIAL_WORD
word_flag = read_int(file, offset * 3, 1)
return WORD if word_flag == 1 else PARTIAL_WORD
else:
ch = ord(s[0]) - ord('a')
file_val = read_int(file, offset + 1 + ch)
ch = character_map[s[0]]
ch_bitmap = read_int(file, (offset * 3) + 1, 8)
if (ch_bitmap & (1 << ch) == 0):
return NO_WORD
ch_index = 0
shift_count = 0
while(ch_bitmap != 0 and shift_count < ch):
if ch_bitmap & 1:
ch_index += 1
ch_bitmap >>= 1
shift_count += 1

file_val = read_address(file, (offset + NODE_HEADER_LEN + ch_index) * 3)
if file_val == 0xFFFFFF:
return WORD if len(s) == 1 else NO_WORD
elif file_val > 0:
Expand All @@ -176,7 +180,7 @@ def search(file, offset, s: str):
# Given an open file, a key, and a list of valid prefixes, find all possible words/prefixes
def get_words(file, input, last_result):
# Note that each key has up to 4 possible chars, so we'll need to try all of them
chars = keypad_dict[input]
chars = key_map[input]
output_words = []
output_prefixes = []
for prefix in (last_result.words + last_result.pres):
Expand Down Expand Up @@ -253,7 +257,7 @@ def poll_keys_modified(current_keys):
force_break_word = True
held_modified_keys.append(k)
now = time.monotonic()
key_dict = keypad_dict[k]
key_dict = key_map[k]
if k is not last_modified_key or (now - last_modified_time > 0.6):
flush_last_modified()
emit_raw_text(key_dict[0])
Expand Down Expand Up @@ -304,7 +308,7 @@ def poll_keys():
key_queue.append(hk)

## MAIN LOOP
with open("library.t9l", "rb") as fp:
with open("library.t9l2", "rb") as fp:
while True:
word_index = 0
current_word = ""
Expand Down Expand Up @@ -355,8 +359,8 @@ def poll_keys():
elif c < '2' or c > '9':
break
# emit for any number keys that don't have letters (if user has customized layout)
elif ord(keypad_dict[c][0]) < ord('a'):
emit_raw_text(keypad_dict[c][0])
elif ord(key_map[c][0]) < ord('a'):
emit_raw_text(key_map[c][0])
break
else:
# search the dictionary!
Expand Down
1 change: 1 addition & 0 deletions fw/core/key_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
key_map = {'1':["1"],'2':["a","b","c"],'3':["d","e","f"],'4':["g","h","i"],'5':["j","k","l"],'6':["m","n","o"],'7':["p","q","r","s"],'8':["t","u","v"],'9':["w","x","y","z"],'0':[" ","0","\n"],'#':[".",",","?","!"]}
Binary file added fw/core/library.t9l2
Binary file not shown.
1 change: 1 addition & 0 deletions fw/core/version.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2.0.0
Binary file removed fw/macos/library.t9l
Binary file not shown.
File renamed without changes.
2 changes: 1 addition & 1 deletion fw/pico/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* `adafruit_hid/consumer_control_code.mpy`
* `adafruit_hid/consumer_control.mpy`
* `adafruit_hid/gamepad.mpy`
* `adafruit_hid/keyboard_layout_us.mpy`
* `adafruit_hid/keyboard_layout.mpy` (It may be called `keyboard_layout_us.py` - if so, rename it without the `_us`)
* `adafruit_hid/keyboard.mpy`
* `adafruit_hid/keycode.mpy`
* `adafruit_hid/mouse.mpy`
Expand Down
Binary file removed fw/pico/library.t9l
Binary file not shown.
4 changes: 2 additions & 2 deletions fw/qtpy2040/keyboard.py → fw/pico/t9_keyboard.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import usb_hid
import adafruit_hid.keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keyboard_layout import KeyboardLayout
import adafruit_hid.keycode

class Keyboard():
def __init__(self):
self.keyboard = adafruit_hid.keyboard.Keyboard(usb_hid.devices)
self.keyboard_layout = KeyboardLayoutUS(self.keyboard)
self.keyboard_layout = KeyboardLayout(self.keyboard)

def press(self, key):
self.keyboard.press(key)
Expand Down
2 changes: 1 addition & 1 deletion fw/qtpy2040/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* `adafruit_hid/consumer_control_code.mpy`
* `adafruit_hid/consumer_control.mpy`
* `adafruit_hid/gamepad.mpy`
* `adafruit_hid/keyboard_layout_us.mpy`
* `adafruit_hid/keyboard_layout.mpy` (It may be called `keyboard_layout_us.py` - if so, rename it without the `_us`)
* `adafruit_hid/keyboard.mpy`
* `adafruit_hid/keycode.mpy`
* `adafruit_hid/mouse.mpy`
Expand Down
Binary file removed fw/qtpy2040/library.t9l
Binary file not shown.
4 changes: 2 additions & 2 deletions fw/tiny2040/keyboard.py → fw/qtpy2040/t9_keyboard.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import usb_hid
import adafruit_hid.keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keyboard_layout import KeyboardLayout
import adafruit_hid.keycode

class Keyboard():
def __init__(self):
self.keyboard = adafruit_hid.keyboard.Keyboard(usb_hid.devices)
self.keyboard_layout = KeyboardLayoutUS(self.keyboard)
self.keyboard_layout = KeyboardLayout(self.keyboard)

def press(self, key):
self.keyboard.press(key)
Expand Down
15 changes: 15 additions & 0 deletions fw/test/led.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Led():
def __init__(self):
self.color = None

def show_red(self):
print("set led to red")
self.color = 'r'

def show_green(self):
print("set led to green")
self.color = 'g'

def show_blue(self):
print("set led to blue")
self.color = 'b'
6 changes: 6 additions & 0 deletions fw/test/t9_display.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Display():
def __init__(self):
pass

def display_results(self, result, index):
pass
30 changes: 30 additions & 0 deletions fw/test/t9_keyboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class Keyboard():
def __init__(self):
self.buffer = ""

def press(self, key):
if key == Keycode.BACKSPACE:
self.buffer = self.buffer[:-1]

def release_all(self):
pass

def write(self, text):
self.buffer = self.buffer + text
# pynput virtual typing doesn't seem to work on Mavericks, so just print the output
print("buffer:", self.buffer)

class Keycode():
BACKSPACE = "backspace"
F1 = "F1"
F2 = "F2"
F3 = "F3"
F4 = "F4"
F5 = "F5"
F6 = "F6"
F7 = "F7"
F8 = "F8"
F9 = "F9"
F10 = "F10"
F11 = "F11"
F12 = "F12"
55 changes: 55 additions & 0 deletions fw/test/t9_keypad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import threading
import time
import sys

test_word = "actual"

class Keypad():
def __init__(self, keys):
self.pressed_keys = set()
threading.Thread(target=self.scripted_keys).start()

def scripted_keys(self):
time.sleep(3)
for c in test_word:
k = key_map[c]
self.pressed_keys.add(k)
time.sleep(0.5)
self.pressed_keys.remove(k)
time.sleep(0.5)

key_map = {
'b' : '2',
'a' : '2',
'c' : '2',
'ç' : '2',
'á' : '2',
'd' : '3',
'e' : '3',
'é' : '3',
'f' : '3',
'g' : '4',
'h' : '4',
'i' : '4',
'í' : '4',
'j' : '5',
'k' : '5',
'l' : '5',
'm' : '6',
'n' : '6',
'o' : '6',
'ó' : '6',
'p' : '7',
'q' : '7',
'r' : '7',
's' : '7',
't' : '8',
'u' : '8',
'ü' : '8',
'ú' : '8',
'v' : '8',
'w' : '9',
'x' : '9',
'y' : '9',
'z' : '9'
}
2 changes: 1 addition & 1 deletion fw/tiny2040/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* `adafruit_hid/consumer_control_code.mpy`
* `adafruit_hid/consumer_control.mpy`
* `adafruit_hid/gamepad.mpy`
* `adafruit_hid/keyboard_layout_us.mpy`
* `adafruit_hid/keyboard_layout.mpy` (It may be called `keyboard_layout_us.py` - if so, rename it without the `_us`)
* `adafruit_hid/keyboard.mpy`
* `adafruit_hid/keycode.mpy`
* `adafruit_hid/mouse.mpy`
Expand Down
Binary file removed fw/tiny2040/library.t9l
Binary file not shown.
4 changes: 2 additions & 2 deletions fw/pico/keyboard.py → fw/tiny2040/t9_keyboard.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import usb_hid
import adafruit_hid.keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keyboard_layout import KeyboardLayout
import adafruit_hid.keycode

class Keyboard():
def __init__(self):
self.keyboard = adafruit_hid.keyboard.Keyboard(usb_hid.devices)
self.keyboard_layout = KeyboardLayoutUS(self.keyboard)
self.keyboard_layout = KeyboardLayout(self.keyboard)

def press(self, key):
self.keyboard.press(key)
Expand Down
Binary file removed fw/win32/library.t9l
Binary file not shown.
File renamed without changes.
2 changes: 1 addition & 1 deletion library_generator/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
library.t9l
.gradle/
build/
output/
.idea/
local.properties
Loading