Sprachreferenz
In dieser Referenz wird der Aufbau des Formelsatzes diskutiert.
Struktur des Formelsatzes
Ein Formelsatz stellt einen String oder eine unmittelbare Verkettung mehrerer aufeinanderfolgender Strings dar und besteht aus Anweisungen (Statements) und Kommentaren.
Eine Anweisung besteht aus verschiedenen Termen, die wiederum konstante numerische Werte oder Zeichenketten, Zugriffe auf Variablen, Operatoren mit ihren Operanden, Funktionsaufrufe mit Parametern, geklammerte Terme und Strukturen, oder weitere verschachtelte Terme sein können. Anweisungen werden immer mit einem ;
abgeschlossen.
Alle Terme verarbeiten und produzieren immer zeitgestempelte Daten, die ja für den smartCORE typisch sind. Diese Daten werden zeitrichtig miteinander verrechnet, wobei für jeden Eingangszeitstempel der an einem Term beteiligten Datenströme ein Ausgangswert berechnet wird. Dabei wird der Signalverlauf zwischen zwei Zeitstempeln als konstant mit Bezug auf den älteren Wert im Zeitintervall angenommen, d.h. der ältere Wert gilt bis unmittelbar zum Zeitstempel des jüngeren Wertes. Eine weiterführende Verarbeitung der Daten kann also nur dann erfolgen, wenn zu einem Zeitpunkt alle beteiligten Eingangswerte des Terms ebenfalls gültige Werte besitzen. Der gültige Zeitbereich zur Berechnung endet für einen Datenstrom mit dem jüngsten Zeitstempel. Die folgende Tabelle zeigt ein Beispiel für zwei zeitgestempelte Datenquellen A und B und eine Berechnung:
Zeitpunkt | A | B | A + B |
---|---|---|---|
1 | (nicht definiert) | 10 | (nicht definiert) |
2 | 5 | 15 | |
3 | 20 | 25 | |
5 | 30 | 35 | |
8 | 8 | 38 | |
13 | 9 | 40 | 49 |
26 | 5 | 45 | |
27 | 2 | 42 | |
30 | (ausstehend) | 50 | (ausstehend) |
Das bedeutet unmittelbar, dass für Daten, die nur selten bereitgestellt werden oder z.B. durch das Abschalten einer Systemkomponenten gar nicht mehr geliefert werden, alle davon abhängigen Berechnungen zunächst nur bis zu diesem jüngsten Zeitstempel verarbeitet und dann dort blockiert werden. Liegen die jüngsten Daten weiter als inputTimeoutS Sekunden in der Vergangenheit, werden die Werte mit diesem Zeitversatz zwangsweise fortgeführt, um die weiteren Berechnungen nicht vollständig zu blockieren und interne Pufferüberläufe zu verhindern.
Mit dem Parameter evaluationTimeMs wird nur das Zeitintervall in ms festgelegt, in dem der Interpreter über alle Terme alle zu diesem Zeitpunkt verfügbaren Datenpunkte verarbeitet. Dies ist mit einer gewissen "Blindleistung" verbunden. Deshalb sollte das Intervall mindestens so lang gewählt werden, dass in jedem Zyklus auch Daten verarbeitet werden können, aber bevorzugt auch so kurz, dass die Anzahl der einzelnen Datenpunkte in einem überschaubaren Rahmen (ca. < 100) bleibt. Hat man z.B. ausschließlich Datenquellen, die im 2 Sekunden Takt neue Werte liefern, könnte man evaluationTimeMs auf 5000 ms einstellen. Bei Datenquellen, die mit 1 kHz ihre Datenpunkte zuliefern, ist eine evaluationTimeMs von 100 ms sinnvoll. Eine kürzere Zykluszeit ist auch dann sinnvoll, wenn mit den Ergebnissen des Formelsatzes Steuerungsaufgaben im Prozess realisiert werden sollen.
Durch die zeitrichtige Verarbeitung der Daten spielt die Reihenfolge der Ausdrücke im Formelsatz letztlich keine Rolle. Allerdings wird aus Gründen der Lesbarkeit empfohlen, nur Variablen zu verwenden, die zuvor zugewiesen wurden.
Kommentare
Kommentare werden - wie in den Programmiersprachen C und C++ - entweder mit einem //
begonnen und enden dann automatisch am Zeilenende, oder sie werden mit /*
begonnen und mit einem */
beendet. Die Notation /* ... */
kann wie bei einem Klammerausdruck geschachtelt angewendet werden.
// Im Kommentar beginnt "-hier keine Zeichenkette und [-hier keine Matrix
s1 = "dies ist ein Text"; // ab hier Kommentar
s2 = "hier geht's gleich wieder weiter";
// Hilfreiche Markierung für komplexe Datenstrukturen
Tensor = [ /* Slice 1: */ [ [111, 112]
, [121, 122] ]
, /* Slice 2: */ [ [211, 212]
, [221, 222] ]
, /* Slice 3: */ [ [311, 312]
, [321, 322] ] ];
/* Dieser Abschnitt ist nur für die Inbetriebnahme
T1 = 12.34 * Tensor;
*/
Die Zeichenfolge //
wird nicht berücksichtigt, wenn sie in einer mit "..."
oder '...'
markierten Zeichenkette oder innerhalb eines /* ... */
Kommentars steht. Im Beispiel werden also die Zuweisungen auf a
und e
nicht interpretiert, sehr wohl aber die Zuweisung auf z
.
/* Im Block-Kommentar wird // ignoriert */
s1 = "Dies ist ein //-Text und weder ein Kommentar noch eine Formel: 1+1";
/*
a = b + c;
e = f * g;
// */ z = b / g;
Die Kommentar Anfangs- und Endmarken /*...*/
werden jeweils nicht berücksichtigt, wenn sie nach einem "sichtbaren" //
notiert sind. Im Beispiel ist der Kommentar Block nun selbst auskommentiert, die Zuweisungen auf a
und e
werden ausgeführt, nicht mehr hingegen die Zuweisung auf z
.
// Im Zeilen-Kommentar werden /* und */ ignoriert
// /*
a = b + c;
e = f * g;
// */ z = b / g;
Wird eine begonnener Block-Kommentar /*...*/
nicht geschlossen, wird dies als Fehler angezeigt.
Bezeichner und Variablen
Für die Namen von Variablen (Signale, Kanäle) gelten folgende Konventionen
- das erste Zeichen muss aus der Gruppe a-z, A-Z oder _ sein
- alle folgenden Zeichen müssen aus der Gruppe a-z, A-Z, 0-9 oder _ sein
- Groß- und Kleinschreibung wird unterschieden
- alle nicht genannten Zeichen, wie z.B. Umlaute, Sonderzeichen, Klammern, mathematische Symbole, Leerzeichen, sind nicht zulässig!
Alle Bezeichner unterscheiden Groß- und Kleinschreibung (case-sensitive), d.h. "varIABLE", "Variable" und "VARiable" sind drei unterschiedliche Bezeichner.
Es ist möglich, Daten bestehender smartCORE Kanäle unter Angabe des Kanalnamens in die Berechnung mit einzubeziehen. Muss hierfür auf smartCORE Kanäle zugegriffen werden, deren Name die vorherige Regel verletzt, besteht eine Möglichkeit mittels folgender Notationen
$'Some + strange.å =channel ů /name!'
$"Some + strange.å =channel ů /name!"
Gleiches gilt für Kanäle, die an den smartCORE zurück gegeben werden sollen und eine spezielle Namensgebung benötigen.
Auf eine Variable darf nur an genau einer Stelle eine Zuweisung erfolgen. Variablen ohne eine lokale Zuweisung werden im Kontext des smartCORE als Datenquelle gesucht. Dies gilt auch für Variablen, die sekundäre Ausgänge eines Funktionsblocks darstellen und mit den Eigenschaften definiert werden.
Konstanten
Folgende Bezeichner sind als Konstanten im Kontext des Formeltextes verfügbar:
Bezeichner | Wert | Typ |
---|---|---|
true | true | <bool> |
on | true | <bool> |
yes | true | <bool> |
high | true | <bool> |
false | false | <bool> |
off | false | <bool> |
no | false | <bool> |
low | false | <bool> |
pi | <dbl> | |
magic | <uint> | |
j | <cxFlt> |
Sollte es Variablen im smartCORE mit einem dieser Namen geben, kann die spezielle Notation für die Variablen $'…' (siehe Bezeichner) die Unterscheidung gewährleisten. Im Übrigen hat die Verwendung des Bezeichners als Konstante Priorität.
Anweisungen
Die im Rahmen der Berechnungsvorschrift unterstützten Anweisungen umfassen die folgenden Kategorien
- arithmetisch-logische Operatoren
- mathematisch-trigonometrische Funktionen
- zeitliche Filterfunktionen (wie z.B. Differentiation und Integration von Funktionen)
- numerische Algorithmen (z.B. FFT, Dekomposition von Matrizen, Interpolation, Prädiktion)
- Abtastung (Sampling) von Messdaten
- Zählfunktionen und Timer
- Auswahlfunktionen, bit-orientierte Funktionen sowie Funktionen zur Umwandlung von Datentypen (Casts)
- die Unterstützung von Sequenzen von Ausdrücken und Zuweisungen
- Text-(String-)Funktionen
Bedingte Ausführung, Schleifen
Durch die fortlaufende Verarbeitung strömender, zeitgestempelter Daten ist jeder Term wie ein elektrisches Bauteil oder Filter in einer elektrischen Schaltung zu verstehen. Damit wird die Implementierung klassischer Steuer-Konstrukte, die den Ablauf des Formeltextes beeinflussen, sinnfrei. Deshalb sind folgende Sprachelemente oder ihre Varianten im Berechnungsmodul nicht zu finden:
- IF-THEN-ELSE
- WHILE-DO
- REPEAT-UNTIL
- FOR-TO-DO, FOR-EACH
- SWITCH-CASE
Allerdings gibt es verschiedene Funktionen, mit denen z.B. aus verschiedenen Signalquellen ausgewählt werden kann. Dies würde einem Relais oder Multiplexer in der Schaltungstechnik entsprechen.
Andere Funktionen erlauben das Speichern von Werten zu einem bestimmten Zeitpunkt oder die Erkennung von Werteänderungen bzw. Flanken.
Aus ähnlichen Gründen gibt es derzeit noch keine Implementierung für benutzerdefinierte Prozeduren oder Funktionen.
Funktionen zur Unterstützung von Sequenzen von Ausdrücken und Zuweisungen
Wir unterscheiden im Formelsatz zwischen zeitunabhängig sowie zeitabhängig ausgeführten Anweisungen.
Zeitunabhängige Ausführung
Jede Anweisung innerhalb des Formelsatzes muss mit einem Semikolon (;
) abgeschlossen werden. Sie darf, z.B. zur besseren Lesbarkeit, über mehrere Zeilen verteilt und mit Leerzeichen oder Tabulatoren eingerückt werden.
Es können mehrere Anweisungen innerhalb einer Zeile durch Semikolon getrennt spezifiziert werden.
Zeitabhängige Reihung von Ausdrücken
Darüberhinaus besteht die Möglichkeit zur gesichert aufeinanderfolgenden Ausführung von Ausdrücken, z.B. zum Aufbau einer Parameterliste für Funktionen.
Hierfür sind diese Ausdrücke durch ein Komma (,
) voneinander zu trennen.
Datentypen und Typkonvertierung
Datentypen
Das Math Modul wählt für die Berechnung jeweils einen passenden Datentyp aus, um mit bestmöglicher Darstellung die gewünschten Operationen ausführen zu können. Die Typen sind im folgenden
Datentyp | Bezeichnung | Beschreibung | Bitlänge | Minimum | Maximum |
---|---|---|---|---|---|
boolean | <bool> | Wahrheitswert | 1 | false, no, off, low | true, on, yes, high |
unsigned integer | <uint> | Vorzeichenlose Ganzzahl | 64 | 0 | 18446744073709551615 |
integer | <int> | Vorzeichenbehaftete Ganzzahl | 64 | -9223372036854775808 | +9223372036854775807 |
Kleinste Auflösung | Wertebereich | ||||
double | <dbl> | Fließkommazahl mit doppelter Genauigkeit | 64 | ±5.0E-324 | ±1.7976931348623157E+308 |
complex float | <cxFlt> | komplexe Fließkommazahl mit einfacher Genauigkeit (*) | 2x 32 | ±1.175494E-38 je Re-/Im-Komponente | ±3.402823E+38 je Re-/Im-Komponente |
string | <str> | Text, UTF-8 | N/A | N/A | N/A |
In der Regel muss man sich um den Typ nicht kümmern, bei der Übergabe an den smartCORE wird in den dafür angegebenen Datentyp passend umgewandelt.
In dieser Funktionsbeschreibung wird der Datentyp üblicherweise in spitze Klammern gesetzt, also z.B. <bool>
oder <str>
.
Automatische Typkonvertierung
Bei der Verarbeitung numerischer Daten mittels binärer Operatoren erfolgt folgende automatische Typkonvertierung mit Bezug auf die Typen der Operatoren
bool | uint | int | dbl | cxFlt | str | |
---|---|---|---|---|---|---|
bool | bool | uint | int | dbl | cxFlt | str |
uint | uint | uint | int | dbl | cxFlt | str |
int | int | int | int | dbl | cxFlt | str |
dbl | dbl | dbl | doudblle | dbl | cxFlt | str |
cxFlt | cxFlt | cxFlt | cxFlt | cxFlt | cxFlt | str |
str | str | str | str | str | str | str |
Damit bleiben so lange wie möglich
- Boolsche Werte auch nur true/false
- die Eigenschaften von uint Werten für Bit-Operationen erhalten
- das Vorzeichen bei der Verwendung von int-Werten erhalten
- die Genauigkeit von Fließkommazahlen erhalten
- der komplexe Zahlenraum verfügbar
Besonderheiten für bool
-
Bei der Umwandlung nach
<bool>
gilt implizit der Vergleich auf ungleich 0, um zum Wahrheitswert zu kommen. -
Bei der Umwandlung von
<bool>
in einen numerischen Typ wird false zu 0 und true zu 1. -
Bei der Addition zweier Boolescher Werte erfolgt die Konvertierung nach
<uint>
. -
Bei der Subtraktion zweier Boolescher Werte erfolgt die Konvertierung nach
<int>
. -
Bei der Verrechnung von Datenfeldern vom Typ
<bool>
ist das Ergebnis immer<int>
.
Besonderheiten für uint
-
Bei der Subtraktion zweiter Vorzeichen loser
<uint>
-<uint>
ist das Ergebnis<int>
, sofern der zweite Operand größer als der erste ist. Damit wird der meist völlig unsinnige, nicht gewollte Überlauf in Größen von 2^64 vermieden. -
Bei der Verrechnung von Datenfeldern vom Typ
<uint>
ist das Ergebnis immer<int>
.
Manuelle Typkonvertierung
Es ist ebenso möglich, unter Verwendung folgender Funktionen eine manuelle Typkonvertierung durchzuführen
Funktion | Beschreibung |
---|---|
bool(x) | Konvertierung des Arguments x in nach bool |
uint(x) | Konvertierung des Arguments x in nach uint |
int(x) | Konvertierung des Arguments x in nach int |
dbl(x) | Konvertierung des Arguments x in nach double |
str(x) | Konvertierung des Arguments x in nach string |
Numerische Typen
Direkt im Formeltext und den Konfigurationsobjekten für die Eigenschaften von Funktionen können numerische Werte auch in unterschiedlichen Zahlensystemen angegeben werden. Beispiele für Schreibweisen sind:
aBool = true; // alt.: false, on, off, yes, no, high, low
anUnsignedInt = 42; // alt.: 42u
aHexUInt = 0x2A; // alt.: 2Ah
anOctUInt = 052; // alt.: 52o
aBinUInt = 0b10'1010; // alt.: 101010b
aSignedInt = -42;
aDouble = 3.14159; // alt.: 1.6022e–19, pi, !! decimal-point
aComplex = 2.7+1.0*j; // alt.: cx(2.7, 1.0)
Gruppentrennzeichen
Für vorzeichenlose (<uint>
) oder Vorzeichen behaftete (<int>
) Ganzzahlen können die einzelnen Ziffern durch einfügen eines einfachen Anführungszeichen '
in Gruppen unterteilt werden. Dieses Zeichen spielt bei der Interpretation des Zahlenwertes keine Rolle, vereinfacht aber das Lesen. Es darf nicht am Anfang oder am Ende einer Zahl stehen.
aHexUInt = FFe7'0815'337Eh; // 0815 ist hier kein String!
aBinUInt = 0b1011'0110'1010;
aLargeInt = 1'000'000'000;
Vorzeichen und Negation
Durch das Voranstellen eines -
Operators wird für jedes Zahlenformat der negative Wert gebildet (2er-Komplement).
Das Voranstellen des +
Operators ist erlaubt, aber ohne Funktion.
Der ~
Operator invertiert auf der bit-Ebene eines <uint>
die Ganzzahl (Beispiel: ~0b1010'0011'1100
-> 0b1111...1111'0101'1100'0011
).
Hexadezimales Zahlenformat
Die Darstellung einer Zahl im hexadezimalen System (Basis 16) erfolgt entweder durch Voranstellen eines 0x
oder 0X
oder durch das Anhängen des Buchstabens h
. Die möglichen Ziffern sind 0
.. 9
, a
.. f
bzw. A
.. F
und das Gruppentrennzeichen.
Die Angabe einer Zahl im hexadezimalen System wird immer als vorzeichenlos interpretiert, kann aber durch die Operatoren -
negiert oder ~
invertiert werden.
Oktales Zahlenformat
Die Darstellung einer Zahl im oktalen System (Basis 8) erfolgt entweder durch Voranstellen einer 0
(Null) oder durch das Anhängen des Buchstabens o
. Die möglichen Ziffern sind 0
.. 7
und das Gruppentrennzeichen.
Die Angabe einer Zahl im oktalen System wird immer als vorzeichenlos interpretiert, kann aber durch die Operatoren -
negiert oder ~
invertiert werden.
Binäres Zahlenformat
Die Darstellung einer Zahl im binären System (Basis 2) erfolgt entweder durch Voranstellen eines 0b
oder 0B
oder durch das Anhängen des Buchstabens b
. Die möglichen Ziffern sind 0
.. 1
und das Gruppentrennzeichen.
Die Angabe einer Zahl im binären System wird immer als vorzeichenlos interpretiert, kann aber durch die Operatoren -
negiert oder ~
invertiert werden..
Zeichenketten (Stringliterale)
Konstante Zeichenketten werden innerhalb einfacher '
oder doppelter "
Anführungszeichen eingefasst. Enthält eine Zeichenkette selbst wieder ein '
oder "
, muss dieses durch Voranstellen eines \
-Escape-Zeichens markiert werden. Beispiele:
s1a = '17" (inch)';
s1b = "17\" (inch)";
s2a = "Hat's funktioniert?";
s2b = 'Hat\'s funktioniert?';
Generell können Sonderzeichen in Zeichenketten, die z.B. bei der Interpretation oder Generierung von seriellen Geräteprotokollen Verwendung finden, durch folgende Escape-Sequenzen dargestellt werden:
Zeichen | ASCII Name | ASCII Wert | Escape-Sequenz |
---|---|---|---|
Newline (Linefeed) | NL (LF) | 10 | \n |
Horizontal tab | HT | 9 | \t |
Vertical tab | VT | 11 | \v |
Backspace | BS | 8 | \b |
Carriage return | CR | 13 | \r |
Formfeed | FF | 12 | \f |
Alert | BEL | 7 | \a |
Escape | ESC | 27 | \e |
Backslash | \ | 92 | \\ |
Question mark | ? | 63 | \? |
Single quotation mark | ' | 39 | \' |
Double quotation mark | " | 34 | \" |
Character from octal code | NNN = 000 .. 377 (octal digits) | \oNNN \o{N...} | |
Character from decimal code | NNN = 000 .. 255 (decimal digits) | \dNNN \d{N...} | |
Character from hex code | NN = 00 .. FF (hex digits) | \xNN \x{N...} | |
UTF characer(s) from decimal code | UTF8-Multibyte | NNNN = x0000 .. xFFFF x00000000 .. x0010FFFF | \uNNNN \UNNNNNNNN \u{N...} |
Die Kodierung der Zeichen erfolgt intern immer in UTF-8 (UTF-8 – Wikipedia).
Arithmetisch-Logische Operatoren
In diesem Abschnitt werden die arithmetisch-logische Operatoren inklusive ihrer Rangordnung und Assoziativität diskutiert.
Wir bezeichnen einen Operator ° als links-assoziativ, wenn folgendes gilt
a ° b ° c = (a ° b) ° c
und entsprechend als rechts-assoziativ, wenn gilt
a ° b ° c = a ° (b ° c)
Ein Operator *
hat eine höhere Rangordnung (Präzedenz, Bindungsstärke) als Operator +
, wenn unabhängig von den Assoziativitäten beider Operatoren gilt
a + b * c = a + (b * c)
Es wird empfohlen, bei nicht sofort ersichtlicher Bindung von Termen, die beabsichtigte Auswertungsreihenfolge durch reichliche Verwendung von (...)
zu erzwingen. Gerade bei logischen Operatoren oder Vergleichsoperatoren ist die Präzedenz nicht immer geläufig. Bei komplexen Ausdrücken ist zusätzlich die Nutzung mehrerer aufeinanderfolgender Zeilen und eine geeignete Ausrichtung der Terme zur Gliederung empfohlen.
res = A && (B || (C && D) || (E && (F || G))); // Variante 1
opt = A && ( B // optionaler Kommentar
|| (C && D) // optionaler Kommentar
|| (E && ( F // optionaler Kommentar
|| G) // optionaler Kommentar
)
); // Variante 2
Operatoren
Die folgende Tabelle enthält die verfügbaren Operatorn, sortiert mit aufsteigender Präzedenz. Alle hier aufgeführten Operatoren arbeiten auf den zuvor eingeführten Datentypen. Für die bit-orientierten logischen Verknüpfungen und Operationen sind eigene Funktionen verfügbar.
Operator | Zuweisung | Assoziativität | Beschreibung | Beispiel | Ergebnistyp |
---|---|---|---|---|---|
; | links | zeitunabhängig ausgeführte Sequenz von Befehlen | y = 7; x = 4 | void | |
, | links | Reihung einzelner Ausdrücke | a = 4, b = 6, a + b | wie letzter Ausdruck | |
+= | JA | rechts | Verbundzuweisung bzgl. Addition | a += 17 | automatisch |
-= | JA | rechts | Verbundzuweisung bzgl. Subtraktion | a -= b | automatisch |
*= | JA | rechts | Verbundzuweisung bzgl. Multiplikation | a *= b | automatisch |
/= | JA | rechts | Verbundzuweisung bzgl. Division | a *= b | automatisch |
%= | JA | rechts | Verbundzuweisung bzgl. Modulo | b %= 4 | automatisch |
= | JA | rechts | Zuweisung | a = 23 * b | wie zugewiesener Ausdruck |
? : | rechts | ternärer Operator if-else | (a > 23) ? 12 : -50 | wie resultierender Body | |
|| | links | logische Disjunktion (OR) | a || b | <bool> | |
&& | links | logische Konjunktion (AND) | a && b | <bool> | |
^^ | links | logische Antivalenz (XOR) | a ^^ b | <bool> | |
== | links | Gleichheit | a == b | <bool> | |
!= | links | Ungleichheit | a != b | <bool> | |
< | links | kleiner als | a < b | <bool> | |
<= | links | kleiner oder gleich | a <= b | <bool> | |
> | links | größer als | a > b | <bool> | |
>= | links | größer oder gleich | a >= b | <bool> | |
+ | links | Addition | a + b | automatisch | |
- | links | Subtraktion | a - b | automatisch | |
* | links | Multiplikation | a * b | automatisch | |
/ | links | Division | a / b | automatisch | |
% | links | Modulo | a % b | automatisch | |
^ | rechts | Potenzierung | a ^ 0.333 | <dbl> | |
! | rechts | logische Invertierung (NOT) | !(a && b) | <bool> | |
~ | rechts | bit-orientierte Invertierung | ~a | <uint> | |
+ | rechts | positives Vorzeichen | 6 / +a | automatisch | |
- | rechts | negatives Vorzeichen | 9 * -a | automatisch |
Zuweisungsoperatoren
Auf eine Variable darf nur an genau einer einzigen Stelle im Formeltext eine Zuweisung erfolgen. Variablen ohne eine lokale Zuweisung werden im Kontext des smartCORE als Datenquelle gesucht.
Bei Zuweisungsoperatoren muss zwingend auf der linken Seite eine Variable (x
, y
, …), ein Element einer zuvor bereits zugewiesenen Matrix (V[2]
, M[0,2]
, …) oder der Initialisier für eine diskret abgetastete Variable (k@0
, …) stehen, in der das Ergebnis abgelegt wird. Das Ergebnis des Ausdrucks bleibt der zugewiesene Wert. Damit wird auch die Reihung von Zuweisungen oder die Verwendung einer Zuweisung innerhalb eines Ausdrucks möglich.
Operatoren für Datenfelder
Werden die Operatoren +, -, * und / auf Datenfelder (Vektoren, Matrizen, Tensoren) angewendet, gelten die in der Mathematik üblichen Regeln bezüglich der Dimension der Operanden und der daraus folgenden Dimension des Ergebnisses. Es gelten folgende Regelungen:
-
Für die Addition und Subtraktion müssen die beteiligten Datenfelder die selbe Dimension besitzen. Die Operation erfolgt jeweils auf die Elemente mit gleichen Indices.
-
Ein Datenfeld kann beliebig mit einem Skalar multipliziert werden:
DF * s
oders * DF
. Die Operation wird für jedes Element ausgeführt. -
Ein Datenfeld kann nur durch einen Skalar geteilt werden:
DF / s
Die Operation wird für jedes Element ausgeführt. -
Für die Multiplikation muss das erste Datenfeld so viele Spalten haben, wie das zweite Datenfeld Zeilen besitzt. Das Ergebnis enthält so viele Zeilen wie das erste Datenfeld und so viele Spalten wie das zweite Datenfeld.
Da bei Vektoren hier nicht explizit zwischen Zeilen- und Spaltenvektor unterschieden wird, gelten folgende Regelungen:-
Das Produkt zweiter Vektoren gleicher Länge ist immer das Skalar-Produkt.
-
Für das Vektorprodukt 2- und 3-komponentiger Vektoren ist die Funktion
Cross(v1, v2)
zu verwenden. -
Das Produkt aus Vektor und Matrix liest den Vektor als Zeilenvektor.
-
Das Produkt aus Matrix und Vektor liest den Vektor als Spaltenvektor.
-
Übereinstimmung von Datenfeldern
Zwei Datenfelder sind genau dann gleich, wenn sie vom Datentyp, in der Dimension und allen Elementen exakt übereinstimmen. Eine automatische Typkonvertierung findet vor dem Vergleich nicht statt. Zwei Datenfelder sind ungleich, wenn sie nicht gleich im Hinblick auf die vorherige Definition sind.
Andere Vergleiche sind nicht verfügbar.
Klammern
Folgende Klammer-Paare werden auch in geschachtelten Ausdrücken erkannt. Jede öffnende Klammer muss in der richtigen Reihenfolge korrekt geschlossen werden.
Klammerung | Bedeutung | Beispiel |
---|---|---|
( ... ) | Bündelung in der Auswertereihenfolge, Gruppierung von Funktionsparametern | (a+b)/2, max(a, b, 50) |
[ ... ] | Reserviert für Darstellung von Vektoren, Matrizen und Tensoren und Indizierung von Variablen, die Indizierung zählt immer von 0…(N-1), für Vektoren : V[<idx>] , Zeilen-/Spaltenvektor wird nicht unterschiedenfür Matritzen: M[<row>, <col>] für Tensoren: T[<slice>,<row>, <col>] | a = [1, 2, 3], b = [[1.1, 1.2], [2.1, 2.2]], a[1] + b[0,1] |
{ ... } | - Klammerung für JSON-Objekte, die zur Parametrierung von Funktionsblöcken verwendet werden können. Da diese sehr umfangreich werden können, wird empfohlen über einen Link $ref: '<path>' auf eine externe JSON Ressource zu verweisen, die in einem gesonderten Container des Berechnungsmoduls gesammelt wird. | timer({ interval: 60 }) |
? ... : | Der Ausdruck zwischen ? und : ist implizit mit (…) umgeben. |
Funktionen
Funktionen für das Math Modul werden vom Plugin selbst, zukünftig auch über weitere Themen-Plugins für das Mathe Modul, bereit gestellt und sind grundsätzlich in C++ implementiert.
Funktionsaufruf
Jede Funktion besteht aus einem Funktionsnamen, der der üblichen Bezeichner-Syntax folgt
- das erste Zeichen ist aus der Gruppe a-z, A-Z oder _ sein
- alle folgenden Zeichen sind aus der Gruppe a-z, A-Z, 0-9 oder _ sein
- Groß- und Kleinschreibung wird unterschieden
gefolgt von einer Parameterliste, die in ( ... )
eine durch Komma separierte Liste einzelner Terme beinhaltet.
Bei der Beschreibung von komplexen Funktionsblöcken werden die Parameter der Funktion auch als Eingänge und der Rückgabewert der Funktion als Ausgang bezeichnet.
Bezeichner für Namensräume können über einen '.' dem Funktionsnamen vorangestellt werden. Im Übrigen folgen die Bezeichner für Namensräume den gleichen Regeln.
Beispiele für Funktionsaufrufe:
f1 = functionNoParam();
f2 = functionOneParam(x);
f3a = functionOptionalParam(x,a);
f3b = functionOptionalParam(x,a,b);
f3c = functionOptionalParam(x,a,b,c);
f4 = functionWithConfig(x,y,z, {...});
Für die Parameter wird unterschieden in verpflichtende und optionale Parameter (Eingänge), sowie einen Konfigurationsobjekt für die Funktionseigenschaften als letztes Element. Die Bedeutung der Parameter kann mit der Anzahl der genutzten Parameter variieren, dies ist der Beschreibung der Funktion zu entnehmen.
Um Redundanzen und damit verbundenen Fehler zu vermeiden, wird in der Beschreibung der Funktion mit Platzhaltern ...
gearbeitet. In der Regel werden alle Varianten mit optionalen Parametern aufgeführt, sowie ein zusätzlicher Eintrag, in dem nur das Konfigurationsobjekt beschrieben wird:
f1 = aFunction(x);
f2 = aFunction(x, y);
// Optionale Konfiguration für alle Varianten
fx = aFunction(..., { eigenschaft1: <typ>
, eigenschaft2: <typ>
});
Ist das Konfigurationsobjekt in einer Variante verpflichtend, ist es direkt in dieser Variante auch als Platzhalter aufgeführt.
f1 = bFunction(x, {...}); // Konfiguration verpflichtend
f2 = bFunction(x, y, z); // Konfiguration optional
// Konfiguration für alle Varianten
fx = bFunction(..., { eigenschaft1: <typ>
, eigenschaft2: <typ>
});
Jede Verwendung einer Funktion im Formeltext wird durch eine eigene Instanz abgebildet, in der z.B. Zustände oder Aggregationen von Berechnungszyklus zu Berechnungszyklus gespeichert werden können. Beispiele hierfür sind
- Zähler
- Summierer, Integratoren
- Filter
- Hysterese
- Logik-Elemente mit Speicherfunktion
- Funktionen mit Zeitbezug
Funktionen werden in der Regel dann ausgeführt und erzeugen einen Ausgabewert, wenn für alle Parameter gültige Werte vorliegen und mindestens ein Parameter einen neuen Zeitstempel führt. Darüber hinaus werden manche Funktionen auch dann ausgeführt, wenn eine (interne) Zeitbedingung erfüllt ist.
Bestimmte Funktionen, wie z.B. intergrate()
oder stopwatch()
, markieren ihre Ausgangsdaten so, dass zwischen zwei konkreten Datenpunkten von nachfolgenden Berechnungen auch linear interpoliert werden kann. Dies verbessert die Qualität der Berechnung, da hier bekannt ist, dass ein konstantes Eingangssignal zu einem linearen Ausgangssignal führen wird.
Funktionen können auch unabhängig von den Zeitstempeln der Parameterwerte oder dem Verarbeitungszyklus zusätzliche Ausgabewerte im Datenstrom platzieren, z.B. wenn mit Ablauf eines Zeitgebers ein Signal zurückgestellt werden muss oder durch die Funktion selbst die Form des Ausgangssignals definiert ist. Beispiel sind dafür die Funktionen posedge()
und negedge()
, die beim Erkennen einer Flanke jeweils einen Impuls mit der Dauer von 1 Nano Sekunde in den Ausgabe Datenstrom schreiben.
Konfiguration der Funktionseigenschaften
Das Verhalten bestimmter Funktionen kann durch Eigenschaften eingestellt, optimiert oder verändert werden. Diese Eigenschaften werden in einem Konfigurationsobjekt {...}
an letzter Position der Parameterliste an die Funktion übergeben.
Die implementierte Syntax lässt gegenüber dem JSON Standard einige Vereinfachungen aber auch sinnvolle Erweiterungen zu, um z.B. die JSON-Darstellung in der "math"-Eigenschaft von zu vielen \
-Escape-Sequenzen zu bereinigen:
- Namen der Eigenschaften oder ENUM-Tags müssen nicht in Hochkomma eingefasst werden
- Die Verwendung von Anführungszeichen
"..."
und'...'
für die Zuweisung von Strings ist gleichgestellt. - Für Integer-Zahlenwerte können alle Notationen, wie z.B.
0x...
oder0...
für die Eingabe verwendet werden. - Einige Eigenschaften erlauben alternativ zur Angabe eines Zahlenwertes auch bestimmte ENUM-Tags, um z.B. die entsprechende Funktion abzuschalten (
'off'
) oder auf Standardwerte ('def'
,'auto'
, ...) zu setzen. - Kommentare in beiden Notationen
// ...
und/* ... */
sind zulässig
Folgende Typen sind für Eigenschaften vorgesehen:
Typ | Werte | Beispiel |
---|---|---|
<bool> | true, on, yes, 1 false, off, no, 0 | edge: yes |
<uint> | ganzzahlige, positive Werte auch in 0x- (hex) oder 0- (octal) Notation | range: 0x7F |
<int> | ganzzahlige Werte mit Vorzeichen | min: -10 |
<dbl> | Fließkommazahlen (Dezimal-Punkt!) auch in exponential-Schreibweise 1.35e-22 | delay: 13.45 |
<cxFlt> | komplexe Fließkommazahl | pole: (-5, 0.8) |
<str> | Zeichenkette / Text, Anführungszeichen nur zwingend, wenn Leerzeichen, Komma oder Sonderzeichen enthalten sind. | storage: 'total_km' |
<var> | Variant, kann alle zuvor genannten Typen annehmen | preset: 0x2F |
<enum> | Bestimmte enumerierende Tags, siehe Beschreibung der Funktion | mode: peak2 |
off/<typ> | Alternative aus Enum-Tags oder einem regulären Typ <typ> | lower: off, upper: 12.6 |
[<typ>] | Komma-Separierter Vektor mit Daten vom Typ <typ> | map: [1,2,4,8] |
Mit Hilfe des reservierten Schlüsselwortes $ref
kann der Inhalt der Konfiguration vollständig aus einer Ressource bezogen werden. Dies kann eine Konfiguration im zuvor beschriebenen, vereinfachten JSON Format sein oder aber auch ein beliebiger Text oder sogar binärer Inhalt, sofern die Funktion dieses benötigt und unterstützt.
f = someConfigExpectingFunction({$ref:'someNamedResource'});
Die Konfiguration ist grundsätzlich über die Laufzeit konstant. Eine Berechnung der Eigenschaftswerte ist nicht zulässig. Eine Verknüpfung zu Variablen im Formelsatz ist nicht möglich. Manche Funktionen haben dafür optionale Parameter, die es ermöglichen, Einstellungen wie z.B. Grenzwerte auch dynamisch zu verändern.
Die in der Dokumentation aufgeführten Eigenschaften sind optional, sofern sie nicht als verpflichtend (mandatory) gekennzeichnet sind.
Persistente Zustandsinformationen
Bestimmte Funktionen, die bevorzugt zur Aggregation von Signalen oder Ereignissen verwendet werden, können ihren internen Zustand über die persistenten Kanäle des smartCORE speichern und nach einem Neustart der Software auf dem zuvor gespeicherten Zustand aufbauen und die Berechnung fortsetzen. Beispiele dafür sind
- Zähler
- Integratoren
- Klassierungen
Dazu werden im Bereich der JSON Konfiguration der entsprechenden Funktion der Name für einen Speicherort über die Eigenschaft storage angegeben. Der Inhalt dieser automatisch erzeugt und verwalteten Variable ist in der Regel nicht durch den Anwender oder andere Funktionseinheiten des smartCORE interpretierbar. Eine Nutzung des selben Speichers an verschiedenen Stellen im Formeltext ist nicht zulässig.
Falls in dem Speicher ein Zustand mit einem Bezug zu einer physikalischen Größe persistiert wird, ist zu empfehlen, eine Kennung für deren Einheit an den Namen mit anzuhängen (z.B. 'totalDistance_km'), um die Lesbarkeit und spätere Wartung des Formeltextes zu vereinfachen.
Auf den Inhalt des persistenten Speichers kann ausschließlich über die selbe Funktion wieder zugegriffen werden.
In dem folgendem Beispiel wird ein Zählerstand persistiert:
f = someCounterFunction(someCondition, {storage:'some.counter.variable'});
Alle persitierten Daten werden periodisch als BLOB in einer SQLite Datenbank abgelegt. Einträge in dieser Datenbank werden nicht automatisch entfernt.
Präprozessor-Direktiven
Über Präprozessor-Direktiven können Funktionen des Math Modul Compilers, der Verarbeitung von Variablen und der Ausführung des Codes beeinflusst werden. Die Präprozessor-Direktiven müssen am Anfang einer Zeile im Formel-Text stehen und enden mit dem ersten Zeilenende, das nicht in einer Klammer-Ebene verborgen ist. Kommentare können nach oben genannten Regeln eingefügt werden.
Die Direktiven werden durch ein #
-Zeichen gefolgt von einem Bezeichner, der nach den oben genannten Regeln gebildet wird, gekennzeichnet. Welche Parameter der Direktive folgen und wie diese formatiert sind, hängt von der Funktion ab. Mögliche Formate sind zum Beispiel:
#directiveA <parameter>
#directiveB <name>[<parameter>](<parameter>){<code>}
#directiveC <key>, <key>=<value>, …
Die Präprozessor-Direktiven sind in diesem Dokument zusammengestellt.