Login
Newsletter
Werbung

Do, 12. Mai 2011, 15:00

Heimautomatisierung für Hardwarebastler (Teil 3)

Funktionen für den Editor

Innerhalb des Editors muss ein Plug-In folgende Aufgaben erfüllen:

  • mitteilen, was es kann, wie es angesprochen werden soll und welche Ein-/Ausgänge es besitzt
  • Daten zur Verfügung stellen, welche das Layout des Konfigurationsdialoges festlegen
  • die vom Konfigurationsdialog zurückgegebenen Daten empfangen
  • aus einem Projektfile geladene Daten intern verarbeiten
  • die intern gehaltenen Daten so zur Verfügung stellen, dass sie in einem APCP-Projektfile abgespeichert werden können

Für all diese Aufgaben werden wieder Funktionen erwartet, welche vom Plug-In bereitgestellt werden müssen.

static char   libname[]="Random Generator";

OAPC_EXT_API char *oapc_get_name(void)
{
   return libname;
}

Diese Funktion teilt dem Hauptprogramm mit, wie das Plug-In heißt. Dieser Name wird dann überall dort angezeigt, wo das Plug-In verwendet wird. Wichtig hier: der Name ist in einer globalen Variablen libname gespeichert, so dass sicher gestellt ist, dass diese Daten auch nach Verlassen der Funktion noch gültig sind.

OAPC_EXT_API unsigned long oapc_get_capabilities(void)
{
   return OAPC_HAS_INPUTS|OAPC_HAS_OUTPUTS|
          OAPC_HAS_XML_CONFIGURATION|OAPC_ACCEPTS_PLAIN_CONFIGURATION|
          OAPC_ACCEPTS_IO_CALLBACK|
          OAPC_FLOWCAT_DATA;
}

Diese Funktion liefert verschiedene Flags zurück, welche festlegen, was das Plug-In kann und welche Features es besitzt. Die ersten beiden Konstanten OAPC_HAS_INPUTS und OAPC_HAS_OUTPUTS legen fest, dass es sowohl Ein- als auch Ausgänge hat, von diesen hängt dann ab, ob die im folgenden beschriebenen zwei Funktionen vorhanden sein müssen oder nicht. Das Flag OAPC_HAS_XML_CONFIGURATION ist ein wenig seltsam, zumindest in der aktuellen Softwareversion muss dieses eigentlich immer vorhanden sein. Es legt fest, dass die Konfiguration per XML-Struktur übermittelt wird, Alternativen dazu existieren derzeit keine. Ähnlich scheint es sich mit OAPC_ACCEPTS_PLAIN_CONFIGURATION zu verhalten, darüber wird festgelegt, wie die im Konfigurationsdialog eingegebenen Daten zurückgeliefert werden. Auch hier ist derzeit nur diese eine Variante vorgesehen. Anders die nächste Konstante OAPC_ACCEPTS_IO_CALLBACK. Ist diese gesetzt, werden die Ausgänge des Plug-Ins nicht zyklisch abgefragt, vielmehr kann das Plug-In dem OpenPlayer per Callback-Funktion mitteilen, dass neue Daten vorhanden sind, welche von den Ausgängen abgeholt werden können. Dieses Flag ist somit für die Verwendung des Plug-Ins in Player oder Debugger relevant.

Die letzte Konstante legt fest, innerhalb welcher Kategorie das Plug-In im Editor aufgelistet werden soll. Hiermit wurde dem Zufallszahlen-Plug-In die Kategorie »Daten« zugeordnet. Andere Varianten existieren mit OAPC_FLOWCAT_CONVERSION für »Datenkonvertierung«, OAPC_FLOWCAT_LOGIC für »Logikoperationen«, OAPC_FLOWCAT_CALC für »Rechenoperationen«, OAPC_FLOWCAT_FLOW für »Flusssteuerung«, OAPC_FLOWCAT_IO für »Eingang/Ausgang« und OAPC_FLOWCAT_MOTION für die Flow-Elemente-Kategorie »Bewegung«.

Mit den nächsten beiden Funktionen wird der Applikation mitgeteilt, dass die Digitaleingänge 0 und 1 sowie die Ausgänge 0 (digital) und 1 (numerisch) verwendet werden sollen:

OAPC_EXT_API unsigned long oapc_get_input_flags(void)
{
   return OAPC_DIGI_IO0|OAPC_DIGI_IO1;
}

OAPC_EXT_API unsigned long oapc_get_output_flags(void)
{
   return OAPC_DIGI_IO0|OAPC_NUM_IO1;
}

