can-api-generator 2.5.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 (111) hide show
  1. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/PKG-INFO +38 -5
  2. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/README.md +37 -4
  3. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/__about__.py +1 -1
  4. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/code_generator.py +3 -0
  5. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/main.py +34 -10
  6. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/header_template.jinja +8 -3
  7. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/source_template.jinja +7 -1
  8. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/tx_macros.jinja +126 -8
  9. can_api_generator-2.6.0/tests/test_config_file.py +132 -0
  10. can_api_generator-2.6.0/tests/test_seed.py +32 -0
  11. can_api_generator-2.6.0/tests/test_tx_timing_delta.py +308 -0
  12. can_api_generator-2.6.0/uv.lock +905 -0
  13. can_api_generator-2.5.0/Advantics_Bootloader.kcd +0 -384
  14. can_api_generator-2.5.0/manual_api_test.c +0 -93
  15. can_api_generator-2.5.0/manual_api_test.h +0 -60
  16. can_api_generator-2.5.0/manual_api_test.o +0 -0
  17. can_api_generator-2.5.0/test_1_rx_over_can +0 -0
  18. can_api_generator-2.5.0/test_api.c +0 -78
  19. can_api_generator-2.5.0/test_api.h +0 -68
  20. can_api_generator-2.5.0/test_kcd_api_test_mux_with_same_signal_names_labeled_mux_variants.c +0 -686
  21. can_api_generator-2.5.0/test_mux_with_same_signal_names_labeled_mux_variants.c +0 -237
  22. can_api_generator-2.5.0/test_mux_with_same_signal_names_labeled_mux_variants.h +0 -265
  23. can_api_generator-2.5.0/test_test_1_rx_over_can.c +0 -65
  24. can_api_generator-2.5.0/test_test_basic_multiplex_rx_over_can.c +0 -67
  25. can_api_generator-2.5.0/test_test_mux_with_partially_labled_index_rx_over_can.c +0 -67
  26. can_api_generator-2.5.0/test_test_mux_with_same_signal_names_labeled_mux_variants_rx_over_can.c +0 -67
  27. can_api_generator-2.5.0/tests/test_config_file.py +0 -68
  28. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/.gitignore +0 -0
  29. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/LICENSE.txt +0 -0
  30. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/manual_cag_test.py +0 -0
  31. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/pyproject.toml +0 -0
  32. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/__init__.py +0 -0
  33. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/cbdb_generator.py +0 -0
  34. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/msg_processor.py +0 -0
  35. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/callback_macros.jinja +0 -0
  36. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/canframe.jinja +0 -0
  37. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/declaration_macros.jinja +0 -0
  38. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/dyndec_header.jinja +0 -0
  39. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/dyndec_source.jinja +0 -0
  40. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/encoding_macros.jinja +0 -0
  41. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/templates/rx_macros.jinja +0 -0
  42. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/types.py +0 -0
  43. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/src/can_api_generator/utils.py +0 -0
  44. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/__init__.py +0 -0
  45. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/cantool_test_utils.jinja +0 -0
  46. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/compat_shim.jinja +0 -0
  47. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/dyndec_test_harness.c +0 -0
  48. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/multiple_busses_test.kcd +0 -0
  49. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_1.kcd +0 -0
  50. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_basic_multiplex.kcd +0 -0
  51. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_big_endian.kcd +0 -0
  52. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_can_db_model_dep.py +0 -0
  53. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_cbdb_generator.py +0 -0
  54. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_cli.py +0 -0
  55. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_cross_interface_compat.jinja +0 -0
  56. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_cross_interface_compat.py +0 -0
  57. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_dyndec_round_trip.py +0 -0
  58. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_float_wrong_length.kcd +0 -0
  59. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_message_parser.py +0 -0
  60. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_missing_consumer.kcd +0 -0
  61. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_missing_producer.kcd +0 -0
  62. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_mux_group_count_out_of_order.kcd +0 -0
  63. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_mux_with_labeled_index.kcd +0 -0
  64. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_mux_with_partially_labled_index.kcd +0 -0
  65. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_mux_with_same_signal_names.kcd +0 -0
  66. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_mux_with_same_signal_names_labeled_mux_variants.kcd +0 -0
  67. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_mux_with_scale_and_offset.kcd +0 -0
  68. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_non_encodable_0.kcd +0 -0
  69. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_overlapping_signals.kcd +0 -0
  70. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_periodic_tx_over_can.jinja +0 -0
  71. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_procedures.jinja +0 -0
  72. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_prog_source_template.jinja +0 -0
  73. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_rx_over_can.jinja +0 -0
  74. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_direction.kcd +0 -0
  75. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/double_plain.kcd +0 -0
  76. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/double_scaled.kcd +0 -0
  77. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/double_scaled_offset.kcd +0 -0
  78. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/float_plain.kcd +0 -0
  79. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/float_scaled.kcd +0 -0
  80. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/float_scaled_offset.kcd +0 -0
  81. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_matching_fractional_scaled.kcd +0 -0
  82. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_matching_fractional_scaled_offset.kcd +0 -0
  83. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_matching_plain.kcd +0 -0
  84. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_matching_scaled.kcd +0 -0
  85. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_matching_scaled_offset.kcd +0 -0
  86. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_nonmatching_fractional_scaled.kcd +0 -0
  87. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_nonmatching_plain.kcd +0 -0
  88. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_nonmatching_scaled.kcd +0 -0
  89. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/sint_nonmatching_scaled_offset.kcd +0 -0
  90. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/test_decoding_numerical_value.jinja +0 -0
  91. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/test_encoding_numerical_value.jinja +0 -0
  92. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/test_signal_encoding_wrong_float_size.kcd +0 -0
  93. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_matching_fractional_scaled.kcd +0 -0
  94. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_matching_plain.kcd +0 -0
  95. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_matching_scaled.kcd +0 -0
  96. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_matching_scaled_offset.kcd +0 -0
  97. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_nonmatching_fractional_scaled.kcd +0 -0
  98. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_nonmatching_plain.kcd +0 -0
  99. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_nonmatching_scaled.kcd +0 -0
  100. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_encoding/uint_nonmatching_scaled_offset.kcd +0 -0
  101. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_signal_info.kcd +0 -0
  102. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_time_callback_arg.py +0 -0
  103. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_too_large_message.kcd +0 -0
  104. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_tx_function.py +0 -0
  105. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_tx_over_can.jinja +0 -0
  106. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_utils.py +0 -0
  107. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_vcan_round_trip.py +0 -0
  108. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_yaml_cbdb.py +0 -0
  109. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_yaml_input.py +0 -0
  110. {can_api_generator-2.5.0 → can_api_generator-2.6.0}/tests/test_yaml_kcd_equivalence.py +0 -0
  111. {can_api_generator-2.5.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.5.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
@@ -244,6 +244,31 @@ slot (`now % period == offset`); the missed slot is skipped, not caught up.
244
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
245
245
  `--tx-signal-store`.
246
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
+
247
272
  ### Encode All Signals
248
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
249
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
@@ -666,6 +691,8 @@ Options:
666
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
667
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
668
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`.
669
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
670
697
  generated and decoded signals reach the application only through the RX callback. See [Global RX](#global-rx). Only meaningful together with `-r`.
671
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
@@ -682,6 +709,8 @@ Options:
682
709
  to an existing project's frame struct naming.
683
710
  * `-lm`, `--length-member-name TEXT` — override the name of the `CanFrame` length member (default `length`).
684
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.
685
714
  * `-v`, `--verbose` — dump the processed message dicts that get fed into the Jinja templates. Useful when debugging template or generator changes.
686
715
  * `--help` — show the CLI help and exit.
687
716
 
@@ -722,11 +751,15 @@ Options:
722
751
  ### Config file
723
752
 
724
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
725
- one file can serve `api`, `cbdb` and `dyndec`; a command reads only its own section. Keys are the long option names without the leading `--` (dashes or
726
- underscores both work), and values are the option values (`true`/`false` for flags):
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.
727
757
 
728
758
  ```yaml
729
759
  api:
760
+ db-file: db.kcd
761
+ library-name: my_api
762
+ node-name: DUT
730
763
  periodic-tx: true
731
764
  tx-signal-store: true
732
765
  time-as-tx-callback-arg: true
@@ -736,5 +769,5 @@ dyndec:
736
769
  ```
737
770
 
738
771
  Anything given on the command line overrides the file. Every flag has a `--no-` form for this, e.g.
739
- `can-api-gen api --config can.yaml --no-tx-signal-store db.kcd my_api DUT`. Positional arguments (`DB_FILE`, `LIBRARY_NAME`, `NODE_NAME`, …) always come from
740
- the command line. An unknown key or an invalid value is an error.
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.
@@ -218,6 +218,31 @@ slot (`now % period == offset`); the missed slot is skipped, not caught up.
218
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
219
219
  `--tx-signal-store`.
220
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
+
221
246
  ### Encode All Signals
222
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
223
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
@@ -640,6 +665,8 @@ Options:
640
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
641
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
642
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`.
643
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
644
671
  generated and decoded signals reach the application only through the RX callback. See [Global RX](#global-rx). Only meaningful together with `-r`.
645
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
@@ -656,6 +683,8 @@ Options:
656
683
  to an existing project's frame struct naming.
657
684
  * `-lm`, `--length-member-name TEXT` — override the name of the `CanFrame` length member (default `length`).
658
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.
659
688
  * `-v`, `--verbose` — dump the processed message dicts that get fed into the Jinja templates. Useful when debugging template or generator changes.
660
689
  * `--help` — show the CLI help and exit.
661
690
 
@@ -696,11 +725,15 @@ Options:
696
725
  ### Config file
697
726
 
698
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
699
- one file can serve `api`, `cbdb` and `dyndec`; a command reads only its own section. Keys are the long option names without the leading `--` (dashes or
700
- underscores both work), and values are the option values (`true`/`false` for flags):
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.
701
731
 
702
732
  ```yaml
703
733
  api:
734
+ db-file: db.kcd
735
+ library-name: my_api
736
+ node-name: DUT
704
737
  periodic-tx: true
705
738
  tx-signal-store: true
706
739
  time-as-tx-callback-arg: true
@@ -710,5 +743,5 @@ dyndec:
710
743
  ```
711
744
 
712
745
  Anything given on the command line overrides the file. Every flag has a `--no-` form for this, e.g.
713
- `can-api-gen api --config can.yaml --no-tx-signal-store db.kcd my_api DUT`. Positional arguments (`DB_FILE`, `LIBRARY_NAME`, `NODE_NAME`, …) always come from
714
- the command line. An unknown key or an invalid value is an error.
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.
@@ -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.5.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
@@ -19,9 +20,10 @@ def _load_config(ctx, param, value):
19
20
  """Turn the command's section of a YAML config file into click defaults.
20
21
 
21
22
  The file maps command names to option tables, e.g. {"api": {"periodic-tx":
22
- true}}. Keys are long option names (dashes or underscores). The values
23
- become ctx.default_map, so every option given on the command line still
24
- takes precedence over the file.
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.
25
27
  """
26
28
  if value is None:
27
29
  return
@@ -39,14 +41,17 @@ def _load_config(ctx, param, value):
39
41
  if not isinstance(section, dict):
40
42
  raise click.BadParameter(
41
43
  f"section '{ctx.info_name}' must map option names to values", ctx, param)
42
- known = {p.name for p in ctx.command.params
43
- if isinstance(p, click.Option) and p.name != "config"}
44
+ known = {p.name: p for p in ctx.command.params if p.name != "config"}
44
45
  defaults = {}
45
46
  for key, option_value in section.items():
46
47
  name = str(key).replace("-", "_")
47
48
  if name not in known:
48
49
  raise click.BadParameter(
49
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)
50
55
  defaults[name] = option_value
51
56
  ctx.default_map = {**(ctx.default_map or {}), **defaults}
52
57
 
@@ -54,9 +59,9 @@ def _load_config(ctx, param, value):
54
59
  config_option = click.option(
55
60
  "--config", type=click.Path(exists=True, dir_okay=False),
56
61
  is_eager=True, expose_value=False, callback=_load_config,
57
- help="YAML file providing defaults for this command's options, under a "
58
- "section named after the command (api, cbdb, dyndec). Options given "
59
- "on the command line override the file.")
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.")
60
65
 
61
66
 
62
67
  @click.group()
@@ -171,6 +176,14 @@ def main():
171
176
  "due message to it in a single pass and returns the number "
172
177
  "accepted. A 0 return leaves last_tx and send_now untouched, "
173
178
  "so the message is retried. Only meaningful with -p.")
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.")
174
187
  @click.option("--prefix-signal-struct-names/--no-prefix-signal-struct-names", default=False,
175
188
  help="Prefix every per-message signal struct type name with the "
176
189
  "CamelCase API prefix (the same prefix used for "
@@ -182,6 +195,10 @@ def main():
182
195
  "their include-guard tokens) with the CamelCase API prefix, so "
183
196
  "context structs from two coexisting APIs do not share one "
184
197
  "definition.")
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.")
185
202
  @click.option("-v", "--verbose/--no-verbose", default=False,
186
203
  help="Print the Data that is passed to the template that "
187
204
  "generates the code")
@@ -191,12 +208,18 @@ def api(db_file, library_name, node_name, force, encode_all_signals_func,
191
208
  rx_callback_context, tx_callback_context, msg_as_rx_callback_arg,
192
209
  rx_signal_store, with_rx_valid, msg_as_tx_callback_arg,
193
210
  tx_signal_store, time_as_rx_callback_arg, time_as_tx_callback_arg,
194
- tx_function, prefix_signal_struct_names,
195
- prefix_context_struct_names, verbose):
211
+ tx_function, tx_timing, prefix_signal_struct_names,
212
+ prefix_context_struct_names, seed, verbose):
196
213
  """
197
214
  Generate the static C API for NODE_NAME from DB_FILE (.kcd or .yaml)
198
215
  """
