Skip to content
Merged
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
2 changes: 1 addition & 1 deletion scripts/superkey/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def autokey_quick_msg(self, index: int):
self.__send_packet(MessageID.REQUEST_AUTOKEY_QUICK_MSG, struct.pack('<B', index))
self.__check_reply_empty()

def autokey_wait(self, delay: float = 0.25):
def autokey_wait(self, delay: float = 0.1):
"""
Waits until the autokey buffer is empty.
NOTE: This is not an interface call - it periodically polls `autokey_count()`.
Expand Down
12 changes: 12 additions & 0 deletions scripts/utility/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#
# @file scripts/utility/__init__.py
# @brief Init file for the utility module.
#
# @author Chris Vig (chris@invictus.so)
# @date 2025-09-10
# @cpyrt © 2025 by Chris Vig. Licensed under the GNU General Public License v3 (GPLv3).
#

# ------------------------------------------------------ IMPORTS -------------------------------------------------------

from .wordlist import *
85 changes: 85 additions & 0 deletions scripts/utility/wordlist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#
# @file scripts/utility/wordlist.py
# @brief Python module defining the WordList class.
#
# @author Chris Vig (chris@invictus.so)
# @date 2025-09-10
# @cpyrt © 2025 by Chris Vig. Licensed under the GNU General Public License v3 (GPLv3).
#

# ------------------------------------------------------ IMPORTS -------------------------------------------------------

import random
from urllib import request

# ------------------------------------------------------ EXPORTS -------------------------------------------------------

__all__ = [
'WordList'
]

# ----------------------------------------------------- CONSTANTS ------------------------------------------------------

DEFAULT_WORD_LIST_URL = 'https://raw.githubusercontent.com/first20hours/google-10000-english/refs/heads/master/google-10000-english-usa-no-swears.txt'

# ------------------------------------------------------- TYPES --------------------------------------------------------

class WordList:
"""
Class providing access to a list of words which may be selected from randomly.
"""
def __init__(self, url: str = DEFAULT_WORD_LIST_URL):
"""
Initializes a new instance using the file at the specified URL.
"""
# Load file from URL
self.url = url
with request.urlopen(url) as response:
data = response.read()

# Get word list by splitting up lines
self.words = str(data, encoding='utf8').splitlines()

# Get word list sorted by length and determine minimum and maximum
self.words_by_length = sorted(self.words, key=len)
self.min_length = len(self.words_by_length[0])
self.max_length = len(self.words_by_length[-1])

# Find start index for each length
self.words_by_length_indices = { }
for idx, word in enumerate(self.words_by_length):
for length in range(self.min_length, len(word) + 1):
if length not in self.words_by_length_indices:
self.words_by_length_indices[length] = idx

def __len__(self):
"""
Returns the number of words in this word list.
"""
return len(self.words)

def _start_idx(self, length: int) -> int:
"""
Returns the start index in the sorted list for words with the specified length.
"""
if length < self.min_length:
return 0
elif length > self.max_length:
return len(self)
else:
return self.words_by_length_indices[length]

def random(self, min_length: int = None, max_length: int = None):
"""
Returns a randomly selected word from this word list.
"""
# Sanity check range
if min_length is None or min_length < self.min_length:
min_length = self.min_length
if max_length is None or max_length > self.max_length:
max_length = self.max_length

# Get indices in the sorted list for the requested lengths
start_idx = self._start_idx(min_length)
end_idx = self._start_idx(max_length + 1)
return self.words_by_length[random.randrange(start_idx, end_idx)]
109 changes: 109 additions & 0 deletions scripts/wordcopy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#
# @file scripts/wordcopy.py
# @brief Trainer script for copying English words.
#
# @author Chris Vig (chris@invictus.so)
# @date 2025-09-10
# @cpyrt © 2025 by Chris Vig. Licensed under the GNU General Public License v3 (GPLv3).
#

# ------------------------------------------------------ IMPORTS -------------------------------------------------------

import argparse
import time

from superkey import *
from utility import wordlist

# ----------------------------------------------------- CONSTANTS ------------------------------------------------------

DEFAULT_COUNT = 20
DEFAULT_DELAY = 3.
DEFAULT_WPM = 20.
DEFAULT_MINLEN = 2
DEFAULT_MAXLEN = 8

# ----------------------------------------------------- PROCEDURES -----------------------------------------------------

def _parse_args():
"""
Parses the command line arguments.
"""
parser = argparse.ArgumentParser(description='Word Copy Trainer')
parser.add_argument('--port', type=str, default=SUPERKEY_DEFAULT_PORT, help='Serial port to connect to.')
parser.add_argument('--baudrate', type=int, default=SUPERKEY_DEFAULT_BAUDRATE, help='Serial port baud rate.')
parser.add_argument('--timeout', type=float, default=SUPERKEY_DEFAULT_TIMEOUT, help='Serial port timeout (s).')
parser.add_argument('--count', type=int, default=DEFAULT_COUNT, help='Number of words to key.')
parser.add_argument('--delay', type=float, default=DEFAULT_DELAY, help='Seconds to delay between each word.')
parser.add_argument('--wpm', type=float, default=DEFAULT_WPM, help='Words per minute.')
parser.add_argument('--minlen', type=int, default=DEFAULT_MINLEN, help='Minimum word length.')
parser.add_argument('--maxlen', type=int, default=DEFAULT_MAXLEN, help='Maximum word length.')
return parser.parse_args()

def _main(port: str,
baudrate: int,
timeout: float,
count: int,
delay: float,
wpm: float,
minlen: int,
maxlen: int):
"""
Runs the trainer.
"""
# Build word list
wl = wordlist.WordList()

# Open SuperKey interface
with Interface(port = port, baudrate = baudrate, timeout = timeout) as intf:

try:

# Get initial settings
initial_wpm = intf.get_wpm()
initial_trainer_mode = intf.get_trainer_mode()

# Override settings
intf.set_wpm(wpm)
intf.set_trainer_mode(True)

# Run as many times as commanded
for _ in range(count):

# Get a random word and key it
word = wl.random(min_length=minlen, max_length=maxlen).upper()
intf.autokey(word, block=True)

# Print the word after a delay
time.sleep(delay)
print(word)
time.sleep(delay * .5)

except KeyboardInterrupt:

# Cancel autokey
intf.panic()

finally:

# Restore initial settings
intf.set_wpm(initial_wpm)
intf.set_trainer_mode(initial_trainer_mode)


# -------------------------------------------------- IMPORT PROCEDURE --------------------------------------------------

if __name__ == '__main__':

# Parse arguments
args = _parse_args()

# Run main procedure
_main(port = args.port,
baudrate = args.baudrate,
timeout = args.timeout,
count = args.count,
delay = args.delay,
wpm = args.wpm,
minlen = args.minlen,
maxlen = args.maxlen)