0
# Machine Interface System
1
2
Plover's machine interface system provides abstracted communication with stenotype hardware through a unified API. It supports various connection methods including serial, USB, and keyboard input, enabling integration with both commercial stenotype machines and alternative input devices.
3
4
## Capabilities
5
6
### Base Machine Interface
7
8
Abstract base class defining the standard interface for all stenotype machine implementations.
9
10
```python { .api }
11
class StenotypeBase:
12
"""Base class for stenotype machine interfaces."""
13
14
KEYS_LAYOUT: str = ''
15
"""String describing the physical key layout of the machine."""
16
17
ACTIONS: tuple = ()
18
"""Tuple of available machine-specific actions."""
19
20
KEYMAP_MACHINE_TYPE: str = None
21
"""Machine type identifier for keymap compatibility."""
22
23
def __init__(self):
24
"""
25
Initialize machine interface.
26
27
Sets up machine instance with default configuration,
28
ready for keymap assignment and capture initialization.
29
"""
30
31
def set_keymap(self, keymap: dict) -> None:
32
"""
33
Set key mapping configuration.
34
35
Args:
36
keymap: Dictionary mapping physical keys to steno keys
37
38
Configures how physical machine keys map to stenographic keys.
39
"""
40
41
def start_capture(self) -> None:
42
"""
43
Start capturing strokes from machine.
44
45
Begins communication with machine hardware and starts
46
processing input for stroke detection.
47
48
Raises:
49
ConnectionError: If unable to connect to machine
50
"""
51
52
def stop_capture(self) -> None:
53
"""
54
Stop capturing strokes from machine.
55
56
Ceases communication with machine hardware and stops
57
all input processing.
58
"""
59
60
def add_stroke_callback(self, callback) -> None:
61
"""
62
Add callback for stroke events.
63
64
Args:
65
callback: Function to call when stroke detected
66
67
Callback signature: callback(stroke_keys: list[str])
68
"""
69
70
def remove_stroke_callback(self, callback) -> None:
71
"""
72
Remove previously added stroke callback.
73
74
Args:
75
callback: Function to remove from callbacks
76
"""
77
78
def add_state_callback(self, callback) -> None:
79
"""
80
Add callback for machine state changes.
81
82
Args:
83
callback: Function to call when state changes
84
85
Callback signature: callback(state: str)
86
States: 'stopped', 'initializing', 'connected', 'disconnected'
87
"""
88
89
def remove_state_callback(self, callback) -> None:
90
"""
91
Remove previously added state callback.
92
93
Args:
94
callback: Function to remove from callbacks
95
"""
96
97
def set_suppression(self, enabled: bool) -> None:
98
"""
99
Set output suppression state.
100
101
Args:
102
enabled: True to suppress machine output, False to allow
103
104
Controls whether machine generates its own output
105
in addition to Plover's processing.
106
"""
107
108
def suppress_last_stroke(self, send_backspaces: bool) -> None:
109
"""
110
Suppress the last stroke's output.
111
112
Args:
113
send_backspaces: Whether to send backspaces to undo output
114
115
Undoes the effect of the most recent stroke.
116
"""
117
118
@classmethod
119
def get_actions(cls) -> tuple:
120
"""
121
Get available machine actions.
122
123
Returns:
124
Tuple of action strings available for this machine type
125
126
Actions vary by machine and may include special functions.
127
"""
128
129
@classmethod
130
def get_keys(cls) -> tuple:
131
"""
132
Get available machine keys.
133
134
Returns:
135
Tuple of key strings available on this machine
136
137
Keys correspond to physical keys that can be mapped.
138
"""
139
140
@classmethod
141
def get_option_info(cls) -> dict:
142
"""
143
Get machine-specific option information.
144
145
Returns:
146
Dictionary describing available configuration options
147
148
Provides metadata for GUI configuration interfaces.
149
"""
150
```
151
152
### Threaded Machine Base
153
154
Base class adding threading support for machines requiring background processing.
155
156
```python { .api }
157
class ThreadedStenotypeBase(StenotypeBase, threading.Thread):
158
"""Base class with threading support for background processing."""
159
160
def run(self) -> None:
161
"""
162
Main thread execution method.
163
164
Override this method to implement machine-specific
165
background processing loop.
166
"""
167
```
168
169
### Serial Machine Base
170
171
Specialized base class for machines using serial port communication.
172
173
```python { .api }
174
class SerialStenotypeBase(ThreadedStenotypeBase):
175
"""Base class for serial port stenotype machines."""
176
177
SERIAL_PARAMS: dict = {
178
'baudrate': 9600,
179
'bytesize': 8,
180
'parity': 'N',
181
'stopbits': 1,
182
'timeout': 2.0,
183
'xonxoff': False,
184
'rtscts': False
185
}
186
"""Default serial port parameters."""
187
```
188
189
## Machine State Constants
190
191
Standard machine state identifiers used throughout the system.
192
193
```python { .api }
194
STATE_STOPPED: str = 'stopped'
195
"""Machine is not running or connected."""
196
197
STATE_INITIALIZING: str = 'initializing'
198
"""Machine is starting up or connecting."""
199
200
STATE_RUNNING: str = 'connected'
201
"""Machine is connected and receiving strokes."""
202
203
STATE_ERROR: str = 'disconnected'
204
"""Machine encountered error or lost connection."""
205
```
206
207
## Available Machine Types
208
209
### Keyboard Machine
210
Uses computer keyboard as stenotype input device.
211
212
**Plugin Name**: `Keyboard`
213
**Connection**: Direct keyboard input capture
214
**Configuration**: Keyboard layout selection, key mapping
215
**Use Case**: Practice, accessibility, no dedicated hardware
216
217
### TX Bolt Protocol Machines
218
Supports machines using the TX Bolt communication protocol.
219
220
**Plugin Name**: `TX Bolt`
221
**Connection**: Serial port communication
222
**Protocol**: TX Bolt binary format
223
**Machines**: Many commercial stenotype machines
224
225
### Gemini PR Protocol Machines
226
Supports machines using the Gemini PR communication protocol.
227
228
**Plugin Name**: `Gemini PR`
229
**Connection**: Serial or USB communication
230
**Protocol**: Gemini PR packet format
231
**Machines**: Neutrino Group machines, some others
232
233
### ProCAT Machines
234
Support for ProCAT stenotype machines.
235
236
**Plugin Name**: `ProCAT`
237
**Connection**: Serial port communication
238
**Protocol**: ProCAT-specific format
239
**Machines**: ProCAT stenotype models
240
241
### Stentura Machines
242
Support for Stentura stenotype machines.
243
244
**Plugin Name**: `Stentura`
245
**Connection**: Serial port communication
246
**Protocol**: Stentura-specific format
247
**Machines**: Stentura stenotype models
248
249
### Passport Machines
250
Support for Passport stenotype machines.
251
252
**Plugin Name**: `Passport`
253
**Connection**: Serial port communication
254
**Protocol**: Passport-specific format
255
**Machines**: Passport stenotype models
256
257
## Usage Examples
258
259
```python
260
from plover.registry import registry
261
from plover.machine.base import STATE_RUNNING, STATE_ERROR
262
263
# Get available machines
264
machines = registry.list_plugins('machine')
265
for machine in machines:
266
print(f"Available machine: {machine.name}")
267
268
# Get specific machine
269
keyboard_plugin = registry.get_plugin('machine', 'Keyboard')
270
KeyboardMachine = keyboard_plugin.obj
271
272
# Create machine instance
273
machine = KeyboardMachine()
274
275
# Set up callbacks
276
def on_stroke(stroke_keys):
277
print(f"Stroke received: {stroke_keys}")
278
279
def on_state_change(state):
280
if state == STATE_RUNNING:
281
print("Machine connected and ready")
282
elif state == STATE_ERROR:
283
print("Machine connection error")
284
285
machine.add_stroke_callback(on_stroke)
286
machine.add_state_callback(on_state_change)
287
288
# Configure keymap
289
keymap = {
290
'q': 'S-',
291
'w': 'T-',
292
'e': 'K-',
293
'r': 'P-',
294
't': 'W-',
295
'y': 'H-',
296
'u': 'R-',
297
'i': 'A-',
298
'o': 'O-',
299
'p': '*',
300
# ... more key mappings
301
}
302
machine.set_keymap(keymap)
303
304
# Start machine
305
try:
306
machine.start_capture()
307
print("Machine started successfully")
308
except ConnectionError as e:
309
print(f"Failed to start machine: {e}")
310
311
# Later, stop machine
312
machine.stop_capture()
313
314
# Get machine information
315
keys = machine.get_keys()
316
actions = machine.get_actions()
317
options = machine.get_option_info()
318
319
print(f"Machine keys: {keys}")
320
print(f"Machine actions: {actions}")
321
print(f"Configuration options: {options}")
322
```
323
324
## Machine Configuration Options
325
326
### Keyboard Machine Options
327
- `layout`: Keyboard layout ('QWERTY', 'Dvorak', 'Colemak')
328
- `arpeggiate`: Enable arpeggiate mode for chording
329
330
### Serial Machine Options
331
- `port`: Serial port device path or name
332
- `baudrate`: Communication speed (9600, 19200, 38400, etc.)
333
- `bytesize`: Data bits (7, 8)
334
- `parity`: Parity setting ('N', 'E', 'O')
335
- `stopbits`: Stop bits (1, 2)
336
- `timeout`: Read timeout in seconds
337
- `xonxoff`: Software flow control
338
- `rtscts`: Hardware flow control
339
340
### USB Machine Options
341
- `vendor_id`: USB vendor ID for device identification
342
- `product_id`: USB product ID for device identification
343
- `interface`: USB interface number
344
345
## Developing Custom Machines
346
347
### Basic Machine Implementation
348
349
```python
350
from plover.machine.base import StenotypeBase
351
352
class CustomMachine(StenotypeBase):
353
KEYS_LAYOUT = 'STENO_KEYS'
354
ACTIONS = ('action1', 'action2')
355
356
def __init__(self):
357
super().__init__()
358
self._stroke_callbacks = []
359
self._state_callbacks = []
360
361
def start_capture(self):
362
# Initialize hardware connection
363
# Start background processing
364
self._notify_state('connected')
365
366
def stop_capture(self):
367
# Stop hardware communication
368
self._notify_state('stopped')
369
370
def _notify_stroke(self, keys):
371
for callback in self._stroke_callbacks:
372
callback(keys)
373
374
def _notify_state(self, state):
375
for callback in self._state_callbacks:
376
callback(state)
377
378
@classmethod
379
def get_option_info(cls):
380
return {
381
'port': {
382
'type': 'choice',
383
'choices': ['COM1', 'COM2', '/dev/ttyUSB0'],
384
'default': 'COM1'
385
}
386
}
387
```
388
389
### Threaded Machine Implementation
390
391
```python
392
from plover.machine.base import ThreadedStenotypeBase
393
import threading
394
import time
395
396
class ThreadedCustomMachine(ThreadedStenotypeBase):
397
def __init__(self):
398
super().__init__()
399
self._running = False
400
401
def start_capture(self):
402
self._running = True
403
self.start() # Start thread
404
405
def stop_capture(self):
406
self._running = False
407
self.join() # Wait for thread
408
409
def run(self):
410
"""Background thread for stroke processing."""
411
while self._running:
412
# Read from hardware
413
# Process strokes
414
# Notify callbacks
415
time.sleep(0.01)
416
```
417
418
### Serial Machine Implementation
419
420
```python
421
from plover.machine.base import SerialStenotypeBase
422
import serial
423
424
class SerialCustomMachine(SerialStenotypeBase):
425
SERIAL_PARAMS = {
426
'baudrate': 19200,
427
'timeout': 1.0
428
}
429
430
def __init__(self):
431
super().__init__()
432
self._serial = None
433
434
def start_capture(self):
435
port = self._settings.get('port', 'COM1')
436
self._serial = serial.Serial(port, **self.SERIAL_PARAMS)
437
super().start_capture()
438
439
def stop_capture(self):
440
super().stop_capture()
441
if self._serial:
442
self._serial.close()
443
444
def run(self):
445
while self._running and self._serial:
446
data = self._serial.read(10)
447
if data:
448
stroke = self._parse_stroke(data)
449
if stroke:
450
self._notify_stroke(stroke)
451
```
452
453
## Types
454
455
```python { .api }
456
from typing import Dict, List, Tuple, Optional, Callable, Any, Union
457
from threading import Thread
458
459
StrokeKeys = List[str]
460
StrokeCallback = Callable[[StrokeKeys], None]
461
StateCallback = Callable[[str], None]
462
463
MachineState = str
464
MachineKeymap = Dict[str, str]
465
MachineOptions = Dict[str, Any]
466
MachineActions = Tuple[str, ...]
467
468
OptionInfo = Dict[str, Dict[str, Any]]
469
SerialParams = Dict[str, Union[int, float, str, bool]]
470
471
CallbackList = List[Callable]
472
```