Hier ist besonders darauf zu achten, dass jeder Ausgang nur einmal mit einem entsprechenden Flag für einen Datentyp belegt wird. Eine Angabe wie z.B. OAPC_CHAR_IO7|OAPC_BIN_IO7 wäre unzulässig, da ein Ein- bzw. Ausgang nur exakt einen Datentyp unterstützen und nicht gleichzeitig für Text- und Binärdaten benutzt werden kann.

Die nächste Funktion – bzw. die Daten, welche von ihr erzeugt und zurück gegeben werden – hat es in sich. Hier wird der Applikation mitgeteilt, welches Symbol zur Verwendung im Flow-Editor benutzt werden soll, wie das Layout des Konfigurationsdialoges aussehen soll, und welche Parameter mit welchen Wertebereichen dort angezeigt werden sollen:

OAPC_EXT_API char *oapc_get_config_data(void* instanceData)
{
   struct instData *data;

   data=(struct instData*)instanceData;
   sprintf(xmldescr,xmltempl,flowImage,data->config.numRange);
   return xmldescr;
}

Wie zu sehen ist, passiert hier nicht all zu viel, die Informationen selbst stecken in einer XML-Struktur, welche an dieser Stelle zusammengesetzt und zurückgegeben wird. Diese besteht in diesem Beispiel aus drei Teilen: der Defintion des Bildes für den Floweditor, der Definition des Panels für die Parameter sowie der Definition des Hilfe-Panels, in welchem die Ein- und Ausgänge sowie deren Funktion erklärt werden:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<oapc-config>
 <flowimage>%s</flowimage>
 <dialogue>
  <general>
   <param>
    <name>numrange</name>
    <text>Numeric Range</text>
    <type>integer</type>
    <default>%d</default>
    <min>2</min>
    <max>10000</max>
   </param>
  </general>
  <helppanel>
   <in0>CLK - generate random digital value</in0>
   <in1>CLK - generate random numeric value</in1>
   <out0>RND - random digital value</out0>
   <out1>RND - random numeric value</out1>
  </helppanel>
 </dialogue>
</oapc-config>

An Stelle des Platzhalters %s im XML-Tag <flowimage /> wird das Bild eingefügt. Hier wird ein Base64-kodiertes PNG-Bild in der Größe 106x50 Pixel erwartet. PNG-Bilder lassen sich mit quasi jedem Zeichenprogramm erzeugen, die Base64-Codierung kann beispielsweise online auf www.motobit.com gemacht werden. Das Ergebnis dieser Codierung ist dann ein Textstring, welcher nur noch aus druckbaren Zeichen besteht, die sich als normales Character-Array in die XML-Struktur einfügen.

Mit Hilfe des <general />-Tags wird eine eigene Tab-Pane erzeugt, in der alle Elemente angeordnet werden, welche sich zwischen diesen befinden. Hier in diesem Beispiel wird ein Eingabefeld (<param></param>) für Ganzzahlen (<type>integer</type>) angelegt, welches Werte im Bereich von 2 (<min>2</min>) bis 10000 (<max>10000</max>) akzeptiert und mit dem Wert aus data->config.numRange vorbelegt ist (<default>%d</default>). An dieser Stelle wird der Wert aus der Variablen numRange für den Default-Wert verwendet, damit sicher gestellt ist, dass bereits geänderte und möglicherweise aus einem Projektfile geladene Werte auch korrekt angezeigt werden.

Alles, was vom <helppanel />-Tag eingeschlossen ist, landet wiederum in einem eigenen Tab-Pane Beschreibung, in dem Sinn und Zweck der verschiedenen Ein- und Ausgänge noch einmal kurz erklärt sind. Dieses Panel sollte in keinem Plug-In fehlen, da es als Gedächtnisstütze hilfreich ist und oftmals den Griff zum Manual überflüssig macht.

Ein besonderes Augenmerk soll hier noch auf das Tag <name>numrange</name> für das Integer-Eingabefeld gelegt werden. Der hier vergebene Name muss innerhalb eines Plug-Ins eindeutig sein, da er benötigt wird, um zu ermitteln, welchen Wert der Benutzer hier eingegeben hat:

OAPC_EXT_API void oapc_set_config_data(void* instanceData,const char *name,const char *value)
{
   struct instData *data;

   data=(struct instData*)instanceData;
   if (strcmp(name,"numrange")==0) data->config.numRange=atoi(value);
}

