Skip to content

Commit

Permalink
Introduce an optional parameter to use non-default padding (#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
nuts4coffee authored Feb 5, 2024
1 parent 31b4fa6 commit 2ec017f
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 11 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.23.0] - 2024-02-05

### Changed

- Padding in a frame can now be changed from zeros to padding with ones

## [0.22.0] - 2024-01-30

### Added
Expand Down
21 changes: 15 additions & 6 deletions ldfparser/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def __init__(self, frame_id: int, name: str) -> None:
self.frame_id = frame_id
self.name = name


class LinUnconditionalFrame(LinFrame):
"""
LinUnconditionalFrame represents an unconditional frame consisting of signals
Expand All @@ -42,19 +43,24 @@ class LinUnconditionalFrame(LinFrame):
:type length: int
:param signals: Signals of the frame
:type signals: Dict[int, LinSignal]
:param pad_with_zero: If True, pad with zeros during frame encoding. Otherwise, pad with ones.
Default: True
:type pad_with_zero: Boolean
"""

def __init__(self, frame_id: int, name: str, length: int, signals: Dict[int, 'LinSignal']):
def __init__(self, frame_id: int, name: str, length: int, signals: Dict[int, 'LinSignal'], pad_with_zero: bool = True):
super().__init__(frame_id, name)
self.publisher = None
self.length = length
self.signal_map = sorted(signals.items(), key=lambda x: x[0])
self._packer = LinUnconditionalFrame._frame_pattern(self.length, self.signal_map)
self._packer = LinUnconditionalFrame._frame_pattern(self.length, self.signal_map, pad_with_zero)