216
+ if seed is not None:
217
+ random.seed(seed)
199
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")
200
223
  try:
201
224
  basepath = Path(library_name)
202
225
  source_path = basepath.parent / f"{basepath.stem}.c"
@@ -263,6 +286,7 @@ def api(db_file, library_name, node_name, force, encode_all_signals_func,
263
286
  tx_function=tx_function,
264
287
  time_as_rx_callback_arg=time_as_rx_callback_arg,
265
288
  time_as_tx_callback_arg=time_as_tx_callback_arg,
289
+ tx_timing=tx_timing,
266
290
  )
267
291
  if not user_supplied_canframe:
268
292
  with (header_path.parent / 'canframe.h').open('w+') as canframe_header_file:
@@ -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' %}
@@ -2,13 +2,18 @@
2
2
  {% from "encoding_macros.jinja" import rotate_multiplex_func_call %}
3
3
  {% from "callback_macros.jinja" import callback_param_types, callback_args %}
4
4
 
5
- {% macro define_tx_context_struct(tx_callback, flag_type, time_type, send_now, tx_callback_context=false, msg_as_tx_callback_arg=false, ctx_name='MsgTxContext', time_as_tx_callback_arg=false) -%}
5
+ {% macro define_tx_context_struct(tx_callback, flag_type, time_type, send_now, tx_callback_context=false, msg_as_tx_callback_arg=false, ctx_name='MsgTxContext', time_as_tx_callback_arg=false, tx_timing='absolute') -%}
6
6
  #ifndef {{ ctx_name | upper }}
