Zum Hauptinhalt springen

Sprachreferenz

In dieser Referenz wird der Aufbau des Formelsatzes diskutiert.

Struktur des Formelsatzes

Ein Formelsatz besteht aus einer mittels ;-Zeichen verbundenen und abgeschlossenen Liste von Termen , sowie optional Kommentaren und Steuerkonstrukten.

Steuerkonstrukt
Term 1;     // optionaler Kommentar
Term 2;
Term 3;      /* Beschreibung */

Terme

Ein Term ist

  • ein konstanter boolscher oder numerischer Wert oder eine Zeichenkette
    Beispiele: false, 14, -23.5, "Hello"

  • ein Variablen-Name
    Beispiele: Speed, $"GPS.Location"

  • ein durch einen Klammer-Operator gruppierter Term
    Beispiele: (Term + Term) * Term, [Term, Term, Term],

  • ein durch einen Unären-Operator modifizierter Term
    Beispiele: ! Term, - Term, ~ Term

  • ein durch einen Operator verknüpftes Paar von Termen
    Beispiele: Term = Term, Term + Term, Term ? Term : Term

  • eine durch ,-Operatoren verknüpfte Liste von Termen
    Term, Term, Term, ...

  • ein Funktionsaufruf
    funktion(...)

  • ein Konfigurationsobjekt, das durch {...} abgegrenzt ist.
    Das Konfigurationsobjekt folgt einer eigenen Syntax, z.B. einer vereinfachten JSON Kodierung.

Terme, Operatoren und Funktionen werden ausschließlich nach der zeitlichen Verfügbarkeit von Daten verarbeitet, die notierte Reihenfolge spielt für die Berechnung keine Rolle.

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;
hinweis

Wird eine begonnener Block-Kommentar /*...*/ nicht geschlossen, wird dies als Fehler angezeigt.

Steuerkonstrukte

Steuerkonstrukte beginnen am Anfang der Zeile mit einem #-Zeichen und laufen bis zum nächsten Zeilenende, das nicht in einer Klammerungs-Ebene verborgen ist. Der Aufbau eines Steuerkonstrukts folgt je nach Funktion einer entsprechend definierten Syntax. Kommentare können eingefügt werden.

#controlA <parameter>
#controlB <name>[<parameter>](<parameter>){<code>}
#controlC <key>, <key>=<value>, …

Über Steuerkonstrukte können Funktionen des Math Modul Compilers, der Verarbeitung von Variablen und der Ausführung des Codes beeinflusst werden.

Ein Steuerkonstrukt kann damit Auswirkungen auf die nachfolgend notierten Terme haben.

Die Steuerkostrukte sind in diesem Dokument zusammengestellt.

Umsetzung der gewünschten Funktionalität

Werkzeugkasten

Für die Umsetzung der gewünschten Funktionalität stehen Funktionen und Operatoren in folgenden Kategorien zur Verfügung:

  • 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

Mit fortschreitender Entwicklung des Math Moduls werden immer wieder neue Funktionen aufgenommen und selbstverständlich auch Fehler in bestehenden Funktionen behoben. Abhängig von der ausgerollten smartCORE Version steht ein bestimmter Versionsstand zur Verfügung. Dieser wird in Form einer fortlaufenden Katalog-Nummer auch im Log-File angezeigt, im Beispiel V11:

...|...|...|INFO   |module.math:  (II)  "Welcome to mathExpression library, git '0ed7f17-dirty', build 'Feb  2 2026 15:30:55'"
...|...|...|INFO |module.math: (II) "Package 'mathExpCore', V11 (using V11)"

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.

Zeitrichtige Berechnung

Alle Terme, Opertoren und Funktionen verarbeiten und produzieren immer zeitgestempelte Daten, die ja für den smartCORE typisch sind. Diese Daten werden zeitrichtig miteinander verrechnet, wobei in der Regel für jeden Eingangszeitstempel der an einem Term beteiligten Datenströme ein Ausgangswert berechnet wird. Dabei wird der Signalverlauf zwischen zwei Zeitstempeln als konstant (Halteglied 0-ter Ordnung) 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 seinem jüngsten Zeitstempel. Die folgende Tabelle zeigt ein Beispiel für zwei zeitgestempelte Datenquellen A und B und eine Berechnung:

