Skip to content

Commit 7ba6ddd

Browse files
authored
Add timing parameter to CLI tools (#1869)
1 parent 2eb8f53 commit 7ba6ddd

File tree

3 files changed

+103
-5
lines changed

3 files changed

+103
-5
lines changed

can/logger.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import can
1818
from can import Bus, BusState, Logger, SizedRotatingLogger
1919
from can.typechecking import TAdditionalCliArgs
20-
from can.util import cast_from_string
20+
from can.util import _dict2timing, cast_from_string
2121

2222
if TYPE_CHECKING:
2323
from can.io import BaseRotatingLogger
@@ -58,6 +58,19 @@ def _create_base_argument_parser(parser: argparse.ArgumentParser) -> None:
5858
help="Bitrate to use for the data phase in case of CAN-FD.",
5959
)
6060

61+
parser.add_argument(
62+
"--timing",
63+
action=_BitTimingAction,
64+
nargs=argparse.ONE_OR_MORE,
65+
help="Configure bit rate and bit timing. For example, use "
66+
"`--timing f_clock=8_000_000 tseg1=5 tseg2=2 sjw=2 brp=2 nof_samples=1` for classical CAN "
67+
"or `--timing f_clock=80_000_000 nom_tseg1=119 nom_tseg2=40 nom_sjw=40 nom_brp=1 "
68+
"data_tseg1=29 data_tseg2=10 data_sjw=10 data_brp=1` for CAN FD. "
69+
"Check the python-can documentation to verify whether your "
70+
"CAN interface supports the `timing` argument.",
71+
metavar="TIMING_ARG",
72+
)
73+
6174
parser.add_argument(
6275
"extra_args",
6376
nargs=argparse.REMAINDER,
@@ -109,6 +122,8 @@ def _create_bus(parsed_args: argparse.Namespace, **kwargs: Any) -> can.BusABC:
109122
config["data_bitrate"] = parsed_args.data_bitrate
110123
if getattr(parsed_args, "can_filters", None):
111124
config["can_filters"] = parsed_args.can_filters
125+
if parsed_args.timing:
126+
config["timing"] = parsed_args.timing
112127

113128
return Bus(parsed_args.channel, **config)
114129

@@ -143,6 +158,36 @@ def __call__(
143158
setattr(namespace, self.dest, can_filters)
144159

145160

161+
class _BitTimingAction(argparse.Action):
162+
def __call__(
163+
self,
164+
parser: argparse.ArgumentParser,
165+
namespace: argparse.Namespace,
166+
values: Union[str, Sequence[Any], None],
167+
option_string: Optional[str] = None,
168+
) -> None:
169+
if not isinstance(values, list):
170+
raise argparse.ArgumentError(None, "Invalid --timing argument")
171+
172+
timing_dict: Dict[str, int] = {}
173+
for arg in values:
174+
try:
175+
key, value_string = arg.split("=")
176+
value = int(value_string)
177+
timing_dict[key] = value
178+
except ValueError:
179+
raise argparse.ArgumentError(
180+
None, f"Invalid timing argument: {arg}"
181+
) from None
182+
183+
if not (timing := _dict2timing(timing_dict)):
184+
err_msg = "Invalid --timing argument. Incomplete parameters."
185+
raise argparse.ArgumentError(None, err_msg)
186+
187+
setattr(namespace, self.dest, timing)
188+
print(timing)
189+
190+
146191
def _parse_additional_config(unknown_args: Sequence[str]) -> TAdditionalCliArgs:
147192
for arg in unknown_args:
148193
if not re.match(r"^--[a-zA-Z][a-zA-Z0-9\-]*=\S*?$", arg):

doc/bit_timing.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
Bit Timing Configuration
22
========================
33

4-
.. attention::
5-
This feature is experimental. The implementation might change in future
6-
versions.
7-
84
The CAN protocol, specified in ISO 11898, allows the bitrate, sample point
95
and number of samples to be optimized for a given application. These
106
parameters, known as bit timings, can be adjusted to meet the requirements

test/test_logger.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,63 @@ def test_parse_can_filters_list(self):
139139
)
140140
assert results.can_filters == expected_can_filters
141141

142+
def test_parse_timing(self) -> None:
143+
can20_args = self.baseargs + [
144+
"--timing",
145+
"f_clock=8_000_000",
146+
"tseg1=5",
147+
"tseg2=2",
148+
"sjw=2",
149+
"brp=2",
150+
"nof_samples=1",
151+
"--app-name=CANalyzer",
152+
]
153+
results, additional_config = can.logger._parse_logger_args(can20_args[1:])
154+
assert results.timing == can.BitTiming(
155+
f_clock=8_000_000, brp=2, tseg1=5, tseg2=2, sjw=2, nof_samples=1
156+
)
157+
assert additional_config["app_name"] == "CANalyzer"
158+
159+
canfd_args = self.baseargs + [
160+
"--timing",
161+
"f_clock=80_000_000",
162+
"nom_tseg1=119",
163+
"nom_tseg2=40",
164+
"nom_sjw=40",
165+
"nom_brp=1",
166+
"data_tseg1=29",
167+
"data_tseg2=10",
168+
"data_sjw=10",
169+
"data_brp=1",
170+
"--app-name=CANalyzer",
171+
]
172+
results, additional_config = can.logger._parse_logger_args(canfd_args[1:])
173+
assert results.timing == can.BitTimingFd(
174+
f_clock=80_000_000,
175+
nom_brp=1,
176+
nom_tseg1=119,
177+
nom_tseg2=40,
178+
nom_sjw=40,
179+
data_brp=1,
180+
data_tseg1=29,
181+
data_tseg2=10,
182+
data_sjw=10,
183+
)
184+
assert additional_config["app_name"] == "CANalyzer"
185+
186+
# remove f_clock parameter, parsing should fail
187+
incomplete_args = self.baseargs + [
188+
"--timing",
189+
"tseg1=5",
190+
"tseg2=2",
191+
"sjw=2",
192+
"brp=2",
193+
"nof_samples=1",
194+
"--app-name=CANalyzer",
195+
]
196+
with self.assertRaises(SystemExit):
197+
can.logger._parse_logger_args(incomplete_args[1:])
198+
142199
def test_parse_additional_config(self):
143200
unknown_args = [
144201
"--app-name=CANalyzer",

0 commit comments

Comments
 (0)