7
7
  #define {{ ctx_name | upper }}
8
8
  struct {{ ctx_name }} {
9
9
  uint32_t arb_id;
10
10
  {{ flag_type }} enable;
11
+ {%- if tx_timing == 'delta' %}
12
+ {{ flag_type }} pending;
13
+ uint32_t remaining;
14
+ {%- else %}
11
15
  {{ time_type }} last_tx;
16
+ {%- endif %}
12
17
  uint32_t period;
13
18
  uint32_t offset;
14
19
  uint32_t offset_seed;
@@ -25,10 +30,13 @@ struct {{ ctx_name }} {
25
30
  #endif
26
31
  {%- endmacro %}
27
32
 
28
- {% macro declare_period_update_function(api_name, context_type) -%}
33
+ {% macro declare_period_update_function(api_name, context_type, tx_timing='absolute') -%}
29
34
  static inline void {{ api_name }}_update_tx_period({{ context_type }} *txctx, uint32_t period) {
30
35
  txctx->period = period;
31
36
  txctx->offset = txctx->offset_seed % period;
37
+ {%- if tx_timing == 'delta' %}
38
+ txctx->remaining = txctx->offset + 1;
39
+ {%- endif %}
32
40
  }
33
41
  {%- endmacro %}
34
42
 
@@ -92,8 +100,8 @@ void {{ api_name }}_{{ message.name }}_commit(struct {{ api_struct_prefix }}TxSi
92
100
  }
93
101
  {%- endmacro %}
94
102
 
95
- {% macro declare_periodic_tx_function(api_name, api_struct_prefix, can_frame_type, return_type, time_type, flag_type, tx_function=false) -%}
96
- {{ return_type }} {{ api_name }}_periodic_tx(struct {{ api_struct_prefix }}TxSignals *tx_signals, {% if not tx_function %}{{ can_frame_type }} *output_frame, {% endif %}{{ time_type }} now)
103
+ {% macro declare_periodic_tx_function(api_name, api_struct_prefix, can_frame_type, return_type, time_type, flag_type, tx_function=false, time_arg='now') -%}
104
+ {{ return_type }} {{ api_name }}_periodic_tx(struct {{ api_struct_prefix }}TxSignals *tx_signals, {% if not tx_function %}{{ can_frame_type }} *output_frame, {% endif %}{{ time_type }} {{ time_arg }})
97
105
  {%- endmacro %}
98
106
 
99
107
  {% macro tx_callback_call(message, msg_arg, tx_callback_context, msg_as_tx_callback_arg, time_as_tx_callback_arg) -%}
@@ -190,14 +198,122 @@ void {{ api_name }}_{{ message.name }}_commit(struct {{ api_struct_prefix }}TxSi
190
198
  }
191
199
  {%- endmacro %}
192
200
 
193
- {% macro declare_periodic_tx_init_function(api_name, api_struct_prefix, time_type, can_frame_type='CanFrame', tx_function=false) -%}
194
- void {{ api_name }}_periodic_tx_init(struct {{ api_struct_prefix }}TxSignals *tx_signals, {{ time_type }} now
201
+ {#
202
+ # --tx-timing delta: dt is the number of ticks since the previous call. Each
203
+ # message counts down; when its countdown runs out the message is marked
204
+ # pending (if enabled; a disabled message's slot is skipped, its countdown
205
+ # keeps running) and rescheduled to its next slot on the original grid, so a
206
+ # stall over several periods sends once and keeps the phase.
207
+ # Without a frame sink one pending frame is returned per call, in message
208
+ # order; the rest are drained by calling again with dt = 0. With a sink
209
+ # (--tx-function) every pending message is handed over in one pass, and a
210
+ # frame the sink rejects stays pending for the next call.
211
+ #}
212
+ {% macro define_periodic_tx_delta_function(messages, api_name, api_struct_prefix, can_frame_type, return_type, flag_type, tx_callback, send_now, double_buffered=False, tx_callback_context=false, msg_as_tx_callback_arg=false, tx_signal_store=false, tx_function=false) -%}
213
+ {%- set tx_messages = messages | selectattr('direction', 'in', ['tx', 'rt']) | list %}
214
+ {%- set frame_arg = '&frame' if tx_function else 'output_frame' -%}
215
+ {{ declare_periodic_tx_function(api_name, api_struct_prefix, can_frame_type, return_type, 'uint32_t', flag_type, tx_function, 'dt') }} {
216
+ {%- if tx_function %}
217
+ {{ can_frame_type }} frame;
218
+ {{ return_type }} queued = 0;
219
+ {%- endif %}
220
+ {%- for message in tx_messages %}
221
+ {%- set ctx = 'tx_signals->' ~ message.name ~ '_context' %}
222
+ if (dt >= {{ ctx }}.remaining) {
223
+ {{ ctx }}.pending = {{ ctx }}.enable;
224
+ {{ ctx }}.remaining = {{ ctx }}.period - (dt - {{ ctx }}.remaining) % {{ ctx }}.period;
225
+ } else {
226
+ {{ ctx }}.remaining -= dt;
227
+ }
228
+ {%- endfor %}
229
+ {%- for message in tx_messages %}
230
+ {%- set ctx = 'tx_signals->' ~ message.name ~ '_context' %}
231
+
232
+ if ({% if send_now %}{{ ctx }}.send_now || {% endif %}({{ ctx }}.pending && {{ ctx }}.enable)) {
233
+ {%- if not tx_signal_store %}
234
+ {%- if msg_as_tx_callback_arg %}
235
+ struct {{ message.struct_name }} {{ message.name }}_tx_storage;
236
+ {%- endif %}
237
+ void *{{ message.name }}_tx_data = 0;
238
+ {%- endif %}
239
+ {%- if not tx_function %}
240
+ {%- if send_now %}
241
+ {{ ctx }}.send_now = 0;
242
+ {%- endif %}
243
+ {{ ctx }}.pending = 0;
244
+ {%- endif %}
245
+ {%- if tx_signal_store %}
246
+ {%- set msg_arg = ('tx_signals->' ~ message.name ~ '_signals_committed') if double_buffered else ('&tx_signals->' ~ message.name ~ '_signals') %}
247
+ {%- if tx_callback %}
248
+ if ({{ ctx }}.tx_callback) {
249
+ (void){{ tx_callback_call(message, msg_arg, tx_callback_context, msg_as_tx_callback_arg, false) }};
250
+ }
251
+ {%- endif %}
252
+ {{ message_encoding_function_call(message, msg_arg, frame_arg) }};
253
+ {%- if tx_function %}
254
+ if ((*tx_signals->tx_function)(tx_signals->tx_function_context, &frame)) {
255
+ {%- if send_now %}
256
+ {{ ctx }}.send_now = 0;
257
+ {%- endif %}
258
+ {{ ctx }}.pending = 0;
259
+ {%- endif %}
260
+ {%- for signal in message.all_signals %}
261
+ {%- if signal.multiplex %}
262
+ {%- if double_buffered %}
263
+ {% if tx_function %} {% endif %}{{ rotate_multiplex_func_call(message, signal, '&tx_signals->' ~ message.name ~ '_signals_committed->' ~ signal.name) }};
264
+ {%- else %}
265
+ {% if tx_function %} {% endif %}{{ rotate_multiplex_func_call(message, signal, '&tx_signals->' ~ message.name ~ '_signals.' ~ signal.name) }};
266
+ {%- endif %}
267
+ {%- endif %}
268
+ {%- endfor %}
269
+ {%- if tx_function %}
270
+ queued++;
271
+ }
272
+ {%- else %}
273
+ return 1;
274
+ {%- endif %}
275
+ {%- else %}
276
+ if ({{ ctx }}.tx_callback) {
277
+ {{ message.name }}_tx_data = {{ tx_callback_call(message, '&' ~ message.name ~ '_tx_storage', tx_callback_context, msg_as_tx_callback_arg, false) }};
278
+ }
279
+ if ({{ message.name }}_tx_data) {
280
+ {%- if tx_function %}
281
+ {{ message_encoding_function_call(message, '(struct ' ~ message.struct_name ~ ' *)' ~ message.name ~ '_tx_data', frame_arg) }};
282
+ if ((*tx_signals->tx_function)(tx_signals->tx_function_context, &frame)) {
283
+ {%- if send_now %}
284
+ {{ ctx }}.send_now = 0;
285
+ {%- endif %}
286
+ {{ ctx }}.pending = 0;
287
+ queued++;
288
+ }
289
+ } else {
290
+ // nothing to send for this slot; a send_now request stays set
291
+ {{ ctx }}.pending = 0;
292
+ }
293
+ {%- else %}
294
+ {{ message_encoding_function_call(message, '(struct ' ~ message.struct_name ~ ' *)' ~ message.name ~ '_tx_data', frame_arg) }};
295
+ return 1;
296
+ }
297
+ {%- endif %}
298
+ {%- endif %}
299
+ }
300
+ {%- endfor %}
301
+ {%- if tx_function %}
302
+ return queued;
303
+ {%- else %}
304
+ return 0;
305
+ {%- endif %}
306
+ }
307
+ {%- endmacro %}
308
+
309
+ {% macro declare_periodic_tx_init_function(api_name, api_struct_prefix, time_type, can_frame_type='CanFrame', tx_function=false, tx_timing='absolute') -%}
310
+ void {{ api_name }}_periodic_tx_init(struct {{ api_struct_prefix }}TxSignals *tx_signals{% if tx_timing != 'delta' %}, {{ time_type }} now{% endif %}
195
311
  {%- if tx_function %}, uint16_t (*tx_function)(void *, const {{ can_frame_type }} *), void *tx_function_context{% endif %})
196
312
  {%- endmacro %}
197
313
 
198
- {% macro define_periodic_tx_init_function(api_name, api_struct_prefix, time_type, messages, default_period, double_buffered=False, tx_signal_store=False, can_frame_type='CanFrame', tx_function=false) -%}
314
+ {% macro define_periodic_tx_init_function(api_name, api_struct_prefix, time_type, messages, default_period, double_buffered=False, tx_signal_store=False, can_frame_type='CanFrame', tx_function=false, tx_timing='absolute') -%}
199
315
  {% set tx_messages = messages | selectattr('direction', 'in', '["tx", "rt"]') -%}
200
- {{ declare_periodic_tx_init_function(api_name, api_struct_prefix, time_type, can_frame_type, tx_function) }} {
316
+ {{ declare_periodic_tx_init_function(api_name, api_struct_prefix, time_type, can_frame_type, tx_function, tx_timing) }} {
201
317
  memset(tx_signals, 0, sizeof(*tx_signals));
202
318
  {%- if tx_function %}
203
319
  tx_signals->tx_function = tx_function;
@@ -205,7 +321,9 @@ void {{ api_name }}_periodic_tx_init(struct {{ api_struct_prefix }}TxSignals *tx
205
321
  {%- endif %}
206
322
  {%- for msg in tx_messages %}
207
323
  tx_signals->{{ msg.name }}_context.arb_id = {{ msg.arb_id }};
324
+ {%- if tx_timing != 'delta' %}
208
325
  tx_signals->{{ msg.name }}_context.last_tx = now;
326
+ {%- endif %}
209
327
  tx_signals->{{ msg.name }}_context.offset_seed = {{ msg.offset_seed }}ull;
210
328
  {%- if double_buffered and tx_signal_store %}
211
329
  tx_signals->{{ msg.name }}_signals = &tx_signals->{{ msg.name }}_signals_storage[0];