ZeitpunktABA + B
1(nicht definiert)10(nicht definiert)
2515
32025
53035
8838
1394049
26545
27242
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. Das Math Modul bietet verschiedene Möglichkeiten, diese blockierenden Situationen zu entschärfen.

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 (Batch-Processing). 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 eher 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.

Zeitdiskrete Berechnung

Die zeitrichtige Berechnnung kann nicht realisiert werden, wenn Variablen mit einer zirkulären Referenz auf sich selbst zurück geführt werden. Beispiel für ein solches Konstrukt ist die Implementierung eines einfachen gewichteten Filter-Algorithmus:

y_out = weight * y_out + (1 - weight) * x_in;

Nach zuvor eingeführter Regel wird y_out für alle Zeitpunkte neu berechnet, die durch die Eingangsdaten weight, y_out und x_in definiert werden. Während weight und x_in unabhängig als Konstant gesetzt (damit immer gültig) oder aus einem Messsignal fortgeführt werden, wird y_out zunächst gar nicht gefüllt. Was definiert den ersten Wert für y_out? Und wenn dieser mit Zeitstempel gesetzt ist, kann y_out maximal bis genau zu diesem Zeitstempel berechnet werden - also gar nicht, denn dieser Zeitpunkt ist ja schon zugewiesen.

In der Regelungs- und Steuerungstechnik sowie auch in Programmiersprachen interpretiert man den oben aufgeführten Ausdruck jedoch so, dass der "neue" y_out-Wert sich nach Vorschrift aus dem "alten" y_out ergibt. Das "neue" y_out liegt dann um einen Abtastschritt in der Zukunft, sodass die nächste Berechnung entsprechend ausgeführt werden kann.

yout(t+Δt)=wyout(t)+(1w)x(t)y_{out}(t+\Delta t)=w \cdot y_{out}(t) + (1-w) \cdot x(t)

Das Math Modul erkennt zirkuläre Referenzen (auch über Umwege) und stellt betroffene Variablen auf eine diskrete Berechnungsvorschrift um. Die Variablen schreiten nun in einem festen Zeitintervall und Raster voran, dass durch den Parameter discreteSampleTimeMs bestimmt wird. Auf das Raster werden alle diskreten Berechnungen getaktet.

Um die Frage des Anfangswertes zu lösen, wird ein spezielle Syntax definiert, die nur einmalig zur Ausführung kommt: der Variable wird der Zuweisung des Startwertes das Suffix @0 angehängt:

y_out@0 = x_in;
y_out = weight * y_out + (1 - weight) * x_in;

Der erste Verfügbare Wert auf der Eingangsvariable x_in wird in diesem Beispiel als Startwert am unmittelbar folgenden Rasterpunkt für y_out verwendet. Ab diesem Moment läuft die Berechung und Zuweisung von y_out im festen ausgerichteten Zeitintervall.

warnung

Durch die Abtastung können schnelle Signaländerungen, die zwischen zwei Abtastzeitpunkten liegen, verloren gehen. catch(x, {...}) kann verwendet werden, um kritische, schnellere Signalanteile aus dem Intervall zu berücksichtigen.

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.

vorsicht

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:

BezeichnerWertTyp
truetrue<bool>
ontrue<bool>
yestrue<bool>
hightrue<bool>
falsefalse<bool>
offfalse<bool>
nofalse<bool>
lowfalse<bool>
pi3.14159265...3.14159265...<dbl>
magic4242<uint>
j(0,1)(0, 1)<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.

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

DatentypBezeichnungBeschreibungBitlängeMinimumMaximum
boolean<bool>Wahrheitswert1false, no, off, lowtrue, on, yes, high
unsigned integer<uint>Vorzeichenlose Ganzzahl64018446744073709551615
integer<int>Vorzeichenbehaftete Ganzzahl64-9223372036854775808+9223372036854775807
Kleinste AuflösungWertebereich
double<dbl>Fließkommazahl mit doppelter Genauigkeit64±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-8N/AN/AN/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.

info

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

booluintintdblcxFltstr
boolbooluintintdblcxFltstr
uintuintuintintdblcxFltstr
intintintintdblcxFltstr
dbldbldbldoudblledblcxFltstr
cxFltcxFltcxFltcxFltcxFltcxFltstr
strstrstrstrstrstrstr

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

FunktionBeschreibung
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.

hinweis

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.

hinweis

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.

hinweis

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. für reguläre Ausdrücke bei der Textsuche oder bei der Interpretation oder Generierung von seriellen Geräteprotokollen Verwendung finden, durch folgende Escape-Sequenzen dargestellt werden:

