Skip to content

Commit 7ec08f2

Browse files
authored
Add support for Morse code prosigns. (#36)
* Add support for Morse code prosigns. * Update scripts readme.
1 parent 74b1aa6 commit 7ec08f2

8 files changed

Lines changed: 632 additions & 428 deletions

File tree

scripts/autokey.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
_ASCII_BACKSPACE = 0x08
2121
_ASCII_NEWLINE = 0x0D
2222
_ASCII_ESCAPE = 0x1B
23+
_ASCII_BACKSLASH = 0x5C
2324

2425
# ----------------------------------------------------- PROCEDURES -----------------------------------------------------
2526

@@ -73,6 +74,11 @@ def _immediate_mode(port: str = SUPERKEY_DEFAULT_PORT,
7374
# Open interface
7475
with Interface(port=port, baudrate=baudrate, timeout=timeout) as intf:
7576

77+
# Tracking variables
78+
last_char_was_backslash = False
79+
prosign_count = 0
80+
prosign_string = ''
81+
7682
# Loop until commanded to quit
7783
while True:
7884

@@ -88,8 +94,30 @@ def _immediate_mode(port: str = SUPERKEY_DEFAULT_PORT,
8894
# Panic if the user pressed backspace key
8995
if code == _ASCII_BACKSPACE:
9096
intf.panic()
97+
prosign_count = 0
98+
prosign_string = ''
99+
last_char_was_backslash = False
100+
continue
101+
102+
# Start a prosign if the user pressed backslash key
103+
if code == _ASCII_BACKSLASH:
104+
print(chr(_ASCII_BACKSLASH), end='', flush=True)
105+
prosign_count = 2
106+
prosign_string = ''
107+
last_char_was_backslash = True
91108
continue
92109

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
120+
93121
# Ignore unknown characters, but allow newlines to print
94122
should_autokey = _should_autokey(code)
95123
should_print = should_autokey or code == _ASCII_NEWLINE
@@ -106,7 +134,22 @@ def _immediate_mode(port: str = SUPERKEY_DEFAULT_PORT,
106134

107135
# Send character to keyer
108136
if should_autokey:
109-
intf.autokey(char)
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
149+
else:
150+
intf.autokey(char)
151+
152+
# Send character to console
110153
if echo and should_print:
111154
print(char, end='', flush=True)
112155

scripts/readme.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ The following command line arguments are supported:
3030
- `--timeout` - Serial port timeout, in seconds.
3131
- `--silent` - If specified, input will not be echoed back to the console.
3232

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+
3342
## Interactive Python Environment
3443

3544
The `interactive.py` script can be used to quickly enter a REPL-like environment for interfacing with your SuperKey. It

scripts/superkey/interface.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import serial
1313
import struct
14-
from typing import Optional, Tuple
14+
from typing import Iterable, Optional, Tuple
1515

1616
from .constants import *
1717
from .types import *
@@ -110,11 +110,22 @@ def __exit__(self, exc_type, exc_value, traceback):
110110
if self.serial is not None and self.serial.is_open:
111111
self.serial.close()
112112

113-
def autokey(self, string: str):
113+
def autokey(self, string: str, flags: Iterable[AutokeyFlag] = []):
114114
"""
115-
Sends the `REQUEST_AUTOKEY` command. Queues the specified string to be automatically keyed.
115+
Sends the `REQUEST_AUTOKEY_EX` command. Queues the specified string to be automatically keyed.
116116
"""
117-
self.__send_packet(MessageID.REQUEST_AUTOKEY, bytes(string, encoding='ascii') + b'\x00')
117+
# Get flag byte, setting each bit individually
118+
flag_byte = 0
119+
for flag in flags:
120+
flag_byte = flag_byte | (1 << int(flag))
121+
122+
# Assemble payload
123+
payload = (struct.pack('<B', flag_byte) + # First byte is flags
124+
bytes(string, encoding='ascii') + # Then the string
125+
b'\x00') # Null terminator needs to be added manually
126+
127+
# Send packet and check reply
128+
self.__send_packet(MessageID.REQUEST_AUTOKEY_EX, payload)
118129
self.__check_reply_empty()
119130

120131
def get_buzzer_enabled(self) -> bool:

scripts/superkey/types.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# ------------------------------------------------------ EXPORTS -------------------------------------------------------
1515

1616
__all__ = [
17+
'AutokeyFlag',
1718
'CodeElement',
1819
'IOPin',
1920
'IOPolarity',
@@ -25,6 +26,15 @@
2526

2627
# ------------------------------------------------------- TYPES --------------------------------------------------------
2728

29+
# Autokey option flags (corresponds to keyer_autokey_flag_t)
30+
AutokeyFlag = IntEnum(
31+
'AutokeyFlag',
32+
[
33+
'NO_LETTER_SPACE',
34+
],
35+
start = 0
36+
)
37+
2838
# Morse code elements (corresponds to wpm_element_t)
2939
CodeElement = IntEnum(
3040
'CodeElement',
@@ -94,6 +104,7 @@
94104
[
95105
# Requests
96106
'REQUEST_AUTOKEY',
107+
'REQUEST_AUTOKEY_EX',
97108
'REQUEST_GET_BUZZER_ENABLED',
98109
'REQUEST_GET_BUZZER_FREQUENCY',
99110
'REQUEST_GET_INVERT_PADDLES',

src/main/application/intf_port.c

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ static void process_message( intf_header_t const * header, void const * payload
109109
*/
110110
static void process_message_request_autokey( intf_header_t const * header, void const * payload );
111111

112+
/**
113+
* @fn process_message_request_autokey_ex( intf_header_t const *, void const * )
114+
* @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_AUTOKEY_EX` message ID.
115+
*/
116+
static void process_message_request_autokey_ex( intf_header_t const * header, void const * payload );
117+
112118
/**
113119
* @fn process_message_request_get_buzzer_enabled( intf_header_t const *, void const * )
114120
* @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_GET_BUZZER_ENABLED` message ID.
@@ -386,6 +392,10 @@ static void process_message( intf_header_t const * header, void const * payload
386392
process_message_request_autokey( header, payload );
387393
break;
388394

395+
case INTF_MESSAGE_REQUEST_AUTOKEY_EX:
396+
process_message_request_autokey_ex( header, payload );
397+
break;
398+
389399
case INTF_MESSAGE_REQUEST_GET_BUZZER_ENABLED:
390400
process_message_request_get_buzzer_enabled( header, payload );
391401
break;
@@ -501,9 +511,10 @@ static void process_message( intf_header_t const * header, void const * payload
501511

502512
static void process_message_request_autokey( intf_header_t const * header, void const * payload )
503513
{
504-
// Ensure string is null terminated
505514
char const * str = ( char const * )payload;
506-
if( str[ header->size - 1 ] != NULL_CHAR )
515+
516+
// Ensure we have enough data and string is null terminated
517+
if( header->size == 0 || str[ header->size - 1 ] != NULL_CHAR )
507518
{
508519
send_empty_packet( INTF_MESSAGE_REPLY_INVALID_PAYLOAD );
509520
return;
@@ -518,6 +529,34 @@ static void process_message_request_autokey( intf_header_t const * header, void
518529
} /* process_message_request_autokey() */
519530

520531

532+
static void process_message_request_autokey_ex( intf_header_t const * header, void const * payload )
533+
{
534+
typedef struct
535+
{
536+
keyer_autokey_flag_field_t flags;
537+
char c;
538+
} format_t;
539+
540+
format_t const * pkt = ( format_t const * )payload;
541+
char const * str = ( char const * )( & pkt->c );
542+
543+
// Ensure we have enough data and string is null terminated
544+
if( header->size < sizeof( format_t ) ||
545+
str[ header->size - sizeof( keyer_autokey_flag_field_t ) - 1 ] != NULL_CHAR )
546+
{
547+
send_empty_packet( INTF_MESSAGE_REPLY_INVALID_PAYLOAD );
548+
return;
549+
}
550+
551+
// Key string
552+
keyer_autokey_str_ex( str, pkt->flags );
553+
554+
// Send reply
555+
send_empty_packet( INTF_MESSAGE_REPLY_SUCCESS );
556+
557+
} /* process_message_request_autokey_ex() */
558+
559+
521560
static void process_message_request_get_buzzer_enabled( intf_header_t const * header, void const * payload )
522561
{
523562
( void )payload;

src/main/application/intf_types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ typedef uint16_t intf_message_t;
4242
enum
4343
{
4444
INTF_MESSAGE_REQUEST_AUTOKEY, /**< Queues a string to be autokeyed. */
45+
INTF_MESSAGE_REQUEST_AUTOKEY_EX, /**< Queues a string to be autokeyed with flags. */
4546
INTF_MESSAGE_REQUEST_GET_BUZZER_ENABLED,/**< Get buzzer enablement. */
4647
INTF_MESSAGE_REQUEST_GET_BUZZER_FREQUENCY,/**< Get buzzer frequency. */
4748
INTF_MESSAGE_REQUEST_GET_INVERT_PADDLES,/**< Gets paddle inversion setting. */

0 commit comments

Comments
 (0)