diff --git a/protocol/protocol.md b/protocol/protocol.md
index 5b0679d..595d757 100644
--- a/protocol/protocol.md
+++ b/protocol/protocol.md
@@ -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)
`01` - blink
`10` - reverse
`11` - underline |
+| 5-3 (`C`) | `000` - base color (or most recent EFA)
`001` - blue
`010` - red
`011` - pink
`100` - green
`101` - turquoise
`110` - yellow
`111` - white |
+| 2-0 (`S`) | `000` - base (or most recent EFA)
`001` - APL
`010` - PS 2
`011` - PS 3
`100` - PS 4
`101` - PS 5
`110` - PS 6
`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
diff --git a/pycoax/coax/__init__.py b/pycoax/coax/__init__.py
index 2fe13ba..9012455 100644
--- a/pycoax/coax/__init__.py
+++ b/pycoax/coax/__init__.py
@@ -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 (
diff --git a/pycoax/coax/features.py b/pycoax/coax/features.py
new file mode 100644
index 0000000..fc4e15b
--- /dev/null
+++ b/pycoax/coax/features.py
@@ -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
diff --git a/pycoax/coax/protocol.py b/pycoax/coax/protocol.py
index b817ac0..4046b09 100644
--- a/pycoax/coax/protocol.py
+++ b/pycoax/coax/protocol.py
@@ -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}')
diff --git a/pycoax/examples/30_get_features.py b/pycoax/examples/30_get_features.py
new file mode 100755
index 0000000..fac1555
--- /dev/null
+++ b/pycoax/examples/30_get_features.py
@@ -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)
diff --git a/pycoax/examples/40_eab.py b/pycoax/examples/40_eab.py
new file mode 100755
index 0000000..d0ee785
--- /dev/null
+++ b/pycoax/examples/40_eab.py
@@ -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))
diff --git a/pycoax/tests/test_features.py b/pycoax/tests/test_features.py
new file mode 100644
index 0000000..c4e05e4
--- /dev/null
+++ b/pycoax/tests/test_features.py
@@ -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()
diff --git a/pycoax/tests/test_protocol.py b/pycoax/tests/test_protocol.py
index 2cfe4c1..112f981 100644
--- a/pycoax/tests/test_protocol.py
+++ b/pycoax/tests/test_protocol.py
@@ -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):