mirror of
https://github.com/lowobservable/coax.git
synced 2026-02-27 01:19:52 +00:00
Merge branch 'master' into i2
This commit is contained in:
@@ -187,6 +187,22 @@ implemented as a second buffer that shadows the regen buffer; this buffer only
|
||||
contains extended attribute bytes that control the formatting of the characters
|
||||
in the regen buffer - it does not include any characters.
|
||||
|
||||
An extended attribute byte is considered an Extended Field Attribute (EFA) if
|
||||
the byte shadows an attribute byte in the regen buffer, or an Extended
|
||||
Character Attribute (ECA) if it shadows a character byte.
|
||||
|
||||
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||
| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| | _M_ | _M_ | _C_ | _C_ | _C_ | _S_ | _S_ | _S_ |
|
||||
|
||||
Bits:
|
||||
|
||||
| Bits | Description |
|
||||
| --------- | ----------- |
|
||||
| 7-6 (`M`) | `00` - normal (or most recent EFA)<br>`01` - blink<br>`10` - reverse<br>`11` - underline |
|
||||
| 5-3 (`C`) | `000` - base color (or most recent EFA)<br>`001` - blue<br>`010` - red<br>`011` - pink<br>`100` - green<br>`101` - turquoise<br>`110` - yellow<br>`111` - white |
|
||||
| 2-0 (`S`) | `000` - base (or most recent EFA)<br>`001` - APL<br>`010` - PS 2<br>`011` - PS 3<br>`100` - PS 4<br>`101` - PS 5<br>`110` - PS 6<br>`111` - PS 7 |
|
||||
|
||||
### Keyboard
|
||||
|
||||
Keypresses are stored in a FIFO buffer. If there are any keypresses, the scan
|
||||
@@ -217,33 +233,37 @@ Registers can be read-only, write-only, or read-write.
|
||||
|
||||
### Commands
|
||||
|
||||
| Feature | Command | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Value |
|
||||
| ------- | ------- |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-----:|
|
||||
| Base | `POLL` | _X_ | _X_ | `0` | `0` | `0` | `0` | `0` | `1` | `0` | `1` | `0x01` |
|
||||
| Base | `POLL_ACK` | `0` | `0` | `0` | `1` | `0` | `0` | `0` | `1` | `0` | `1` | `0x11` |
|
||||
| Base | `READ_STATUS` | `0` | `0` | `0` | `0` | `1` | `1` | `0` | `1` | `0` | `1` | `0x0d` |
|
||||
| Base | `READ_TERMINAL_ID` | `0` | `0` | `0` | `0` | `1` | `0` | `0` | `1` | `0` | `1` | `0x09` |
|
||||
| Base | `READ_EXTENDED_ID` | `0` | `0` | `0` | `0` | `0` | `1` | `1` | `1` | `0` | `1` | `0x07` |
|
||||
| Base | `READ_ADDRESS_COUNTER_HI` | `0` | `0` | `0` | `0` | `0` | `1` | `0` | `1` | `0` | `1` | `0x05` |
|
||||
| Base | `READ_ADDRESS_COUNTER_LO` | `0` | `0` | `0` | `1` | `0` | `1` | `0` | `1` | `0` | `1` | `0x15` |
|
||||
| Base | `READ_DATA` | `0` | `0` | `0` | `0` | `0` | `0` | `1` | `1` | `0` | `1` | `0x03` |
|
||||
| Base | `READ_MULTIPLE` | `0` | `0` | `0` | `0` | `1` | `0` | `1` | `1` | `0` | `1` | `0x0b` |
|
||||
| Base | `RESET` | `0` | `0` | `0` | `0` | `0` | `0` | `1` | `0` | `0` | `1` | `0x02` |
|
||||
| Base | `LOAD_CONTROL_REGISTER` | `0` | `0` | `0` | `0` | `1` | `0` | `1` | `0` | `0` | `1` | `0x0a` |
|
||||
| Base | `LOAD_SECONDARY_CONTROL` | `0` | `0` | `0` | `1` | `1` | `0` | `1` | `0` | `0` | `1` | `0x1a` |
|
||||
| Base | `LOAD_MASK` | `0` | `0` | `0` | `1` | `0` | `1` | `1` | `0` | `0` | `1` | `0x16` |
|
||||
| Base | `LOAD_ADDRESS_COUNTER_HI` | `0` | `0` | `0` | `0` | `0` | `1` | `0` | `0` | `0` | `1` | `0x04` |
|
||||
| Base | `LOAD_ADDRESS_COUNTER_LO` | `0` | `0` | `0` | `1` | `0` | `1` | `0` | `0` | `0` | `1` | `0x14` |
|
||||
| Base | `WRITE_DATA` | `0` | `0` | `0` | `0` | `1` | `1` | `0` | `0` | `0` | `1` | `0x0c` |
|
||||
| Base | `CLEAR` | `0` | `0` | `0` | `0` | `0` | `1` | `1` | `0` | `0` | `1` | `0x06` |
|
||||
| Base | `SEARCH_FORWARD` | `0` | `0` | `0` | `1` | `0` | `0` | `0` | `0` | `0` | `1` | `0x10` |
|
||||
| Base | `SEARCH_BACKWARD` | `0` | `0` | `0` | `1` | `0` | `0` | `1` | `0` | `0` | `1` | `0x12` |
|
||||
| Base | `INSERT_BYTE` | `0` | `0` | `0` | `0` | `1` | `1` | `1` | `0` | `0` | `1` | `0x0e` |
|
||||
| Base | `START_OPERATION` | `0` | `0` | `0` | `0` | `1` | `0` | `0` | `0` | `0` | `1` | `0x08` |
|
||||
| Base | `DIAGNOSTIC_RESET` | `0` | `0` | `0` | `1` | `1` | `1` | `0` | `0` | `0` | `1` | `0x1c` |
|
||||
|
||||
_The hexadecimal value above represents the value of the 8-bit command byte; this is
|
||||
bits 9-2 shifted two bits to the right._
|
||||
| Feature | Command | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||
| ------- | ------- |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
||||
| Base | `POLL` | _X_ | _X_ | `0` | `0` | `0` | `0` | `0` | `1` | `0` | `1` |
|
||||
| Base | `POLL_ACK` | `0` | `0` | `0` | `1` | `0` | `0` | `0` | `1` | `0` | `1` |
|
||||
| Base | `READ_STATUS` | `0` | `0` | `0` | `0` | `1` | `1` | `0` | `1` | `0` | `1` |
|
||||
| Base | `READ_TERMINAL_ID` | `0` | `0` | `0` | `0` | `1` | `0` | `0` | `1` | `0` | `1` |
|
||||
| Base | `READ_EXTENDED_ID` | `0` | `0` | `0` | `0` | `0` | `1` | `1` | `1` | `0` | `1` |
|
||||
| Base | `READ_ADDRESS_COUNTER_HI` | `0` | `0` | `0` | `0` | `0` | `1` | `0` | `1` | `0` | `1` |
|
||||
| Base | `READ_ADDRESS_COUNTER_LO` | `0` | `0` | `0` | `1` | `0` | `1` | `0` | `1` | `0` | `1` |
|
||||
| Base | `READ_DATA` | `0` | `0` | `0` | `0` | `0` | `0` | `1` | `1` | `0` | `1` |
|
||||
| Base | `READ_MULTIPLE` | `0` | `0` | `0` | `0` | `1` | `0` | `1` | `1` | `0` | `1` |
|
||||
| Base | `RESET` | `0` | `0` | `0` | `0` | `0` | `0` | `1` | `0` | `0` | `1` |
|
||||
| Base | `LOAD_CONTROL_REGISTER` | `0` | `0` | `0` | `0` | `1` | `0` | `1` | `0` | `0` | `1` |
|
||||
| Base | `LOAD_SECONDARY_CONTROL` | `0` | `0` | `0` | `1` | `1` | `0` | `1` | `0` | `0` | `1` |
|
||||
| Base | `LOAD_MASK` | `0` | `0` | `0` | `1` | `0` | `1` | `1` | `0` | `0` | `1` |
|
||||
| Base | `LOAD_ADDRESS_COUNTER_HI` | `0` | `0` | `0` | `0` | `0` | `1` | `0` | `0` | `0` | `1` |
|
||||
| Base | `LOAD_ADDRESS_COUNTER_LO` | `0` | `0` | `0` | `1` | `0` | `1` | `0` | `0` | `0` | `1` |
|
||||
| Base | `WRITE_DATA` | `0` | `0` | `0` | `0` | `1` | `1` | `0` | `0` | `0` | `1` |
|
||||
| Base | `CLEAR` | `0` | `0` | `0` | `0` | `0` | `1` | `1` | `0` | `0` | `1` |
|
||||
| Base | `SEARCH_FORWARD` | `0` | `0` | `0` | `1` | `0` | `0` | `0` | `0` | `0` | `1` |
|
||||
| Base | `SEARCH_BACKWARD` | `0` | `0` | `0` | `1` | `0` | `0` | `1` | `0` | `0` | `1` |
|
||||
| Base | `INSERT_BYTE` | `0` | `0` | `0` | `0` | `1` | `1` | `1` | `0` | `0` | `1` |
|
||||
| Base | `START_OPERATION` | `0` | `0` | `0` | `0` | `1` | `0` | `0` | `0` | `0` | `1` |
|
||||
| Base | `DIAGNOSTIC_RESET` | `0` | `0` | `0` | `1` | `1` | `1` | `0` | `0` | `0` | `1` |
|
||||
| All | `READ_FEATURE_ID` | _F_ | _F_ | _F_ | _F_ | `0` | `1` | `1` | `1` | `0` | `1` |
|
||||
| EAB | `READ_DATA` | _F_ | _F_ | _F_ | _F_ | `0` | `0` | `1` | `1` | `0` | `1` |
|
||||
| EAB | `LOAD_MASK` | _F_ | _F_ | _F_ | _F_ | `0` | `1` | `0` | `1` | `0` | `1` |
|
||||
| EAB | `WRITE_ALTERNATE` | _F_ | _F_ | _F_ | _F_ | `1` | `0` | `1` | `0` | `0` | `1` |
|
||||
| EAB | `READ_MULTIPLE` | _F_ | _F_ | _F_ | _F_ | `1` | `0` | `1` | `1` | `0` | `1` |
|
||||
| EAB | `WRITE_UNDER_MASK` | _F_ | _F_ | _F_ | _F_ | `1` | `1` | `0` | `0` | `0` | `1` |
|
||||
| EAB | `READ_STATUS` | _F_ | _F_ | _F_ | _F_ | `1` | `1` | `0` | `1` | `0` | `1` |
|
||||
|
||||
## References
|
||||
|
||||
@@ -253,3 +273,4 @@ bits 9-2 shifted two bits to the right._
|
||||
* NS DP8340
|
||||
* NS DP8341
|
||||
* NS DP8344
|
||||
* IRMA Technical Reference
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = '0.4.2'
|
||||
__version__ = '0.5.0'
|
||||
|
||||
@@ -31,7 +31,19 @@ from .protocol import (
|
||||
search_backward,
|
||||
insert_byte,
|
||||
start_operation,
|
||||
diagnostic_reset
|
||||
diagnostic_reset,
|
||||
read_feature_id,
|
||||
eab_read_data,
|
||||
eab_load_mask,
|
||||
eab_write_alternate,
|
||||
eab_read_multiple,
|
||||
eab_write_under_mask,
|
||||
eab_read_status
|
||||
)
|
||||
|
||||
from .features import (
|
||||
Feature,
|
||||
get_features
|
||||
)
|
||||
|
||||
from .exceptions import (
|
||||
|
||||
29
pycoax/coax/features.py
Normal file
29
pycoax/coax/features.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
coax.features
|
||||
~~~~~~~~~~~~~
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from .protocol import read_feature_id
|
||||
|
||||
class Feature(Enum):
|
||||
"""Terminal feature."""
|
||||
|
||||
EAB = 0x79
|
||||
|
||||
def get_features(interface, **kwargs):
|
||||
"""Get the features a terminal supports."""
|
||||
known_ids = set([feature.value for feature in Feature])
|
||||
|
||||
features = dict()
|
||||
|
||||
for address in range(2, 16):
|
||||
id_ = read_feature_id(interface, address, **kwargs)
|
||||
|
||||
if id_ is not None and id_ in known_ids:
|
||||
feature = Feature(id_)
|
||||
|
||||
features[feature] = address
|
||||
|
||||
return features
|
||||
@@ -11,7 +11,7 @@ from .parity import odd_parity
|
||||
class Command(Enum):
|
||||
"""Terminal command."""
|
||||
|
||||
# Read Commands
|
||||
# Base
|
||||
POLL = 0x01
|
||||
POLL_ACK = 0x11
|
||||
READ_STATUS = 0x0d
|
||||
@@ -22,7 +22,6 @@ class Command(Enum):
|
||||
READ_DATA = 0x03
|
||||
READ_MULTIPLE = 0x0b
|
||||
|
||||
# Write Commands
|
||||
RESET = 0x02
|
||||
LOAD_CONTROL_REGISTER = 0x0a
|
||||
LOAD_SECONDARY_CONTROL = 0x1a
|
||||
@@ -37,6 +36,17 @@ class Command(Enum):
|
||||
START_OPERATION = 0x08
|
||||
DIAGNOSTIC_RESET = 0x1c
|
||||
|
||||
# Feature
|
||||
READ_FEATURE_ID = 0x07
|
||||
|
||||
# EAB Feature
|
||||
EAB_READ_DATA = 0x03
|
||||
EAB_LOAD_MASK = 0x05
|
||||
EAB_WRITE_ALTERNATE = 0x0a
|
||||
EAB_READ_MULTIPLE = 0x0b
|
||||
EAB_WRITE_UNDER_MASK = 0x0c
|
||||
EAB_READ_STATUS = 0x0d
|
||||
|
||||
class PollAction(Enum):
|
||||
"""Terminal POLL action."""
|
||||
|
||||
@@ -326,22 +336,61 @@ def diagnostic_reset(interface):
|
||||
"""Execute a DIAGNOSTIC_RESET command."""
|
||||
raise NotImplementedError
|
||||
|
||||
def pack_command_word(command):
|
||||
def read_feature_id(interface, feature_address, **kwargs):
|
||||
"""Execute a READ_FEATURE_ID command."""
|
||||
command_word = pack_command_word(Command.READ_FEATURE_ID, feature_address)
|
||||
|
||||
response = _execute_read_command(interface, command_word, 1, allow_trta_response=True,
|
||||
**kwargs)
|
||||
|
||||
if response is None:
|
||||
return None
|
||||
|
||||
return response[0]
|
||||
|
||||
def eab_read_data(interface, feature_address, **kwargs):
|
||||
"""Execute a EAB_READ_DATA command."""
|
||||
command_word = pack_command_word(Command.EAB_READ_DATA, feature_address)
|
||||
|
||||
return _execute_read_command(interface, command_word, **kwargs)
|
||||
|
||||
def eab_load_mask(interface, feature_address, mask, **kwargs):
|
||||
"""Execute a EAB_LOAD_MASK command."""
|
||||
command_word = pack_command_word(Command.EAB_LOAD_MASK, feature_address)
|
||||
|
||||
_execute_write_command(interface, command_word, bytes([mask]), **kwargs)
|
||||
|
||||
def eab_write_alternate(interface, feature_address, data, **kwargs):
|
||||
"""Execute a EAB_WRITE_ALTERNATE command."""
|
||||
command_word = pack_command_word(Command.EAB_WRITE_ALTERNATE, feature_address)
|
||||
|
||||
_execute_write_command(interface, command_word, data, **kwargs)
|
||||
|
||||
def eab_read_multiple(interface, feature_address, **kwargs):
|
||||
"""Execute a EAB_READ_MULTIPLE command."""
|
||||
command_word = pack_command_word(Command.EAB_READ_MULTIPLE, feature_address)
|
||||
|
||||
return _execute_read_command(interface, command_word, 32,
|
||||
validate_response_length=False, **kwargs)
|
||||
|
||||
def eab_write_under_mask(interface, feature_address, byte, **kwargs):
|
||||
"""Execute a EAB_WRITE_UNDER_MASK command."""
|
||||
command_word = pack_command_word(Command.EAB_WRITE_UNDER_MASK, feature_address)
|
||||
|
||||
_execute_write_command(interface, command_word, bytes([byte]), **kwargs)
|
||||
|
||||
def eab_read_status(interface, feature_address, **kwargs):
|
||||
"""Execute a EAB_READ_STATUS command."""
|
||||
command_word = pack_command_word(Command.EAB_READ_STATUS, feature_address)
|
||||
|
||||
return _execute_read_command(interface, command_word, **kwargs)[0]
|
||||
|
||||
def pack_command_word(command, feature_address=None):
|
||||
"""Pack a command into a 10-bit command word."""
|
||||
return (command.value << 2) | 0x1
|
||||
if feature_address is not None and (feature_address < 2 or feature_address > 15):
|
||||
raise ValueError(f'Invalid feature address: {feature_address}')
|
||||
|
||||
def is_command_word(word):
|
||||
"""Is command word bit set?"""
|
||||
return (word & 0x1) == 1
|
||||
|
||||
def unpack_command_word(word):
|
||||
"""Unpack a 10-bit command word."""
|
||||
if not is_command_word(word):
|
||||
raise ProtocolError(f'Word does not have command bit set: {word}')
|
||||
|
||||
command = (word >> 2) & 0x1f
|
||||
|
||||
return Command(command)
|
||||
return (feature_address << 6 if feature_address is not None else 0) | (command.value << 2) | 0x1
|
||||
|
||||
def pack_data_word(byte, set_parity=True):
|
||||
"""Pack a data byte into a 10-bit data word."""
|
||||
@@ -385,10 +434,7 @@ def _execute_read_command(interface, command_word, response_length=1,
|
||||
return trta_value
|
||||
|
||||
if validate_response_length and len(response) != response_length:
|
||||
command = unpack_command_word(command_word)
|
||||
|
||||
raise ProtocolError((f'Expected {response_length} word {command.name} '
|
||||
f'response: {response}'))
|
||||
raise ProtocolError((f'Expected {response_length} word response: {response}'))
|
||||
|
||||
return unpack_data_words(response) if unpack else response
|
||||
|
||||
@@ -408,9 +454,7 @@ def _execute_write_command(interface, command_word, data=None, **kwargs):
|
||||
receive_length=1, **kwargs)
|
||||
|
||||
if len(response) != 1:
|
||||
command = unpack_command_word(command_word)
|
||||
|
||||
raise ProtocolError(f'Expected 1 word {command.name} response: {response}')
|
||||
raise ProtocolError(f'Expected 1 word response: {response}')
|
||||
|
||||
if response[0] != 0:
|
||||
raise ProtocolError(f'Expected TR/TA response: {response}')
|
||||
|
||||
14
pycoax/examples/30_get_features.py
Executable file
14
pycoax/examples/30_get_features.py
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
|
||||
from common import create_serial, create_interface
|
||||
|
||||
from coax import get_features
|
||||
|
||||
with create_serial() as serial:
|
||||
interface = create_interface(serial)
|
||||
|
||||
features = get_features(interface)
|
||||
|
||||
print(features)
|
||||
75
pycoax/examples/40_eab.py
Executable file
75
pycoax/examples/40_eab.py
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
from itertools import chain
|
||||
|
||||
from common import create_serial, create_interface
|
||||
|
||||
from coax import Feature, get_features, load_address_counter_hi, load_address_counter_lo, write_data, eab_write_alternate, eab_load_mask
|
||||
|
||||
def eab_alternate_zip(regen_buffer, eab_buffer):
|
||||
return bytes(chain(*zip(regen_buffer, eab_buffer)))
|
||||
|
||||
with create_serial() as serial:
|
||||
interface = create_interface(serial)
|
||||
|
||||
features = get_features(interface)
|
||||
|
||||
if Feature.EAB not in features:
|
||||
sys.exit('No EAB feature found.')
|
||||
|
||||
eab_address = features[Feature.EAB]
|
||||
|
||||
print(f'EAB feature found at address {eab_address}')
|
||||
|
||||
# Protected Normal
|
||||
load_address_counter_hi(interface, 0)
|
||||
load_address_counter_lo(interface, 80)
|
||||
|
||||
regen_buffer = bytes.fromhex('e0 08 00 af 91 8e 93 84 82 93 84 83 00 ad 8e 91 8c 80 8b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 09')
|
||||
|
||||
write_data(interface, regen_buffer)
|
||||
|
||||
# Protected Intense
|
||||
load_address_counter_hi(interface, 0)
|
||||
load_address_counter_lo(interface, 160)
|
||||
|
||||
regen_buffer = bytes.fromhex('e8 08 00 af 91 8e 93 84 82 93 84 83 00 a8 8d 93 84 8d 92 84 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 09')
|
||||
|
||||
write_data(interface, regen_buffer)
|
||||
|
||||
# Normal EFA
|
||||
load_address_counter_hi(interface, 1)
|
||||
load_address_counter_lo(interface, 64)
|
||||
|
||||
regen_buffer = bytes.fromhex('e0 08 00 ad 8e 91 8c 80 8b 00 a4 a5 a0 00 00 00 00 00 00 00 00 00 00 b7 bf 00 a1 bf 00 b1 bf 00 ac bf 00 a6 bf 00 a2 bf 00 b8 bf 00 b6 bf 00 00 09 e0')
|
||||
eab_buffer = bytes.fromhex('00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 00 00 10 00 00 18 00 00 20 00 00 28 00 00 30 00 00 38 00 00 00 00 00')
|
||||
|
||||
eab_write_alternate(interface, eab_address, eab_alternate_zip(regen_buffer, eab_buffer))
|
||||
|
||||
# Blink EFA
|
||||
load_address_counter_hi(interface, 1)
|
||||
load_address_counter_lo(interface, 144)
|
||||
|
||||
regen_buffer = bytes.fromhex('e0 08 00 a1 8b 88 8d 8a 00 a4 a5 a0 00 00 00 00 00 00 00 00 00 00 00 b7 bf 00 a1 bf 00 b1 bf 00 ac bf 00 a6 bf 00 a2 bf 00 b8 bf 00 b6 bf 00 00 09 e0')
|
||||
eab_buffer = bytes.fromhex('40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 00 00 10 00 00 18 00 00 20 00 00 28 00 00 30 00 00 38 00 00 00 00 00')
|
||||
|
||||
eab_write_alternate(interface, eab_address, eab_alternate_zip(regen_buffer, eab_buffer))
|
||||
|
||||
# Reverse EFA
|
||||
load_address_counter_hi(interface, 1)
|
||||
load_address_counter_lo(interface, 224)
|
||||
|
||||
regen_buffer = bytes.fromhex('e0 08 00 b1 84 95 84 91 92 84 00 a4 a5 a0 00 00 00 00 00 00 00 00 00 b7 bf 00 a1 bf 00 b1 bf 00 ac bf 00 a6 bf 00 a2 bf 00 b8 bf 00 b6 bf 00 00 09 e0')
|
||||
eab_buffer = bytes.fromhex('80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 00 00 10 00 00 18 00 00 20 00 00 28 00 00 30 00 00 38 00 00 00 00 00')
|
||||
|
||||
eab_write_alternate(interface, eab_address, eab_alternate_zip(regen_buffer, eab_buffer))
|
||||
|
||||
# Underline EFA
|
||||
load_address_counter_hi(interface, 2)
|
||||
load_address_counter_lo(interface, 48)
|
||||
|
||||
regen_buffer = bytes.fromhex('e0 08 00 b4 8d 83 84 91 8b 88 8d 84 00 a4 a5 a0 00 00 00 00 00 00 00 b7 bf 00 a1 bf 00 b1 bf 00 ac bf 00 a6 bf 00 a2 bf 00 b8 bf 00 b6 bf 00 00 09 e0')
|
||||
eab_buffer = bytes.fromhex('c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 00 00 10 00 00 18 00 00 20 00 00 28 00 00 30 00 00 38 00 00 00 00 00')
|
||||
|
||||
eab_write_alternate(interface, eab_address, eab_alternate_zip(regen_buffer, eab_buffer))
|
||||
69
pycoax/tests/test_features.py
Normal file
69
pycoax/tests/test_features.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import unittest
|
||||
from unittest.mock import Mock, call, patch
|
||||
|
||||
import context
|
||||
|
||||
from coax.features import Feature, get_features
|
||||
|
||||
class GetFeaturesTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.interface = Mock()
|
||||
|
||||
patcher = patch('coax.features.read_feature_id')
|
||||
|
||||
self.read_feature_id_mock = patcher.start()
|
||||
|
||||
self.addCleanup(patch.stopall)
|
||||
|
||||
def test_with_known_feature(self):
|
||||
# Arrange
|
||||
def read_feature_id(interface, feature_address, **kwargs):
|
||||
if feature_address == 7:
|
||||
return 0x79
|
||||
|
||||
return None
|
||||
|
||||
self.read_feature_id_mock.side_effect = read_feature_id
|
||||
|
||||
# Act
|
||||
features = get_features(self.interface)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(features, { Feature.EAB: 7 })
|
||||
|
||||
def test_with_unknown_feature(self):
|
||||
# Arrange
|
||||
def read_feature_id(interface, feature_address, **kwargs):
|
||||
if feature_address == 7:
|
||||
return 0x99
|
||||
|
||||
return None
|
||||
|
||||
self.read_feature_id_mock.side_effect = read_feature_id
|
||||
|
||||
# Act
|
||||
features = get_features(self.interface)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(features, { })
|
||||
|
||||
def test_all_feature_addresses_are_enumerated(self):
|
||||
# Act
|
||||
features = get_features(self.interface)
|
||||
|
||||
# Assert
|
||||
calls = self.read_feature_id_mock.call_args_list
|
||||
|
||||
self.assertEqual(calls, [call(self.interface, address) for address in range(2, 16)])
|
||||
|
||||
def test_receive_timeout_is_passed_to_read_feature_id(self):
|
||||
# Act
|
||||
features = get_features(self.interface, receive_timeout=10)
|
||||
|
||||
# Assert
|
||||
calls = self.read_feature_id_mock.call_args_list
|
||||
|
||||
self.assertEqual(calls, [call(self.interface, address, receive_timeout=10) for address in range(2, 16)])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -4,7 +4,7 @@ from unittest.mock import Mock
|
||||
import context
|
||||
|
||||
from coax import PollResponse, KeystrokePollResponse, ProtocolError
|
||||
from coax.protocol import Command, Status, TerminalType, TerminalId, Control, SecondaryControl, pack_command_word, unpack_command_word, pack_data_word, unpack_data_word, pack_data_words, unpack_data_words, _execute_read_command, _execute_write_command
|
||||
from coax.protocol import Command, Status, TerminalType, TerminalId, Control, SecondaryControl, pack_command_word, pack_data_word, unpack_data_word, pack_data_words, unpack_data_words, _execute_read_command, _execute_write_command
|
||||
|
||||
class PollResponseTestCase(unittest.TestCase):
|
||||
def test_is_power_on_reset_complete(self):
|
||||
@@ -108,18 +108,6 @@ class PackCommandWordTestCase(unittest.TestCase):
|
||||
def test(self):
|
||||
self.assertEqual(pack_command_word(Command.POLL_ACK), 0b0001000101)
|
||||
|
||||
class UnpackCommandWordTestCase(unittest.TestCase):
|
||||
def test(self):
|
||||
# Act
|
||||
command = unpack_command_word(0b0001000101)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(command, Command.POLL_ACK)
|
||||
|
||||
def test_command_bit_not_set_error(self):
|
||||
with self.assertRaisesRegex(ProtocolError, 'Word does not have command bit set'):
|
||||
unpack_command_word(0b0001000100)
|
||||
|
||||
class PackDataWordTestCase(unittest.TestCase):
|
||||
def test(self):
|
||||
self.assertEqual(pack_data_word(0x00), 0b0000000010)
|
||||
@@ -190,7 +178,7 @@ class ExecuteReadCommandTestCase(unittest.TestCase):
|
||||
self.interface.transmit_receive = Mock(return_value=[])
|
||||
|
||||
# Act and assert
|
||||
with self.assertRaisesRegex(ProtocolError, 'Expected 1 word READ_TERMINAL_ID response'):
|
||||
with self.assertRaisesRegex(ProtocolError, 'Expected 1 word response'):
|
||||
_execute_read_command(self.interface, command_word)
|
||||
|
||||
def test_receive_timeout_is_passed_to_interface(self):
|
||||
@@ -225,7 +213,7 @@ class ExecuteWriteCommandTestCase(unittest.TestCase):
|
||||
self.interface.transmit_receive = Mock(return_value=[])
|
||||
|
||||
# Act and assert
|
||||
with self.assertRaisesRegex(ProtocolError, 'Expected 1 word WRITE_DATA response'):
|
||||
with self.assertRaisesRegex(ProtocolError, 'Expected 1 word response'):
|
||||
_execute_write_command(self.interface, command_word, bytes.fromhex('de ad be ef'))
|
||||
|
||||
def test_not_trta_response(self):
|
||||
|
||||
Reference in New Issue
Block a user