mirror of
https://github.com/lowobservable/oec.git
synced 2026-01-11 23:53:04 +00:00
IBM 3179 EAB support
This commit is contained in:
parent
b5bea24c4d
commit
5c773e8358
@ -33,6 +33,7 @@ emulation.
|
||||
|
||||
Only CUT (Control Unit Terminal) type terminals are supported. I have tested oec with the following terminals:
|
||||
|
||||
* IBM 3179
|
||||
* IBM 3278-2
|
||||
* IBM 3472
|
||||
* IBM 3483-V (InfoWindow II)
|
||||
|
||||
@ -2,13 +2,13 @@ import sys
|
||||
import os
|
||||
import signal
|
||||
import logging
|
||||
from coax import open_serial_interface, TerminalType
|
||||
from coax import open_serial_interface, TerminalType, Feature
|
||||
|
||||
from .args import parse_args
|
||||
from .interface import InterfaceWrapper
|
||||
from .controller import Controller
|
||||
from .device import get_ids, get_features, get_keyboard_description, UnsupportedDeviceError
|
||||
from .terminal import Terminal
|
||||
from .device import get_ids, get_features, UnsupportedDeviceError
|
||||
from .terminal import Terminal, get_model, get_keyboard_description
|
||||
from .tn3270 import TN3270Session
|
||||
|
||||
# VT100 emulation is not supported on Windows.
|
||||
@ -40,34 +40,35 @@ def _get_keymap(_args, keyboard_description):
|
||||
return KEYMAP_3278_TYPEWRITER
|
||||
|
||||
def _create_device(args, interface, device_address, _poll_response):
|
||||
# Read the terminal identifiers.
|
||||
(terminal_id, extended_id) = get_ids(interface, device_address)
|
||||
|
||||
logger.info(f'Terminal ID = {terminal_id}')
|
||||
logger.info(f'Terminal ID = {terminal_id}, Extended ID = {extended_id}')
|
||||
|
||||
if terminal_id.type != TerminalType.CUT:
|
||||
raise UnsupportedDeviceError('Only CUT type terminals are supported')
|
||||
|
||||
logger.info(f'Extended ID = {extended_id}')
|
||||
model = get_model(terminal_id, extended_id)
|
||||
|
||||
if extended_id is not None:
|
||||
logger.info(f'Model = IBM {extended_id[2:6]} or equivalent')
|
||||
if model is not None:
|
||||
logger.info(f'Model = IBM {model} or equivalent')
|
||||
|
||||
features = get_features(interface, device_address)
|
||||
|
||||
# The 3179 includes an EAB but does not respond to the READ_FEATURE_ID
|
||||
# command.
|
||||
if model == '3179':
|
||||
features[Feature.EAB] = 7
|
||||
|
||||
logger.info(f'Features = {features}')
|
||||
|
||||
keyboard_description = get_keyboard_description(terminal_id, extended_id)
|
||||
|
||||
logger.info(f'Keyboard = {keyboard_description}')
|
||||
|
||||
# Read the terminal features.
|
||||
features = get_features(interface, device_address)
|
||||
|
||||
logger.info(f'Features = {features}')
|
||||
|
||||
# Get the keymap.
|
||||
keymap = _get_keymap(args, keyboard_description)
|
||||
|
||||
logger.info(f'Keymap = {keymap.name}')
|
||||
|
||||
# Create the terminal.
|
||||
terminal = Terminal(interface, device_address, terminal_id, extended_id, features, keymap)
|
||||
|
||||
return terminal
|
||||
|
||||
@ -34,8 +34,8 @@ class Device:
|
||||
"""Execute one or more commands."""
|
||||
return self.interface.execute(address_commands(self.device_address, commands))
|
||||
|
||||
def execute_jumbo_write(self, data, create_first, create_subsequent, first_chunk_max_length_adjustment=-1):
|
||||
"""Execute a jumbo write command that can be split."""
|
||||
def prepare_jumbo_write(self, data, create_first, create_subsequent, first_chunk_max_length_adjustment=-1):
|
||||
"""Prepare a jumbo write command that can be split."""
|
||||
max_length = None
|
||||
|
||||
# The 3299 multiplexer appears to have some frame length limit, after which it will
|
||||
@ -55,7 +55,7 @@ class Device:
|
||||
if len(commands) > 1 and logger.isEnabledFor(logging.DEBUG):
|
||||
logger.debug(f'Jumbo write split into {len(commands)}')
|
||||
|
||||
return self.execute(commands)
|
||||
return commands
|
||||
|
||||
class UnsupportedDeviceError(Exception):
|
||||
"""Unsupported device."""
|
||||
@ -111,65 +111,6 @@ def get_features(interface, device_address):
|
||||
|
||||
return parse_features(ids, commands)
|
||||
|
||||
def get_keyboard_description(terminal_id, extended_id):
|
||||
is_3278 = extended_id is None or not int(extended_id[0:2], 16) & 0x80
|
||||
|
||||
if is_3278:
|
||||
description = '3278'
|
||||
|
||||
id_map = {
|
||||
0b0001: 'APL',
|
||||
0b0010: 'TEXT',
|
||||
0b0100: 'TYPEWRITER-PSHICO',
|
||||
0b0101: 'APL',
|
||||
0b0110: 'TEXT',
|
||||
0b0111: 'APL-PSHICO',
|
||||
0b1000: 'DATAENTRY-2',
|
||||
0b1001: 'DATAENTRY-1',
|
||||
0b1010: 'TYPEWRITER',
|
||||
0b1100: 'DATAENTRY-2',
|
||||
0b1101: 'DATAENTRY-1',
|
||||
0b1110: 'TYPEWRITER'
|
||||
}
|
||||
|
||||
if terminal_id.keyboard in id_map:
|
||||
description += '-' + id_map[terminal_id.keyboard]
|
||||
|
||||
return description
|
||||
|
||||
id_ = int(extended_id[0:2], 16) & 0x1f
|
||||
|
||||
is_user = int(extended_id[0:2], 16) & 0x20
|
||||
|
||||
if is_user:
|
||||
description = 'USER'
|
||||
|
||||
if id_ in [1, 2, 3, 4]:
|
||||
description += f'-{id_}'
|
||||
|
||||
return description
|
||||
|
||||
is_ibm = not int(extended_id[6:8], 16) & 0x80
|
||||
|
||||
description = 'IBM' if is_ibm else 'UNKNOWN'
|
||||
|
||||
is_enhanced = int(extended_id[6:8], 16) & 0x01
|
||||
|
||||
if is_enhanced:
|
||||
if id_ == 1:
|
||||
return description + '-ENHANCED'
|
||||
|
||||
return None
|
||||
|
||||
if id_ == 1:
|
||||
return description + '-TYPEWRITER'
|
||||
elif id_ == 2:
|
||||
return description + '-DATAENTRY'
|
||||
elif id_ == 3:
|
||||
return description + '-APL'
|
||||
|
||||
return None
|
||||
|
||||
def _jumbo_write_split_data(data, max_length, first_chunk_max_length_adjustment=-1):
|
||||
if max_length is None:
|
||||
return [data]
|
||||
|
||||
@ -175,12 +175,18 @@ class Display:
|
||||
return True
|
||||
|
||||
def _write_data(self, data):
|
||||
self.terminal.execute_jumbo_write(data, WriteData, Data, -1)
|
||||
self.terminal.execute(self.terminal.prepare_jumbo_write(data, WriteData, Data, -1))
|
||||
|
||||
def _eab_write_alternate(self, data):
|
||||
# The EAB mask on a 3179 terminal appears to get reset regularly resulting
|
||||
# in the EAB buffer not being updated correctly. This does not affect
|
||||
# later terminals, loading the mask here for all terminals is simpler.
|
||||
#
|
||||
# The EAB_WRITE_ALTERNATE command data must be split so that the two bytes
|
||||
# do not get separated, otherwise the write will be incorrect.
|
||||
self.terminal.execute_jumbo_write(data, lambda chunk: EABWriteAlternate(self.eab_address, chunk), Data, -2)
|
||||
commands = [EABLoadMask(self.eab_address, 0xff), *self.terminal.prepare_jumbo_write(data, lambda chunk: EABWriteAlternate(self.eab_address, chunk), Data, -2)]
|
||||
|
||||
self.terminal.execute(commands)
|
||||
|
||||
def _split_address(address):
|
||||
if address is None:
|
||||
|
||||
@ -81,3 +81,75 @@ class Terminal(Device):
|
||||
def load_control_register(self):
|
||||
"""Execute a LOAD_CONTROL_REGISTER command."""
|
||||
self.execute(LoadControlRegister(self.control))
|
||||
|
||||
def get_model(terminal_id, extended_id):
|
||||
if extended_id is None:
|
||||
return None
|
||||
|
||||
model = extended_id[2:6]
|
||||
|
||||
# The 3179 does return an extended ID, but it does not include the model
|
||||
# like later terminals.
|
||||
if model == '0000':
|
||||
model = '3179'
|
||||
|
||||
return model
|
||||
|
||||
def get_keyboard_description(terminal_id, extended_id):
|
||||
is_3278 = extended_id is None or not int(extended_id[0:2], 16) & 0x80
|
||||
|
||||
if is_3278:
|
||||
description = '3278'
|
||||
|
||||
id_map = {
|
||||
0b0001: 'APL',
|
||||
0b0010: 'TEXT',
|
||||
0b0100: 'TYPEWRITER-PSHICO',
|
||||
0b0101: 'APL',
|
||||
0b0110: 'TEXT',
|
||||
0b0111: 'APL-PSHICO',
|
||||
0b1000: 'DATAENTRY-2',
|
||||
0b1001: 'DATAENTRY-1',
|
||||
0b1010: 'TYPEWRITER',
|
||||
0b1100: 'DATAENTRY-2',
|
||||
0b1101: 'DATAENTRY-1',
|
||||
0b1110: 'TYPEWRITER'
|
||||
}
|
||||
|
||||
if terminal_id.keyboard in id_map:
|
||||
description += '-' + id_map[terminal_id.keyboard]
|
||||
|
||||
return description
|
||||
|
||||
id_ = int(extended_id[0:2], 16) & 0x1f
|
||||
|
||||
is_user = int(extended_id[0:2], 16) & 0x20
|
||||
|
||||
if is_user:
|
||||
description = 'USER'
|
||||
|
||||
if id_ in [1, 2, 3, 4]:
|
||||
description += f'-{id_}'
|
||||
|
||||
return description
|
||||
|
||||
is_ibm = not int(extended_id[6:8], 16) & 0x80
|
||||
|
||||
description = 'IBM' if is_ibm else 'UNKNOWN'
|
||||
|
||||
is_enhanced = int(extended_id[6:8], 16) & 0x01
|
||||
|
||||
if is_enhanced:
|
||||
if id_ == 1:
|
||||
return description + '-ENHANCED'
|
||||
|
||||
return None
|
||||
|
||||
if id_ == 1:
|
||||
return description + '-TYPEWRITER'
|
||||
elif id_ == 2:
|
||||
return description + '-DATAENTRY'
|
||||
elif id_ == 3:
|
||||
return description + '-APL'
|
||||
|
||||
return None
|
||||
|
||||
@ -8,7 +8,7 @@ from coax.protocol import TerminalId
|
||||
import context
|
||||
|
||||
from oec.interface import InterfaceWrapper
|
||||
from oec.device import address_commands, format_address, get_ids, get_features, get_keyboard_description, _jumbo_write_split_data
|
||||
from oec.device import address_commands, format_address, get_ids, get_features, _jumbo_write_split_data
|
||||
|
||||
from mock_interface import MockInterface
|
||||
|
||||
@ -161,27 +161,6 @@ class GetFeaturesTestCase(unittest.TestCase):
|
||||
# Assert
|
||||
self.assertEqual(features, { Feature.EAB: 7 })
|
||||
|
||||
class GetKeyboardDescriptionTestCase(unittest.TestCase):
|
||||
def test(self):
|
||||
CASES = [
|
||||
(10, None, '3278-TYPEWRITER'),
|
||||
(0, 'c1347200', 'IBM-TYPEWRITER'),
|
||||
(10, '41347200', '3278-TYPEWRITER'),
|
||||
(0, 'c2347200', 'IBM-DATAENTRY'),
|
||||
(0, 'c3347200', 'IBM-APL'),
|
||||
(0, 'c1348301', 'IBM-ENHANCED'),
|
||||
(0, 'e1347200', 'USER-1'),
|
||||
(0, 'e4347200', 'USER-4')
|
||||
]
|
||||
|
||||
for (keyboard, extended_id, expected_description) in CASES:
|
||||
with self.subTest(keyboard=keyboard, extended_id=extended_id):
|
||||
terminal_id = TerminalId(0b0000_0100 | (keyboard << 4))
|
||||
|
||||
description = get_keyboard_description(terminal_id, extended_id)
|
||||
|
||||
self.assertEqual(description, expected_description)
|
||||
|
||||
class JumboWriteSplitDataTestCase(unittest.TestCase):
|
||||
def test_no_split_strategy(self):
|
||||
for data in [bytes(range(0, 64)), (bytes.fromhex('00'), 64)]:
|
||||
|
||||
@ -7,7 +7,7 @@ import context
|
||||
|
||||
from oec.interface import InterfaceWrapper
|
||||
from oec.device import UnsupportedDeviceError
|
||||
from oec.terminal import Terminal
|
||||
from oec.terminal import Terminal, get_keyboard_description
|
||||
from oec.display import Display, StatusLine
|
||||
from oec.keymap_3278_typewriter import KEYMAP
|
||||
|
||||
@ -75,6 +75,27 @@ class TerminalGetPollActionTestCase(unittest.TestCase):
|
||||
# Act and assert
|
||||
self.assertEqual(self.terminal.get_poll_action(), PollAction.ENABLE_KEYBOARD_CLICKER)
|
||||
|
||||
class GetKeyboardDescriptionTestCase(unittest.TestCase):
|
||||
def test(self):
|
||||
CASES = [
|
||||
(10, None, '3278-TYPEWRITER'),
|
||||
(0, 'c1347200', 'IBM-TYPEWRITER'),
|
||||
(10, '41347200', '3278-TYPEWRITER'),
|
||||
(0, 'c2347200', 'IBM-DATAENTRY'),
|
||||
(0, 'c3347200', 'IBM-APL'),
|
||||
(0, 'c1348301', 'IBM-ENHANCED'),
|
||||
(0, 'e1347200', 'USER-1'),
|
||||
(0, 'e4347200', 'USER-4')
|
||||
]
|
||||
|
||||
for (keyboard, extended_id, expected_description) in CASES:
|
||||
with self.subTest(keyboard=keyboard, extended_id=extended_id):
|
||||
terminal_id = TerminalId(0b0000_0100 | (keyboard << 4))
|
||||
|
||||
description = get_keyboard_description(terminal_id, extended_id)
|
||||
|
||||
self.assertEqual(description, expected_description)
|
||||
|
||||
def _create_terminal(interface):
|
||||
terminal_id = TerminalId(0b11110100)
|
||||
extended_id = 'c1348300'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user