mirror of
https://github.com/lowobservable/oec.git
synced 2026-05-05 07:24:23 +00:00
Detect unsupported DFT terminals
This commit is contained in:
33
README.md
33
README.md
@@ -1,22 +1,31 @@
|
|||||||
# oec
|
# oec
|
||||||
|
|
||||||
IBM 3270 terminal controller - an open replacement for the IBM 3174 Establishment Controller.
|
IBM 3270 terminal controller - a replacement for the IBM 3174.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
This is a work in progress - as of now it only provides basic TN3270 and VT100 emulation.
|
The goal of this project is to create an open replacement for the IBM 3174 Establishment Controller, specifically for users looking to connect a IBM 3270 type terminal to the Hercules emulator. It is a work in progress and is far from providing all the features of the 3174, but it does provide basic TN3270 and VT100 emulation.
|
||||||
|
|
||||||
- [x] TN3270
|
- [x] TN3270
|
||||||
- [x] Basic TN3270
|
- [x] Basic TN3270
|
||||||
- [ ] EAB
|
- [ ] TN3270E
|
||||||
- [ ] TN3270E
|
- [ ] EAB (Extended Attribute Buffer)
|
||||||
- [ ] SSL/TLS
|
- [ ] SSL/TLS
|
||||||
- [ ] Non-English character sets
|
- [ ] Non-English character sets
|
||||||
- [x] VT100
|
- [x] VT100
|
||||||
- [ ] Connection menu
|
- [ ] Connection menu
|
||||||
- [ ] Multiple logical terminals
|
- [ ] MLT (Multiple Logical Terminals)
|
||||||
|
|
||||||
|
## Supported Terminals
|
||||||
|
|
||||||
|
Only CUT (Control Unit Terminal) type terminals are supported. I have tested oec with the following terminals:
|
||||||
|
|
||||||
|
* IBM 3278-2
|
||||||
|
* IBM 3483-V (InfoWindow II)
|
||||||
|
|
||||||
|
You may have to modify the key mapping to support your specific terminal configuration.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -27,7 +36,7 @@ Then configure a Python virtual environment and install dependencies:
|
|||||||
```
|
```
|
||||||
python -m venv VIRTUALENV
|
python -m venv VIRTUALENV
|
||||||
. VIRTUALENV/bin/activate
|
. VIRTUALENV/bin/activate
|
||||||
pip install -r requirements.txt --no-deps
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
Assuming your interface is connected to `/dev/ttyACM0` and you want to connect to a TN3270 host named `mainframe`:
|
Assuming your interface is connected to `/dev/ttyACM0` and you want to connect to a TN3270 host named `mainframe`:
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ oec.controller
|
|||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
from coax import poll, poll_ack, load_control_register, PollAction, \
|
from coax import poll, poll_ack, load_control_register, PollAction, \
|
||||||
KeystrokePollResponse, ReceiveTimeout, ReceiveError, ProtocolError
|
KeystrokePollResponse, TerminalType, ReceiveTimeout, \
|
||||||
|
ReceiveError, ProtocolError
|
||||||
|
|
||||||
from .terminal import Terminal, read_terminal_ids
|
from .terminal import Terminal, UnsupportedTerminalError, read_terminal_ids
|
||||||
from .keyboard import Key
|
from .keyboard import Key
|
||||||
from .session import SessionDisconnectedError
|
from .session import SessionDisconnectedError
|
||||||
|
|
||||||
@@ -76,7 +77,11 @@ class Controller:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if not self.terminal:
|
if not self.terminal:
|
||||||
self._handle_terminal_attached(poll_response)
|
try:
|
||||||
|
self._handle_terminal_attached(poll_response)
|
||||||
|
except UnsupportedTerminalError as error:
|
||||||
|
self.logger.error(f'Unsupported terminal: {error}')
|
||||||
|
return
|
||||||
|
|
||||||
if poll_response:
|
if poll_response:
|
||||||
self._handle_poll_response(poll_response)
|
self._handle_poll_response(poll_response)
|
||||||
@@ -89,6 +94,9 @@ class Controller:
|
|||||||
|
|
||||||
self.logger.info(f'Terminal ID = {terminal_id}, Extended ID = {extended_id}')
|
self.logger.info(f'Terminal ID = {terminal_id}, Extended ID = {extended_id}')
|
||||||
|
|
||||||
|
if terminal_id.type != TerminalType.CUT:
|
||||||
|
raise UnsupportedTerminalError('Only CUT type terminals are supported')
|
||||||
|
|
||||||
# Get the keymap.
|
# Get the keymap.
|
||||||
keymap = self.get_keymap(terminal_id, extended_id)
|
keymap = self.get_keymap(terminal_id, extended_id)
|
||||||
|
|
||||||
|
|||||||
@@ -93,3 +93,6 @@ class Terminal:
|
|||||||
cursor_blink=self.display.cursor_blink)
|
cursor_blink=self.display.cursor_blink)
|
||||||
|
|
||||||
return control
|
return control
|
||||||
|
|
||||||
|
class UnsupportedTerminalError(Exception):
|
||||||
|
"""Unsupported terminal."""
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
ptyprocess==0.6.0
|
ptyprocess==0.6.0
|
||||||
pycoax==0.3.1
|
pycoax==0.4.0
|
||||||
pyserial==3.4
|
pyserial==3.4
|
||||||
pyte==0.8.0
|
pyte==0.8.0
|
||||||
pytn3270==0.5.0
|
pytn3270==0.5.0
|
||||||
sliplib==0.3.0
|
sliplib==0.5.0
|
||||||
sortedcontainers==2.1.0
|
sortedcontainers==2.1.0
|
||||||
wcwidth==0.1.7
|
wcwidth==0.1.7
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from oec.session import SessionDisconnectedError
|
|||||||
from oec.keyboard import KeyboardModifiers, Key
|
from oec.keyboard import KeyboardModifiers, Key
|
||||||
from oec.keymap_3278_2 import KEYMAP as KEYMAP_3278_2
|
from oec.keymap_3278_2 import KEYMAP as KEYMAP_3278_2
|
||||||
|
|
||||||
TERMINAL_IDS = (TerminalId(0b11110100), 'c1348300')
|
CUT_TERMINAL_IDS = (TerminalId(0b11110100), 'c1348300')
|
||||||
|
DFT_TERMINAL_IDS = (TerminalId(0b00000001), None)
|
||||||
|
|
||||||
class RunLoopTestCase(unittest.TestCase):
|
class RunLoopTestCase(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -21,6 +22,8 @@ class RunLoopTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
self.controller = Controller(self.interface, lambda terminal_id, extended_id: KEYMAP_3278_2, self.create_session_mock)
|
self.controller = Controller(self.interface, lambda terminal_id, extended_id: KEYMAP_3278_2, self.create_session_mock)
|
||||||
|
|
||||||
|
self.controller.logger = Mock()
|
||||||
|
|
||||||
self.controller.connected_poll_period = 1
|
self.controller.connected_poll_period = 1
|
||||||
|
|
||||||
patcher = patch('oec.controller.poll')
|
patcher = patch('oec.controller.poll')
|
||||||
@@ -35,7 +38,7 @@ class RunLoopTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
self.read_terminal_ids_mock = patcher.start()
|
self.read_terminal_ids_mock = patcher.start()
|
||||||
|
|
||||||
self.read_terminal_ids_mock.return_value = TERMINAL_IDS
|
self.read_terminal_ids_mock.return_value = CUT_TERMINAL_IDS
|
||||||
|
|
||||||
patcher = patch('oec.controller.load_control_register')
|
patcher = patch('oec.controller.load_control_register')
|
||||||
|
|
||||||
@@ -80,6 +83,14 @@ class RunLoopTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
self.controller.session.handle_host.assert_called()
|
self.controller.session.handle_host.assert_called()
|
||||||
|
|
||||||
|
def test_unsupported_terminal_attached(self):
|
||||||
|
self.read_terminal_ids_mock.return_value = DFT_TERMINAL_IDS
|
||||||
|
|
||||||
|
self._assert_run_loop(0, PowerOnResetCompletePollResponse(0xa), 0, True)
|
||||||
|
|
||||||
|
self.assertIsNone(self.controller.terminal)
|
||||||
|
self.assertIsNone(self.controller.session)
|
||||||
|
|
||||||
def test_keystroke(self):
|
def test_keystroke(self):
|
||||||
self._assert_run_loop(0, PowerOnResetCompletePollResponse(0xa), 0, True)
|
self._assert_run_loop(0, PowerOnResetCompletePollResponse(0xa), 0, True)
|
||||||
self._assert_run_loop(0, KeystrokePollResponse(0b0110000010), 0, True)
|
self._assert_run_loop(0, KeystrokePollResponse(0b0110000010), 0, True)
|
||||||
|
|||||||
Reference in New Issue
Block a user