Scripts allow the user to customize impulses in many ways. These include, among others, the evaluation of signals, the parsing of signal sources, the search for signal positions and the definition or extension of charts.
The Signal Scripts production allows the users to combine signals using mathematical operations, generate references, implement protocol parsers, extract statistical informations or search for conflicts automatically.
If you are reading signal and trace data from a TCP, file, serial or other port input and with a format unknown to the impulse tool, you can get on the right track with a scripted reader. Using a scripted reader is an easy way to read data from any format.
By using scripts, connected devices or applications can be stimulated and their events processed. This could be for example a CAN bus message or a byte sequence via TCP.
The Find dialogue helps you to find signal patterns using script expression.
RecJs files are wave files based on scripts. You can create signal references, define test vectors for your design, or script a custom reader. Everything is based on the same simple API used in signal scripts and serializers.
You can extend most diagram types using scripts. The scripting API varies depending on the chart type.
Multiple signal ports of varying types can be combined into one, synchronizing the received signals.
Scripted stubs allow the simulation of the dependencies of models such as the YAKINDU State Charts.
The Scripting Envronment
impulse is build on Java and Signal Script uses the JSR223 Scripting API of Java.
With the current release only JavaScript is supported. Further release will be extended to all availabe JSR223 languages.
Scripting Objects
Signal Script usually works with Java objects (e.g. input streams, signal reader and writer object, ...).
Its up to the user to work with script language dependend objects (like JavaScript objects) but its usually not required and to our understanding not useful. Primitives from java are converted into script language (JavaScript) primitives.
But there are limits: JavaScript, for example, has only one number type (64 float), so that a long value cannot be converted and is given as a long object.
Scripting APIs
Signal Script contains APIs (impulse JDK) to
plus several extention specific APIs.
How to get started with scripting ?
Have a look at the script examples. Select a script that is close to your requirements, than copy script and optinal settings into the dialogue or editor.
Rhino > Nashorn > GraalVM(?)
impulse 1.x started with a Rhino JavaScript engine. If the Rhino engine was used (Java 7 or higher), impulse automatically used the Rhino compatibility library. Note that Rhino and Rhinoceros had a slightly different interface to Java. Please refer to the Scripting Java (Rhino) and Rhino Migration Guide (to Nashorn).
impulse 2.0 uses the Nashort Engine by default (Rhino is no longer supported) and assumes compatibility with the upcoming GraalVM.
To access a Java class, use the Java.type(typeName) function.
var FileClass = Java.type('java.io.File');
Existing code accessing e.g. java.io.File or Packages.de.toem.impulse.samples.GroupPointer should be rewritten to use the Java.type(name) function.
Java objects can be constructed with JavaScript's new
keyword.
var FileClass = Java.type('java.io.File'); var file = new FileClass("myFile.md");
To access a Java class, you typically have to add a snippet like:
var FileClass = Java.type('java.io.File');
However, impulse automatically adds typical signal script class definitions, depending on the application.
For example the Signal Script production adds the following definitions:
var ISample = Java.type("de.toem.impulse.samples.ISample"); var ISamples = Java.type("de.toem.impulse.samples.ISamples"); var GroupPointer = Java.type("de.toem.impulse.samples.iterator.GroupPointer"); var SamplePointer = Java.type("de.toem.impulse.samples.iterator.SamplePointer"); var SamplesIterator = Java.type("de.toem.impulse.samples.iterator.SamplesIterator"); var AttachedLabel = Java.type("de.toem.impulse.values.AttachedLabel"); var AttachedRelation = Java.type("de.toem.impulse.values.AttachedRelation"); var Enumeration = Java.type("de.toem.impulse.values.Enumeration"); var Logic = Java.type("de.toem.impulse.values.Logic"); var Struct = Java.type("de.toem.impulse.values.Struct"); var StructMember = Java.type("de.toem.impulse.values.StructMember"); ...
For further information please refer to the corresponding documentation.
Static fields of a Java class or fields of a Java object can be accessed like JavaScript properties.
var JavaPI = Java.type('java.lang.Math').PI;
Java methods can be called like JavaScript functions.
var file = new (Java.type('java.io.File'))("test.md"); var fileName = file.getName();
var IntArray = Java.type("int[]"); var iarr = new IntArray(5);
This chapter shall point out the differences between Nashorn (JDK8+) and GraalVM.
As GraalVM is not completed, this chapter will extend over time.
Conversion of primitives
JavaScript has just a single Number type, wheras Jaca supports multiple types.
The conversion of types may differ between both engines. See below logs:
while ( iter.hasNext()) { var current = iter.next(out); // returns a Java long value console.log(current,current.getClass()); } # Nashorn log 1282 ;0 ;class java.lang.Long 1419 ;1112500000 ;class java.lang.Long 1435 ;2387500000 ;class java.lang.Long 1444 ;4887500000 ;class java.lang.Long # GraalVM log 623 ;0 ;class java.lang.Integer 653 ;1112500000 ;class java.lang.Integer 684 ;2.3875E9 ;class java.lang.Double 691 ;4.8875E9 ;class java.lang.Double
The psoido typing snippet has the form:
or
The first options refers to a java instance, whereas the second option referes to a class.
The string_name_or_binary_name of the psiodo typing snippet may be in form of:
Examples:
var current /*:java.lang.Long:*/ = iter.next(out); var b = current.byteValue(); var Long /*:@java.lang.Long:*/ = Java.type("java.lang.Long"); var l = Long.numberOfLeadingZeros(current);
Besides the recJs script recording format, which has its own editor, most scripts are bound to configuration items like plots, ports and charts.
The associated element dialogs contain text fields for inserting and editing the script.
You can use the built-in script editor instead of the text field in the dialog. Click on "Edit in the eclipse script editor" to open a new Eclipse editor. Once you save the content, the configuration element is updated.
Log and exception messages of the script execution are sent to the impulse console view.
Signal Script usually works with Java objects (e.g. input streams, signal reader and writer object, ...).
To allow content assist on java objects in dynamic type checking languages (e.g. JavaScript), impulse utilizes a so-called psoido typing.
var myVar /*:ISamplesReader:*/ = generator.getReader(); // psoido typing in JavaScript
Psoido typing snippets tell the environment the java type of a variable so that the system can prepare suggestions for content support. The snippets have no effect on the JavaScript execution (they are removed before processing or take the form of a comment). Their use is optional, but they are very helpful if you do not want to read reference documents all the time.
The java class definition of the psiodo typing snippet may be in form of:
The typical goal of a script is to read the content of source signals and create any kind of output signal. A typical script for the Signal Script Production looks like this:
// input: an array of all input signals // in0: primary input of type ISamplePointer,IReadableSamples // in1..: additional inputs of type ISamplePointer,IReadableSamples // out: output signal of type IFloatSamplesWriter // console: console output of type MessageConsoleStream // iter: iterator of type ISamplesIterator // progress: progess control of type IScriptProgress while ( iter.hasNext()) { var current <:Long:> = iter.next(out); out.write(current, false, input[0].floatValue() + input[1].floatValue()); }
SamplesIterator instance iter allows to iterate over events of multiple source signals. 'iter.next()' returns the current domain value (e.g current time). All input variable can be accessed as signal pointers: For each event found, the iterator set the pointers to the current position, e.g. floatValue() returns the float value at that position.
There are 2 important interfaces for reading the signal data, IReadableSamplesand ISamplesPointer.
The IReadableSamples interface defines the basic reader interface. It has methods like:
So you can get the total number of available samples and use one of the accessor methods to get the value at a given index.
The ISamplesPointer interface allows to modify an index and read the value at the current index. You find functions like:
If an input has a an additional dimension (struct members or arrays) you might read the value object and use the member accessors:
If you use a SamplesIterator, all pointers are updated by the iterator automatically. There is no need to use 'goNext()','goPrev()',... implicitly.
while ( iter.hasNext()) { .... in0.intValue() .... }
Independent from its source and type, impulse organizes all signal data by using these elements:
The record generator help you to prepare a record with all its contents. If you create a reader,
your class will be derived from IRecordGenerator, if you are working with recJs
scripts or a scripted reader, a IRecordGenerator variable (generator) will be provided. The
"Signal Script" production does not require to use a record generator, as the signal and
reader setup is done internally.
To create a record , the following steps are
required:
// Init the record generator.initRecord("Example Record", TimeBase.ns); // Create scopes and signals var signals = generator.addScope(null, "External Signals"); var int = generator.addSignal(signals, "Sin", "", ProcessType.Discrete, SignalType.Integer, SignalDescriptor.DEFAULT); var struct = generator.addSignal(signals, "Birt", "", ProcessType.Discrete, SignalType.Struct, SignalDescriptor.DEFAULT); var floatArray = generator.addSignal(signals, "XY", "", ProcessType.Discrete, SignalType.Float, new SignalDescriptor(SignalDescriptor.CONTENT_DEFAULT, 2, ISample.FLOAT_WIDTH_64, ISample.FORMAT_DEFAULT)); open(tStart); // writing samples close(tEnd);
Each signal type comes with a writer interface.
The float signal of the above example uses the IFloatSamplesWriter interface (integer: IIntegerSamplesWriter, text: ITextSamplesWriter, ...).
A simple write includes the parameters for domain position, a tag flag and one or more parameters describing the value.
public interface IFloatSamplesWriter extends INumberSamplesWriter { // default interface boolean write(long units, double value); boolean write(long units, boolean tag, float value); boolean write(long units, boolean tag, double value); boolean write(long units, boolean tag, BigDecimal value); boolean write(long units, boolean tag, Number value); boolean write(long units, boolean tag, float[] value); boolean write(long units, boolean tag, double[] value); // named interface for scripts boolean writeFloat(long units, boolean tag, float value); boolean writeDouble(long units, boolean tag, double value); boolean writeBig(long units, boolean tag, BigDecimal value); boolean writeFloatArray(long units, boolean tag, float[] value); boolean writeFloatArgs(long units, boolean tag, float... value); boolean writeDoubleArray(long units, boolean tag, double[] value); boolean writeDoubleArgs(long units, boolean tag, double... value); }
The parameters have the following meaning: