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
217 changes: 127 additions & 90 deletions scripts/autokey.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import argparse
from msvcrt import getch
import re

from superkey import *

Expand Down Expand Up @@ -66,114 +67,150 @@ def _buffered_mode(port: str = SUPERKEY_DEFAULT_PORT,
"""
with Interface(port=port, baudrate=baudrate, timeout=timeout) as intf:
while True:
try:

# Get next input from user
line = input('> ')
# Get next input from user
line = input('> ')

# Check for special commands
if line == ':q' or line == ':quit' or line == ':exit':
# Exit program
break
# Helper functions
def line_equals(x: str) -> bool:
return line.casefold() == x.casefold()
def line_starts_with(x: str) -> bool:
return line.casefold().startswith(x.casefold())

elif line == ':!' or line == ':panic':
# Panic
intf.panic()
continue
# Check for special commands
if line_equals(':q') or line_equals(':quit') or line_equals(':exit'):
# Exit program
break

elif line == ':wpm':
# Print WPM status
print(f'WPM: {intf.get_wpm():.1f}')
continue
elif line_equals(':!') or line_equals(':panic'):
# Panic
intf.panic()
continue

elif line[:5] == ':wpm ':
# Set WPM
try:
intf.set_wpm(float(line[5:]))
except ValueError:
print('Invalid WPM?')
continue
elif line_equals(':wpm'):
# Print WPM status
print(f'WPM: {intf.get_wpm():.1f}')
continue

elif line == ':buzzer':
# Print buzzer status
print(f'Buzzer: {'On' if intf.get_buzzer_enabled() else 'Off'} ({intf.get_buzzer_frequency()} Hz)')
continue
elif line_starts_with(':wpm '):
# Set WPM
try:
intf.set_wpm(float(line[5:]))
except ValueError:
print('Invalid WPM?')
continue

elif line == ':buzzer off':
# Turn buzzer off
intf.set_buzzer_enabled(False)
continue
elif line_equals(':buzzer'):
# Print buzzer status
print(f'Buzzer: {'On' if intf.get_buzzer_enabled() else 'Off'} ({intf.get_buzzer_frequency()} Hz)')
continue

elif line == ':buzzer on':
# Turn buzzer on
intf.set_buzzer_enabled(True)
continue
elif line_equals(':buzzer off'):
# Turn buzzer off
intf.set_buzzer_enabled(False)
continue

elif line[:18] == ':buzzer frequency ':
# Set buzzer frequency
try:
intf.set_buzzer_frequency(int(line[18:]))
except ValueError:
print('Invalid frequency?')
continue
elif line_equals(':buzzer on'):
# Turn buzzer on
intf.set_buzzer_enabled(True)
continue

elif line == ':paddle':
# Print paddle mode
mode = intf.get_paddle_mode()
if mode == PaddleMode.IAMBIC:
print('Paddle mode: IAMBIC')
elif mode == PaddleMode.ULTIMATIC:
print('Paddle mode: ULTIMATIC')
elif mode == PaddleMode.ULTIMATIC_ALTERNATE:
print('Paddle mode: ULTIMATIC_ALTERNATE')
else:
print('Paddle mode: unknown?')
continue
elif line_starts_with(':buzzer frequency '):
# Set buzzer frequency
try:
intf.set_buzzer_frequency(int(line[18:]))
except ValueError:
print('Invalid frequency?')
continue

elif line == ':paddle iambic':
# Set paddles to iambic mode
intf.set_paddle_mode(PaddleMode.IAMBIC)
continue
elif line_equals(':paddle'):
# Print paddle mode
mode = intf.get_paddle_mode()
if mode == PaddleMode.IAMBIC:
print('Paddle mode: Iambic')
elif mode == PaddleMode.ULTIMATIC:
print('Paddle mode: Ultimatic')
elif mode == PaddleMode.ULTIMATIC_ALTERNATE:
print('Paddle mode: Ultimatic Alternate')
else:
print('Paddle mode: unknown?')
continue

elif line == ':paddle ultimatic':
# Set paddles to ultimatic mode
intf.set_paddle_mode(PaddleMode.ULTIMATIC)
continue
elif line_equals(':paddle iambic'):
# Set paddles to iambic mode
intf.set_paddle_mode(PaddleMode.IAMBIC)
continue

elif line == ':paddle ultimatic_alternate':
# Set paddles to ultimatic alternate mode
intf.set_paddle_mode(PaddleMode.ULTIMATIC_ALTERNATE)
continue
elif line_equals(':paddle ultimatic'):
# Set paddles to ultimatic mode
intf.set_paddle_mode(PaddleMode.ULTIMATIC)
continue

elif line == ':trainer':
# Print trainer mode status
print(f'Trainer mode: {'On' if intf.get_trainer_mode() else 'Off'}')
continue
elif line_equals(':paddle ultimatic_alternate'):
# Set paddles to ultimatic alternate mode
intf.set_paddle_mode(PaddleMode.ULTIMATIC_ALTERNATE)
continue

elif line == ':trainer on':
# Enable trainer mode
intf.set_trainer_mode(True)
continue
elif line_equals(':trainer'):
# Print trainer mode status
print(f'Trainer mode: {'On' if intf.get_trainer_mode() else 'Off'}')
continue

elif line == ':trainer off':
# Disable trainer mode
intf.set_trainer_mode(False)
continue
elif line_equals(':trainer on'):
# Enable trainer mode
intf.set_trainer_mode(True)
continue

elif line[0] == ':':
# Unknown command?
print('Unknown command?')
continue
elif line_equals(':trainer off'):
# Disable trainer mode
intf.set_trainer_mode(False)
continue

elif line_starts_with(':qm'):
# Handle this with regex for easier processing
if match := re.match(r'^:qm get (\d+)$', line, re.IGNORECASE):
print(intf.get_quick_msg(int(match.group(1))))
continue
elif match := re.match(r'^:qm set (\d+) (.+)$', line, re.IGNORECASE):
intf.set_quick_msg(int(match.group(1)), match.group(2))
continue
elif match := re.match(r'^:qm del (\d+)$', line, re.IGNORECASE):
intf.invalidate_quick_msg(int(match.group(1)))
continue
elif match := re.match(r'^:qm (\d+)$', line, re.IGNORECASE):
intf.autokey_quick_msg(int(match.group(1)))
continue

elif line_starts_with(':'):
# Unknown command?
print('Unknown command?')
continue

# Split line into tokens and send in either normal or prosign mode
tokens = line.split('\\')
prosign = False
for token in tokens:
if prosign and len(token) > 1:
intf.autokey(token[:-1], flags=[AutokeyFlag.NO_LETTER_SPACE])
intf.autokey(token[-1])
elif len(token) != 0:
intf.autokey(token)
prosign = not prosign
# Split line into tokens and send in either normal or prosign mode
tokens = line.split('\\')
prosign = False
for token in tokens:
if prosign and len(token) > 1:
intf.autokey(token[:-1], flags=[AutokeyFlag.NO_LETTER_SPACE])
intf.autokey(token[-1])
elif len(token) != 0:
intf.autokey(token)
prosign = not prosign

# Ultra-graceful error handling
except InvalidMessageError:
print('SuperKey responds: invalid message!')
except InvalidSizeError:
print('SuperKey responds: invalid size!')
except InvalidCRCError:
print('SuperKey responds: invalid CRC!')
except InvalidPayloadError:
print('SuperKey responds: invalid payload!')
except InvalidValueError:
print('SuperKey responds: invalid value!')
except InterfaceError:
print('SuperKey responds: unknown error!')


def _immediate_mode(port: str = SUPERKEY_DEFAULT_PORT,
Expand Down
54 changes: 49 additions & 5 deletions scripts/superkey/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
'InvalidSizeError',
'InvalidCRCError',
'InvalidPayloadError',
'InvalidValueError',
'Interface',
'InteractiveInterface',
]
Expand Down Expand Up @@ -128,6 +129,13 @@ def autokey(self, string: str, flags: Iterable[AutokeyFlag] = []):
self.__send_packet(MessageID.REQUEST_AUTOKEY_EX, payload)
self.__check_reply_empty()

def autokey_quick_msg(self, index: int):
"""
Sends the `REQUEST_AUTOKEY_QUICK_MSG` command. Keys a quick message.
"""
self.__send_packet(MessageID.REQUEST_AUTOKEY_QUICK_MSG, struct.pack('<B', index))
self.__check_reply_empty()

def get_buzzer_enabled(self) -> bool:
"""
Sends the `REQUEST_GET_BUZZER_ENABLED` command. Returns whether the buzzer is enabled or not.
Expand Down Expand Up @@ -191,6 +199,13 @@ def get_paddle_mode(self) -> PaddleMode:
self.__send_packet(MessageID.REQUEST_GET_PADDLE_MODE)
return PaddleMode(self.__check_reply('<B')[0])

def get_quick_msg(self, index: int) -> str:
"""
Sends the `REQUEST_GET_QUICK_MSG` command. Returns the text of the specified quick message.
"""
self.__send_packet(MessageID.REQUEST_GET_QUICK_MSG, struct.pack('<B', index))
return str(self.__check_reply(), encoding='ascii')[:-1]

def get_trainer_mode(self) -> bool:
"""
Sends the `REQUEST_GET_TRAINER_MODE` command. Returns whether trainer mode is enabled or not.
Expand All @@ -212,6 +227,13 @@ def get_wpm_scale(self, element: CodeElement) -> float:
self.__send_packet(MessageID.REQUEST_GET_WPM_SCALE, struct.pack('<B', element))
return self.__check_reply('<f')[0]

def invalidate_quick_msg(self, idx: int):
"""
Sends the `REQUEST_INVALIDATE_QUICK_MSG` command. Deletes the specified quick message.
"""
self.__send_packet(MessageID.REQUEST_INVALIDATE_QUICK_MSG, struct.pack('<B', idx))
self.__check_reply_empty()

def panic(self):
"""
Sends the `REQUEST_PANIC` command. Immediately and unconditionally stops keying.
Expand Down Expand Up @@ -282,6 +304,19 @@ def set_paddle_mode(self, mode: PaddleMode):
self.__send_packet(MessageID.REQUEST_SET_PADDLE_MODE, struct.pack('<B', mode))
self.__check_reply_empty()

def set_quick_msg(self, index: int, string: str):
"""
Sends the `REQUEST_SET_QUICK_MSG` command. Sets the text of a quick message.
"""
# Assemble payload
payload = (struct.pack('<B', index) + # First byte is index
bytes(string, encoding='ascii') + # Then the string
b'\x00') # Null terminator needs to be added manually

# Send packet and check reply
self.__send_packet(MessageID.REQUEST_SET_QUICK_MSG, payload)
self.__check_reply_empty()

def set_trainer_mode(self, enabled: bool):
"""
Sends the `SET_TRAINER_MODE` command. Enables or disables trainer mode.
Expand Down Expand Up @@ -361,7 +396,7 @@ def __receive_header(self):
# Unpack the header and verify the size and CRC are correct
return self.__class__.__unpack_header(reply)

def __check_reply(self, format: str) -> Tuple[any, ...]:
def __check_reply(self, format: Optional[str] = None) -> Tuple[any, ...] | bytes:
"""
Attempts to receive a reply with a payload from the device.
"""
Expand All @@ -383,11 +418,20 @@ def __check_reply(self, format: str) -> Tuple[any, ...]:
if payload is not None and self.__class__.__crc16(payload) != crc:
raise InterfaceError('Reply: Invalid CRC?')

# Check payload length
if len(payload) != struct.calcsize(format):
raise InterfaceError('Reply: Invalid payload?')
# Do we have a format string?
if format is not None:

# Check payload length
if len(payload) != struct.calcsize(format):
raise InterfaceError('Reply: Invalid payload?')

# Unpack struct and return
return struct.unpack(format, payload)

else:

return struct.unpack(format, payload)
# Return packed bytes
return payload

def __check_reply_empty(self):
"""
Expand Down
4 changes: 4 additions & 0 deletions scripts/superkey/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
# Requests
'REQUEST_AUTOKEY',
'REQUEST_AUTOKEY_EX',
'REQUEST_AUTOKEY_QUICK_MSG',
'REQUEST_GET_BUZZER_ENABLED',
'REQUEST_GET_BUZZER_FREQUENCY',
'REQUEST_GET_INVERT_PADDLES',
Expand All @@ -114,9 +115,11 @@
'REQUEST_GET_IO_TYPE',
'REQUEST_GET_LED_ENABLED',
'REQUEST_GET_PADDLE_MODE',
'REQUEST_GET_QUICK_MSG',
'REQUEST_GET_TRAINER_MODE',
'REQUEST_GET_WPM',
'REQUEST_GET_WPM_SCALE',
'REQUEST_INVALIDATE_QUICK_MSG',
'REQUEST_PANIC',
'REQUEST_PING',
'REQUEST_RESTORE_DEFAULT_CONFIG',
Expand All @@ -127,6 +130,7 @@
'REQUEST_SET_IO_TYPE',
'REQUEST_SET_LED_ENABLED',
'REQUEST_SET_PADDLE_MODE',
'REQUEST_SET_QUICK_MSG',
'REQUEST_SET_TRAINER_MODE',
'REQUEST_SET_WPM',
'REQUEST_SET_WPM_SCALE',
Expand Down
2 changes: 2 additions & 0 deletions src/main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ set(EXECUTABLE_SOURCE application/buzzer.c
application/keyer.h
application/led.c
application/led.h
application/quick_msg.c
application/quick_msg.h
application/storage.c
application/storage.h
application/strings.c
Expand Down
Loading