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..2ebeb777 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 @@ -301,13 +301,13 @@ 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, 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 @@ -331,10 +331,10 @@ 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, 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..d7548590 --- /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.PXI_TRIG_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.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) + 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() 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]