can-api-generator 2.4.0__tar.gz → 2.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/PKG-INFO +57 -1
  2. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/README.md +55 -0
  3. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/pyproject.toml +1 -0
  4. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/__about__.py +1 -1
  5. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/code_generator.py +3 -0
  6. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/main.py +96 -24
  7. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/header_template.jinja +8 -3
  8. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/source_template.jinja +7 -1
  9. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/tx_macros.jinja +126 -8
  10. can_api_generator-2.6.0/tests/test_config_file.py +132 -0
  11. can_api_generator-2.6.0/tests/test_seed.py +32 -0
  12. can_api_generator-2.6.0/tests/test_tx_timing_delta.py +308 -0
  13. can_api_generator-2.6.0/uv.lock +905 -0
  14. can_api_generator-2.4.0/Advantics_Bootloader.kcd +0 -384
  15. can_api_generator-2.4.0/manual_api_test.c +0 -93
  16. can_api_generator-2.4.0/manual_api_test.h +0 -60
  17. can_api_generator-2.4.0/manual_api_test.o +0 -0
  18. can_api_generator-2.4.0/test_1_rx_over_can +0 -0
  19. can_api_generator-2.4.0/test_api.c +0 -78
  20. can_api_generator-2.4.0/test_api.h +0 -68
  21. can_api_generator-2.4.0/test_kcd_api_test_mux_with_same_signal_names_labeled_mux_variants.c +0 -686
  22. can_api_generator-2.4.0/test_mux_with_same_signal_names_labeled_mux_variants.c +0 -237
  23. can_api_generator-2.4.0/test_mux_with_same_signal_names_labeled_mux_variants.h +0 -265
  24. can_api_generator-2.4.0/test_test_1_rx_over_can.c +0 -65
  25. can_api_generator-2.4.0/test_test_basic_multiplex_rx_over_can.c +0 -67
  26. can_api_generator-2.4.0/test_test_mux_with_partially_labled_index_rx_over_can.c +0 -67
  27. can_api_generator-2.4.0/test_test_mux_with_same_signal_names_labeled_mux_variants_rx_over_can.c +0 -67
  28. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/.gitignore +0 -0
  29. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/LICENSE.txt +0 -0
  30. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/manual_cag_test.py +0 -0
  31. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/__init__.py +0 -0
  32. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/cbdb_generator.py +0 -0
  33. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/msg_processor.py +0 -0
  34. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/callback_macros.jinja +0 -0
  35. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/canframe.jinja +0 -0
  36. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/declaration_macros.jinja +0 -0
  37. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/dyndec_header.jinja +0 -0
  38. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/dyndec_source.jinja +0 -0
  39. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/encoding_macros.jinja +0 -0
  40. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/rx_macros.jinja +0 -0
  41. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/types.py +0 -0
  42. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/src/can_api_generator/utils.py +0 -0
  43. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/__init__.py +0 -0
  44. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/cantool_test_utils.jinja +0 -0
  45. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/compat_shim.jinja +0 -0
  46. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/dyndec_test_harness.c +0 -0
  47. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/multiple_busses_test.kcd +0 -0
  48. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_1.kcd +0 -0
  49. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_basic_multiplex.kcd +0 -0
  50. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_big_endian.kcd +0 -0
  51. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_can_db_model_dep.py +0 -0
  52. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_cbdb_generator.py +0 -0
  53. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_cli.py +0 -0
  54. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_cross_interface_compat.jinja +0 -0
  55. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_cross_interface_compat.py +0 -0
  56. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_dyndec_round_trip.py +0 -0
  57. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_float_wrong_length.kcd +0 -0
  58. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_message_parser.py +0 -0
  59. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_missing_consumer.kcd +0 -0
  60. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_missing_producer.kcd +0 -0
  61. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_mux_group_count_out_of_order.kcd +0 -0
  62. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_mux_with_labeled_index.kcd +0 -0
  63. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_mux_with_partially_labled_index.kcd +0 -0
  64. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_mux_with_same_signal_names.kcd +0 -0
  65. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_mux_with_same_signal_names_labeled_mux_variants.kcd +0 -0
  66. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_mux_with_scale_and_offset.kcd +0 -0
  67. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_non_encodable_0.kcd +0 -0
  68. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_overlapping_signals.kcd +0 -0
  69. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_periodic_tx_over_can.jinja +0 -0
  70. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_procedures.jinja +0 -0
  71. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_prog_source_template.jinja +0 -0
  72. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_rx_over_can.jinja +0 -0
  73. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_direction.kcd +0 -0
  74. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/double_plain.kcd +0 -0
  75. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/double_scaled.kcd +0 -0
  76. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/double_scaled_offset.kcd +0 -0
  77. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/float_plain.kcd +0 -0
  78. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/float_scaled.kcd +0 -0
  79. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/float_scaled_offset.kcd +0 -0
  80. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_matching_fractional_scaled.kcd +0 -0
  81. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_matching_fractional_scaled_offset.kcd +0 -0
  82. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_matching_plain.kcd +0 -0
  83. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_matching_scaled.kcd +0 -0
  84. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_matching_scaled_offset.kcd +0 -0
  85. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_nonmatching_fractional_scaled.kcd +0 -0
  86. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_nonmatching_plain.kcd +0 -0
  87. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_nonmatching_scaled.kcd +0 -0
  88. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_nonmatching_scaled_offset.kcd +0 -0
  89. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/test_decoding_numerical_value.jinja +0 -0
  90. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/test_encoding_numerical_value.jinja +0 -0
  91. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/test_signal_encoding_wrong_float_size.kcd +0 -0
  92. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_matching_fractional_scaled.kcd +0 -0
  93. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_matching_plain.kcd +0 -0
  94. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_matching_scaled.kcd +0 -0
  95. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_matching_scaled_offset.kcd +0 -0
  96. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_nonmatching_fractional_scaled.kcd +0 -0
  97. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_nonmatching_plain.kcd +0 -0
  98. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_nonmatching_scaled.kcd +0 -0
  99. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_nonmatching_scaled_offset.kcd +0 -0
  100. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_signal_info.kcd +0 -0
  101. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_time_callback_arg.py +0 -0
  102. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_too_large_message.kcd +0 -0
  103. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_tx_function.py +0 -0
  104. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_tx_over_can.jinja +0 -0
  105. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_utils.py +0 -0
  106. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_vcan_round_trip.py +0 -0
  107. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_yaml_cbdb.py +0 -0
  108. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_yaml_input.py +0 -0
  109. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/tests/test_yaml_kcd_equivalence.py +0 -0
  110. {can_api_generator-2.4.0 → can_api_generator-2.6.0}/todo.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: can-api-generator
