Skip to content

Commit 2f9a77f

Browse files
authored
Improvements to autokey script. (#37)
1 parent 7ec08f2 commit 2f9a77f

2 files changed

Lines changed: 150 additions & 100 deletions

File tree

scripts/autokey.py

Lines changed: 148 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def _parse_args():
3232
parser.add_argument('--port', type=str, default=SUPERKEY_DEFAULT_PORT, help='Serial port to connect to.')
3333
parser.add_argument('--baudrate', type=int, default=SUPERKEY_DEFAULT_BAUDRATE, help='Serial port baud rate.')
3434
parser.add_argument('--timeout', type=float, default=SUPERKEY_DEFAULT_TIMEOUT, help='Serial port timeout (s).')
35+
parser.add_argument('--immediate', action=argparse.BooleanOptionalAction, help='Use immediate mode?')
3536
parser.add_argument('--silent', action=argparse.BooleanOptionalAction, help='Disable output to console?')
3637
return parser.parse_args()
3738

@@ -57,6 +58,124 @@ def _should_autokey(code: int) -> bool:
5758
(code >= 0x61 and code <= 0x7A)) # lower case letter
5859

5960

61+
def _buffered_mode(port: str = SUPERKEY_DEFAULT_PORT,
62+
baudrate: int = SUPERKEY_DEFAULT_BAUDRATE,
63+
timeout: float = SUPERKEY_DEFAULT_TIMEOUT):
64+
"""
65+
Runs the autokeyer in buffered mode.
66+
"""
67+
with Interface(port=port, baudrate=baudrate, timeout=timeout) as intf:
68+
while True:
69+
70+
# Get next input from user
71+
line = input('> ')
72+
73+
# Check for special commands
74+
if line == ':q' or line == ':quit':
75+
# Exit program
76+
break
77+
78+
elif line == ':!' or line == ':panic':
79+
# Panic
80+
intf.panic()
81+
continue
82+
83+
elif line == ':wpm':
84+
# Print WPM status
85+
print(f'WPM: {intf.get_wpm():.1f}')
86+
continue
87+
88+
elif line[:5] == ':wpm ':
89+
# Set WPM
90+
try:
91+
intf.set_wpm(float(line[5:]))
92+
except ValueError:
93+
print('Invalid WPM?')
94+
continue
95+
96+
elif line == ':buzzer':
97+
# Print buzzer status
98+
print(f'Buzzer: {'On' if intf.get_buzzer_enabled() else 'Off'} ({intf.get_buzzer_frequency()} Hz)')
99+
continue
100+
101+
elif line == ':buzzer off':
102+
# Turn buzzer off
103+
intf.set_buzzer_enabled(False)
104+
continue
105+
106+
elif line == ':buzzer on':
107+
# Turn buzzer on
108+
intf.set_buzzer_enabled(True)
109+
continue
110+
111+
elif line[:12] == ':buzzer frequency':
112+
# Set buzzer frequency
113+
try:
114+
intf.set_buzzer_frequency(int(line[12:]))
115+
except ValueError:
116+
print('Invalid frequency?')
117+
continue
118+
119+
elif line == ':paddle':
120+
# Print paddle mode
121+
mode = intf.get_paddle_mode()
122+
if mode == PaddleMode.IAMBIC:
123+
print('Paddle mode: IAMBIC')
124+
elif mode == PaddleMode.ULTIMATIC:
125+
print('Paddle mode: ULTIMATIC')
126+
elif mode == PaddleMode.ULTIMATIC_ALTERNATE:
127+
print('Paddle mode: ULTIMATIC_ALTERNATE')
128+
else:
129+
print('Paddle mode: unknown?')
130+
continue
131+
132+
elif line == ':paddle iambic':
133+
# Set paddles to iambic mode
134+
intf.set_paddle_mode(PaddleMode.IAMBIC)
135+
continue
136+
137+
elif line == ':paddle ultimatic':
138+
# Set paddles to ultimatic mode
139+
intf.set_paddle_mode(PaddleMode.ULTIMATIC)
140+
continue
141+
142+
elif line == ':paddle ultimatic_alternate':
143+
# Set paddles to ultimatic alternate mode
144+
intf.set_paddle_mode(PaddleMode.ULTIMATIC_ALTERNATE)
145+
continue
146+
147+
elif line == ':trainer':
148+
# Print trainer mode status
149+
print(f'Trainer mode: {'On' if intf.get_trainer_mode() else 'Off'}')
150+
continue
151+
152+
elif line == ':trainer on':
153+
# Enable trainer mode
154+
intf.set_trainer_mode(True)
155+
continue
156+
157+
elif line == ':trainer off':
158+
# Disable trainer mode
159+
intf.set_trainer_mode(False)
160+
continue
161+
162+
elif line[0] == ':':
163+
# Unknown command?
164+
print('Unknown command?')
165+
continue
166+
167+
# Split line into tokens and send in either normal or prosign mode
168+
tokens = line.split('\\')
169+
prosign = False
170+
for token in tokens:
171+
if prosign and len(token) > 1:
172+
intf.autokey(token[:-1], flags=[AutokeyFlag.NO_LETTER_SPACE])
173+
intf.autokey(token[-1])
174+
elif len(token) != 0:
175+
intf.autokey(token)
176+
prosign = not prosign
177+
178+
60179
def _immediate_mode(port: str = SUPERKEY_DEFAULT_PORT,
61180
baudrate: int = SUPERKEY_DEFAULT_BAUDRATE,
62181
timeout: float = SUPERKEY_DEFAULT_TIMEOUT,
@@ -75,8 +194,7 @@ def _immediate_mode(port: str = SUPERKEY_DEFAULT_PORT,
75194
with Interface(port=port, baudrate=baudrate, timeout=timeout) as intf:
76195

77196
# Tracking variables
78-
last_char_was_backslash = False
79-
prosign_count = 0
197+
prosign_active = False
80198
prosign_string = ''
81199

82200
# Loop until commanded to quit
@@ -94,60 +212,41 @@ def _immediate_mode(port: str = SUPERKEY_DEFAULT_PORT,
94212
# Panic if the user pressed backspace key
95213
if code == _ASCII_BACKSPACE:
96214
intf.panic()
97-
prosign_count = 0
98-
prosign_string = ''
99-
last_char_was_backslash = False
100215
continue
101216

102217
# Start a prosign if the user pressed backslash key
103218
if code == _ASCII_BACKSLASH:
104219
print(chr(_ASCII_BACKSLASH), end='', flush=True)
105-
prosign_count = 2
106-
prosign_string = ''
107-
last_char_was_backslash = True
108-
continue
109-
110-
# If the last character was a backslash, and the next character is a digit from 2 to 9, then change the
111-
# number of characters we're expecting for the prosign
112-
if last_char_was_backslash and code >= 0x32 and code <= 0x39:
113-
print(chr(code), end='', flush=True)
114-
prosign_count = code - 0x30
115-
last_char_was_backslash = False
116-
continue
117-
118-
# Unconditionally clear flag
119-
last_char_was_backslash = False
220+
prosign_active = not prosign_active
221+
if prosign_active:
222+
prosign_string = ''
223+
continue
120224

121225
# Ignore unknown characters, but allow newlines to print
122226
should_autokey = _should_autokey(code)
123227
should_print = should_autokey or code == _ASCII_NEWLINE
124228

125-
# Bail if there's nothing to do
126-
if not should_autokey and not should_print:
127-
continue
128-
129229
# Convert character code to string, fixing newlines
130230
if code != _ASCII_NEWLINE:
131231
char = chr(code)
132232
else:
133233
char = '\r\n'
134234

135-
# Send character to keyer
136-
if should_autokey:
137-
138-
# Handle prosigns
139-
if prosign_count != 0:
140-
if len(prosign_string) < prosign_count:
141-
prosign_string += char
142-
if len(prosign_string) == prosign_count:
143-
intf.autokey(prosign_string[:-1], [AutokeyFlag.NO_LETTER_SPACE])
144-
intf.autokey(prosign_string[-1])
145-
prosign_count = 0
146-
prosign_string = ''
147-
148-
# Otherwise, key normally
235+
# Handle prosigns
236+
if prosign_active and should_autokey:
237+
prosign_string += char
238+
elif not prosign_active and len(prosign_string) != 0:
239+
assert code == _ASCII_BACKSLASH, 'Logic error!'
240+
if len(prosign_string) > 1:
241+
intf.autokey(prosign_string[:-1], [AutokeyFlag.NO_LETTER_SPACE])
242+
intf.autokey(prosign_string[-1])
149243
else:
150-
intf.autokey(char)
244+
intf.autokey(prosign_string)
245+
prosign_string = ''
246+
247+
# Send character to keyer
248+
elif should_autokey:
249+
intf.autokey(char)
151250

152251
# Send character to console
153252
if echo and should_print:
@@ -159,8 +258,13 @@ def _immediate_mode(port: str = SUPERKEY_DEFAULT_PORT,
159258

160259
# Read arguments
161260
args = _parse_args()
162-
_immediate_mode(port = args.port,
163-
baudrate = args.baudrate,
164-
timeout = args.timeout,
165-
echo = not args.silent,
166-
print_help = not args.silent)
261+
if args.immediate:
262+
_immediate_mode(port = args.port,
263+
baudrate = args.baudrate,
264+
timeout = args.timeout,
265+
echo = not args.silent,
266+
print_help = not args.silent)
267+
else:
268+
_buffered_mode(port=args.port,
269+
baudrate=args.baudrate,
270+
timeout=args.timeout)

scripts/readme.md

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,10 @@
11
# SuperKey Python Interfae
22

3-
This README file will document SuperKey's Python interface (the `superkey` library). The intent of this library is to
3+
This directory contains the Python API wrapper for SuperKey (the `superkey` library). The intent of this library is to
44
provide a "reference implementation" for working with SuperKey's serial protocol, as well as to provide a way for users
55
to script SuperKey's functionality.
66

7-
The root `scripts` directory also includes several handy stand-alone utility scripts:
8-
9-
- `autokey.py` allows controlling the keyer directly with your keyboard.
10-
- `example.py` shows a demonstration of using the `superkey` library, as well as demonstrating some of SuperKey's
11-
hardware features.
12-
- `interactive.py` creates an "interactive" environment for interfacing with SuperKey with a console-like REPL
13-
experience.
14-
157
`superkey` has no dependencies other than the built-in Python standard library. A future improvement is to improve the
168
packaging for this library, to make it more consumable by other Python projects.
179

18-
## AutoKeyer
19-
20-
The `autokey.py` script accepts user input from the keyboard, and immediately forwards it on to the keyer to be keyed.
21-
Any key press which is not a valid Morse code character is ignored. The following special key presses are supported:
22-
23-
- **Backspace** - Immediately stops keying any string which was previously entered. This can be used to "cancel" input.
24-
- **Escape** - Exits the tool.
25-
26-
The following command line arguments are supported:
27-
28-
- `--port` - Serial port name.
29-
- `--baudrate` - Serial port baud rate. (This should always be 19200.)
30-
- `--timeout` - Serial port timeout, in seconds.
31-
- `--silent` - If specified, input will not be echoed back to the console.
32-
33-
### Notes
34-
35-
- Characters are queued for transmission as they are typed.
36-
- Newlines may be typed to help separate messages, but are ignored by the keyer.
37-
- Prosigns may be entered by preceding the characters with a backslash
38-
- (e.g., entering `\ar` will result in the prosign `· — · — ·` being keyed).
39-
- Prosigns with between 2 and 9 characters are supported by immediately following the backslash with a digit.
40-
- (e.g., entering `\3sos` will result in the prosign `· · · — — — · · ·` being keyed).
41-
42-
## Interactive Python Environment
43-
44-
The `interactive.py` script can be used to quickly enter a REPL-like environment for interfacing with your SuperKey. It
45-
supports the following command line arguments to set up the serial connection:
46-
47-
- `--port` - Serial port name.
48-
- `--baudrate` - Serial port baud rate. (This should always be 19200.)
49-
- `--timeout` - Serial port timeout, in seconds.
50-
51-
In most cases, the only required argument will be `--port`.
52-
53-
Once the interactive session has started, you can interact with your SuperKey by directly calling any public instance
54-
function declared on the `Interface` class. No instance is required.
55-
56-
```
57-
>>> set_buzzer_enabled(True)
58-
>>> get_buzzer_frequency()
59-
700
60-
>>> set_buzzer_frequency(800)
61-
>>> autokey('cq cq de n0vig n0vig k')
62-
```
63-
64-
The `dir()` function may be used to get a list of available functions.
10+
See the [Python Guide](https://github.com/xchrishawk/superkey/wiki/Python-Guide) on the wiki for more details.

0 commit comments

Comments
 (0)