Skip to content

Commit e1bf22e

Browse files
authored
Merge pull request #166 from epage/convert
feat(API): Signal/Frame conversion mode
2 parents 78bdf9b + c3008fb commit e1bf22e

File tree

8 files changed

+425
-33
lines changed

8 files changed

+425
-33
lines changed

docs/convert.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
nixnet.convert
2+
==============
3+
4+
.. automodule:: nixnet.convert
5+
:members:
6+
:show-inheritance:
7+
:inherited-members:
8+
9+
.. toctree::
10+
:maxdepth: 3
11+
:caption: API Reference:
12+
13+
signals
14+
j1939

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ NI-XNET Python Documentation
1313
:caption: API Reference:
1414

1515
session
16+
convert
1617
constants
1718
types
1819
errors

nixnet/_funcs.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -246,17 +246,16 @@ def nx_write_signal_xy(
246246
def nx_convert_frames_to_signals_single_point(
247247
session_ref, # type: int
248248
frame_buffer, # type: bytes
249-
value_buffer, # type: typing.List[float]
250-
timestamp_buffer, # type: typing.List[int]
249+
num_signals, # type: int
251250
):
252-
# type: (...) -> None
251+
# type: (...) -> typing.Tuple[typing.List[_ctypedefs.nxTimestamp_t], typing.List[_ctypedefs.f64]]
253252
session_ref_ctypes = _ctypedefs.nxSessionRef_t(session_ref)
254253
frame_buffer_ctypes = (_ctypedefs.byte * len(frame_buffer))(*frame_buffer) # type: ignore
255254
size_of_frame_buffer_ctypes = _ctypedefs.u32(len(frame_buffer) * _ctypedefs.byte.BYTES)
256-
value_buffer_ctypes = (_ctypedefs.f64 * len(value_buffer))(*value_buffer) # type: ignore
257-
size_of_value_buffer_ctypes = _ctypedefs.u32(len(value_buffer) * _ctypedefs.f64.BYTES)
258-
timestamp_buffer_ctypes = (_ctypedefs.nxTimestamp_t * len(timestamp_buffer))(*timestamp_buffer) # type: ignore
259-
size_of_timestamp_buffer_ctypes = _ctypedefs.u32(len(timestamp_buffer) * _ctypedefs.nxTimestamp_t.BYTES)
255+
value_buffer_ctypes = (_ctypedefs.f64 * num_signals)() # type: ignore
256+
size_of_value_buffer_ctypes = _ctypedefs.u32(_ctypedefs.f64.BYTES * num_signals)
257+
timestamp_buffer_ctypes = (_ctypedefs.nxTimestamp_t * num_signals)() # type: ignore
258+
size_of_timestamp_buffer_ctypes = _ctypedefs.u32(_ctypedefs.nxTimestamp_t.BYTES * num_signals)
260259
result = _cfuncs.lib.nx_convert_frames_to_signals_single_point(
261260
session_ref_ctypes,
262261
frame_buffer_ctypes,
@@ -267,19 +266,20 @@ def nx_convert_frames_to_signals_single_point(
267266
size_of_timestamp_buffer_ctypes,
268267
)
269268
_errors.check_for_error(result.value)
269+
return timestamp_buffer_ctypes, value_buffer_ctypes
270270

271271

272272
def nx_convert_signals_to_frames_single_point(
273273
session_ref, # type: int
274274
value_buffer, # type: typing.List[float]
275-
buffer, # type: bytes
275+
bytes_to_read, # type: int
276276
):
277-
# type: (...) -> int
277+
# type: (...) -> typing.Tuple[bytes, int]
278278
session_ref_ctypes = _ctypedefs.nxSessionRef_t(session_ref)
279279
value_buffer_ctypes = (_ctypedefs.f64 * len(value_buffer))(*value_buffer) # type: ignore
280280
size_of_value_buffer_ctypes = _ctypedefs.u32(len(value_buffer) * _ctypedefs.f64.BYTES)
281-
buffer_ctypes = (_ctypedefs.byte * len(buffer))(*buffer) # type: ignore
282-
size_of_buffer_ctypes = _ctypedefs.u32(len(buffer) * _ctypedefs.byte.BYTES)
281+
buffer_ctypes = (_ctypedefs.byte * bytes_to_read)() # type: ignore
282+
size_of_buffer_ctypes = _ctypedefs.u32(_ctypedefs.byte.BYTES * bytes_to_read)
283283
number_of_bytes_returned_ctypes = _ctypedefs.u32()
284284
result = _cfuncs.lib.nx_convert_signals_to_frames_single_point(
285285
session_ref_ctypes,
@@ -290,7 +290,7 @@ def nx_convert_signals_to_frames_single_point(
290290
ctypes.pointer(number_of_bytes_returned_ctypes),
291291
)
292292
_errors.check_for_error(result.value)
293-
return number_of_bytes_returned_ctypes.value
293+
return buffer_ctypes.raw, number_of_bytes_returned_ctypes.value
294294

295295

296296
def nx_blink(

nixnet/convert.py

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
from __future__ import absolute_import
2+
from __future__ import division
3+
from __future__ import print_function
4+
5+
import itertools
6+
import typing # NOQA: F401
7+
import warnings
8+
9+
from nixnet import _frames
10+
from nixnet import _funcs
11+
from nixnet import _props
12+
from nixnet import _utils
13+
from nixnet import constants
14+
from nixnet import errors
15+
from nixnet import types
16+
17+
from nixnet._session import j1939 as session_j1939
18+
from nixnet._session import signals as session_signals
19+
20+
21+
__all__ = [
22+
"SignalConversionSinglePointSession"]
23+
24+
25+
class SignalConversionSinglePointSession(object):
26+
"""Convert NI-XNET signal data to frame data or vice versa.
27+
28+
Conversion works similar to Single-Point mode. You specify a set of signals
29+
that can span multiple frames. Signal to frame conversion reads a set of
30+
values for the signals specified and writes them to the respective
31+
frame(s). Frame to signal conversion parses a set of frames and returns the
32+
latest signal value read from a corresponding frame.
33+
"""
34+
35+
def __init__(
36+
self,
37+
database_name, # type: typing.Text
38+
cluster_name, # type: typing.Text
39+
signals, # type: typing.Union[typing.Text, typing.List[typing.Text]]
40+
):
41+
# type: (...) -> None
42+
"""Create an XNET session at run time using named references to database objects.
43+
44+
Args:
45+
database_name(str): XNET database name to use for
46+
interface configuration. The database name must use the <alias>
47+
or <filepath> syntax (refer to Databases).
48+
cluster_name(str): XNET cluster name to use for
49+
interface configuration. The name must specify a cluster from
50+
the database given in the database_name parameter. If it is left
51+
blank, the cluster is extracted from the ``signals`` parameter.
52+
signals(list of str): Strings describing signals for the session. The
53+
list syntax is as follows:
54+
55+
``signals`` contains one or more XNET Signal names. Each name must
56+
be one of the following options, whichever uniquely
57+
identifies a signal within the database given:
58+
59+
- ``<Signal>``
60+
- ``<Frame>.<Signal>``
61+
- ``<Cluster>.<Frame>.<Signal>``
62+
- ``<PDU>.<Signal>``
63+
- ``<Cluster>.<PDU>.<Signal>``
64+
65+
``signals`` may also contain one or more trigger signals. For
66+
information about trigger signals, refer to Signal Output
67+
Single-Point Mode or Signal Input Single-Point Mode.
68+
"""
69+
flattened_list = _utils.flatten_items(signals)
70+
71+
self._handle = None # To satisfy `__del__` in case nx_create_session throws
72+
self._handle = _funcs.nx_create_session(
73+
database_name,
74+
cluster_name,
75+
flattened_list,
76+
"",
77+
constants.CreateSessionMode.SIGNAL_CONVERSION_SINGLE_POINT)
78+
self._j1939 = session_j1939.J1939(self._handle)
79+
self._signals = session_signals.Signals(self._handle)
80+
81+
def __del__(self):
82+
if self._handle is not None:
83+
warnings.warn(
84+
'Session was not explicitly closed before it was destructed. '
85+
'Resources on the device may still be reserved.',
86+
errors.XnetResourceWarning)
87+
88+
def __enter__(self):
89+
return self
90+
91+
def __exit__(self, type, value, traceback):
92+
self.close()
93+
94+
def __eq__(self, other):
95+
if isinstance(other, self.__class__):
96+
return self._handle == typing.cast(SignalConversionSinglePointSession, other._handle)
97+
else:
98+
return NotImplemented
99+
100+
def __ne__(self, other):
101+
result = self.__eq__(other)
102+
if result is NotImplemented:
103+
return result
104+
else:
105+
return not result
106+
107+
def __hash__(self):
108+
return hash(self._handle)
109+
110+
def __repr__(self):
111+
# type: () -> typing.Text
112+
return 'Session(handle={0})'.format(self._handle)
113+
114+
def close(self):
115+
# type: () -> None
116+
"""Close (clear) the XNET session."""
117+
if self._handle is None:
118+
warnings.warn(
119+
'Attempting to close NI-XNET session but session was already '
120+
'closed', errors.XnetResourceWarning)
121+
return
122+
123+
_funcs.nx_clear(self._handle)
124+
125+
self._handle = None
126+
127+
@property
128+
def signals(self):
129+
# type: () -> session_signals.Signals
130+
""":any:`nixnet._session.signals.Signals`: Operate on session's signals"""
131+
return self._signals
132+
133+
@property
134+
def j1939(self):
135+
# type: () -> session_j1939.J1939
136+
""":any:`nixnet._session.j1939.J1939`: Returns the J1939 configuration object for the session."""
137+
return self._j1939
138+
139+
@property
140+
def application_protocol(self):
141+
# type: () -> constants.AppProtocol
142+
""":any:`nixnet._enums.AppProtocol`: This property returns the application protocol that the session uses.
143+
144+
The database used with the session determines the application protocol.
145+
"""
146+
return constants.AppProtocol(_props.get_session_application_protocol(self._handle))
147+
148+
@property
149+
def cluster_name(self):
150+
# type: () -> typing.Text
151+
"""str: This property returns the cluster (network) name used with the session."""
152+
return _props.get_session_cluster_name(self._handle)
153+
154+
@property
155+
def database_name(self):
156+
# type: () -> typing.Text
157+
"""str: This property returns the database name used with the session."""
158+
return _props.get_session_database_name(self._handle)
159+
160+
@property
161+
def mode(self):
162+
# type: () -> constants.CreateSessionMode
163+
""":any:`nixnet._enums.CreateSessionMode`: This property returns the mode associated with the session.
164+
165+
For more information, refer to :any:`nixnet._enums.CreateSessionMode`.
166+
"""
167+
return constants.CreateSessionMode(_props.get_session_mode(self._handle))
168+
169+
@property
170+
def protocol(self):
171+
# type: () -> constants.Protocol
172+
""":any:`nixnet._enums.Protocol`: This property returns the protocol that the interface in the session uses."""
173+
return constants.Protocol(_props.get_session_protocol(self._handle))
174+
175+
def _convert_bytes_to_signals(self, bytes):
176+
# type: (bytes) -> typing.Iterable[typing.Tuple[int, float]]
177+
num_signals = len(self.signals)
178+
timestamps, values = _funcs.nx_convert_frames_to_signals_single_point(self._handle, bytes, num_signals)
179+
for timestamp, value in zip(timestamps, values):
180+
yield timestamp.value, value.value
181+
182+
def convert_frames_to_signals(self, frames):
183+
# type: (typing.Iterable[types.Frame]) -> typing.Iterable[typing.Tuple[int, float]]
184+
"""Convert Frames to signals.
185+
186+
The frames passed into the ``frames`` array are read one by one, and
187+
the signal values found are written to internal buffers for each
188+
signal. Frames are identified by their identifier (FlexRay: slot)
189+
field. After all frames in ``frames`` array are processed, the internal
190+
signal buffers' status is returned with the corresponding timestamps
191+
from the frames where a signal value was found. The signal internal
192+
buffers' status is being preserved over multiple calls to this
193+
function.
194+
195+
This way, for example, data returned from multiple calls of nxFrameRead
196+
for a Frame Input Stream Mode session (or any other Frame Input
197+
session) can be passed to this function directly.
198+
199+
.. note:: Frames unknown to the session are silently ignored.
200+
"""
201+
units = itertools.chain.from_iterable(
202+
_frames.serialize_frame(frame.to_raw())
203+
for frame in frames)
204+
bytes = b"".join(units)
205+
return self._convert_bytes_to_signals(bytes)
206+
207+
def _convert_signals_to_bytes(self, signals, num_bytes):
208+
# type: (typing.Iterable[float], int) -> bytes
209+
buffer, number_of_bytes_returned = _funcs.nx_convert_signals_to_frames_single_point(
210+
self._handle,
211+
list(signals),
212+
num_bytes)
213+
return buffer[0:number_of_bytes_returned]
214+
215+
def convert_signals_to_frames(self, signals, frame_type=types.XnetFrame):
216+
# type: (typing.Iterable[float], typing.Type[types.FrameFactory]) -> typing.Iterable[types.Frame]
217+
"""Convert signals to frames.
218+
219+
The signal values written to the ``signals`` array are written to a raw
220+
frame buffer array. For each frame included in the session, one frame
221+
is generated in the array that contains the signal values. Signals not
222+
present in the session are written as their respective default values;
223+
empty space in the frames that signals do not occupy is written with
224+
the frame's default payload.
225+
226+
The frame header values are filled with appropriate values so that this
227+
function's output can be directly written to a Frame Output session.
228+
229+
Args:
230+
signals(list of float): Values corresponding to signals configured
231+
in this session.
232+
frame_type(:any:`nixnet.types.FrameFactory`): A factory for the
233+
desired frame formats.
234+
235+
Yields:
236+
:any:`nixnet.types.Frame`
237+
"""
238+
from_raw = typing.cast(typing.Callable[[types.RawFrame], types.Frame], frame_type.from_raw)
239+
# Unlike some session reads, this should be safe from asking to read too much.
240+
num_frames_to_read = 5
241+
while True:
242+
try:
243+
num_bytes_to_read = num_frames_to_read * _frames.nxFrameFixed_t.size
244+
buffer = self._convert_signals_to_bytes(signals, num_bytes_to_read)
245+
break
246+
except errors.XnetError as e:
247+
if e.error_type == constants.Err.BUFFER_TOO_SMALL:
248+
num_bytes_to_read *= 2
249+
else:
250+
raise
251+
for frame in _frames.iterate_frames(buffer):
252+
yield from_raw(frame)

nixnet/session.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -670,24 +670,3 @@ def write_signal_xy(
670670
timestamp_buffer,
671671
num_pairs_buffer):
672672
_funcs.nx_write_signal_xy(session_ref, timeout, value_buffer, timestamp_buffer, num_pairs_buffer)
673-
674-
675-
def convert_frames_to_signals_single_point(
676-
session_ref,
677-
frame_buffer,
678-
number_of_bytes_for_frames,
679-
value_buffer,
680-
size_of_value_buffer,
681-
timestamp_buffer,
682-
size_of_timestamp_buffer):
683-
raise NotImplementedError("Placeholder")
684-
685-
686-
def convert_signals_to_frames_single_point(
687-
session_ref,
688-
value_buffer,
689-
size_of_value_buffer,
690-
buffer,
691-
size_of_buffer,
692-
number_of_bytes_returned):
693-
raise NotImplementedError("Placeholder")

0 commit comments

Comments
 (0)