@@ -124,6 +124,158 @@ function engine.log(level, fmt, ...)
124
124
api .log (level , " lua" , msg )
125
125
end
126
126
127
+ --- Alias a set of signals in the Cloe data broker.
128
+ ---
129
+ --- @param list table<string , string> # regular expression to alias key
130
+ --- @return table<string , string> # current signal aliases table
131
+ function engine .alias_signals (list )
132
+ -- TODO: Throw an error if simulation already started.
133
+ api .initial_input .signal_aliases = luax .tbl_extend (" force" , api .initial_input .signal_aliases , list )
134
+ return api .initial_input .signal_aliases
135
+ end
136
+
137
+ --- Require a set of signals to be made available via the Cloe data broker.
138
+ ---
139
+ --- @param list string[] signals to merge into main list of required signals
140
+ --- @return string[] # merged list of signals
141
+ function engine .require_signals (list )
142
+ -- TODO: Throw an error if simulation already started.
143
+ api .initial_input .signal_requires = luax .tbl_extend (" force" , api .initial_input .signal_requires , list )
144
+ return api .initial_input .signal_requires
145
+ end
146
+
147
+ --- Optionally alias and require a set of signals from a signals enum list.
148
+ ---
149
+ --- This allows you to make an enum somewhere which the language server
150
+ --- can use for autocompletion and which you can use as an alias:
151
+ ---
152
+ --- ---@enum Sig
153
+ --- local Sig = {
154
+ --- DriverDoorLatch = "vehicle::framework::chassis::.*driver_door::latch",
155
+ --- VehicleMps = "vehicle::sensors::chassis::velocity",
156
+ --- }
157
+ --- cloe.require_signals_enum(Sig, true)
158
+ ---
159
+ --- Later, you can use the enum with `cloe.signal()`:
160
+ ---
161
+ --- cloe.signal(Sig.DriverDoorLatch)
162
+ ---
163
+ --- @param enum table<string , string> input mappging from enum name to signal name
164
+ --- @param alias boolean whether to treat signal names as alias regular expressions
165
+ --- @return nil
166
+ function engine .require_signals_enum (enum , alias )
167
+ -- TODO: Throw an error if simulation already started.
168
+ local signals = {}
169
+ if alias then
170
+ local aliases = {}
171
+ for key , sigregex in pairs (enum ) do
172
+ table.insert (aliases , { sigregex , key })
173
+ table.insert (signals , key )
174
+ end
175
+ engine .alias_signals (aliases )
176
+ else
177
+ for _ , signame in pairs (enum ) do
178
+ table.insert (signals , signame )
179
+ end
180
+ end
181
+ engine .require_signals (signals )
182
+ end
183
+
184
+ --- Return full list of loaded signals.
185
+ ---
186
+ --- Example:
187
+ ---
188
+ --- local signals = cloe.signals()
189
+ --- signals[SigName] = value
190
+ ---
191
+ --- @return table
192
+ function engine .signals ()
193
+ return api .signals
194
+ end
195
+
196
+ --- Return the specified signal.
197
+ ---
198
+ --- If the signal does not exist, nil is returned.
199
+ ---
200
+ --- If you want to set the signal, you need to use `cloe.set_signal()`
201
+ --- or access the value via `cloe.signals()`.
202
+ ---
203
+ --- @param name string signal name
204
+ --- @return any | nil # signal value
205
+ function engine .signal (name )
206
+ return api .signals [name ]
207
+ end
208
+
209
+ --- Set the specified signal with a value.
210
+ ---
211
+ --- @param name string signal name
212
+ --- @param value any signal value
213
+ --- @return nil
214
+ function engine .set_signal (name , value )
215
+ api .signals [name ] = value
216
+ end
217
+
218
+ --- Record the given list of signals into the report.
219
+ ---
220
+ --- This can be called multiple times, but if the signal is already
221
+ --- being recorded, then an error will be raised.
222
+ ---
223
+ --- This should be called before simulation starts,
224
+ --- so not from a scheduled callback.
225
+ ---
226
+ --- You can pass it a list of signals to record, or a mapping
227
+ --- from name to
228
+ ---
229
+ --- @param mapping table<number | string , string | fun (): any> mapping from signal names
230
+ --- @return nil
231
+ function engine .record_signals (mapping )
232
+ validate (" cloe.record_signals(table)" , mapping )
233
+ api .state .report .signals = api .state .report .signals or {}
234
+ local signals = api .state .report .signals
235
+ signals .time = signals .time or {}
236
+ for sig , getter in pairs (mapping ) do
237
+ if type (sig ) == " number" then
238
+ if type (getter ) ~= " string" then
239
+ error (" positional signals can only be signal names" )
240
+ end
241
+ sig = getter
242
+ end
243
+ if signals [sig ] then
244
+ error (" signal already exists: " .. sig )
245
+ end
246
+ signals [sig ] = {}
247
+ end
248
+
249
+ engine .schedule ({
250
+ on = " loop" ,
251
+ pin = true ,
252
+ run = function (sync )
253
+ local last_time = signals .time [# signals .time ]
254
+ local cur_time = sync :time ():ms ()
255
+ if last_time ~= cur_time then
256
+ table.insert (signals .time , cur_time )
257
+ end
258
+
259
+ for name , getter in pairs (mapping ) do
260
+ local value
261
+ if type (name ) == " number" then
262
+ name = getter
263
+ end
264
+ if type (getter ) == " string" then
265
+ value = engine .signal (getter )
266
+ else
267
+ value = getter ()
268
+ end
269
+ if value == nil then
270
+ -- TODO: Improve error message!
271
+ error (" nil value received as signal value" )
272
+ end
273
+ table.insert (signals [name ], value )
274
+ end
275
+ end ,
276
+ })
277
+ end
278
+
127
279
--- Schedule a trigger.
128
280
---
129
281
--- It is not recommended to use this low-level function, as it is viable to change.
0 commit comments