Skip to content

Commit

Permalink
Merge pull request #131 from epage/state
Browse files Browse the repository at this point in the history
feat(state): Report general and CAN session state
  • Loading branch information
epage committed Jul 25, 2017
2 parents 068181e + 8dd8624 commit 6544598
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 1 deletion.
47 changes: 47 additions & 0 deletions nixnet/_enums.py
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1275,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
Expand Down Expand Up @@ -1329,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
Expand Down Expand Up @@ -1491,13 +1535,15 @@ 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
LIN = _cconsts.NX_PROTOCOL_LIN


class AppProtocol(enum.Enum):
"""Application Protocol."""
NONE = _cconsts.NX_APP_PROTOCOL_NONE
J1939 = _cconsts.NX_APP_PROTOCOL_J1939

Expand Down Expand Up @@ -1771,6 +1817,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
Expand Down
22 changes: 22 additions & 0 deletions nixnet/_funcs.py
Expand Up @@ -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
Expand Down
65 changes: 65 additions & 0 deletions nixnet/_session/base.py
Expand Up @@ -5,10 +5,14 @@
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 _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
Expand Down Expand Up @@ -329,6 +333,67 @@ 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 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
"""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
Expand Down
16 changes: 16 additions & 0 deletions nixnet/_utils.py
Expand Up @@ -3,14 +3,18 @@
from __future__ import print_function

import collections
import typing # NOQA: F401

import six

from nixnet import _cconsts
from nixnet import _errors
from nixnet import constants
from nixnet import types


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'))
Expand All @@ -37,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)
47 changes: 46 additions & 1 deletion nixnet/types.py
Expand Up @@ -9,14 +9,59 @@
from nixnet import _errors
from nixnet import constants

__all__ = ['DriverVersion', 'RawFrame', 'CanFrame']
__all__ = ['DriverVersion', 'CanComm', 'RawFrame', 'CanFrame']


DriverVersion = collections.namedtuple(
'DriverVersion',
['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.
Expand Down
58 changes: 58 additions & 0 deletions tests/test_frames.py
Expand Up @@ -8,7 +8,9 @@

import nixnet
from nixnet import _frames
from nixnet import _utils
from nixnet import constants
from nixnet import errors
from nixnet import types


Expand Down Expand Up @@ -200,6 +202,10 @@ 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
print(output_session.can_comm)

assert output_session.database_name == database_name
assert output_session.cluster_name == cluster_name
assert output_session.mode == constants.CreateSessionMode.FRAME_OUT_QUEUED
Expand All @@ -219,6 +225,58 @@ 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


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'
Expand Down

0 comments on commit 6544598

Please sign in to comment.