ZeichenASCII NameASCII WertEscape-Sequenz
Newline (Linefeed)NL (LF)10\n
Horizontal tabHT9\t
Vertical tabVT11\v
BackspaceBS8\b
Carriage returnCR13\r
FormfeedFF12\f
AlertBEL7\a
EscapeESC27\e
Backslash\92\\
Question mark?63\?
Single quotation mark'39\'
Double quotation mark"34\"
Character from octal codeNNN = 000 .. 377
(octal digits)
\oNNN
\o{N...}
Character from decimal codeNNN = 000 .. 255
(decimal digits)
\dNNN
 \d{N...}
Character from hex codeNN = 00 .. FF
(hex digits)
\xNN
\x{N...}
UTF characer(s) from decimal codeUTF8-MultibyteNNNN = 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.

Assoziativität

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)
tipp

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 Operatoren, 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.

OperatorZuweisungAssoziativitätBeschreibungBeispielErgebnistyp
;linkszeitunabhängig ausgeführte Sequenz von Befehleny = 7; x = 4void
,linksReihung einzelner Ausdrückea = 4, b = 6, a + bwie letzter Ausdruck
+=JArechtsVerbundzuweisung bzgl. Additiona += 17automatisch
-=JArechtsVerbundzuweisung bzgl. Subtraktiona -= bautomatisch
*=JArechtsVerbundzuweisung bzgl. Multiplikationa *= bautomatisch
/=JArechtsVerbundzuweisung bzgl. Divisiona *= bautomatisch
%=JArechtsVerbundzuweisung bzgl. Modulob %= 4automatisch
=JArechtsZuweisunga = 23 * bwie zugewiesener Ausdruck
? :rechtsternärer Operator if-else(a > 23) ? 12 : -50wie resultierender Body
||linkslogische Disjunktion (OR)a || b<bool>
&&linkslogische Konjunktion (AND)a && b<bool>
^^linkslogische Antivalenz (XOR)a ^^ b<bool>
==linksGleichheita == b<bool>
!=linksUngleichheita != b<bool>
<linkskleiner alsa < b<bool>
<=linkskleiner oder gleicha <= b<bool>
>linksgrößer alsa > b<bool>
>=linksgrößer oder gleicha >= b<bool>
+linksAdditiona + bautomatisch
-linksSubtraktiona - bautomatisch
*linksMultiplikationa * bautomatisch
/linksDivisiona / bautomatisch
%linksModuloa % bautomatisch
^rechtsPotenzierunga ^ 0.333<dbl>
!rechtslogische Invertierung (NOT)!(a && b)<bool>
~rechtsbit-orientierte Invertierung~a<uint>
+rechtspositives Vorzeichen6 / +aautomatisch
-rechtsnegatives Vorzeichen9 * -aautomatisch

Zuweisungsoperatoren

important

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 oder s * 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.

KlammerungBedeutungBeispiel
( ... )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 unterschieden
fü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.

Funktionsaufruf als Schaltungssymbol

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... oder 0... 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:

TypWerteBeispiel
<bool>trueonyes, 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 Vorzeichenmin: -10
<dbl>Fließkommazahlen (Dezimal-Punkt!) auch in exponential-Schreibweise 1.35e-22delay: 13.45
<time>1Fließkommazahlen als Zeitintervall in Sekunden, mit einer Erweiterung, um optional auch ganzzahlige Minuten, Stunden oder Tage eingeben zu können.longPeriod: 9025.3
longPeriod: 2:30:25.3
<date>1Fließkommazahlen also absoluter Zeitstempel (UTC-Sekunden seit 01.01.1970), mit einer Erweiterung, um optional auch UTC-Datum+Zeitstempel im ISO Format angeben zu können.tBegin: 1770204975.256
tBegin: 2026-02-04T11:36:15.256
<cxFlt>komplexe Fließkommazahlpole: (-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 annehmenpreset: 0x2F
<enum>Bestimmte enumerierende Tags, siehe Beschreibung der Funktionmode: 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'});
important

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.

tipp

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.

vorsicht

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'});
hinweis

Alle persistierten Daten werden periodisch als BLOB in einer SQLite Datenbank abgelegt. Einträge in dieser Datenbank werden nicht automatisch entfernt.

Footnotes

  1. Ab Katalog Version 11 verfügbar. 2