From b89e94fec27031d0e68b1353749957186439517c Mon Sep 17 00:00:00 2001 From: Jeff Liles Date: Tue, 27 Mar 2018 11:55:37 -0500 Subject: [PATCH 1/3] feat(example): Generic synchronization example fix(examples): Added a Terminals enum and modified the synchronization example to use it Modified example to include feedback. Added enum names for terminals. Modified the connect terminal class to accept new terminal enums. Added unit tests Cleaned up files to pass Flake8 tests. Replaced .Next() function with for loop to pull out a single frame. Modified example to use contextlib2 and Lists Adding contextlib2 to testing dependencies Modifying print text to use for loop Adding contextlib2 dependency to requirements_mypy Added contextlib2 to setup.py to resolve jenkins test failures Added comma to seperate six and context lib requirements Removed tests for Generic Synchronization Removed imports for Generic Synchronization Extending line in documentation Added contextlib2 to travis.yml Created synchronization example with tests and documentation --- docs/examples.rst | 2 + docs/examples/generic_synchronization.rst | 25 ++++++ nixnet/_enums.py | 24 ++++++ nixnet/_session/base.py | 8 +- nixnet_examples/generic_synchronization.py | 99 ++++++++++++++++++++++ tests/test_examples.py | 14 +++ 6 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 docs/examples/generic_synchronization.rst create mode 100644 nixnet_examples/generic_synchronization.py diff --git a/docs/examples.rst b/docs/examples.rst index 1e9688c8..0c887e02 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -15,3 +15,5 @@ Examples examples/can_lin_diff examples/programmatic_databases examples/dynamic_database_creation + examples/generic_synchronization + diff --git a/docs/examples/generic_synchronization.rst b/docs/examples/generic_synchronization.rst new file mode 100644 index 00000000..b086b9f6 --- /dev/null +++ b/docs/examples/generic_synchronization.rst @@ -0,0 +1,25 @@ +Generic Synchronization Example +=============================== + +This example demonstrates how to synchronize two :any:`nixnet.session.FrameInStreamSession` +session types on different interfaces. + +These principles can be extended to any session type and quantity. +Sessions are created, configured, and started for each interface sequentially. + +The interfaces that will listen for a clock should be created and configured first. +Interface properties need only be set once per interface. The connect_terminals function +acts like an interface property and needs to be set only once per interface. Listening +sessions can then be started with the "Session Only" scope. The last session to be started +on a particul interface can be started with the normal scope which will cause it to start +listening for a start trigger. + +The interface that will drive the master timebase clock should be created and configured +last. The master timebase is configured to be output via connect_terminals and then the +interface can be started with normal scope. + +Generic Synchronization +----------------------- + +.. literalinclude:: ../../nixnet_examples/generic_synchronization.py + :pyobject: main \ No newline at end of file diff --git a/nixnet/_enums.py b/nixnet/_enums.py index 050f6568..7aa0e9b3 100644 --- a/nixnet/_enums.py +++ b/nixnet/_enums.py @@ -2029,3 +2029,27 @@ class FrameType(enum.Enum): SPECIAL_DELAY = _cconsts.NX_FRAME_TYPE_SPECIAL_DELAY SPECIAL_LOG_TRIGGER = _cconsts.NX_FRAME_TYPE_SPECIAL_LOG_TRIGGER SPECIAL_START_TRIGGER = _cconsts.NX_FRAME_TYPE_SPECIAL_START_TRIGGER + + +class Terminal(enum.Enum): + "Terminals to import/export from the XNET device" + FRONTPANEL_0 = _cconsts.NX_TERM_FRONT_PANEL0 + FRONTPANEL_1 = _cconsts.NX_TERM_FRONT_PANEL1 + PXI_TRIG_0 = _cconsts.NX_TERM_PXI_TRIG0 + PXI_TRIG_1 = _cconsts.NX_TERM_PXI_TRIG1 + PXI_TRIG_2 = _cconsts.NX_TERM_PXI_TRIG2 + PXI_TRIG_3 = _cconsts.NX_TERM_PXI_TRIG3 + PXI_TRIG_4 = _cconsts.NX_TERM_PXI_TRIG4 + PXI_TRIG_5 = _cconsts.NX_TERM_PXI_TRIG5 + PXI_TRIG_6 = _cconsts.NX_TERM_PXI_TRIG6 + PXI_TRIG_7 = _cconsts.NX_TERM_PXI_TRIG7 + FRONT_PANEL_0 = _cconsts.NX_TERM_FRONT_PANEL0 + FRONT_PANEL_1 = _cconsts.NX_TERM_FRONT_PANEL1 + PXI_STAR = _cconsts.NX_TERM_PXI_STAR + PXI_CLK_10 = _cconsts.NX_TERM_PXI_CLK10 + TIMEBASE_10_MHZ = _cconsts.NX_TERM_10_M_HZ_TIMEBASE + TIMEBASE_1_MHZ = _cconsts.NX_TERM_1_M_HZ_TIMEBASE + TIMEBASE_MASTER = _cconsts.NX_TERM_MASTER_TIMEBASE + COMM_TRIGGER = _cconsts.NX_TERM_COMM_TRIGGER + START_TRIGGER = _cconsts.NX_TERM_START_TRIGGER + LOG_TRIGGER = _cconsts.NX_TERM_LOG_TRIGGER diff --git a/nixnet/_session/base.py b/nixnet/_session/base.py index 47e0383c..0e62843f 100644 --- a/nixnet/_session/base.py +++ b/nixnet/_session/base.py @@ -286,7 +286,7 @@ def wait_for_intf_remote_wakeup(self, timeout=10): _funcs.nx_wait(self._handle, constants.Condition.INTF_REMOTE_WAKEUP, 0, timeout) def connect_terminals(self, source, destination): - # type: (typing.Text, typing.Text) -> None + # type: (constants.Terminal, constants.Terminal) -> None """Connect terminals on the XNET interface. This function connects a source terminal to a destination terminal on @@ -304,10 +304,10 @@ def connect_terminals(self, source, destination): source(str): Connection source name. destination(str): Connection destination name. """ - _funcs.nx_connect_terminals(self._handle, source, destination) + _funcs.nx_connect_terminals(self._handle, source.value, destination.value) def disconnect_terminals(self, source, destination): - # type: (typing.Text, typing.Text) -> None + # type: (constants.Terminal, constants.Terminal) -> None """Disconnect terminals on the XNET interface. This function disconnects a specific pair of source/destination terminals @@ -334,7 +334,7 @@ def disconnect_terminals(self, source, destination): source(str): Connection source name. destination(str): Connection destination name. """ - _funcs.nx_disconnect_terminals(self._handle, source, destination) + _funcs.nx_disconnect_terminals(self._handle, source.value, destination.value) def change_lin_schedule(self, sched_index): # type: (int) -> None diff --git a/nixnet_examples/generic_synchronization.py b/nixnet_examples/generic_synchronization.py new file mode 100644 index 00000000..9129cd9e --- /dev/null +++ b/nixnet_examples/generic_synchronization.py @@ -0,0 +1,99 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import six +import time + + +import nixnet +from nixnet import constants +from nixnet import types + + +def main(): + interface1 = 'CAN1' + interface2 = 'CAN2' + + # Create the sessions. ExitStack() can be used for high session counts + with nixnet.FrameInStreamSession(interface1) as interface1_input: + with nixnet.FrameInStreamSession(interface2) as interface2_input: + with nixnet.FrameOutStreamSession(interface2) as interface2_output: + # Set session and interface properties such as termination + # Note that interface properties only need to be set once per interface + interface1_input.intf.baud_rate = 125000 + interface1_input.intf.can_term = constants.CanTerm.ON + + # Connect the start trigger terminals for the listening interface + interface1_input.connect_terminals(constants.Terminal.FRONT_PANEL_0, constants.Terminal.START_TRIGGER) + + # Start the listening interface sessions + interface1_input.start() + + # Set session and interface properties such as termination + # Note that interface properties only need to be set once per interface + interface2_input.intf.baud_rate = 125000 + interface2_input.intf.can_term = constants.CanTerm.ON + interface2_input.intf.echo_tx = True + + # Connect the start trigger terminals for the interface driving the start trigger + interface2_input.connect_terminals(constants.Terminal.START_TRIGGER, constants.Terminal.FRONTPANEL_1) + + # Starting the sessions on the driving interface will trigger the start of the listening sessions + interface2_input.start(constants.StartStopScope.SESSION_ONLY) + interface2_output.start() + + # Request values to transmit from user + user_value = six.moves.input('Enter payload [int, int, ...]: ') + try: + payload_list = [int(x.strip()) for x in user_value.split(",")] + except ValueError: + payload_list = [2, 4, 8, 16] + print('Unrecognized input ({}). Setting data buffer to {}'.format(user_value, payload_list)) + + id = types.CanIdentifier(1) + payload = bytearray(payload_list) + frame = types.CanFrame(id, constants.FrameType.CAN_DATA, payload) + + print('The same values should be received. Press q to quit') + i = 0 + while True: + # Apply the user input to the frame payload + for index, byte in enumerate(payload): + payload[index] = byte + i + + frame.payload = payload + # Write the specified frame to the bus + interface2_output.frames.write([frame]) + print('Sent frame with ID {} payload: {}'.format(id, payload)) + + time.sleep(0.2) + + # Read frames back from both input sessions + frames_to_read = 1 + received_frames = interface1_input.frames.read(frames_to_read) + echoed_frames = interface2_input.frames.read(frames_to_read) + rx_frame_list = list(received_frames) + echo_frame_list = list(echoed_frames) + for frame in rx_frame_list: + print('Received frame: {}'.format(rx_frame_list[0])) + print(' payload={}'.format(list(six.iterbytes(rx_frame_list[0].payload)))) + print('Echoed frame: {}'.format(echo_frame_list[0])) + print(' payload={}'.format(list(six.iterbytes(echo_frame_list[0].payload)))) + + # Subtract the timestamp from first frame in each list to obtain delta + delta_t = rx_frame_list[0].timestamp - echo_frame_list[0].timestamp + print('The delta between received timestamp and echo is: {}', delta_t) + + i += 1 + if max(payload) + i > 0xFF: + i = 0 + inp = six.moves.input('Hit enter to continue (q to quit): ') + if inp.lower() == 'q': + break + + print('Data acquisition stopped.') + + +if __name__ == '__main__': + main() diff --git a/tests/test_examples.py b/tests/test_examples.py index 266e096b..ab7bede9 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -15,6 +15,7 @@ from nixnet_examples import can_frame_stream_io from nixnet_examples import can_signal_conversion from nixnet_examples import can_signal_single_point_io +from nixnet_examples import generic_synchronization from nixnet_examples import lin_dynamic_database_creation from nixnet_examples import lin_frame_stream_io from nixnet_examples import programmatic_database_usage @@ -42,6 +43,8 @@ MockXnetLibrary.nxdb_close_database.return_value = _ctypedefs.u32(0) MockXnetLibrary.nxdb_create_object.return_value = _ctypedefs.u32(0) MockXnetLibrary.nxdb_set_property.return_value = _ctypedefs.u32(0) +MockXnetLibrary.nx_connect_terminals.return_value = _ctypedefs.u32(0) +MockXnetLibrary.nx_disconnect_terminals.return_value = _ctypedefs.u32(0) def six_input(queue): @@ -161,3 +164,14 @@ def test_lin_frame_stream_empty_session(input_values): def test_programmatic_database_usage(input_values): with mock.patch('six.moves.input', six_input(input_values)): programmatic_database_usage.main() + + +@pytest.mark.parametrize("input_values", [ + ['1, 2', 'q'], + ['255, 0', 'q'], +]) +@mock.patch('nixnet._cfuncs.lib', MockXnetLibrary) +@mock.patch('time.sleep', lambda time: None) +def test_generic_synchronization(input_values): + with mock.patch('six.moves.input', six_input(input_values)): + generic_synchronization.main() From c6a2ce95825d43220f30681614a0b2029ced6b5f Mon Sep 17 00:00:00 2001 From: lilesj Date: Sun, 23 Sep 2018 12:55:51 -0500 Subject: [PATCH 2/3] Modified trigger routing to PXI_Trig0 to be compatible with CI test machine --- nixnet_examples/generic_synchronization.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nixnet_examples/generic_synchronization.py b/nixnet_examples/generic_synchronization.py index 9129cd9e..d7548590 100644 --- a/nixnet_examples/generic_synchronization.py +++ b/nixnet_examples/generic_synchronization.py @@ -25,7 +25,7 @@ def main(): interface1_input.intf.can_term = constants.CanTerm.ON # Connect the start trigger terminals for the listening interface - interface1_input.connect_terminals(constants.Terminal.FRONT_PANEL_0, constants.Terminal.START_TRIGGER) + interface1_input.connect_terminals(constants.Terminal.PXI_TRIG_0, constants.Terminal.START_TRIGGER) # Start the listening interface sessions interface1_input.start() @@ -37,7 +37,7 @@ def main(): interface2_input.intf.echo_tx = True # Connect the start trigger terminals for the interface driving the start trigger - interface2_input.connect_terminals(constants.Terminal.START_TRIGGER, constants.Terminal.FRONTPANEL_1) + interface2_input.connect_terminals(constants.Terminal.START_TRIGGER, constants.Terminal.PXI_TRIG_0) # Starting the sessions on the driving interface will trigger the start of the listening sessions interface2_input.start(constants.StartStopScope.SESSION_ONLY) From c7347f054f183e5556a0cf7ee34677dab98ee712 Mon Sep 17 00:00:00 2001 From: lilesj Date: Mon, 24 Sep 2018 21:53:58 -0500 Subject: [PATCH 3/3] Updated tests in test_session.py to use new connect terminal enums --- nixnet/_session/base.py | 8 ++++---- tests/test_session.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nixnet/_session/base.py b/nixnet/_session/base.py index 0e62843f..2ebeb777 100644 --- a/nixnet/_session/base.py +++ b/nixnet/_session/base.py @@ -301,8 +301,8 @@ def connect_terminals(self, source, destination): pair is an internal and the other an external. Args: - source(str): Connection source name. - destination(str): Connection destination name. + source(constants.Terminal): Connection source name. + destination(constants.Terminal): Connection destination name. """ _funcs.nx_connect_terminals(self._handle, source.value, destination.value) @@ -331,8 +331,8 @@ def disconnect_terminals(self, source, destination): Attempting to disconnect a nonconnected terminal results in an error. Args: - source(str): Connection source name. - destination(str): Connection destination name. + source(constants.Terminal): Connection source name. + destination(constants.Terminal): Connection destination name. """ _funcs.nx_disconnect_terminals(self._handle, source.value, destination.value) diff --git a/tests/test_session.py b/tests/test_session.py index 11cd79e1..03ae0f58 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -445,7 +445,7 @@ def test_connect_terminals_failures(can_in_interface): cluster_name, frame_name) as input_session: with pytest.raises(errors.XnetError) as excinfo: - input_session.connect_terminals("FrontPanel0", "FrontPanel1") + input_session.connect_terminals(constants.Terminal.FRONT_PANEL_0, constants.Terminal.FRONT_PANEL_1) assert excinfo.value.error_type in [ constants.Err.SYNCHRONIZATION_NOT_ALLOWED, constants.Err.INVALID_SYNCHRONIZATION_COMBINATION] @@ -464,7 +464,7 @@ def test_disconnect_terminals_failures(can_in_interface): cluster_name, frame_name) as input_session: with pytest.raises(errors.XnetError) as excinfo: - input_session.disconnect_terminals("FrontPanel0", "FrontPanel1") + input_session.disconnect_terminals(constants.Terminal.FRONT_PANEL_0, constants.Terminal.FRONT_PANEL_1) assert excinfo.value.error_type in [ constants.Err.SYNCHRONIZATION_NOT_ALLOWED, constants.Err.INVALID_SYNCHRONIZATION_COMBINATION]