From e894faf28935c3fdb9df69ead56599089ac8d677 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 24 Jul 2017 14:45:59 -0500 Subject: [PATCH 1/4] docs: Placeholders for enums referenced in docs --- nixnet/_enums.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nixnet/_enums.py b/nixnet/_enums.py index 8f8f807b..6f820307 100644 --- a/nixnet/_enums.py +++ b/nixnet/_enums.py @@ -8,6 +8,7 @@ class Err(enum.Enum): + """Error codes returned by NI-XNET.""" # An internal error occurred in the NI-XNET driver. Please contact National # Instruments and provide the information from the file # %LOCALAPPDATA%\\National Instruments\\NI-XNET\\log\\niXntErr.log. On Windows XP, @@ -1038,6 +1039,7 @@ class Err(enum.Enum): class Warn(enum.Enum): + """Warning codes returned by NI-XNET.""" # The CAN FD baud rate you supplied exceeds the capabilities the transceiver # manufacturer specified. In our internal testing, we have found this baud # rate to run, but bus errors may be detected or generated during @@ -1491,6 +1493,7 @@ class CanTcvrCap(enum.Enum): class Protocol(enum.Enum): + """Protocol.""" UNKNOWN = _cconsts.NX_PROTOCOL_UNKNOWN CAN = _cconsts.NX_PROTOCOL_CAN FLEX_RAY = _cconsts.NX_PROTOCOL_FLEX_RAY @@ -1498,6 +1501,7 @@ class Protocol(enum.Enum): class AppProtocol(enum.Enum): + """Application Protocol.""" NONE = _cconsts.NX_APP_PROTOCOL_NONE J1939 = _cconsts.NX_APP_PROTOCOL_J1939 @@ -1771,6 +1775,7 @@ class FrmLinChecksum(enum.Enum): class FrameType(enum.Enum): + """Frame format type.""" CAN_DATA = _cconsts.NX_FRAME_TYPE_CAN_DATA CAN_REMOTE = _cconsts.NX_FRAME_TYPE_CAN_REMOTE CAN_BUS_ERROR = _cconsts.NX_FRAME_TYPE_CAN_BUS_ERROR From f52552da3d3c08ded49f55ee79bffcf4640a0c53 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 24 Jul 2017 18:29:07 -0500 Subject: [PATCH 2/4] chore: Annotate flatten_items --- nixnet/_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixnet/_utils.py b/nixnet/_utils.py index f4b0bc7e..eaf5f236 100644 --- a/nixnet/_utils.py +++ b/nixnet/_utils.py @@ -3,6 +3,7 @@ from __future__ import print_function import collections +import typing # NOQA: F401 import six @@ -11,6 +12,7 @@ def flatten_items(list): + # (typing.Union[typing.Text, typing.List[typing.Text]]) -> typing.Text """Flatten an item list to a string >>> str(flatten_items('Item')) From 7b8e21af7b21f20b835d53d564818ad4984d7190 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 24 Jul 2017 14:46:33 -0500 Subject: [PATCH 3/4] feat(state): Report general session state This includes - time_current - time_start - time_communicating - session info state Plus an explicit check_fault function. There are two controverial parts of this - If the interface isn't communication, time_start and time_communicating throw an error rather than return 0. - Each state property will ignore "fault". Instead you need to explicitly call `check_fault`. --- nixnet/_enums.py | 12 +++++++++ nixnet/_funcs.py | 22 ++++++++++++++++ nixnet/_session/base.py | 56 +++++++++++++++++++++++++++++++++++++++++ tests/test_frames.py | 35 ++++++++++++++++++++++++++ 4 files changed, 125 insertions(+) diff --git a/nixnet/_enums.py b/nixnet/_enums.py index 6f820307..6cdf0bcb 100644 --- a/nixnet/_enums.py +++ b/nixnet/_enums.py @@ -1277,6 +1277,18 @@ class CanFdIsoMode(enum.Enum): class SessionInfoState(enum.Enum): + """State of running session. + + Values: + STOPPED: + All frames in the session are stopped. + STARTED: + All frames in the session are started. + MIX: + Some frames in the session are started while other frames are + stopped. This state may occur when using ``start`` or ``stop`` with + ``StartStopScope.SESSION_ONLY``. + """ STOPPED = _cconsts.NX_SESSION_INFO_STATE_STOPPED STARTED = _cconsts.NX_SESSION_INFO_STATE_STARTED MIX = _cconsts.NX_SESSION_INFO_STATE_MIX diff --git a/nixnet/_funcs.py b/nixnet/_funcs.py index 6559996f..2b4484a0 100644 --- a/nixnet/_funcs.py +++ b/nixnet/_funcs.py @@ -138,6 +138,28 @@ def nx_read_signal_single_point( return timestamp_buffer_ctypes, value_buffer_ctypes +def nx_read_state( + session_ref, # type: int + state_id, # type: _enums.ReadState + t, # type: typing.Any +): + # type: (...) -> typing.Tuple[typing.Any, int] + session_ref_ctypes = _ctypedefs.nxSessionRef_t(session_ref) + state_id_ctypes = _ctypedefs.u32(state_id.value) + state_size_ctypes = _ctypedefs.u32(t.BYTES) + state_value_ctypes = t() + fault_ctypes = _ctypedefs.nxStatus_t() + result = _cfuncs.lib.nx_read_state( + session_ref_ctypes, + state_id_ctypes, + state_size_ctypes, + ctypes.pointer(state_value_ctypes), + ctypes.pointer(fault_ctypes), + ) + _errors.check_for_error(result.value) + return state_value_ctypes.value, fault_ctypes.value + + def nx_write_frame( session_ref, # type: int buffer, # type: typing.Any diff --git a/nixnet/_session/base.py b/nixnet/_session/base.py index 8d548f32..d93c2010 100644 --- a/nixnet/_session/base.py +++ b/nixnet/_session/base.py @@ -5,6 +5,8 @@ import typing # NOQA: F401 import warnings +from nixnet import _ctypedefs +from nixnet import _errors from nixnet import _funcs from nixnet import _props from nixnet import constants @@ -329,6 +331,60 @@ def disconnect_terminals(self, source, destination): """ _funcs.nx_disconnect_terminals(self._handle, source, destination) + @property + def time_current(self): + # type: () -> int + """int: Current interface time.""" + time, _ = _funcs.nx_read_state(self._handle, constants.ReadState.TIME_CURRENT, _ctypedefs.nxTimestamp_t) + return time + + @property + def time_start(self): + # type: () -> int + """int: Time the interface was started.""" + time, _ = _funcs.nx_read_state(self._handle, constants.ReadState.TIME_START, _ctypedefs.nxTimestamp_t) + if time == 0: + # The interface is not communicating. + _errors.check_for_error(constants.Err.SESSION_NOT_STARTED.value) + return time + + @property + def time_communicating(self): + # type: () -> int + """int: Time the interface started communicating. + + The time is usually later than ``time_start`` because the interface + must undergo a communication startup procedure. + """ + time, _ = _funcs.nx_read_state(self._handle, constants.ReadState.TIME_COMMUNICATING, _ctypedefs.nxTimestamp_t) + if time == 0: + # The interface is not communicating. + _errors.check_for_error(constants.Err.SESSION_NOT_STARTED.value) + return time + + @property + def state(self): + # type: () -> constants.SessionInfoState + """:any:`nixnet._enums.SessionInfoState`: Session running state.""" + state, _ = _funcs.nx_read_state(self._handle, constants.ReadState.SESSION_INFO, _ctypedefs.u32) + return constants.SessionInfoState(state) + + @property + def check_fault(self): + # type: () -> None + """Check for an asynchronous fault. + + A fault is an error that occurs asynchronously to the NI-XNET + application calls. The fault cause may be related to network + communication, but it also can be related to XNET hardware, such as a + fault in the onboard processor. Although faults are extremely rare, + nxReadState provides a detection method distinct from the status of + NI-XNET function calls, yet easy to use alongside the common practice + of checking the communication state. + """ + _, fault = _funcs.nx_read_state(self._handle, constants.ReadState.SESSION_INFO, _ctypedefs.u32) + _errors.check_for_error(fault) + @property def intf(self): # type: () -> session_intf.Interface diff --git a/tests/test_frames.py b/tests/test_frames.py index f75a5342..c4c843fb 100644 --- a/tests/test_frames.py +++ b/tests/test_frames.py @@ -9,6 +9,7 @@ import nixnet from nixnet import _frames from nixnet import constants +from nixnet import errors from nixnet import types @@ -200,6 +201,9 @@ def test_session_properties(nixnet_out_interface): database_name, cluster_name, frame_name) as output_session: + print(output_session.time_current) + assert output_session.state == constants.SessionInfoState.STOPPED + assert output_session.database_name == database_name assert output_session.cluster_name == cluster_name assert output_session.mode == constants.CreateSessionMode.FRAME_OUT_QUEUED @@ -219,6 +223,37 @@ def test_session_properties(nixnet_out_interface): assert output_session.queue_size == 2040 +@pytest.mark.integration +def test_session_properties_transition(nixnet_out_interface): + """Verify Session properties relationship to session start/stop.""" + database_name = 'NIXNET_example' + cluster_name = 'CAN_Cluster' + frame_name = 'CANEventFrame1' + + with nixnet.FrameOutQueuedSession( + nixnet_out_interface, + database_name, + cluster_name, + frame_name) as output_session: + with pytest.raises(errors.XnetError): + print(output_session.time_start) + print(output_session.time_communicating) + assert output_session.state == constants.SessionInfoState.STOPPED + + output_session.start() + + print(output_session.time_start) + print(output_session.time_communicating) + assert output_session.state == constants.SessionInfoState.STARTED + + output_session.stop() + + with pytest.raises(errors.XnetError): + print(output_session.time_start) + print(output_session.time_communicating) + assert output_session.state == constants.SessionInfoState.STOPPED + + @pytest.mark.integration def test_frames_container(nixnet_in_interface): database_name = 'NIXNET_example' From 8dd86249c1a06a1978d885f7057e1db069abc7a8 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 24 Jul 2017 18:29:30 -0500 Subject: [PATCH 4/4] feat(API): Add session.can_comm --- nixnet/_enums.py | 30 ++++++++++++++++++++++++++ nixnet/_session/base.py | 9 ++++++++ nixnet/_utils.py | 14 ++++++++++++ nixnet/types.py | 47 ++++++++++++++++++++++++++++++++++++++++- tests/test_frames.py | 23 ++++++++++++++++++++ 5 files changed, 122 insertions(+), 1 deletion(-) diff --git a/nixnet/_enums.py b/nixnet/_enums.py index 6cdf0bcb..827604c3 100644 --- a/nixnet/_enums.py +++ b/nixnet/_enums.py @@ -1343,6 +1343,36 @@ class CanCommState(enum.Enum): class CanLastErr(enum.Enum): + """CAN Last Error + + Values: + NONE: + The last receive or transmit was successful. + STUFF: + More than 5 equal bits have occurred in sequence, which the CAN + specification does not allow. + FORM: + A fixed format part of the received frame used the wrong format. + ACK: + Another node (ECU) did not acknowledge the frame transmit. + + If you call the appropriate ``write`` function and do not have a + cable connected, or the cable is connected to a node that is not + communicating, you see this error repeatedly. The CAN communication + state eventually transitions to Error Passive, and the frame + transmit retries indefinitely. + BIT1: + During a frame transmit (with the exception of the arbitration ID + field), the interface wanted to send a recessive bit (logical 1), + but the monitored bus value was dominant (logical 0). + BIT0: + During a frame transmit (with the exception of the arbitration ID + field), the interface wanted to send a dominant bit (logical 0), + but the monitored bus value was recessive (logical 1). + CRC: + The CRC contained within a received frame does not match the CRC + calculated for the incoming bits. + """ NONE = _cconsts.NX_CAN_LAST_ERR_NONE STUFF = _cconsts.NX_CAN_LAST_ERR_STUFF FORM = _cconsts.NX_CAN_LAST_ERR_FORM diff --git a/nixnet/_session/base.py b/nixnet/_session/base.py index d93c2010..1686d1b7 100644 --- a/nixnet/_session/base.py +++ b/nixnet/_session/base.py @@ -9,8 +9,10 @@ from nixnet import _errors from nixnet import _funcs from nixnet import _props +from nixnet import _utils from nixnet import constants from nixnet import errors +from nixnet import types # NOQA: F401 from nixnet._session import intf as session_intf from nixnet._session import j1939 as session_j1939 @@ -369,6 +371,13 @@ def state(self): state, _ = _funcs.nx_read_state(self._handle, constants.ReadState.SESSION_INFO, _ctypedefs.u32) return constants.SessionInfoState(state) + @property + def can_comm(self): + # type: () -> types.CanComm + """:any:`nixnet.types.CanComm`: CAN Communication state""" + bitfield, _ = _funcs.nx_read_state(self._handle, constants.ReadState.CAN_COMM, _ctypedefs.u32) + return _utils.parse_can_comm_bitfield(bitfield) + @property def check_fault(self): # type: () -> None diff --git a/nixnet/_utils.py b/nixnet/_utils.py index eaf5f236..0ebfa609 100644 --- a/nixnet/_utils.py +++ b/nixnet/_utils.py @@ -9,6 +9,8 @@ from nixnet import _cconsts from nixnet import _errors +from nixnet import constants +from nixnet import types def flatten_items(list): @@ -39,3 +41,15 @@ def flatten_items(list): _errors.check_for_error(_cconsts.NX_ERR_INVALID_PROPERTY_VALUE) return flattened + + +def parse_can_comm_bitfield(bitfield): + # (int) -> types.CanComm + """Parse a CAN Comm bitfield.""" + state = constants.CanCommState(bitfield & 0x0F) + tcvr_err = ((bitfield >> 4) & 0x01) != 0 + sleep = ((bitfield >> 5) & 0x01) != 0 + last_err = constants.CanLastErr((bitfield >> 8) & 0x0F) + tx_err_count = ((bitfield >> 16) & 0x0FF) + rx_err_count = ((bitfield >> 24) & 0x0FF) + return types.CanComm(state, tcvr_err, sleep, last_err, tx_err_count, rx_err_count) diff --git a/nixnet/types.py b/nixnet/types.py index 73657287..a7e0fb1b 100644 --- a/nixnet/types.py +++ b/nixnet/types.py @@ -9,7 +9,7 @@ from nixnet import _errors from nixnet import constants -__all__ = ['DriverVersion', 'RawFrame', 'CanFrame'] +__all__ = ['DriverVersion', 'CanComm', 'RawFrame', 'CanFrame'] DriverVersion = collections.namedtuple( @@ -17,6 +17,51 @@ ['major', 'minor', 'update', 'phase', 'build']) +CanComm_ = collections.namedtuple( + 'CanComm_', + ['state', 'tcvr_err', 'sleep', 'last_err', 'tx_err_count', 'rx_err_count']) + + +class CanComm(CanComm_): + """CAN Communication State. + + Attributes: + state (:any:`nixnet._enums.CanCommState`): Communication State + tcvr_err (bool): Transceiver Error. + Transceiver error indicates whether an error condition exists on + the physical transceiver. This is typically referred to as the + transceiver chip NERR pin. False indicates normal operation (no + error), and true indicates an error. + sleep (bool): Sleep. + Sleep indicates whether the transceiver and communication + controller are in their sleep state. False indicates normal + operation (awake), and true indicates sleep. + last_err (:any:`nixnet._enums.CanLastErr`): Last Error. + Last error specifies the status of the last attempt to receive or + transmit a frame + tx_err_count (int): Transmit Error Counter. + The transmit error counter begins at 0 when communication starts on + the CAN interface. The counter increments when an error is detected + for a transmitted frame and decrements when a frame transmits + successfully. The counter increases more for an error than it is + decreased for success. This ensures that the counter generally + increases when a certain ratio of frames (roughly 1/8) encounter + errors. + When communication state transitions to Bus Off, the transmit error + counter no longer is valid. + rx_err_count (int): Receive Error Counter. + The receive error counter begins at 0 when communication starts on + the CAN interface. The counter increments when an error is detected + for a received frame and decrements when a frame is received + successfully. The counter increases more for an error than it is + decreased for success. This ensures that the counter generally + increases when a certain ratio of frames (roughly 1/8) encounter + errors. + """ + + pass + + class RawFrame(object): """Raw Frame. diff --git a/tests/test_frames.py b/tests/test_frames.py index c4c843fb..71e00e7e 100644 --- a/tests/test_frames.py +++ b/tests/test_frames.py @@ -8,6 +8,7 @@ import nixnet from nixnet import _frames +from nixnet import _utils from nixnet import constants from nixnet import errors from nixnet import types @@ -203,6 +204,7 @@ def test_session_properties(nixnet_out_interface): frame_name) as output_session: print(output_session.time_current) assert output_session.state == constants.SessionInfoState.STOPPED + print(output_session.can_comm) assert output_session.database_name == database_name assert output_session.cluster_name == cluster_name @@ -254,6 +256,27 @@ def test_session_properties_transition(nixnet_out_interface): assert output_session.state == constants.SessionInfoState.STOPPED +def test_parse_can_comm_bitfield(): + """A part of Session.can_comm""" + comm = _utils.parse_can_comm_bitfield(0) + assert comm == types.CanComm( + constants.CanCommState.ERROR_ACTIVE, + tcvr_err=False, + sleep=False, + last_err=constants.CanLastErr.NONE, + tx_err_count=0, + rx_err_count=0) + + comm = _utils.parse_can_comm_bitfield(0xFFFFF6F3) + assert comm == types.CanComm( + constants.CanCommState.INIT, + tcvr_err=True, + sleep=True, + last_err=constants.CanLastErr.CRC, + tx_err_count=255, + rx_err_count=255) + + @pytest.mark.integration def test_frames_container(nixnet_in_interface): database_name = 'NIXNET_example'