@staticmethod
def _frame_pattern(
frame_size: int,
signals: List[Tuple[int, 'LinSignal']]) -> bitstruct.CompiledFormat:
signals: List[Tuple[int, 'LinSignal']],
pad_with_zero: bool = True,
) -> bitstruct.CompiledFormat:
"""
Converts a frame layout into a bitstructure formatting string
Expand All @@ -73,20 +79,23 @@ def _frame_pattern(
:param signals: List of signals and offsets that represent the frame layout
:type signals: List[Tuple[int, LinSignal]] where the tuple's first element is the offset
and the second element is the signal object
:param pad_with_zero: If True, pad with zeros during frame encoding. Otherwise, pad with ones.
Default: True
:type pad_with_zero: Boolean
:raises: ValueError if the signals inside the frame would overlap or span outside the frame
:returns: Bitstruct packer object
:rtype: bitstruct.CompiledFormat
"""
pattern = "<"
frame_bits = frame_size * 8
frame_offset = 0
padding_value = "p" if pad_with_zero else "P"
for (offset, signal) in signals:
if offset < frame_offset:
raise ValueError(f"{signal} is overlapping ")
if offset != frame_offset:
padding = offset - frame_offset
pattern += f"p{padding}"
pattern += f"{padding_value}{padding}"
frame_offset += padding
if frame_offset + signal.width > frame_bits:
raise ValueError(f"{signal} with offset {offset} spans outside frame!")
Expand All @@ -96,7 +105,7 @@ def _frame_pattern(
pattern += f"u{signal.width}"
frame_offset += signal.width
if frame_offset < frame_bits:
pattern += f"p{frame_bits - frame_offset}"
pattern += f"{padding_value}{frame_bits - frame_offset}"
return bitstruct.compile(pattern)

def _get_signal(self, name: str):
Expand Down
8 changes: 7 additions & 1 deletion ldfparser/ldf.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ class LDF():
LDF is a container class that describes a LIN network
"""

def __init__(self):
def __init__(self, pad_with_zero: bool = True):
"""
:param pad_with_zero: If True, pad with zeros during frame encoding. Otherwise, pad with ones.
Default: True
:type pad_with_zero: Boolean
"""
self._source: Dict = None
self._protocol_version: Union[LinVersion, Iso17987Version, J2602Version] = None
self._language_version: Union[LinVersion, Iso17987Version, J2602Version] = None
Expand All @@ -37,6 +42,7 @@ def __init__(self):
self._slave_response_frame: LinDiagnosticResponse = None
self._schedule_tables: Dict[str, ScheduleTable] = {}
self._comments: List[str] = []
self._pad_with_zero = pad_with_zero

def get_protocol_version(self) -> Union[LinVersion, Iso17987Version, J2602Version]:
"""Returns the protocol version of the LIN network"""
Expand Down
9 changes: 6 additions & 3 deletions ldfparser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,20 @@ def parseLDFtoDict(path: str, captureComments: bool = False, encoding: str = Non
warnings.warn("'parseLDFtoDict' is deprecated, use 'parse_ldf_to_dict' instead", DeprecationWarning)
return parse_ldf_to_dict(path, captureComments, encoding)

def parse_ldf(path: str, capture_comments: bool = False, encoding: str = None) -> LDF:
def parse_ldf(path: str, capture_comments: bool = False, encoding: str = None, pad_with_zero: bool = True) -> LDF:
"""
Parses an LDF file into an object
:param path: Path to the LDF file
:type path: str
:param encoding: File encoding, for example 'UTF-8'
:type encoding: str
:param pad_with_zero: If True, pad with zeros during frame encoding. Otherwise, pad with ones.
Default: True
:type pad_with_zero: Boolean
"""
json = parse_ldf_to_dict(path, capture_comments, encoding)
ldf = LDF()
ldf = LDF(pad_with_zero=pad_with_zero)
ldf._source = json

_populate_ldf_header(json, ldf)
Expand Down Expand Up @@ -122,7 +125,7 @@ def _populate_ldf_frames(json: dict, ldf: LDF):
elif 48 <= frame['frame_id'] <= 63:
length = 8

frame_obj = LinUnconditionalFrame(frame['frame_id'], frame['name'], length, signals)
frame_obj = LinUnconditionalFrame(frame['frame_id'], frame['name'], length, signals, pad_with_zero=ldf._pad_with_zero)
ldf._unconditional_frames[frame['name']] = frame_obj

for (_, signal) in signals.items():
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[metadata]
version = 0.22.0
version = 0.23.0
14 changes: 14 additions & 0 deletions tests/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,17 @@ def test_decode_int(self, frame):
def test_decode_with_unit(self, frame, range_type):
decoded = frame.decode(b'\x20\x3F\x08', {'MotorSpeed': range_type}, keep_unit=True)
assert decoded['MotorSpeed'] == '1600.000 rpm'

@pytest.mark.unit
def test_frame_encoding_with_optional_padding1():
signal1 = LinSignal('Signal_1', 8, 255)
signal2 = LinSignal('Signal_2', 4, 255)
signal3 = LinSignal('Signal_3', 1, 255)

frame = LinUnconditionalFrame(1, 'Frame_1', 2, {0: signal1, 8: signal2, 15: signal3}, pad_with_zero=False)
content = frame.encode_raw({
'Signal_2': 10,
'Signal_3': 1
})

assert list(content) == [255, 250] # 10 | ( 1 << 7 | 0x70) = 250
13 changes: 13 additions & 0 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,16 @@ def test_j2602_attributes_default(
assert list(ldf.slaves)[0].response_tolerance == slave_response_tolerance
assert list(ldf.slaves)[0].wakeup_time == slave_wakeup_time
assert list(ldf.slaves)[0].poweron_time == slave_poweron_time

@pytest.mark.unit
@pytest.mark.parametrize(
"pad_with_zero", [True, False, None]
)
def test_padding_option(pad_with_zero):
path = os.path.join(os.path.dirname(__file__), "ldf", "lin20.ldf")
if pad_with_zero is None:
ldf = parse_ldf(path)
assert ldf._pad_with_zero is True
else:
ldf = parse_ldf(path, pad_with_zero=pad_with_zero)
assert ldf._pad_with_zero == pad_with_zero

0 comments on commit 2ec017f

Please sign in to comment.