So bald das Konfigurationspanel eines Plug-Ins mit OK verlassen wurde, kommt diese Funktion zum Zug. Das passiert mehrfach – für jedes in der XML-Struktur definierte Eingabefeld je einmal. Dabei wird im Parameter name der eindeutige Name des Eingabefeldes übergeben und in value der Wert, der vom Benutzer gewählt wurde. Dieser liegt dabei in jedem Fall als Character-Array vor und muss gegebenenfalls entsprechend konvertiert werden. Die Umwandlung in eine Ganzzahl geschieht hier über die Funktion atoi().

Noch ein Wort zur oben aufgeführten XML-Struktur: hier wurden zwei Paneltypen und ein Typ für Dateneingaben vorgestellt. Tatsächlich existieren wesentlich mehr Möglichkeiten. Neben zusätzlichen Panels, welche jeweils eigene Namen haben können gibt es auch Auswahlfelder, statische Texte, Eingabefelder für Texte und Fließpunkt-Zahlen, Dateiauswahldialoge, Buttons zur Auswahl einer Farbe und anderes mehr. Damit ist im Prinzip jede Art von Konfigurationsdialog machbar.

Was jetzt noch fehlt, sind Möglichkeiten, die lokal gehaltenen Konfigurationsdaten in ein gemeinsames Projektfile zu bringen bzw. die dort gespeicherten Daten zurück zu erhalten, wenn so eine .apcp-Projektdatei geladen wird. Auch das ist nicht wirklich kompliziert, allerdings gibt es ein paar Kniffe zu beachten.

static struct libio_config   save_config;

OAPC_EXT_API char *oapc_get_save_data(void* instanceData,unsigned long *length)
{
   struct instData *data;

   data=(struct instData*)instanceData;
   *length=sizeof(struct libio_config);
   save_config.version =htons(1);
   save_config.length  =htons(*length);
   save_config.numRange=htonl(data->config.numRange);

   return (char*)&save_config;
}

Diese Funktion erwartet als Rückgabewert einen Pointer auf den Speicherbereich, in welchem die zu speichernden Daten liegen und in der Variablen, auf die length zeigt, deren Größe. Im Prinzip ist hier der Inhalt der zuvor angelegten Struktur libio_config zurückzugeben – in dieser befindet sich der zu speichernde Parameter numRange. Da das Plug-In zum einen kompatibel zu möglichen zukünftigen Änderungen bleiben soll und zum anderen auch auf anderen Rechnerarchitekturen funktionieren muss, sind allerdings einige Maßnahmen erforderlich.

So werden zusätzlich eine Versionsnummer für die Datenstruktur und deren Länge gespeichert und in den zu übergebenden Daten abgelegt.

Des weiteren werden alle Member der Struktur libio_config mittels htons() und htonl() in das plattformunabhängige Netzwerk-Datenformat umgewandelt. Dieses dient eigentlich dazu, die direkte Datenübertragung zwischen Rechnern unterschiedlicher Architektur zu ermöglichen, kann aber auch dann problemlos verwendet werden, wenn diese Daten »nur« gespeichert werden. Es ist lediglich zu beachten, das beim Laden die Rückkonvertierung mittels ntohs() und ntohl() stattfindet:

OAPC_EXT_API void oapc_set_loaded_data(void* instanceData,unsigned long length,char *loadedData)
{
   struct instData *data;

   data=(struct instData*)instanceData;
   if (length>sizeof(struct libio_config)) length=sizeof(struct libio_config);
   memcpy(&save_config,loadedData,length);
   data->config.version =ntohs(save_config.version);
   data->config.length  =ntohs(save_config.length);
   if ((data->config.version!=1) || (data->config.length!=sizeof(struct libio_config)))
   {
      // do conversion from earlier versions here
   }
   data->config.numRange=ntohl(save_config.numRange);
}

Diese Funktion handhabt die umgekehrte Richtung: in loadedData werden die geladenen Daten mit der Länge length übergeben und anschließend konvertiert. An dieser Stelle ist bei späteren Versionen des Plug-Ins dann auch zu überpüfen, ob die geladenen Daten eventuell konvertiert müssen.

Damit ist ein wesentlicher Teil des Plug-Ins fertiggestellt. Nachdem es erfolgreich mittels eines simplen Aufrufes von make in der Konsole compiliert wurde, kann es im Flow-Editor des OpenEditors erstmalig getestet werden.

Kommentare (Insgesamt: 2 || Alle anzeigen )
Re: wow (Andreas M., Mo, 16. Mai 2011)
wow (ThorstenS, Sa, 14. Mai 2011)
Pro-Linux
Pro-Linux @Facebook
Neue Nachrichten
Werbung