can-api-generator 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,385 @@
1
+ """
2
+ Message_processor
3
+ =================
4
+
5
+ The objects that are constructed from the cantools message and signal types and that compute the
6
+ extra data needed to allow for proper templating to happen
7
+ """
8
+ import functools
9
+ import operator
10
+ import copy
11
+ import random
12
+ from can_db_model.model import MuxMember
13
+ from .utils import is_valid_c_identifier, converrt_to_c_identifier
14
+ from .utils import convert_to_cantools_signal_name
15
+ from .utils import get_signal_type_from_range, get_bit_length_from_type
16
+ from .types import SignalType, SignalDirection, ByteOrder, SignalProcessingException, MessageProcessingException
17
+
18
+
19
+ class CAPIMessage:
20
+ def __init__(self, msg, api_name: str, node_name: str,
21
+ bus_name: str, require_endpoints: bool = True):
22
+ """
23
+ msg is a can_db_model.model.Message. bus_name is the database's bus
24
+ name (the model carries the bus at the top level, not per-message).
25
+
26
+ require_endpoints: when False, missing producers/consumers are not
27
+ errors. Used by the cbdb path, where the decoder is a bus observer
28
+ and direction metadata is irrelevant.
29
+ """
30
+ self.bus = bus_name
31
+ self.name = msg.name
32
+ self.node = node_name
33
+ self.arb_id = msg.id
34
+ self.is_extended = msg.extended
35
+ # prefix for the struct names
36
+ self.api_prefix = api_name
37
+ if msg.length > 8:
38
+ raise MessageProcessingException(
39
+ "Message too large for v2.0B spec",
40
+ self.name, node_name)
41
+ self.length = msg.length
42
+ if len(msg.producers) == 0 and require_endpoints:
43
+ raise MessageProcessingException("No Sender defined",
44
+ self.name, node_name)
45
+ self.periodic = msg.interval is not None
46
+ self.period = msg.interval
47
+ self.signals = list(
48
+ map(lambda sig: CAPISignal(sig, self, node_name,
49
+ require_endpoints), msg.signals))
50
+ self.init_message_direction(msg.producers, self.length)
51
+ self.update_signals_depending_on_direction()
52
+ self.all_signals = copy.copy(self.signals)
53
+ self.build_mux_tree()
54
+
55
+ def init_message_direction(self, senders, length):
56
+ if self.node in senders:
57
+ self.direction = SignalDirection.TX
58
+ if any(map(lambda s: s.direction == SignalDirection.RX
59
+ or s.direction == SignalDirection.RT,
60
+ self.signals)):
61
+ self.direction = SignalDirection.RT
62
+ elif any(map(lambda s: s.direction == SignalDirection.RX,
63
+ self.signals)):
64
+ self.direction = SignalDirection.RX
65
+ elif length == 0:
66
+ self.direction = SignalDirection.RX
67
+ else:
68
+ self.direction = SignalDirection.NA
69
+
70
+ def update_signals_depending_on_direction(self):
71
+ if self.direction == SignalDirection.NA:
72
+ if any(map(lambda s: s.direction == SignalDirection.RX,
73
+ self.signals)):
74
+ self.direction = SignalDirection.RX
75
+ self.signals = list(filter(
76
+ lambda s: s.direction == SignalDirection.RX
77
+ or s.direction == SignalDirection.RT
78
+ or s.is_multiplex,
79
+ self.signals
80
+ ))
81
+ for s in self.signals:
82
+ s.direction = SignalDirection.RX
83
+ elif self.direction == SignalDirection.TX:
84
+ self.signals = list(
85
+ filter(lambda s: s.direction != SignalDirection.RX,
86
+ self.signals))
87
+ for s in self.signals:
88
+ s.direction = SignalDirection.TX
89
+ elif self.direction == SignalDirection.RT:
90
+ for s in self.signals:
91
+ if s.direction == SignalDirection.NA:
92
+ if s.is_multiplex:
93
+ s.direction = SignalDirection.RT
94
+ else:
95
+ s.direction = SignalDirection.TX
96
+ elif self.direction == SignalDirection.RX:
97
+ self.signals = list(
98
+ filter(lambda s: s.direction == SignalDirection.RX
99
+ or s.direction == SignalDirection.RT
100
+ or s.is_multiplex,
101
+ self.signals))
102
+ for s in self.signals:
103
+ s.direction = SignalDirection.RX
104
+
105
+ def build_mux_tree(self):
106
+ muxes = list(filter(lambda s: s.is_multiplex, self.signals))
107
+ for mux in muxes:
108
+ multiplexed_signals = list(filter(lambda s: s.multiplexer == mux.name, self.signals))
109
+ mux.variants = dict([(
110
+ mux.labeled_defines.get(vid, vid),
111
+ list(filter(lambda s: s.mux_group == vid, multiplexed_signals)))
112
+ for vid in set(map(lambda s: s.mux_group, multiplexed_signals))])
113
+ for s in multiplexed_signals:
114
+ self.signals.remove(s)
115
+
116
+ def struct_name(self):
117
+ if ' ' not in self.name:
118
+ return ''.join(map(lambda s: s.capitalize(), self.name.split('_')))
119
+ else:
120
+ return ''.join(map(lambda s: s.capitalize(), self.name.split()))
121
+
122
+ def to_dict(self):
123
+ return {
124
+ 'bus': self.bus,
125
+ 'node': self.node,
126
+ 'arb_id': hex(self.arb_id),
127
+ 'name': converrt_to_c_identifier(self.name),
128
+ 'kcd_name': self.name,
129
+ 'periodic': self.periodic,
130
+ 'period' : self.period,
131
+ 'offset_seed': random.randrange(0, 2**32-1),
132
+ 'api_prefix': self.api_prefix,
133
+ 'struct_name': self.struct_name(),
134
+ 'length': self.length,
135
+ 'direction': str(self.direction),
136
+ 'signals': [ s.to_dict() for s in self.signals],
137
+ 'max_mux_len': max(list(map(lambda m: len(list(m.variants)), filter(lambda s: s.is_multiplex, self.signals))) + [1]),
138
+ 'all_signals': [ s.to_flat_dict() for s in self.all_signals],
139
+ }
140
+
141
+
142
+ class CAPISignal:
143
+ def __init__(self, signal, parent: CAPIMessage,
144
+ node_name: str, require_endpoints: bool = True):
145
+ # can_db_model labels -> the {value: name} dict the rest of this
146
+ # method expects (the old code read cantools' signal.choices).
147
+ choices = {lab.value: lab.name for lab in signal.labels}
148
+
149
+ self.name = signal.name
150
+ self.api_var_name = self.converrt_name_to_c_identifier()
151
+ self.parent = parent
152
+ self.is_multiplex = signal.mux == "multiplexor"
153
+ # figure out message direction
154
+ if node_name in signal.consumers:
155
+ if len(set(signal.consumers)) > 1:
156
+ self.direction = SignalDirection.RT
157
+ else:
158
+ self.direction = SignalDirection.RX
159
+ else:
160
+ self.direction = SignalDirection.NA
161
+ # check if the wire representation is float (needed later)
162
+ self.is_float = signal.type in ("single", "double")
163
+ self.is_signed = signal.type == "signed"
164
+ # determin the size of the signal
165
+ self.byte_order = ByteOrder.LITTLE_ENDIAN
166
+ self.start_bit = signal.start
167
+ if signal.length > 64:
168
+ raise SignalProcessingException(
169
+ "Signals larger than 64 bit are not supported",
170
+ self.name, parent.name, node_name)
171
+ elif self.is_float:
172
+ # there are only two lengths allowed if a float is used
173
+ if signal.length not in [32, 64]:
174
+ raise SignalProcessingException(
175
+ "Signal declared as float but the length is not 32 bit",
176
+ self.name, parent.name, node_name)
177
+ self.length = signal.length
178
+ # determmin byte order (currently only little endian is supported)
179
+ if signal.endianness != "little":
180
+ raise SignalProcessingException(
181
+ "Only little endian encoding is supported",
182
+ self.name, parent.name, node_name)
183
+ # determin range of the signal
184
+ # The model types slope/intercept as float, but the codegen distinguishes
185
+ # integer scaling (emits integer division) from fractional scaling. Keep
186
+ # integral values as int so e.g. slope 2 yields `/ 2`, not `/ 2.0`.
187
+ self.scale = (int(signal.slope)
188
+ if float(signal.slope).is_integer() else signal.slope)
189
+ self.offset = (int(signal.intercept)
190
+ if float(signal.intercept).is_integer() else signal.intercept)
191
+ self.min = signal.min
192
+ self.max = signal.max
193
+ # presentation info for the client sidecar (not used by codegen)
194
+ self.unit = signal.unit
195
+ self.labels = dict(choices)
196
+ if self.is_float:
197
+ # we know it is a float so just put things here for now
198
+ self.theoretical_min = -1000000
199
+ self.theoretical_max = 1000000
200
+ elif self.is_signed:
201
+ self.theoretical_min = - pow(2, self.length - 1) * self.scale + self.offset
202
+ self.theoretical_max = (pow(2, self.length - 1) - 1) * self.scale + self.offset
203
+ else:
204
+ self.theoretical_min = self.offset
205
+ self.theoretical_max = (pow(2, self.length) - 1) * self.scale + self.offset
206
+ if 0 < self.theoretical_min or 0 > self.theoretical_max:
207
+ raise SignalProcessingException(
208
+ "0 lies outside the range of representable values, "
209
+ "this is not supported as it leads to large discrepencies with"
210
+ "the 0 initialized structs",
211
+ self.name, parent.name, node_name)
212
+
213
+ # determin the type of the variable holding the signal in the API
214
+ if self.is_float:
215
+ if self.length == 32:
216
+ self.signal_type = SignalType.FLOAT_32_BIT
217
+ self.api_signal_length = 32
218
+ self.wire_signal_type = SignalType.FLOAT_32_BIT
219
+ elif self.length == 64:
220
+ self.signal_type = SignalType.FLOAT_64_BIT
221
+ self.api_signal_length = 64
222
+ self.wire_signal_type = SignalType.FLOAT_64_BIT
223
+ else:
224
+ raise SignalProcessingException(
225
+ "Only lengths of 32 and 64 bit are allowed for "
226
+ "'single' data type",
227
+ self.name, parent.name, node_name)
228
+ self.neg_sign_extension_prefix = None
229
+ self.neg_check_mask = None
230
+ elif int(self.offset) != self.offset or int(self.scale) != self.scale:
231
+ # Non-integer scale/offset forces the API-visible value into a float,
232
+ # but the wire representation is still an integer. The decode path
233
+ # therefore has to extract the raw integer (sign-extending if needed)
234
+ # before casting to float and applying the slope/intercept — it
235
+ # cannot reinterpret the raw bits as an IEEE 754 float.
236
+ self.signal_type = SignalType.FLOAT_32_BIT
237
+ self.api_signal_length = 32
238
+ if self.is_signed:
239
+ self.wire_signal_type = SignalType.SIGNED_64_BIT
240
+ wire_bits = 64
241
+ if self.length < wire_bits:
242
+ self.neg_sign_extension_prefix = '0b' + ''.join(
243
+ '0' if i < self.length else '1'
244
+ for i in reversed(range(wire_bits)))
245
+ self.neg_check_mask = 1 << self.length - 1
246
+ else:
247
+ self.neg_sign_extension_prefix = None
248
+ self.neg_check_mask = None
249
+ else:
250
+ self.wire_signal_type = SignalType.UNSIGNED_64_BIT
251
+ self.neg_sign_extension_prefix = None
252
+ self.neg_check_mask = None
253
+ else:
254
+ self.signal_type = get_signal_type_from_range(self.theoretical_min,
255
+ self.theoretical_max)
256
+ self.wire_signal_type = self.signal_type
257
+ if self.signal_type is None:
258
+ raise SignalProcessingException(
259
+ "No API type could be found to hold the values "
260
+ "represented on the wire",
261
+ self.name, parent.name, node_name)
262
+ self.api_signal_length = get_bit_length_from_type(self.signal_type)
263
+ if self.is_signed and self.api_signal_length > self.length:
264
+ self.neg_sign_extension_prefix = '0b'+''.join(['0' if i < self.length else '1' for i in reversed(range(self.api_signal_length))])
265
+ self.neg_check_mask = 1 << self.length - 1
266
+ else:
267
+ self.neg_sign_extension_prefix = None
268
+ self.neg_check_mask = None
269
+
270
+ # check if we have labled variables and build a data structure that we can use
271
+ self.labeled_values = dict(
272
+ map(lambda c: (c[0], converrt_to_c_identifier(str(c[1]))),
273
+ choices.items())) if choices else dict()
274
+ self.labeled_defines = dict(
275
+ map(lambda c: (c[0], self.build_lable_define(str(c[1]))),
276
+ choices.items())) if choices else dict()
277
+ self.multiplexer = (signal.mux.of
278
+ if isinstance(signal.mux, MuxMember) else None)
279
+ if self.multiplexer:
280
+ self.mux_group = signal.mux.group
281
+ else:
282
+ self.mux_group = None
283
+
284
+ # do some checks for cases that are not yet supported and are
285
+ # a bit of an edge case
286
+ if self.labeled_values and self.offset != 0 and self.scale != 1:
287
+ raise SignalProcessingException(
288
+ "Having a scaled value with labels is not supported",
289
+ self.name, parent.name, node_name)
290
+ if self.is_multiplex:
291
+ if self.offset != 0 or self.scale != 1:
292
+ raise SignalProcessingException(
293
+ "A Multiplex with scale or offset is not supported",
294
+ self.name, parent.name, node_name)
295
+ self.variants = {}
296
+ if (len(signal.consumers) == 0 and not self.is_multiplex
297
+ and require_endpoints):
298
+ raise SignalProcessingException(
299
+ "No Receiver defined",
300
+ self.name, parent.name, node_name)
301
+
302
+ def signal_mask(self):
303
+ mask = 0
304
+ for i in range(self.length):
305
+ mask += 1 << i
306
+ return str(hex(mask))
307
+
308
+ def converrt_name_to_c_identifier(self):
309
+ ident_candidate = converrt_to_c_identifier(self.name)
310
+ if is_valid_c_identifier(ident_candidate):
311
+ return ident_candidate
312
+ else:
313
+ raise SignalProcessingException(
314
+ "Signal name cannot be converted into valid C identifier",
315
+ self.name, self.parent.name, '')
316
+
317
+ def build_lable_define(self, lable) -> str:
318
+ """
319
+ Buld the define string for the lable of signal
320
+ """
321
+ if type(lable) is int:
322
+ return lable
323
+ return self.parent.name.upper() + '_' + self.name.upper() + '_' + converrt_to_c_identifier(lable).upper()
324
+
325
+
326
+ def resolve_muxed_signal_name(self):
327
+ """
328
+ Generate a unique name for the C identifier in the API for signals
329
+
330
+ In some occasions involving Multiplexes, the signal names in the KCD
331
+ file may be identical for two signals in the same message. These signals then
332
+ differ by
333
+ """
334
+ if not self.is_multiplex:
335
+ return
336
+ #
337
+ all_muxed_signals = functools.reduce(operator.iconcat, self.variants.values(), [])
338
+ all_muxed_names = list(map(lambda s: s.name, all_muxed_signals))
339
+ all_duplicate_signal_names = set([name for name in all_muxed_names if all_muxed_names.count(name) > 1])
340
+ # resolve the name for the signals that have identical names
341
+ for duplicate_name in all_duplicate_signal_names:
342
+ signals_with_duplicate_name = list(filter(lambda s: s.name == duplicate_name, all_muxed_signals))
343
+ for signal in signals_with_duplicate_name:
344
+ # match the signal to the mux index
345
+ for mux_val, variant_signals in self.variants.items():
346
+ if signal not in variant_signals:
347
+ continue
348
+ matching_mux_label = list(filter(lambda lv: lv[1] == mux_val, self.labeled_defines.items()))
349
+ if len(matching_mux_label) == 0:
350
+ signal.api_var_name = '_'.join([self.api_var_name, str(mux_val), signal.api_var_name])
351
+ else:
352
+ matching_mux_label = self.labeled_values[matching_mux_label[0][0]]
353
+ matching_mux_label = converrt_to_c_identifier(matching_mux_label)
354
+ signal.api_var_name = '_'.join([matching_mux_label, signal.api_var_name])
355
+
356
+ def to_dict(self):
357
+ return {
358
+ 'name': self.api_var_name,
359
+ 'kcd_name': self.name,
360
+ 'cantools_name': convert_to_cantools_signal_name(self.api_var_name),
361
+ 'is_scaled': self.scale != 1 or self.offset != 0, # extra code needs to be included if a value is scaled (makes checks shorter in the templates)
362
+ 'scale': self.scale,
363
+ 'offset': self.offset,
364
+ 'start_bit': self.start_bit,
365
+ 'length': self.length,
366
+ 'wire_data_type': str(self.wire_signal_type),
367
+ 'api_length': self.api_signal_length,
368
+ 'labeled_values': self.labeled_values.items(),
369
+ 'label_defines': self.labeled_defines.items(),
370
+ 'direction': str(self.direction),
371
+ 'api_data_type': str(self.signal_type),
372
+ 'sign_extension_prefix': self.neg_sign_extension_prefix,
373
+ 'sign_bit_mask': hex(self.neg_check_mask) if self.neg_check_mask else None,
374
+ 'multiplex': self.is_multiplex,
375
+ 'mask': self.signal_mask(),
376
+ 'theoretical_min': self.theoretical_min,
377
+ 'theoretical_max': self.theoretical_max,
378
+ 'mux_groups': dict([(gid, list(map(lambda v: v.to_dict(), vsigs))) for gid, vsigs in self.variants.items()]),
379
+ }
380
+
381
+ def to_flat_dict(self):
382
+ retval = self.to_dict()
383
+ del(retval['mux_groups'])
384
+ return retval
385
+
@@ -0,0 +1,12 @@
1
+ #ifndef CANFRAME_H
2
+ #define CANFRAME_H
3
+ #include <stdint.h>
4
+
5
+ typedef struct CanFrame {
6
+ uint16_t {{ length_member_name }};
7
+ uint32_t {{ arb_id_member_name }};
8
+ uint64_t {{ payload_member_name }};
9
+ } CanFrame;
10
+ #endif
11
+
12
+
@@ -0,0 +1,23 @@
1
+ {% macro declare_message_struct(message) -%}
2
+ struct {{ message.struct_name }} {
3
+ {%- for signal in message.all_signals %}
4
+ {{ signal.api_data_type }} {{ signal.name }};
5
+ {%- endfor %}
6
+ };
7
+ {%- endmacro %}
8
+
9
+ {% macro generate_msg_defines(message) -%}
10
+ // Message specific defines
11
+ #define {{ message.name | upper }}_ID {{ message.arb_id }}
12
+ #define {{ message.name | upper }}_LENGTH {{ message.length }}
13
+ {%- endmacro %}
14
+
15
+ {% macro generate_label_defines(message) -%}
16
+ {%- for signal in message.all_signals %}
17
+ {%- if signal.label_defines | length > 0 %}// Defines for specific values of {{ signal.name }} as specified by the kcd
18
+ {% endif -%}
19
+ {% for val in signal.label_defines -%}
20
+ #define {{ val[1] }} {{ val[0] }}
21
+ {% endfor -%}
22
+ {%- endfor -%}
23
+ {%- endmacro -%}
@@ -0,0 +1,73 @@
1
+ #ifndef DYNDEC_H
2
+ #define DYNDEC_H
3
+ /*
4
+ * Dynamic CAN signal decoder driven by a descriptor cbdb compiled with
5
+ * `can-api-gen cbdb`. Generated by can-api-generator -- do not edit.
6
+ *
7
+ * The cbdb is a table of message/signal descriptors (bit positions, scale,
8
+ * offset, mux membership). dyndec_process_frame() decodes a received frame
9
+ * against the table and stores every signal as its physical value (float)
10
+ * in a flat store, indexed by the signal's position in the cbdb.
11
+ */
12
+ #include <stdint.h>
13
+ #include "canframe.h"
14
+
15
+ #define DYNDEC_OK 0
16
+ #define DYNDEC_ERR_MAGIC (-1)
17
+ #define DYNDEC_ERR_VERSION (-2)
18
+ #define DYNDEC_ERR_SIZE (-3)
19
+ #define DYNDEC_ERR_BOUNDS (-4)
20
+ #define DYNDEC_ERR_CAPACITY (-5)
21
+ #define DYNDEC_ERR_ORDER (-6)
22
+
23
+ #define DYNDEC_CBDB_FORMAT_VERSION 1
24
+
25
+ /* Index entry for one message record inside the cbdb. */
26
+ typedef struct DyndecMsgRef {
27
+ uint32_t cbdb_offset;
28
+ uint16_t first_value_idx;
29
+ uint16_t n_signals;
30
+ } DyndecMsgRef;
31
+
32
+ typedef struct DyndecContext {
33
+ const uint8_t *cbdb;
34
+ uint32_t cbdb_size;
35
+ uint16_t msg_count;
36
+ uint16_t signal_count;
37
+ DyndecMsgRef *msg_index;
38
+ float *values;
39
+ } DyndecContext;
40
+
41
+ /*
42
+ * Validate `cbdb` and build the message index. `msg_index` and `values`
43
+ * are caller-provided storage; capacities are checked against the cbdb's
44
+ * message and signal counts. The cbdb memory must stay valid and unchanged
45
+ * for the lifetime of the context. The value store is zeroed.
46
+ */
47
+ int dyndec_init(DyndecContext *ctx, const uint8_t *cbdb, uint32_t cbdb_size,
48
+ DyndecMsgRef *msg_index, uint16_t max_msgs,
49
+ float *values, uint16_t max_signals);
50
+
51
+ /*
52
+ * Decode a received frame. Returns the matched message's index, or -1 if
53
+ * the arbitration id is not in the cbdb. Muxed signals are only updated
54
+ * when their selector matches; all other signals of the message are
55
+ * updated with their physical (scaled) value.
56
+ */
57
+ int dyndec_process_frame(DyndecContext *ctx, const CanFrame *frame);
58
+
59
+ /* Find the value-store index for a signal id; -1 if unknown. */
60
+ int32_t dyndec_lookup(const DyndecContext *ctx, uint32_t signal_id);
61
+
62
+ /* Latest stored physical value at a store index from dyndec_lookup(). */
63
+ float dyndec_get_value(const DyndecContext *ctx, int32_t value_idx);
64
+
65
+ /* Signal id stored at a given store index (for enumeration). */
66
+ uint32_t dyndec_signal_id_at(const DyndecContext *ctx, uint16_t value_idx);
67
+
68
+ uint16_t dyndec_signal_count(const DyndecContext *ctx);
69
+
70
+ /* SHA-256 of the KCD file this cbdb was compiled from (32 bytes). */
71
+ const uint8_t *dyndec_kcd_hash(const DyndecContext *ctx);
72
+
73
+ #endif