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.
- can_api_generator/__about__.py +4 -0
- can_api_generator/__init__.py +3 -0
- can_api_generator/cbdb_generator.py +208 -0
- can_api_generator/code_generator.py +148 -0
- can_api_generator/main.py +216 -0
- can_api_generator/msg_processor.py +385 -0
- can_api_generator/templates/canframe.jinja +12 -0
- can_api_generator/templates/declaration_macros.jinja +23 -0
- can_api_generator/templates/dyndec_header.jinja +73 -0
- can_api_generator/templates/dyndec_source.jinja +277 -0
- can_api_generator/templates/encoding_macros.jinja +304 -0
- can_api_generator/templates/header_template.jinja +87 -0
- can_api_generator/templates/rx_macros.jinja +92 -0
- can_api_generator/templates/source_template.jinja +60 -0
- can_api_generator/templates/tx_macros.jinja +149 -0
- can_api_generator/types.py +39 -0
- can_api_generator/utils.py +64 -0
- can_api_generator-1.0.0.dist-info/METADATA +349 -0
- can_api_generator-1.0.0.dist-info/RECORD +22 -0
- can_api_generator-1.0.0.dist-info/WHEEL +4 -0
- can_api_generator-1.0.0.dist-info/entry_points.txt +2 -0
- can_api_generator-1.0.0.dist-info/licenses/LICENSE.txt +166 -0
|
@@ -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,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
|