3
- Version: 2.4.0
3
+ Version: 2.6.0
4
4
  Summary: Program to generate a 'signal' style CAN API in C
5
5
  Project-URL: Documentation, https://sr.ht/~laplace/can-api-generator#readme
6
6
  Project-URL: Issues, https://sr.ht/~laplace/can-api-generator/issues
@@ -20,6 +20,7 @@ Requires-Dist: can-db-model>=0.1
20
20
  Requires-Dist: cantools
21
21
  Requires-Dist: click
22
22
  Requires-Dist: jinja2
23
+ Requires-Dist: pyyaml
23
24
  Requires-Dist: rich
24
25
  Description-Content-Type: text/markdown
25
26
 
@@ -56,6 +57,7 @@ Description-Content-Type: text/markdown
56
57
  - [`api`](#api)
57
58
  - [`cbdb`](#cbdb)
58
59
  - [`dyndec`](#dyndec)
60
+ - [Config file](#config-file)
59
61
 
60
62
  ## Installation
61
63
 
@@ -242,6 +244,31 @@ slot (`now % period == offset`); the missed slot is skipped, not caught up.
242
244
  The `tx_callback` rules are unchanged: in callback-only mode a `NULL` return queues nothing, and `send_now` stays set as well. Works with and without
243
245
  `--tx-signal-store`.
244
246
 
247
+ #### Delta timing (`--tx-timing delta`)
248
+ By default `<api>_periodic_tx` takes an absolute time and fires a message only when `now % period == offset`, so it has to be called on every tick. With
249
+ `--tx-timing delta` it instead takes the number of ticks elapsed since the previous call, and each message counts down on its own:
250
+ ```c
251
+ void <api>_periodic_tx_init(struct <Prefix>TxSignals *tx_signals);
252
+ uint16_t <api>_periodic_tx(struct <Prefix>TxSignals *tx_signals, CanFrame *output_frame, uint32_t dt);
253
+
254
+ uint32_t dt = now - prev;
255
+ prev = now;
256
+ while (<api>_periodic_tx(&tx_signals, &frame, dt)) {
257
+ send(&frame);
258
+ dt = 0; // drain the other messages that fell due
259
+ }
260
+ ```
261
+ Every message whose countdown runs out during `dt` is marked pending; one pending frame is returned per call, in message order, and the rest are drained by calling
262
+ again with `dt = 0`. With `--tx-function` there is no draining: one call hands every pending message to the sink, and a frame the sink rejects stays pending and is
263
+ retried on the next call (unlike absolute timing, where the slot is missed).
264
+ If `dt` spans several periods (a stall), the message is sent once and rescheduled to the next slot on its original grid, so its phase does not change. A message
265
+ whose countdown runs out while `enable` is 0 is skipped, but its countdown keeps running. In callback-only mode a `NULL` callback return sends nothing for that slot.
266
+ `send_now` behaves as with absolute timing; if it coincides with a due slot, a single frame is sent.
267
+
268
+ In `MsgTxContext`, `last_tx` is replaced by `remaining` (ticks until the next slot) and `pending`. `<api>_periodic_tx_init` takes no time argument, and
269
+ `<api>_update_tx_period(ctx, period)` re-phases the message to `offset + 1` ticks from the next call.
270
+ `--time-as-tx-callback-arg` cannot be combined with delta timing, as there is no absolute time to pass.
271
+
245
272
  ### Encode All Signals
246
273
  Some messages use multiplexes combining many signals in to a single message. This may be particularly true for configuration messages that have many related parameters and use
247
274
  multiplexes to fit all parameters in to a single message using many mux groups. If the entire config should be read out and the read back happens periodically, the supervisory controller
@@ -610,6 +637,7 @@ to a mux group. The message list is followed immediately by that message's signa
610
637
  ## Command-line reference
611
638
 
612
639
  All generation is done through subcommands of `can-api-gen`. Run `can-api-gen COMMAND --help` for the authoritative option list.
640
+ Every command also accepts `--config FILE`; see [Config file](#config-file).
613
641
 
614
642
  ### `api`
615
643
 
@@ -663,6 +691,8 @@ Options:
663
691
  `<api>_periodic_tx(tx_signals, now)` then queues every due message through it in one pass and returns the number accepted. A 0 return leaves `last_tx` and
664
692
  `send_now` untouched so the message is retried. See [Queueing frames through a bound TX function](#queueing-frames-through-a-bound-tx-function). Only meaningful
665
693
  together with `-p`.
694
+ * `--tx-timing [absolute|delta]` — `absolute` (default) passes the current time to `<api>_periodic_tx`; `delta` passes the ticks since the previous call. See
695
+ [Delta timing](#delta-timing---tx-timing-delta). Only meaningful together with `-p`; cannot be combined with `--time-as-tx-callback-arg`.
666
696
  * `--rx-signal-store` — generate the permanent per-message RX signal store (the pre-2.0 default). Without it, global RX is callback-only: no store is
667
697
  generated and decoded signals reach the application only through the RX callback. See [Global RX](#global-rx). Only meaningful together with `-r`.
668
698
  * `--with-rx-valid` — in callback-only mode, add a `valid` field to `MsgRxContext` and set it on each matched frame, for struct-layout compatibility with
@@ -679,6 +709,8 @@ Options:
679
709
  to an existing project's frame struct naming.
680
710
  * `-lm`, `--length-member-name TEXT` — override the name of the `CanFrame` length member (default `length`).
681
711
  * `-pm`, `--payload-member-name TEXT` — override the name of the `CanFrame` payload member (default `payload`).
712
+ * `--seed INT` — seed the random number generator that picks each periodic message's `offset_seed`, so repeated runs produce identical output.
713
+ Unseeded by default, so every run picks new values.
682
714
  * `-v`, `--verbose` — dump the processed message dicts that get fed into the Jinja templates. Useful when debugging template or generator changes.
683
715
  * `--help` — show the CLI help and exit.
684
716
 
@@ -715,3 +747,27 @@ Options:
715
747
  * `-lm`, `--length-member-name TEXT` — override the name of the `CanFrame` length member (default `length`).
716
748
  * `-pm`, `--payload-member-name TEXT` — override the name of the `CanFrame` payload member (default `payload`).
717
749
  * `--help` — show the CLI help and exit.
750
+
751
+ ### Config file
752
+
753
+ Instead of repeating a long flag list, put the options in a YAML file and pass it with `--config FILE` to any command. The file has one section per command, so
754
+ one file can serve `api`, `cbdb` and `dyndec`; a command reads only its own section. Keys are the long option names without the leading `--` or the
755
+ positional argument names (dashes or underscores both work), and values are the option values (`true`/`false` for flags). A relative `db-file` is resolved
756
+ against the config file's directory; output files are still written to the working directory.
757
+
758
+ ```yaml
759
+ api:
760
+ db-file: db.kcd
761
+ library-name: my_api
762
+ node-name: DUT
763
+ periodic-tx: true
764
+ tx-signal-store: true
765
+ time-as-tx-callback-arg: true
766
+ id-member-name: can_id
767
+ dyndec:
768
+ id-member-name: can_id
769
+ ```
770
+
771
+ Anything given on the command line overrides the file. Every flag has a `--no-` form for this, e.g.
772
+ `can-api-gen api --config can.yaml --no-tx-signal-store`. Positional arguments given on the command line fill from the left and take precedence over the
773
+ file, so `can-api-gen api --config can.yaml other.kcd other_api DUT` ignores all three from the file. An unknown key or an invalid value is an error.
@@ -31,6 +31,7 @@
31
31
  - [`api`](#api)
32
32
  - [`cbdb`](#cbdb)
33
33
  - [`dyndec`](#dyndec)
34
+ - [Config file](#config-file)
34
35
 
35
36
  ## Installation
36
37
 
@@ -217,6 +218,31 @@ slot (`now % period == offset`); the missed slot is skipped, not caught up.
217
218
  The `tx_callback` rules are unchanged: in callback-only mode a `NULL` return queues nothing, and `send_now` stays set as well. Works with and without
218
219
  `--tx-signal-store`.
219
220
 
221
+ #### Delta timing (`--tx-timing delta`)
222
+ By default `<api>_periodic_tx` takes an absolute time and fires a message only when `now % period == offset`, so it has to be called on every tick. With
223
+ `--tx-timing delta` it instead takes the number of ticks elapsed since the previous call, and each message counts down on its own:
224
+ ```c
225
+ void <api>_periodic_tx_init(struct <Prefix>TxSignals *tx_signals);
226
+ uint16_t <api>_periodic_tx(struct <Prefix>TxSignals *tx_signals, CanFrame *output_frame, uint32_t dt);
227
+
228
+ uint32_t dt = now - prev;
229
+ prev = now;
230
+ while (<api>_periodic_tx(&tx_signals, &frame, dt)) {
231
+ send(&frame);
232
+ dt = 0; // drain the other messages that fell due
233
+ }
234
+ ```
235
+ Every message whose countdown runs out during `dt` is marked pending; one pending frame is returned per call, in message order, and the rest are drained by calling
236
+ again with `dt = 0`. With `--tx-function` there is no draining: one call hands every pending message to the sink, and a frame the sink rejects stays pending and is
237
+ retried on the next call (unlike absolute timing, where the slot is missed).
238
+ If `dt` spans several periods (a stall), the message is sent once and rescheduled to the next slot on its original grid, so its phase does not change. A message
239
+ whose countdown runs out while `enable` is 0 is skipped, but its countdown keeps running. In callback-only mode a `NULL` callback return sends nothing for that slot.
240
+ `send_now` behaves as with absolute timing; if it coincides with a due slot, a single frame is sent.
241
+
242
+ In `MsgTxContext`, `last_tx` is replaced by `remaining` (ticks until the next slot) and `pending`. `<api>_periodic_tx_init` takes no time argument, and
243
+ `<api>_update_tx_period(ctx, period)` re-phases the message to `offset + 1` ticks from the next call.
244
+ `--time-as-tx-callback-arg` cannot be combined with delta timing, as there is no absolute time to pass.
245
+
220
246
  ### Encode All Signals
221
247
  Some messages use multiplexes combining many signals in to a single message. This may be particularly true for configuration messages that have many related parameters and use
222
248
  multiplexes to fit all parameters in to a single message using many mux groups. If the entire config should be read out and the read back happens periodically, the supervisory controller
@@ -585,6 +611,7 @@ to a mux group. The message list is followed immediately by that message's signa
585
611
  ## Command-line reference
586
612
 
587
613
  All generation is done through subcommands of `can-api-gen`. Run `can-api-gen COMMAND --help` for the authoritative option list.
614
+ Every command also accepts `--config FILE`; see [Config file](#config-file).
588
615
 
589
616
  ### `api`
590
617
 
@@ -638,6 +665,8 @@ Options:
638
665
  `<api>_periodic_tx(tx_signals, now)` then queues every due message through it in one pass and returns the number accepted. A 0 return leaves `last_tx` and
639
666
  `send_now` untouched so the message is retried. See [Queueing frames through a bound TX function](#queueing-frames-through-a-bound-tx-function). Only meaningful
640
667
  together with `-p`.
668
+ * `--tx-timing [absolute|delta]` — `absolute` (default) passes the current time to `<api>_periodic_tx`; `delta` passes the ticks since the previous call. See
669
+ [Delta timing](#delta-timing---tx-timing-delta). Only meaningful together with `-p`; cannot be combined with `--time-as-tx-callback-arg`.
641
670
  * `--rx-signal-store` — generate the permanent per-message RX signal store (the pre-2.0 default). Without it, global RX is callback-only: no store is
642
671
  generated and decoded signals reach the application only through the RX callback. See [Global RX](#global-rx). Only meaningful together with `-r`.
643
672
  * `--with-rx-valid` — in callback-only mode, add a `valid` field to `MsgRxContext` and set it on each matched frame, for struct-layout compatibility with
@@ -654,6 +683,8 @@ Options:
654
683
  to an existing project's frame struct naming.
655
684
  * `-lm`, `--length-member-name TEXT` — override the name of the `CanFrame` length member (default `length`).
656
685
  * `-pm`, `--payload-member-name TEXT` — override the name of the `CanFrame` payload member (default `payload`).
686
+ * `--seed INT` — seed the random number generator that picks each periodic message's `offset_seed`, so repeated runs produce identical output.
687
+ Unseeded by default, so every run picks new values.
657
688
  * `-v`, `--verbose` — dump the processed message dicts that get fed into the Jinja templates. Useful when debugging template or generator changes.
658
689
  * `--help` — show the CLI help and exit.
659
690
 
@@ -690,3 +721,27 @@ Options:
690
721
  * `-lm`, `--length-member-name TEXT` — override the name of the `CanFrame` length member (default `length`).
691
722
  * `-pm`, `--payload-member-name TEXT` — override the name of the `CanFrame` payload member (default `payload`).
692
723
  * `--help` — show the CLI help and exit.
724
+
725
+ ### Config file
726
+
727
+ Instead of repeating a long flag list, put the options in a YAML file and pass it with `--config FILE` to any command. The file has one section per command, so
728
+ one file can serve `api`, `cbdb` and `dyndec`; a command reads only its own section. Keys are the long option names without the leading `--` or the
729
+ positional argument names (dashes or underscores both work), and values are the option values (`true`/`false` for flags). A relative `db-file` is resolved
730
+ against the config file's directory; output files are still written to the working directory.
731
+
732
+ ```yaml
733
+ api:
734
+ db-file: db.kcd
735
+ library-name: my_api
736
+ node-name: DUT
737
+ periodic-tx: true
738
+ tx-signal-store: true
739
+ time-as-tx-callback-arg: true
740
+ id-member-name: can_id
741
+ dyndec:
742
+ id-member-name: can_id
743
+ ```
744
+
745
+ Anything given on the command line overrides the file. Every flag has a `--no-` form for this, e.g.
746
+ `can-api-gen api --config can.yaml --no-tx-signal-store`. Positional arguments given on the command line fill from the left and take precedence over the
747
+ file, so `can-api-gen api --config can.yaml other.kcd other_api DUT` ignores all three from the file. An unknown key or an invalid value is an error.
@@ -28,6 +28,7 @@ dependencies = [
28
28
  'jinja2',
29
29
  'click',
30
30
  'rich',
31
+ 'pyyaml',
31
32
  ]
32
33
 
33
34
  [project.urls]
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2025-present Alexande Becker <nabla.becker@mailbox.org>
2
2
  #
3
3
  # SPDX-License-Identifier: LGPL-3.0-only
4
- __version__ = "2.4.0"
4
+ __version__ = "2.6.0"
@@ -52,6 +52,7 @@ def generate_api_implementation(msgs: list[dict],
52
52
  tx_function: bool = False,
53
53
  time_as_rx_callback_arg: bool = False,
54
54
  time_as_tx_callback_arg: bool = False,
55
+ tx_timing: str = "absolute",
55
56
  ):
56
57
  header_template = jinja_env.get_template("header_template.jinja")
57
58
  source_template = jinja_env.get_template("source_template.jinja")
@@ -94,6 +95,7 @@ def generate_api_implementation(msgs: list[dict],
94
95
  tx_function=tx_function,
95
96
  time_as_rx_callback_arg=time_as_rx_callback_arg,
96
97
  time_as_tx_callback_arg=time_as_tx_callback_arg,
98
+ tx_timing=tx_timing,
97
99
  )
98
100
  source_code = source_template.render(
99
101
  messages=msgs,
@@ -123,6 +125,7 @@ def generate_api_implementation(msgs: list[dict],
123
125
  tx_function=tx_function,
124
126
  time_as_rx_callback_arg=time_as_rx_callback_arg,
125
127
  time_as_tx_callback_arg=time_as_tx_callback_arg,
128
+ tx_timing=tx_timing,
126
129
  )
127
130
  canframe_header_code = canframe_template.render(
128
131
  arb_id_member_name=arb_id_member_name,
@@ -1,6 +1,7 @@
1
1
  from can_api_generator.types import SignalDirection
2
2
  from importlib.metadata import files
3
3
  import hashlib
4
+ import random
4
5
  import json
5
6
  import click
6
7
  import rich
@@ -12,6 +13,55 @@ from can_api_generator.cbdb_generator import generate_cbdb, build_sidecar, CbdbG
12
13
  from can_api_generator.utils import converrt_to_c_identifier
13
14
  from jinja2 import Environment, FileSystemLoader
14
15
  from pathlib import Path
16
+ import yaml
17
+
18
+
19
+ def _load_config(ctx, param, value):
20
+ """Turn the command's section of a YAML config file into click defaults.
21
+
22
+ The file maps command names to option tables, e.g. {"api": {"periodic-tx":
23
+ true}}. Keys are long option names or positional argument names (dashes
24
+ or underscores). The values become ctx.default_map, so everything given on
25
+ the command line still takes precedence over the file. Relative file
26
+ arguments are resolved against the config file's directory.
27
+ """
28
+ if value is None:
29
+ return
30
+ try:
31
+ with open(value) as config_file:
32
+ data = yaml.safe_load(config_file)
33
+ except (OSError, yaml.YAMLError) as e:
34
+ raise click.BadParameter(f"cannot read config file: {e}", ctx, param)
35
+ if data is None:
36
+ data = {}
37
+ if not isinstance(data, dict):
38
+ raise click.BadParameter(
39
+ "the config file must map command names to option tables", ctx, param)
40
+ section = data.get(ctx.info_name) or {}
41
+ if not isinstance(section, dict):
42
+ raise click.BadParameter(
43
+ f"section '{ctx.info_name}' must map option names to values", ctx, param)
44
+ known = {p.name: p for p in ctx.command.params if p.name != "config"}
45
+ defaults = {}
46
+ for key, option_value in section.items():
47
+ name = str(key).replace("-", "_")
48
+ if name not in known:
49
+ raise click.BadParameter(
50
+ f"unknown option '{key}' in section '{ctx.info_name}'", ctx, param)
51
+ if (isinstance(known[name], click.Argument)
52
+ and isinstance(known[name].type, click.Path)
53
+ and isinstance(option_value, str)):
54
+ option_value = str(Path(value).parent / option_value)
55
+ defaults[name] = option_value
56
+ ctx.default_map = {**(ctx.default_map or {}), **defaults}
57
+
58
+
59
+ config_option = click.option(
60
+ "--config", type=click.Path(exists=True, dir_okay=False),
61
+ is_eager=True, expose_value=False, callback=_load_config,
62
+ help="YAML file providing defaults for this command's options and "
63
+ "arguments, under a section named after the command (api, cbdb, "
64
+ "dyndec). Anything given on the command line overrides the file.")
15
65
 
16
66
 
17
67
  @click.group()
@@ -23,13 +73,14 @@ def main():
23
73
 
24
74
 
25
75
  @main.command()
76
+ @config_option
26
77
  @click.argument("DB_File", type=click.Path(exists=True, dir_okay=False),
27
78
  required=True, nargs=1)
28
79
  @click.argument("Library_Name", type=str, required=True)
29
80
  @click.argument("Node_Name", type=str, required=True)
30
- @click.option("-f", "--force", is_flag=True, required=False, default=False,
81
+ @click.option("-f", "--force/--no-force", required=False, default=False,
31
82
  help="Overwrite existing output files")
32
- @click.option("-a", "--encode-all-signals-func", is_flag=True, default=False,
83
+ @click.option("-a", "--encode-all-signals-func/--no-encode-all-signals-func", default=False,
33
84
  help="Generate a function for each message that encodes every signal into frames at least once,"
34
85
  " this is helpful when some signals are inside of a mux, but all signals need to be sent "
35
86
  " regardless of how many frames need to be encoded to send the state of each signal at least"
@@ -50,74 +101,74 @@ def main():
50
101
  @click.option("-pm", "--payload-member-name", type=str, default="payload",
51
102
  help="The name of the CanFrame struct member that holds the "
52
103
  "payload as uint64")
53
- @click.option("-r", "--global-rx", is_flag=True, default=False,
104
+ @click.option("-r", "--global-rx/--no-global-rx", default=False,
54
105
  help="Generate a function and api-wide struct that updates an"
55
106
  " api message struct upon reception of a matching frame")
56
- @click.option("-p", "--periodic-tx", is_flag=True, default=False,
107
+ @click.option("-p", "--periodic-tx/--no-periodic-tx", default=False,
57
108
  help="Generate code that will define a TX struct as well as a function "
58
109
  "that periodically sends the messages according to their "
59
110
  "periodicity")
60
- @click.option("--ti-compatible", is_flag=True, default=False,
111
+ @click.option("--ti-compatible/--no-ti-compatible", default=False,
61
112
  help="Generate C code that can be compiled using the TI C2000 compiler"
62
113
  " The TI compiler does not handle variable declarations inside of case"
63
114
  " blocks so the variable declaration is moved to the top of a switch")
64
- @click.option("--send-now", is_flag=True, default=False,
115
+ @click.option("--send-now/--no-send-now", default=False,
65
116
  help="Add mechanism in to the periodic tx function that allows to immediately "
66
117
  "send a message even if it is not enabled or has just been sent."
67
118
  " This is useful for updating critical signals as early as possible after a change")
68
- @click.option("--double-buffered", is_flag=True, default=False,
119
+ @click.option("--double-buffered/--no-double-buffered", default=False,
69
120
  help="Generate double-buffered signal storage so that an ISR can safely decode"
70
121
  " RX frames without exposing a partially-decoded struct to readers, and so"
71
122
  " that user code can stage TX signal updates without racing the periodic TX"
72
123
  " encoder. Swaps are performed via per-message pointer swaps; an explicit"
73
124
  " <api>_<msg>_commit call is required to publish staged TX values.")
74
- @click.option("--rx-callback-context", is_flag=True, default=False,
125
+ @click.option("--rx-callback-context/--no-rx-callback-context", default=False,
75
126
  help="Add a void *rx_callback_context field to MsgRxContext and "
76
127
  "pass it as the sole argument to the RX callback, widening "
77
128
  "its signature to void (*rx_callback)(void *).")
78
- @click.option("--tx-callback-context", is_flag=True, default=False,
129
+ @click.option("--tx-callback-context/--no-tx-callback-context", default=False,
79
130
  help="Add a void *tx_callback_context field to MsgTxContext and "
80
131
  "pass it as the sole argument to the TX callback, widening "
81
132
  "its signature to void (*tx_callback)(void *).")
82
- @click.option("--msg-as-rx-callback-arg", is_flag=True, default=False,
133
+ @click.option("--msg-as-rx-callback-arg/--no-msg-as-rx-callback-arg", default=False,
83
134
  help="Pass a void * to the message's just-decoded signal struct "
84
135
  "as the first argument to the RX callback. Composes with "
85
136
  "--rx-callback-context, which appends the context pointer as "
86
137
  "a second argument. Only meaningful together with -r.")
87
- @click.option("--rx-signal-store", is_flag=True, default=False,
138
+ @click.option("--rx-signal-store/--no-rx-signal-store", default=False,
88
139
  help="Generate a permanent per-message RX signal store in the "
89
140
  "global RX struct (the default before v2.0). Without it, "
90
141
  "global RX is callback-only: decoded signals reach the "
91
142
  "application solely through the per-message RX callback and "
92
143
  "are never stored. Only meaningful together with -r.")
93
- @click.option("--with-rx-valid", is_flag=True, default=False,
144
+ @click.option("--with-rx-valid/--no-with-rx-valid", default=False,
94
145
  help="In callback-only mode (no --rx-signal-store), add a valid "
95
146
  "field to MsgRxContext and set it on each matched frame, for "
96
147
  "struct-layout compatibility with store-based APIs. No effect "
97
148
  "when --rx-signal-store is set.")
98
- @click.option("--msg-as-tx-callback-arg", is_flag=True, default=False,
149
+ @click.option("--msg-as-tx-callback-arg/--no-msg-as-tx-callback-arg", default=False,
99
150
  help="Pass a void * to the message's store struct (the one about "
100
151
  "to be encoded) as the first argument to the TX callback. "
101
152
  "Composes with --tx-callback-context, which appends the "
102
153
  "context pointer as a second argument. Only meaningful "
103
154
  "together with -p.")
104
- @click.option("--tx-signal-store", is_flag=True, default=False,
155
+ @click.option("--tx-signal-store/--no-tx-signal-store", default=False,
105
156
  help="Generate a permanent per-message TX signal store in the "
106
157
  "periodic-TX struct (the default before v2.0). Without it, "
107
158
  "periodic TX is callback-only: each scheduled message's "
108
159
  "callback returns the struct to encode, and a NULL return or "
109
160
  "missing callback sends nothing. Only meaningful with -p.")
110
- @click.option("--time-as-rx-callback-arg", is_flag=True, default=False,
161
+ @click.option("--time-as-rx-callback-arg/--no-time-as-rx-callback-arg", default=False,
111
162
  help="Pass the current time (the now argument of "
112
163
  "<api>_process_received_frame) to the RX callback as a "
113
164
  "uint64_t, after the message pointer and before the context "
114
165
  "pointer. Only meaningful together with -r.")
115
- @click.option("--time-as-tx-callback-arg", is_flag=True, default=False,
166
+ @click.option("--time-as-tx-callback-arg/--no-time-as-tx-callback-arg", default=False,
116
167
  help="Pass the current time (the now argument of "
117
168
  "<api>_periodic_tx) to the TX callback as a uint64_t, after "
118
169
  "the message pointer and before the context pointer. Only "
119
170
  "meaningful together with -p.")
120
- @click.option("--tx-function", is_flag=True, default=False,
171
+ @click.option("--tx-function/--no-tx-function", default=False,
121
172
  help="Bind a frame sink to the periodic-TX API instead of "
122
173
  "returning one frame per call. <api>_periodic_tx_init takes "
123
174
  "a uint16_t (*tx_function)(void *, const <frame> *) and its "
@@ -125,18 +176,30 @@ def main():
125
176
  "due message to it in a single pass and returns the number "
126
177
  "accepted. A 0 return leaves last_tx and send_now untouched, "
127
178
  "so the message is retried. Only meaningful with -p.")
128
- @click.option("--prefix-signal-struct-names", is_flag=True, default=False,
179
+ @click.option("--tx-timing", type=click.Choice(["absolute", "delta"]),
180
+ default="absolute", show_default=True,
181
+ help="Timing argument of the periodic tx function. 'absolute' takes "
182
+ "the current time and sends a message when it hits its "
183
+ "period/offset slot. 'delta' takes the ticks elapsed since the "
184
+ "previous call and counts each message down; without "
185
+ "--tx-function the messages that are due are sent one per call "
186
+ "and drained by further calls with dt=0. Only meaningful with -p.")
187
+ @click.option("--prefix-signal-struct-names/--no-prefix-signal-struct-names", default=False,
129
188
  help="Prefix every per-message signal struct type name with the "
130
189
  "CamelCase API prefix (the same prefix used for "
131
190
  "<Prefix>RxSignals / <Prefix>TxSignals), so two similar APIs "
132
191
  "(e.g. two versions of a database) can coexist in one "
133
192
  "translation unit with distinct struct names.")
134
- @click.option("--prefix-context-struct-names", is_flag=True, default=False,
193
+ @click.option("--prefix-context-struct-names/--no-prefix-context-struct-names", default=False,
135
194
  help="Prefix the MsgRxContext / MsgTxContext struct type names (and "
136
195
  "their include-guard tokens) with the CamelCase API prefix, so "
137
196
  "context structs from two coexisting APIs do not share one "
138
197
  "definition.")
139
- @click.option("-v", "--verbose", is_flag=True, default=False,
198
+ @click.option("--seed", type=int, default=None,
199
+ help="Seed for the random number generator that picks each "
200
+ "message's offset_seed, so repeated runs produce identical "
201
+ "output. Unseeded by default.")
202
+ @click.option("-v", "--verbose/--no-verbose", default=False,
140
203
  help="Print the Data that is passed to the template that "
141
204
  "generates the code")
142
205
  def api(db_file, library_name, node_name, force, encode_all_signals_func,
@@ -145,12 +208,18 @@ def api(db_file, library_name, node_name, force, encode_all_signals_func,
145
208
  rx_callback_context, tx_callback_context, msg_as_rx_callback_arg,
146
209
  rx_signal_store, with_rx_valid, msg_as_tx_callback_arg,
147
210
  tx_signal_store, time_as_rx_callback_arg, time_as_tx_callback_arg,
148
- tx_function, prefix_signal_struct_names,
149
- prefix_context_struct_names, verbose):
211
+ tx_function, tx_timing, prefix_signal_struct_names,
212
+ prefix_context_struct_names, seed, verbose):
150
213
  """
151
214
  Generate the static C API for NODE_NAME from DB_FILE (.kcd or .yaml)
152
215
  """
216
+ if seed is not None:
217
+ random.seed(seed)
153
218
  db_path = Path(db_file)
219
+ if tx_timing == "delta" and time_as_tx_callback_arg:
220
+ raise click.UsageError(
221
+ "--time-as-tx-callback-arg needs the absolute time and cannot be "
222
+ "combined with --tx-timing delta")
154
223
  try:
155
224
  basepath = Path(library_name)
156
225
  source_path = basepath.parent / f"{basepath.stem}.c"
@@ -217,6 +286,7 @@ def api(db_file, library_name, node_name, force, encode_all_signals_func,
217
286
  tx_function=tx_function,
218
287
  time_as_rx_callback_arg=time_as_rx_callback_arg,
219
288
  time_as_tx_callback_arg=time_as_tx_callback_arg,
289
+ tx_timing=tx_timing,
220
290
  )
221
291
  if not user_supplied_canframe:
222
292
  with (header_path.parent / 'canframe.h').open('w+') as canframe_header_file:
@@ -228,10 +298,11 @@ def api(db_file, library_name, node_name, force, encode_all_signals_func,
228
298
 
229
299
 
230
300
  @main.command()
301
+ @config_option
231
302
  @click.argument("DB_File", type=click.Path(exists=True, dir_okay=False),
232
303
  required=True, nargs=1)
233
304
  @click.argument("Output_Base", type=str, required=True)
234
- @click.option("-f", "--force", is_flag=True, required=False, default=False,
305
+ @click.option("-f", "--force/--no-force", required=False, default=False,
235
306
  help="Overwrite existing output files")
236
307
  def cbdb(db_file, output_base, force):
237
308
  """
@@ -267,8 +338,9 @@ def cbdb(db_file, output_base, force):
267
338
 
268
339
 
269
340
  @main.command()
341
+ @config_option
270
342
  @click.argument("Library_Name", type=str, required=True)
271
- @click.option("-f", "--force", is_flag=True, required=False, default=False,
343
+ @click.option("-f", "--force/--no-force", required=False, default=False,
272
344
  help="Overwrite existing output files")
273
345
  @click.option("-im", "--id-member-name", type=str, default="arbitration_id",
274
346
  help="The name of the CanFrame struct member that holds the "
@@ -47,17 +47,22 @@
47
47
  {%- endif %}
48
48
  {% endif %}
49
49
  {%- if periodic_tx %}
50
- {{ define_tx_context_struct(true, 'uint16_t', 'uint64_t', send_now, tx_callback_context=tx_callback_context, msg_as_tx_callback_arg=msg_as_tx_callback_arg, ctx_name=tx_context_struct_name, time_as_tx_callback_arg=time_as_tx_callback_arg) }}
50
+ {{ define_tx_context_struct(true, 'uint16_t', 'uint64_t', send_now, tx_callback_context=tx_callback_context, msg_as_tx_callback_arg=msg_as_tx_callback_arg, ctx_name=tx_context_struct_name, time_as_tx_callback_arg=time_as_tx_callback_arg, tx_timing=tx_timing) }}
51
51
 
52
52
  {{ generate_global_tx_struct(messages, api_name_struct_prefix, double_buffered, tx_signal_store=tx_signal_store, ctx_name=tx_context_struct_name, can_frame_type=can_frame_type, tx_function=tx_function) }}
53
53
 
54
- {{ declare_periodic_tx_init_function(api_name, api_name_struct_prefix, 'uint64_t', can_frame_type, tx_function) }};
54
+ {{ declare_periodic_tx_init_function(api_name, api_name_struct_prefix, 'uint64_t', can_frame_type, tx_function, tx_timing) }};
55
55
 
56
56
  {{ declare_get_msg_tx_context_by_id(messages, api_name, api_name_struct_prefix, 'struct ' ~ tx_context_struct_name) }};
57
57
 
58
- {{ declare_period_update_function(api_name, 'struct ' ~ tx_context_struct_name) }}
58
+ {{ declare_period_update_function(api_name, 'struct ' ~ tx_context_struct_name, tx_timing) }}
59
+ {%- if tx_timing == 'delta' %}
60
+
61
+ {{ declare_periodic_tx_function(api_name, api_name_struct_prefix, can_frame_type, 'uint16_t', 'uint32_t', 'uint16_t', tx_function, 'dt') }};
62
+ {%- else %}
59
63
 
60
64
  {{ declare_periodic_tx_function(api_name, api_name_struct_prefix, can_frame_type, 'uint16_t', 'uint64_t', 'uint16_t', tx_function) }};
65
+ {%- endif %}
61
66
  {%- if double_buffered and tx_signal_store %}
62
67
  {%- for message in messages %}
63
68
  {%- if message.direction == 'tx' or message.direction == 'rt' %}
@@ -7,6 +7,7 @@
7
7
  {% from "rx_macros.jinja" import generate_global_rx_frame_process_function -%}
8
8
  {% from "rx_macros.jinja" import define_global_rx_init_function -%}
9
9
  {% from "tx_macros.jinja" import define_periodic_tx_function -%}
10
+ {% from "tx_macros.jinja" import define_periodic_tx_delta_function -%}
10
11
  {% from "tx_macros.jinja" import define_periodic_tx_init_function -%}
11
12
  {% from "tx_macros.jinja" import define_get_msg_tx_context_by_id -%}
12
13
  {% from "tx_macros.jinja" import define_commit_function -%}
@@ -21,12 +22,17 @@
21
22
  {%- endif %}
22
23
  {%- endif %}
23
24
  {%- if periodic_tx %}
25
+ {%- if tx_timing == 'delta' %}
26
+
27
+ {{ define_periodic_tx_delta_function(messages, api_name, api_name_struct_prefix, can_frame_type, 'uint16_t', 'uint16_t', true, send_now, double_buffered, tx_callback_context=tx_callback_context, msg_as_tx_callback_arg=msg_as_tx_callback_arg, tx_signal_store=tx_signal_store, tx_function=tx_function) }}
28
+ {%- else %}
24
29
 
25
30
  {{ define_periodic_tx_function(messages, api_name, api_name_struct_prefix, can_frame_type, 'uint16_t', 'uint64_t', 'uint16_t', true, send_now, double_buffered, tx_callback_context=tx_callback_context, msg_as_tx_callback_arg=msg_as_tx_callback_arg, tx_signal_store=tx_signal_store, ctx_name=tx_context_struct_name, tx_function=tx_function, time_as_tx_callback_arg=time_as_tx_callback_arg) }}
31
+ {%- endif %}
26
32
 
27
33
  {{ define_get_msg_tx_context_by_id(messages, api_name, api_name_struct_prefix, 'struct ' ~ tx_context_struct_name) }}
28
34
 
29
- {{ define_periodic_tx_init_function(api_name, api_name_struct_prefix, 'uint64_t', messages, '1000', double_buffered, tx_signal_store=tx_signal_store, can_frame_type=can_frame_type, tx_function=tx_function) }}
35
+ {{ define_periodic_tx_init_function(api_name, api_name_struct_prefix, 'uint64_t', messages, '1000', double_buffered, tx_signal_store=tx_signal_store, can_frame_type=can_frame_type, tx_function=tx_function, tx_timing=tx_timing) }}
30
36
  {%- if double_buffered and tx_signal_store %}
31
37
  {%- for message in messages %}
32
38
  {%- if message.direction == 'tx' or message.direction == 'rt' %}