Lorby's AAO (Axis And Ohs) - Deep Dive

  • Cessna 208 True Airspeed Calibration

    Wie schon angedeutet, verhält sich das Event (>K:TRUE_AIRSPEED_CAL_SET) zum Einstellen der Airspeed Kalibrierung leider genau so, wie im AAO Thread Post #329 das Event (>K:AILERON_TRIM_SET). Kurz zusammengefasst: im positiven Bereich lässt sich über das Event die Calibrierung setzen, im negativen Bereich wird immer 1 Prozentpunkt niedriger gesetzt als gesendet wird. Details zu diesem Verhalten bitte im o.g. verlinkten Post nachlesen. Irgendwie ist aber bei diesem Event noch mehr im Argen...


    Es fällt auf, dass beim Drehen des zugehörigen Drehknopfes mit der Maus die Calibrierung in 0.5° Schritten verstellt werden kann (Screenshot links). Will man jedoch den Wert über das Event setzen, wird auf die nächste Ganzzahl gerundet (genau das selbe Rundungsverhalten wie beim AILERON_TRIM_SET, also im negativen Bereich immer auf die nächste kleinere Ganzzahl, im positiven Bereich bei .5 ab- und nicht aufgerundet) (Screenshot rechts).



    Wie soll ich denn .5 setzen, wenn es das Event gar nicht kann?

    Also nach Alternativen Events gesucht. Im Gegensatz zum AILERON_TRIM, wo das (>K:AILERON_TRIM_SET_EX1) als Alternative existiert, habe ich hier nur (>K:TRUE_AIRSPEED_CAL_DEC) und (>K:TRUE_AIRSPEED_CAL_INC) gefunden.



    Getestet - die funktionieren auch. Auch im negativen Bereich. Und das sogar in .25er-Schritten, nicht nur .5. Mein Problem: Wenn ich aber .5er Schritte machen will, um das Mausverhalten genau nachzubilden - wie mache ich das? (Ok, das wäre trivial). Wie mache ich das, wenn ich eine beliebige Schrittgröße erlauben möchte (jetzt wird's deutlich kniffeliger)? Ihr wisst aus o.g. Post schon, dass ich ein Freund davon bin, dem Script über die Script-Variable einen beliebigen Wert mitgeben zu können - und das Script macht das dann schon richtig... Das ist also jetzt unsere Herausforderung.


    .5er Schritte wären einfach - einfach 2x das Event triggern (also 1·(>K:TRUE_AIRSPEED_CAL_INC)·1·(>K:TRUE_AIRSPEED_CAL_INC). Aber wie mache ich so etwas mit einer variablen Anzahl von Wiederholungen?


    Hierzu gibt es 2 mögliche Ansätze, die ich hier vorstellen möchte.


    Variante 1: Nutzung einer Schleife

    In anderen Programmiersprachen bietet sich für so ein Vorgehen eine Schleife an: x mal den selben Code ausführen. Üblich sind hierfür Befehle wie "For" oder "While". Diese sind in RPN nicht vorhanden. Wäre ja auch zu einfach. Was uns aber weiterhelfen könnte ist der "g" Befehl: "GoTo Label". Wie würde man so etwas implementieren (Spoiler: Das Script funktioniert so nicht! Aber zur Herleitung und Erklärung der Fallstricke ist es gut geeignet):


    Code
    :1
    (L:Generic-___Test)
    0·>·if{·1·(>K:TRUE_AIRSPEED_CAL_INC)
    ········(L:Generic-___Test)·--·(>L:Generic-___Test)
    ········g1·}


    Die Variable (L:Generic-___Test) ist die Script-Variable (nicht an den ____ stören, das ist einfach nur Teil des Namens) - die also den vom Assignment eingestellten Wert enthält. Sagen wir mal der Wert wäre 5. Nach dem Ausführen des Scriptes müsste die Airspeed Calibration also um 5x0.25° vergrößert worden sein (vorausgesetzt wir sind noch nicht am Anschlag). Gehen wir von einer Initialstellung von 0° aus, dann sollte danach also 1,25° eingestellt sein.

    Was passiert im Script:


    Zeile 1: Das :1 ist kein Tippfehler sondern das "Label 1", stellt euch "Markierung 1" vor.

    Zeile 2: Hier holen wir den Wert der Script-Variable auf den Stack. In unserem Beispiel also 5.

    Zeile 3: Ist der Wert größer als 0? Wenn ja dann einmal (>K:TRUE_AIRSPEED_CAL_INC) aufrufen (also die Calibrierung um 0.25° erhöhen)

    Zeile 4: Ich nehme mir nochmal den Wert der Script-Variablen (also 5), erniedrige den um 1 und schreibe den neuen Wert (jetzt 4) wieder zurück in die Script-Variable

    Zeile 5: Das g1 weißt RPN an, jetzt an der Stelle mit dem Label 1 (deshalb die 1 hinter dem g) mit dem Script fortzufahren. (hätte ich das Label :7 genannt, hätte ich g7 ausführen müssen, um die Ausführung am Label 7 fortzusetzen)

    D.h. jetzt sind wir auf einmal wieder in

    Zeile 1: mit Label 1. Dann

    Zeile 2: Wert der Script-Variablen, jetzt aber 4 (nicht mehr 5 wie zu Beginn)

    Zeile ...


    Ok, ich denke ihr versteht. Wir machen das jetzt 5 mal, dann trifft die if Bedingung in Zeile 3 nicht mehr zu und das Script hört auf. Ziel erreicht? Ich verrate es Euch: nein, das Script wird nur 1x ausgeführt, erhöht wird also nur um 0.25.


    Was ist das Problem?

    1. SimConnect (die API, an die AAO das Script schickt) kann im RPN Script nur nach vorne springen nicht rückwärts. Wir könnten also Teile vom Script überspringen - aber eine Schleife ist damit NICHT möglich

    2. SimConnect ersetzt die Script-Variable VOR dem Ausführen des Scripts. Wenn wir das Script starten, starten wir in Wahrheit folgendes (bleiben wir beim Wert 5 der Script-Variablen):

    Code
    :1
    5
    0·>·if{·1·(>K:TRUE_AIRSPEED_CAL_INC)
    ········5·--·(>L:Generic-___Test)
    ········g1·}

    Das bedeutet, dass wir zwar die Script-Variable auf 4 runter setzen - aber (wenn der Sprung funktionieren würde) der if immer wieder gegen 5 prüfen würde, NICHT gegen den NEUEN Inhalt der Script-Variablen!


    Die Lösung: wir lassen das RPN Script nicht über die SimConnect API ausführen, sondern direkt im Sim. Dies geschieht über den Befehl SIMPROC:

    Das Script sieht dann wie folgt aus (und funktioniert auch noch :) ) :


    Code
    SIMPROC:
    :1
    (L:Generic-___Test)
    0·>·if{·1·(>K:TRUE_AIRSPEED_CAL_INC)
    ········(L:Generic-___Test)·--·(>L:Generic-___Test)
    ········g1·}


    Was mir an der Stelle wichtig ist und ihr Euch wirklich merken sollt: ALLE Variablen, die gelesen werden (also die Werte auf den Stack kopiert, also ohne das >) werden (ohne SIMPROC:) VOR der Ausführung des Scripts durch den Wert ersetzt! Ich wette, ihr werdet trotzdem noch darauf hereinfallen... :)


    Fortsetzung im nächsten Post - hab die 10.000 Zeichen überschritten...

  • Fortsetzung von Post #1


    Ok, Lösung Variante 2: 

    AAO (nicht die Sprache RPN!) sieht die Möglichkeit der wiederholten Ausführung eines Events schon vor:


    (>K:TRUE_AIRSPEED_CAL_INC|5)


    Dieser Befehl würde AAO veranlassen, das Event 5 mal auszuführen (wegen |5 am Ende).


    Wie könnte unser Script also aussehen:

    Code
    1·(>K:TRUE_AIRSPEED_CAL_INC|(L:Generic-___Test))


    AAO erkennt das als Fehler (rote LED) - weil AAO nicht vorsieht, hinter dem | eine Variable zu setzen. Es funktioniert aber :)

    Warum funktioniert es? Weil die Variable VOR dem Ausführen des Scriptes durch deren Wert ersetzt wird. So dass beim Ausführen dann einfach nur (>K:TRUE_AIRSPEED_CAL_INC|5) ausgeführt wird - und das versteht AAO. Wir machen uns hier also diese spezielle (in der Variante 1 zuvor gerade als schlecht klassifizierte) Eigenschaft positiv zu Nutzen!


    Gehen wir einen Schritt weiter. Stellen wir uns vor, wir müssten die Anzahl der Wiederholungen berechnen. Beispiel: Wir wollen auf 3° stellen - ganz egal, welche Einstellung gerade aktuell ist.

    Die Script-Variable soll also jetzt 3 enthalten - aber es sollen keine 3 Schritte sein, sondern 3° Calibierungsstellung.

    Als erstes müssen wir ausrechnen, wie viele Wiederholungen wir bräuchten. Das ist nicht schwer: (L:Generic-___Test)·(A:AIRSPEED·TRUE·CALIBRATE,·Degrees)·-·0.25·/. Wir nehmen also den Soll-Wert (3°), ziehen die aktuelle Stellung davon ab (sagen wir 0.5°), es bleiben also 2.5°. Nun dividieren wir das durch die Schrittgröße, also 0.25°. Heraus kommt: 10. Wir brauchen also 10 Wiederholungen.


    Wie würde unser Script also jetzt aussehen?


    Code
    1·(>K:TRUE_AIRSPEED_CAL_INC|(L:Generic-___Test)·(A:AIRSPEED·TRUE·CALIBRATE,·Degrees)·-·0.25·/)

    Das funktioniert nicht. Wir erinnern uns - AAO erwartet hinter dem | eine Zahl - da steht aber (auch nach Ersetzen der Variablen durch ihren Wert) eine Formel. Das kann nicht funktionieren.


    Also ein anderer Ansatz: Wir berechnen den Wert und speichern den in einer Variablen. Und dann haben wir hinter dem | keine Formel mehr:


    Code
    (L:Generic-___Test)·(A:AIRSPEED·TRUE·CALIBRATE,·Degrees)·-·0.25·/·(>L:Funatic_Test)
    1·(>K:TRUE_AIRSPEED_CAL_INC|(L:Funatic_Test))

    Also - wir berechnen die Anzahl der Wiederholungen und speichern sie in der Variablen L:Funatic_Test. Ihr könnt jederzeit eigene Variablen erfinden. Einfach ein gültiger Name und darauf zugreifen. Funktioniert. Um sicherzustellen, dass meine Variable nicht mit einem anderen Script "kollidiert" (Bspw könnte (L:Test) oder (L:Temp) durchaus auch in einem anderen Script schon benutzt werden), habe ich mir angewöhnt meine Variablen alle mit Funatic anfangen zu lassen. Das dürfte auf der Welt sonst glaub keiner tun :)


    So. Funktioniert das Script? Nein. Denn ... der Wert der Variablen (und auch der (L:Funatic_Test)) wird VOR der Ausführung des Scriptes an Stelle der Variablen geschrieben. Die schöne Berechnung, die wir da gerade tun, ist also für die Katz...

    Um genau zu sein: Nein, ist sie nicht. Die berechnete Anzahl an Wiederholungen würde beim NÄCHSTEN Aufruf des Scriptes ausgeführt werden (weil auch da, werden die Variablen durch den DANN aktuellen Wert ersetzt) :)


    Viele Fallstricke. Jetzt die funktionierende Lösung:


    Code
    (L:Generic-___Test)·(A:AIRSPEED·TRUE·CALIBRATE,·Degrees)·-·0.25·/·(>L:Funatic_Test)
    (SPLIT:100)
    1·(>K:TRUE_AIRSPEED_CAL_INC|(L:Funatic_Test))

    Was tun wir hier? Das (SPLIT:100) ist eine AAO Erweiterung, gehört nicht zu RPN. Und macht folgendes:

    1. AAO führt das Script bis zum "Split" Befehl aus.

    2. AAO wartet 100ms (das ist die Zahl hinter dem : )

    3. AAO startet den Rest vom Script als NEUES Script. Und was passiert beim Starten eines Scriptes? Die Variable wird durch... ihr wisst schon... Das "Split" hat das Script in der Ausführung in 2 Scripte geteilt. Und DESHALB können wir der Variablen in der ersten Zeile den berechneten Wert zuweisen und in der dritten Zeile wird dieser neue Variableninhalt dann korrekt als Wert eingesetzt.


    Wichtig: In ALLEN genannten Lösungen bzw. Lösungsschritten FEHLT noch die Behandlung der Verkleinerung der Kalibrierungseinstellung. Das habe ich jetzt bewusst außen vor gelassen, um die Scripte nicht unübersichtlicher werden zu lassen. Für eine "produktive" Lösung müssen die natürlich noch ergänzt werden!



    So - und als letzten Hirnausrenker zum Schluß:

    Wie habe ich das denn überhaupt getestet? Wenn ich im AAO den Test-Knopf drücke, wie kann ich dann einstellen, welchen Wert die Script-Variable für meine Tests haben soll (das gilt für alle meine Tests mit dem Test-Button und der Script-Variable)?

    Ihr kennt die Lösung - aber kommt ihr auch drauf? Zeile 1...


    Code
    3·(>L:Generic-___Test)·(SPLIT:100)
    (L:Generic-___Test)·(A:AIRSPEED·TRUE·CALIBRATE,·Degrees)·-·0.25·/·(>L:Funatic_Test)
    (SPLIT:100)
    1·(>K:TRUE_AIRSPEED_CAL_INC|(L:Funatic_Test))


    :)



    Viele Grüße,


    Thomas

  • Hallo Thomas,


    mit "Deep Dive" hast du einen sehr guten Thread-Namen gewählt. Jedenfalls passt er sehr gut zu deinen hervorragend ausgeführten und sehr umfangreichen Erklärungen. Hierfür kann man nicht genug Daumen hochheben. Das sind Erklärungen, die man im Netz nicht findet, für die man ansonsten Fachbücher kaufen müsste (wenn man sie überhaupt findet).


    Den beiden Lösungen konnte ich sogar folgen, nachdem ich es 10x gelesen hatte. Aber anwenden kann ich das noch lange nicht. Es ist in etwa so, dass man spanisch und/oder italienisch einigermaßen versteht, aber nicht sprechen kann.


    Jedenfalls hab ich nun den GOTO-Befehl (so hieß das damals in Basic) und das AAO-eigene SPLIT verinnerlicht. Ich muss das mal nachstellen.


    Das alles macht unglaublich Spaß - es macht auch richtig Spaß, sich in diese Programmierungen hineinzudenken. Dankeschön :)

  • Nutzung von B:-Variablen

    am Beispiel des King Air 350i Fuel Quantity Source Schalters.



    Der Event Observer in AAO zeigt keine Reaktion, wenn der Schalter mit der Maus bedient wird. Also mal nach Variablen gesucht, die vom Namen passen könnten - und im Variablen Observer angezeigt. Trotz vieler Versuche - ich finde weder ein Event noch eine Variable, die ich mit diesem Schalter in Verbindung bringen kann.


    Also mal über den Ansatz des Behavior Windows versuchen herauszufinden, was da geht. Das habe ich in im AAO Forum in den Posts 240 und 279 schon beschrieben, deshalb hier nur in Kurzfassung das Ergebnis:



    Der in orange markierte Code wird beim Bewegen des Hebels ausgeführt. Zum besseren Verständnis des Codes habe ich die Zeilenumbrüche ein wenig geändert:

    Code
    p0 2 min 0 max (>O:INSTRUMENT_Tank_Source_Position)
    (O:INSTRUMENT_Tank_Source_Position) s0 
    l0 0 == if{ 0 (>O:XMLVAR_INSTRUMENT_Tank_Selection_Source) g1 } 
    l0 1 == if{ 1 (>O:XMLVAR_INSTRUMENT_Tank_Selection_Source) g1 }
    l0 2 == if{ 2 (>O:XMLVAR_INSTRUMENT_Tank_Selection_Source) g1 }
    :1 1 (>O:_ButtonAnimVar)

    p0 ist der Wert, der an das Script gesendet wird (in AAO wäre das die Script-Variable...). In Schnellfassung nochmal das Script analysieren:

    Zeile 1: Der übergebene Wert muss zwischen 0 und 2 liegen (inklusive) bzw. wird dann entsprechend auf 0 oder 2 limitiert und anschließend in die Variable

    (O:INSTRUMENT_Tank_Source_Position) geschrieben.

    Zeile 2: Der Wert wird aus der Variablen wieder ausgelesen und in das Register 0 übernommen (Denkt an den Post 1 hier im Thread - in der SimConnect API (sprich: in AAO) hätte dieses Vorgehen nicht funktioniert, weil in Zeile 1 die Variable gesetzt wird und in Zeile 2 ausgelesen - über die SimConnect API wäre der Wert der Variablen in Zeile 2 aber schon VOR der Berechnung in Zeile 1 durch den Wert zum Startzeitpunkt des Scriptes ersetzt worden!).

    Zeile 3,4,5: Abhängig vom Wert in Register 0 (l0 - nicht 10 sondern kleines L und 0) wird der Wert 0, 1 oder 2 in die Variable (O:XMLVAR_INSTRUMENT_Tank_Selection_Source) geschrieben und der if Vergleich über den Goto Befehl g1 abgebrochen und das Script in Zeile 6 fortgesetzt

    Zeile 6: Es wird der Wert 1 in die Variable (O:_ButtonAnimVar) geschrieben.


    Das Script ist unnötig kompliziert geschrieben und enthält zudem noch die "Unschönheit", dass es in Zeile 2 den Wert der Variablen auf den Stack lädt, den aber nie mehr entfernt - sprich, das Script hat am Ende einen Wert auf dem Stack liegen. Das ist nicht schlimm, aber unschön. Ein sp0 statt s0 hätte das beseitigt.


    Das Script ließe sich wie folgt deutlich kürzer schreiben:

    Code
    p0 2 min 0 max 
    d
    (>O:INSTRUMENT_Tank_Source_Position)
    (>O:XMLVAR_INSTRUMENT_Tank_Selection_Source) 
    1 (>O:_ButtonAnimVar)

    Versucht mal zu verstehen, warum dieses Script genau das selbe tut wie das vorherige - obwohl es viel weniger Befehle benutzt..


    Aber zurück zum Schalter: Man sieht sehr schön, dass das Script genau 3 Werte kennt: 0, 1 und 2. Man sieht aber auch, dass nur O:-Variablen angesprochen werden. Und diese lassen sich aus AAO definitiv NICHT lesen oder setzen!


    Ein Blick auf den Reiter Dec verrät ein wenig mehr:



    Das "Dec"rement, also das erniedrigen der Schalterstellung liest die aktuelle Schalterstellung aus, subtrahiert davon den übergebenen Wert - und schreibt den in die Variable (>B:INSTRUMENT_Tank_Source_Set).


    Was sind nun B:-Variablen: 

    Gemäß Microsoft SDK handelt es sich bei B:-Variablen um Variablen die "Input Events" darstellen.



    Diese B:-Variablen lassen sich von "außen" eigentlich auch nicht ansprechen - aber Oliver von AAO hat sich dafür einen "Hack" einfallen lassen.

    Kurz zusammengefasst: AAO modifziert eine Datei im MSFS und stellt darüber eine Schnittstelle zwischen AAO und den B:-Variablen her.

    Wir schreiben im Script in eine L:-Variable, AAO erkennt, dass diese Variable eine Verknüpfung zu einer B:-Variable sein soll und triggert die zuvor geschriebene Schnittstelle, die dann den Wert der L:-Variable in die entsprechende B:-Variable schreibt. Das Vorgehen ist im AAO Handbuch in Kapitel 20 beschrieben - ich will das korrekte Einrichten dennoch hier nochmal mit meinen eigenen Worten beschreiben.

    AAO Zugriff auf B:-Variablen einrichten

    1. Anlegen eines BVar-Profils

    Über Extras / Map BVars to LVars (MSFS) öffnet ihr das "Manage LVar to BVar hook" Fenster. Dort tragt ihr bei Profilname einen Namen ein (passend zum jeweiligen Flugzeugtyp ist ein guter Ansatz) und klickt auf "Add as new".



    2. Erstellen eines BVar Mappings

    Anschließend wird das neue Profil bei "Select Profile" ausgewählt. In die 3 Felder unten schreibt ihr folgenden Code hinein:


    LVar: (L:Funatic_BVar_Tank_Source, Number)

    BVar get: (B:INSTRUMENT_Tank_Source, Number)

    BVar set: (L:Funatic_BVar_Tank_Source, Number) (>B:INSTRUMENT_Tank_Source_Set)


    Dann setzt ihr den Haken bei "Sync LVar with BVar get", klickt anschließend auf "Add as new", "Enable Hook" und "Activate Profiles".



    Die Meldung den Sim neu zu starten an dieser Stelle bitte ignorieren.


    3. BVar Profile einem Flugzeug zuordnen

    Nun das Fenster mit "Close" schließen und über "Extras / Map BVar profiles to aircraft" den "Map aircraft configs to BVar profiles" Dialog öffnen.

    Dort wählt ihr links das neue Profil aus, klickt rechts auf die King Air und anschließend auf den "Map" Button.



    Jetzt Apply, close - und dann den Sim neu starten...


    • Das Profile Mapping müsst ihr nur noch anfassen, wenn ihr ein neues Profil anlegt oder ein Profil entfernen wollt oder ein Profil einem anderen Flugzeug zuweisen wollt.
    • Das "Enable Hook" kann ggfs. nach einem Sim Update nötig werden, da AAO wie gesagt eine Datei im Sim modifiziert. Sollte ein Update diese Datei aktualisieren ist der "Hook" damit auch wieder weg.
    • Das "Activate Profile" (und damit leider auch der Sim Neustart) ist definitiv nach jeder Änderung in den BVar Mappings (die Seite, auf der der "Activate Profiles" Button ist) notwendig. Mir ist es auch schon passiert, dass ich nach einem Sim Update oder nach einem AAO Update das "Activate Profiles" nochmals durchführen musste. Wenn die B:-Variablen einmal funktioniert haben und funktionieren auf einmal nicht mehr, dann ist das auf jeden Fall der erste Anlaufpunkt. Und den Neustart des MSFS nicht vergessen!

    Fortsetzung im nächsten Post...

  • ...


    Was haben wir denn jetzt überhaupt getan? 

    Über das "LVar" Feld haben wir eine neue L:-Variable angelegt: (L:Funatic_BVar_Tank_Source, Number). Ihr könnt in AAO jederzeit (z.B. auch in Scripten) eine eigene L:-Variable "erzeugen". Einfach L: und dann einen Namen nach Wahl. Wie schon in einem anderen Post beschrieben, nenne ich meine Variablen alle "Funatic" am Anfang, damit es keine Überschneidung mit anderen Variablen von Flugzeugen oder anderen Scripten gibt - oder sagen wir mal, dass die Wahrscheinlichkeit nahezu 0 ist...

    Etwas ist jedoch neu hier: Die L: Variable hat ein ", Number" am Ende, also einen Variablentyp oder -einheit. Warum dies? Gemäß AAO Handbuch Kapitel 5, Absatz "Local Variables" sind Variablen OHNE Variablentyp NUR innerhalb von AAO benutzbar. Dies ist vorteilhaft, weil es die Überschneidung mit anderen Variablen nochmals minimiert. In diesem Fall MUSS die Variable aber vom "Hook" gelesen werden - und der läuft nicht in AAO sondern im Sim. Deshalb MUSS die Variable einen Typ bekommen (Vorsicht: bei String-Variablen ist es genau umgekehrt, die müssen den Typ ", String" benutzen, wenn sie nur in AAO und ohne Typ-Angabe, wenn sie auch außerhalb von AAO genutzt werden sollen! Eine Variable vom Type String brauchen wir in diesem Fall jedoch nicht.).


    Über das "BVar get" Feld haben wir angegeben, wie der aktuelle Status des Schalters ausgelesen werden kann. Das kann wie in diesem Fall einfach nur ein Variablenname sein. Aber hier kann auch RPN Code hinterlegt werden falls nötig.


    Wie bin ich jetzt auf (B:INSTRUMENT_Tank_Source, Number) gekommen?

    B:-Variablen haben alle die Eigenschaft, dass sie zum Setzen eines Wertes einen bestimmtes Wort an den Variablennamen anhängen. Typische Beispiele sind "_Set", "_Toggle", "_Push", "_Dec", "_Inc" und weitere. Nicht jede Variable bietet alle "Anhängsel" - da hilft nur ausprobieren.

    In unserem Fall war es jedoch umgekehrt. Wir haben beim Setzen den Code (>B:INSTRUMENT_Tank_Source_Set) gefunden - und wissen dadurch, dass die B:-Variable (B:INSTRUMENT_Tank_Source) - eben ohne _Set - heißt!

    Und üblicherweise hat auch die B:-Variable einen Typ - und da wir wissen, dass sie die Wert 0, 1 oder 2 annehmen kann ist ", Number" doch ein verdammt guter Kandidat ;)


    Im "BVar set" haben wir nun das Script hinterlegt, das ausgeführt werden soll, wenn wir die Variable setzen wollen. Das ist in diesem Fall ganz einfach: Wir nehmen den Wert unserer L:-Variablen und übertragen ihn unverändert in die B:-Variable (mit _Set und ohne Typ!). Man kann aber auch hier RPN Code hinterlegen falls nötig.


    Der Haken "Sync LVar with BVar get" aktiviert, dass AAO ca. 5-10 mal pro Sekunde das bei "BVar get" stehende Script ausführt und der Wert in die Variable aus dem Feld "LVar" übertragen wird. Damit wird also der Wert der B:-Variablen zurück in die L:-Variable synchronisiert. Ich empfehle diese Synchronisierung nur dann zu machen, wenn die Schalterstellung nicht über eine andere Variable ausgelesen werden kann. Klar sind 5-10 Aufrufe pro Sekunde für einen heutigen Prozessor ein Klacks. Aber wenn es anders geht, warum sollten wir dann die CPU unnötige Dinge ausführen lassen? Es werden im Laufe der Zeit sicher noch mehr B:-Variablen dazu kommen...

    Und wie benutzt man nun diese B:-Variable?

    Wenn die "Sync" Funktion aktiviert wurde, dann sollte der erste Test darin bestehen zu prüfen, ob die L:-Variable die korrekte Schalterstellung anzeigt. Also den Variablen Observer in AAO starten und die L:-Variable aus dem "LVar" Feld eintragen - NICHT die B:-Variable, auf die können wir aus AAO NICHT direkt zugreifen!



    Wenn man jetzt den Schalter mit der Maus bewegt, sieht man, dass sich der Wert auf 2 ändert in der Stellung "Auxiliary", auf 1 in der Stellung "Main" und 0 in der Stellung "Test". Perfekt! Damit wissen wir jetzt auch, welche Stellung die zu Anfang herausgefundenen Werte 0, 1 und 2 bedeuten.


    Zum Setzen des Schalters muss nun einfach der gewünschte Wert in die L:-Variable geschrieben werden, also z.B. die 2 für die Auxiliary Stellung:


    Und der Schalter bewegt sich... Ziel erreicht!


    Mit 0 bewegt sich der Schalter kurz nach oben, kehrt dann aber sofort wieder in die Mittelstellung zurück. Dies liegt daran, dass der Schalter oben "gehalten" werden muss. Wie ich das hin bekommen habe erkläre ich aber in einem anderen Post - ist schon genug für heute...

    Fehlersuche

    Es gibt nicht viele Möglichkeiten, wie man hier zur Fehlersuche vorgehen kann. Ich gehe jedoch immer wie folgt vor:

    1. Überprüfen, ob der "Hook" aktiviert ist, das richtige BVar-Profil dem Flugzeug zugeordnet ist und das LVAR->BVar Mapping korrekt ist (also der richtige Code in den 3 Feldern "LVar", "BVar get" und "BVar set" steht). Zur Sicherheit DAVOR Neustart von MSFS und AAO.
    2. Überprüfung auf Schreibfehler, fehlende Leerzeichen, ungültige Zeichen... Klingt trivial, ist mir aber schon x mal passiert. Beliebte Fehlerquelle: Der · aus dem AAO Script Editor - Das ergibt keine Fehlermeldung, sorgt aber dafür, dass es nicht funktioniert. Tipp: Wenn ihr den Code aus dem UNTEREN Fenster des Script-Editors kopiert, werden die Punkte automatisch durch Leerzeichen ersetzt...
    3. Falls nicht geschehen temporär den "BVar get Sync" aktivieren. Wenn die L:-Variable je nach Schalterstellung andere Werte anzeigt, dann funktioniert der BVar-Zugriff prinzipiell und ihr habt "nur" ein Problem mit dem Zuweisen.
    4. Falls das Problem nur beim Zuweisen auftritt, dann den Code im "BVar set" so einfach wie möglich gestalten. In diesem Beispiel würde ich dann 2 (>B:INSTRUMENT_Tank_Source_Set) in das "BVar set" Feld eintippen (NICHT Cut&Paste, da könnten sich versteckte Sonderzeichen mit einschleichen). In diesem Fall würde der Schalter nur auf 2 (>L:Funatic_BVar_Tank_Source,Number) reagieren und ihr müsstet ihn mit der Maus zurück in die "Main" Stellung bringen. Aber es geht ja nur um die Fehlersuche - da ist dieses Verhalten durchaus ausreichend.



    So, genug für heute...

    Wie immer nehme ich gerne Hinweise oder Rückfragen entgegen :)


    Gruß,

    Thomas

  • Mehrstufenschalter vorwärts/rückwärts "toggeln"

    am Beispiel des A320 ADIRS Mode Selectors



    Der ADIRS 1 Mode Select Schalter ist ein Schalter mit den 3 Schaltstellungen Off (0), Navigation (1) und Attitude (2). Der Schalter lässt sich ganz einfach über (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) abfragen und über # (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) setzen (# steht dabei für eine der Zahlen 0, 1 oder 2).


    Wer keinen Drehregler für den Schalter zur Verfügung hat (oder zur Verfügung stellen möchte), kann zur Steuerung vermutlich nur einen Button nutzen, der den Schalter jeweils eine Stellung weiter bewegt. Hat man 2 Buttons zur Verfügung, kann man den einen Button zum "Hochschalten" und den anderen zum "Runterschalten" nutzen. Die benötigten Scripte sind sehr einfach:


    Hochschalten: (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) ++ 2 min (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number)

    Runterschalten: (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) -- 0 max (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number)


    Hat man aber nur 1 Button zur Verfügung muss man sich überlegen, wie man den Schalter von einer Endposition wieder in die andere Richtung bewegen kann.


    Der einfachste Ansatz ist über einen Modulo:


    Weiterschalten: (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) ++ 3 % (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number)


    Der Schalter wird sich dabei also von 0 nach 1, dann nach 2 und dann wieder nach 0 bewegen - also Off -> Nav -> Att -> Off -> ...


    Damit lassen sich durch einen simplen "Weiterschalten"-Button alle Schalterpositionen wiederholt erreichen. Nur wird sich der Schalter real so nie bewegen lassen. Aus der Endstellung (Off bzw. Att) kann als nächste Stellung nicht der Anfang, sondern nur die vorherige Position eingedreht werden. Noch deutlicher wäre dies bei einem Schalter mit mehr als 3 Schalterstellungen, sagen wir mal 5. Hier wäre die korrekte Folge also 0 -> 1 -> 2 -> 3 -> 4 -> 3 -> 2 -> 1 -> 0 -> ...


    Wie können wir dies mittels AAO umsetzen?


    Ist der Schalter an einer Endposition, ist die nächste Position klar. Aber in jeder anderen Stellung kann die nächste Position nur ermittelt werden, wenn die aktuelle Drehrichtung bekannt ist - sind wir gerade am "Hoch-" oder "Runterdrehen"?

    Da jeder Aufruf des RPN Scripts keinerlei Bezug zur vorherigen Position hat, müssen wir uns diesen Bezug selbst herstellen. Sprich: wir müssen uns merken, ob wir gerade hoch oder runter drehen.


    Für diesen Zweck definieren wir uns einfach eine eigene Variable, in diesem Beispiel nenne ich sie einfach mal (L:Funatic_ADIRS1_Mode_Selector_Direction). Der Name kann völlig frei gewählt werden (entsprechend den erlaubten Zeichen) - er sollte halt nur mit keiner bereits existierenden Variablen in einem AAO Script kollidieren...

    Wir speichern den Wert 1 fürs Hochdrehen und -1 fürs Runterdrehen. Damit wird die Berechnung der nächsten Position einfach:


    (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) (L:Funatic_ADIRS1_Mode_Selector_Direction) + (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number)


    Wie bekommen wir jetzt die "Richtungsänderung" hin? Nun, die findet immer an den beiden Endpositionen statt. Sind wir in der Position 0, wissen wir, dass es hoch geht. Sind wir in der Position 2, wissen wir, dass es runter geht. Das Script könnte also so aussehen:


    Code
    (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) 0 == if{  1 (>L:Funatic_ADIRS1_Mode_Selector_Direction) }
    (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) 2 == if{ -1 (>L:Funatic_ADIRS1_Mode_Selector_Direction) }
    (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) (L:Funatic_ADIRS1_Mode_Selector_Direction) + (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number)

    Wird das Script funktionieren?

    Nein. Ihr erinnert euch: Variablen, auf die lesend zugegriffen wird, werden schon VOR dem Ausführen des Scriptes durch den Wert ersetzt.


    Spielen wir das nochmal am Beispiel durch. Schalter soll auf 1 sitzen ((L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) ist also 1), die Drehrichtung ist "hoch", die Variable (L:Funatic_ADIRS1_Mode_Selector_Direction) enthält also den Wert 1.


    Das Script würde also wie folgt ausgeführt:

    Code
    1 0 == if{  1 (>L:Funatic_ADIRS1_Mode_Selector_Direction) }
    1 2 == if{ -1 (>L:Funatic_ADIRS1_Mode_Selector_Direction) }
    1 1 + (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number)

    Die Variable (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) würde also den Wert 2 erhalten, der Schalter wird nach rechts bewegt. Alles korrekt. Beim nächsten Aufruf wird es jedoch falsch ((L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) ist jetzt 2!):


    Code
    2 0 == if{  1 (>L:Funatic_ADIRS1_Mode_Selector_Direction) }
    2 2 == if{ -1 (>L:Funatic_ADIRS1_Mode_Selector_Direction) }
    2 1 + (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number)

    Die Variable (L:Funatic_ADIRS1_Mode_Selector_Direction) würde korrekt die -1 zugewiesen bekommen - die Berechnung der neuen Position würde jedoch noch mit +1 erfolgen - der Schalter würde auf die 3 gesetzt werden (die es nicht gibt - d.h. er würde auf der 2 stehen bleiben).


    Und beim darauffolgenden Aufruf passt es dann wieder

    Code
    2 0 == if{  1 (>L:Funatic_ADIRS1_Mode_Selector_Direction) }
    2 2 == if{ -1 (>L:Funatic_ADIRS1_Mode_Selector_Direction) }
    2 -1 + (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number)

    Jetzt würde der Schalter korrekt auf die Position 1 bewegt werden. Die -1, die der Variablen (L:Funatic_ADIRS1_Mode_Selector_Direction) zugewiesen wird ist unnötig (der Wert steht ja schon drin) - ist aber auch kein Problem.


    Das gleiche Verhalten wird sich am anderen Ende zeigen, also bei Schalterstellung 0. Wir haben jeweils am Ende eine "Leertastung"...


    Wie bekommen wir die nun raus? Wir dürfen intern nicht mit Variablen rechnen, sondern nur mit Registern. Nehmen wir das Register 0. s0 schreibt den Wert vom Stack in das Register (lässt den Wert aber auf dem Stack stehen), sp0 schreibt den Wert vom Stack in das Register und löscht den Wert vom Stack und l0 (kleines L 0) schreibt den Wert vom Register wieder auf den Stack. Das Register ist beim nächsten Aufruf des Scripts aber wieder leer! Wir können das Register also NICHT nutzen, um uns die Drehrichtung zwischen den Scriptaufrufen zu speichern, das müssen wir nach wie vor in unserer selbst definierten Variablen machen!


    Code
    (L:Funatic_ADIRS1_Mode_Selector_Direction) sp0
    (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) 0 == if{  1 s0 (>L:Funatic_ADIRS1_Mode_Selector_Direction) }
    (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) 2 == if{ -1 s0 (>L:Funatic_ADIRS1_Mode_Selector_Direction) }
    (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) l0 + (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number)

    Zeile 1 speichert den Wert der Drehrichtungsvariablen im Register. Das müssen wir tun, weil wir nur bei den Endstellungen das Register beschreiben, d.h. in der Mittelposition würde das Register ohne diese Zeile den Wert 0 haben und unser Regler würde "festsitzen"

    Zeile 2 prüft, ob unser Schalter am unteren Anschlag ist. Falls ja, wird die Drehrichtung auf "hoch" (also +1) geändert.

    Zeile 3 prüft, ob unser Schalter am oberen Anschlag ist. Falls ja, wird die Drehrichtung auf "runter" (also -1) geändert.

    Zeile 4 dreht den Schalter um die in den 3 Zeilen davor festgelegte Drehrichtung weiter.


    Einen kleinen Fehler haben wir aber noch im Script. Wer kommt von alleine drauf...?


    Wenn wir das Script zum allersten Mal aufrufen - dann enhält unsere Drehrichtungsvariable (L:Funatic_ADIRS1_Mode_Selector_Direction) den Wert 0 - ihr wurde ja noch nie ein anderer Wert zugewiesen.

    Idealerweise befindet sich dann aber auch der ADIRS Schalter in der Anfangsposition. Damit wird in Zeile 2 der if ausgeführt und die Richtungsvariable korrekt auf 1 (also hoch) gesetzt. Funktioniert doch alles!

    Aber was, wenn jemand zuerst mit der Maus den Schalter auf die 1 setzt? Und erst danach unser Script per Button aufruft? Dann wird unser Script nichts tun. Der Schalter ist in der Position 1, wir addieren 0 - und bleiben bei der 1. Für den Benutzer sieht es aus, als ob unser Script nicht funktioniert. Diesen Fall sollten wir also prüfen und abfangen


    Code
    (L:Funatic_ADIRS1_Mode_Selector_Direction) sp0
    l0 0 == if{ 1 s0 (>L:Funatic_ADIRS1_Mode_Selector_Direction) }
    (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) 0 == if{ 1 s0 (>L:Funatic_ADIRS1_Mode_Selector_Direction) }
    (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) 2 == if{ -1 s0 (>L:Funatic_ADIRS1_Mode_Selector_Direction) }
    (L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number) l0 + (>L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB, Number)

    Die neue Zeile 2 prüft also zuerst, ob unsere Richtungsvariable auf 0 steht - und falls ja, wird sie auf 1 gesetzt. Damit kann es nicht mehr vorkommen, dass wir den Schalter um 0 Positionen bewegen...



    Viel Spaß beim Nachvollziehen :)


    Gruß,

    Thomas

  • Dynamischer Drehregler 

    am Beispiel Heading Bug.


    Guten Abend zusammen,

    da ich heute keine Zeit mehr habe, werfe ich Euch jetzt mal ein Script einfach nur hier rein. Ihr könnt es gerne mal ausprobieren (ich hab's an der Cessna 172 getestet).

    Script-Erklärungen und die Hinweise, wie ihr was feintunen könnt, folgen dann in den nächsten Tagen - aber es wird eine Kopfnuss...


    Konfiguration in AAO: Drehregler, -1 für links, +1 für rechts...



    Viel Spaß beim Ausprobieren!


    Bis morgen,

    Thomas

  • So, heute fange ich wie versprochen mit der Erläuterung an. Zum Nachvollziehen bitte die Cessna 172 laden - mit der habe ich die Variablen usw. getestet. Natürlich lässt sich das Script auch für andere Aktionen und Flugzeuge nutzen - nur heißen dann halt die Variablen und Events anders. Aber dazu nochmal mehr Hinweise am Ende der Erläuterungen...


    Das was ich jetzt erkläre sind meine Gedanken, die ich mir zur Umsetzung gemacht habe. Das soll nicht heißen, dass man es nicht anders umsetzen kann oder dass es die beste Lösung ist. Es ist halt die, die mir eingefallen ist. Hinweise, Vorschläge, Fragen oder sonstiges Feedback ist explizit herzlich willkommen!


    Problem

    Ich habe einen Drehknopf (Bravo Throttle, Midi Controller, ...) über den ich den Heading Bug (oder die Altitude, Speed, ...) steuern möchte. Jeder "Tick" beim Drehen des Drehknopfes löst in AAO ein Event aus. Im Falle des Heading Bugs in der 172 kann man dies z.B. über folgendes Script verarbeiten:


    (>K:HEADING_BUG_INC)


    Wenn man aber den Heading Bug über einen größeren Winkel ändern möchte, dann dreht man sich einen Wolf... Insbesondere beim FBW A320 bin ich damit SEHR langsam unterwegs - aber auch dazu später noch mehr Details.


    Abhilfe kann man schaffen, indem man über einen anderen Drehknopf (wer hat schon so viele übrig???) oder über eine Kombination mit einem Button ein Script aufruft, das den Wert nicht in 1° Schritten sondern z.B. in 5° oder 10° Schritten verändert. Das war auch mein bisheriges Vorgehen.


    Aufgrund der gerade angesprochenen FBW A320 "Trägheit" ist mir bei der Ursachenforschung die Idee gekommen, zu versuchen, den Drehknopf "dynamisch" reagieren zu lassen. Dass er also selbständig die Drehung beschleunigt und abbremst. Kennt man ja vom Drehen mit dem Mausrad - oder über die AAO Funktion "Adaptive", wenn man lange auf einen Button drückt. Leider ist die Funktion beim Drehknopf nicht anwendbar, weil eben kein "langer Knopfdruck" geschickt wird sondern viele kurze...


    Theorie

    Wie bin ich an so etwas herangegangen? Was bedeutet denn "dynamisch"?

    Ich habe mir vorgestellt, ich wäre das AAO Script. Da draußen sitzt ein Benutzer, der an einem Drehknopf dreht, der mir bei jedem Tick einen Impuls schickt. Und meine Aufgabe als AAO Script besteht jetzt darin, den Benutzer dabei zu unterstützen, möglichst schnell seine Zieleinstellung zu finden. Klingt ein wenig verrückt - aber mir helfen solche Blickwinkel :)


    Ich will den Benutzer unterstützen - aber was weiß ich denn eigentlich? Ich weiß, dass er dreht. Ich weiß, in welche Richtung er dreht. Aber weiß ich sonst noch irgendwas? Ich kenne sein Ziel nicht - das macht es schwer ihn irgendwie zu unterstützen. Welche Hinweise gibt mir der Benutzer denn noch, die mir helfen zu verstehen, was er will?


    Mir sind 2 Hinweise eingefallen:

    1. Die Drehgeschwindigkeit. Wenn der Benutzer langsam dreht, dann ist er vermutlich nicht weit von seinem Ziel entfernt. Dreht er den Knopf schnell, dann hat er vermutlich einen größeren Winkel zu überbrücken.
    2. Die Dauer der Drehung. Wenn er schon lange dreht, dann wird der Winkel auch ein größerer Winkel sein - dreht er nur kurz, ist er vermutlich schnell an seinem Ziel.

    Gerade wenn der Benutzer schon nah an seinem Ziel ist, könnte eine "Unterstützung" durch das Script eher nervig sein und genau das Gegenteil bewirken. Der Benutzer schießt über das Ziel hinaus...


    Weitere Indikatoren, die ich mit einfließen lassen könnte, sind mir leider nicht eingefallen. Aber vielleicht habt Ihr ja noch eine Idee...?


    Was bedeutet "unterstützen" denn überhaupt? Ich habe das so interpretiert, dass ich - wenn ich (als Script) der Meinung bin, dass der Benutzer einen größeren Winkel zurücklegen möchte, dann den Heading Bug in größeren Schritten bewege. Und wenn ich der Meinung bin, der Benutzer wäre kurz vor seinem Ziel - dann bewege ich den Heading Bug nur in kleinen Schritten. Um mir das besser vorzustellen, habe ich mir eine kleine Matrix gemalt:



    Was soll das bedeuten: Wenn der Benutzer nur kurz und langsam dreht - dann finde ich das im oberen linken Feld der Matrix. Dreht der Benutzer nur kurz aber schnell, dann im linken unteren Feld. Dreht er schnell und lange, dann ist das das rechte untere Feld der Matrix. Ihr versteht schon...


    Und was sollen die Zahlen mir sagen? Das sind (beispielhaft!) die Werte, um die der Heading Bug in der jeweiligen Situation pro "Tick" weiter bewegt wird. Mit dieser Matrix wird der Heading Bug also immer gleich langsam bleiben, egal wie lange der Benutzer schon dreht - so lange er nur langsam dreht (Zeile "langsam"). Dreht er schnell, wird der Heading Bug dagegen schneller, je länger der Benutzer dreht (Zeile "schnell").


    So eine Tabelle ist im Script hinterlegt - und das ist genau eine der Möglichkeiten, wie ihr das Script "feintunen" könnt! Die Werte müssen auch keine ganzen Zahlen sein. Ich könnte z.B. auch nur 0,5° pro Impuls einstellen - das wäre dann eine Unterstützung durch "Verlangsamung"...

    Bitte auch nicht vergessen: Wenn der Benutzer schnell dreht, dann bewegt sich der Heading Bug ja allein schon durch die höhere Anzahl an Impulsen in der selben Zeit deutlich weiter, als wenn der Benutzer langsam dreht. Deshalb sollte der "Schnell" Wert auch nicht zu hoch gesetzt werden. Aber... das könnt ihr alle selbst austesten und für euch das beste herausholen...


    Die Tabelle muss nicht 3x3 Felder groß sein. Man könnte z.B. auch sagen "durchwachsene Drehgeschwindigkeit" möchte ich nicht unterscheiden. Ich möchte nur "langsam und schnell" - dann wäre die Tabelle eben 2x3 Felder groß. Oder - und dazu habe ich mich entschieden - man unterscheidet nur zwischen "schnell/langsam" und "kurz/lang". Die Tabelle wäre dann 4 Felder groß - und diese ist genau so im Script hinterlegt:




    Jetzt habe ich mir also überlegt, wie ich abhängig von Drehdauer und Drehgeschwindigkeit die "Geschwindigkeit" des Heading Bug ändern kann. Ist doch schonmal eine Basis.

    Aber... AAO gibt mir doch weder Drehdauer noch Drehgeschwindigkeit... Also alles vergebene Liebesmüh?


    ...

  • Ermittlung der Drehgeschwindigkeit eines Drehreglers

    Was bedeutet denn Drehgeschwindigkeit? Im Falle eines Drehreglers in AAO könnte man die Geschwindigkeit als Anzahl Impulse pro Zeit ausdrücken. Viele Impulse pro Sekunde (oder halbe Sekunde oder...) bedeutet schnell, wenige Impulse in der selben Zeit bedeutet langsam...


    Impulse bekommen wir von AAO - mit jedem "Tick" vom Drehregler wird 1x das Script gestartet. Wie kann ich das (möglichst einfach) in Anzahl Impulse pro Zeit umwandeln?


    Die von mir ausgewählten Methode basiert auf der Tatsache, dass jeder Aufruf eines Scripts PARALLEL erfolgt (das gilt nicht nur für Drehregler - das ist generell in AAO so!). D.h. wenn ich einen Button klicke und ein Event / Script auslöse und drücke anschließend einen anderen Button und löse damit ein anderes (oder auch das gleiche) Event aus, dann wartet AAO NICHT, bis das erste Event fertig ist, sondern startet - während das erste noch läuft - das neue Script.


    Stellen wir uns mal vor, ein Script läuft genau 1 Sekunde. Ich erzeuge im Abstand von genau 100ms einen neuen Aufruf eines Scripts. Dann laufen also nach 10 Aufrufen 10 Scripte parallel. Dann ist das erste Script fertig und das 11. wird gestartet. Ich habe also wieder 10 parallel am laufen... D.h. wenn ich herausfinden kann, wie viele Scripte parallel laufen, dann kann ich meine Impulsrate (wieviele Scripte werden pro Zeit gestartet) darüber ermitteln.


    Dies habe ich wie folgt gelöst:


    Code
    (L:Funatic_TriggerParallel)·++·(>L:Funatic_TriggerParallel)
    
    (SPLIT:800)
    
    (L:Funatic_TriggerParallel)·--·(>L:Funatic_TriggerParallel)


    Zu Beginn des Scriptes zähle ich also meinen "Parallelzähler" um 1 hoch. Dann warte ich 800ms - und dann setze ich den Zähler wieder um 1 runter.

    Läuft nur 1 Script, dann geht der Zähler von 0 auf 1 und nach einer Sekunde wieder von 1 auf 0.


    Wird ein zweites Script gestartet, während das erste noch läuft, geht der Zähler beim 2. Script nicht von 0 auf 1 sondern von 1 auf 2. Dann beendet sich das erste Script, der Zähler geht von 2 auf 1 zurück. Wenn dann das zweite Script zu Ende kommt, dann wird der Zähler von 1 auf 0 zurück gesetzt.


    Einfach mal ausprobieren. Den o.g. Code in ein Script, einen Drehregler zuweisen und die Variable (L:Funatic_TriggerParallel) in den Variablen-Observer. Script Editor schließen (sonst löst AAO keine Events/Scripte aus!), am Drehregler drehen und den Wert beobachten... :)


    Verständnisfrage: Warum muss ich (SPLIT:800) einsetzen und nicht (WAIT:800)?


    Die Dauer, wie lange das Script wartet, muss nicht 800ms sein. Ihr könnt den Wert ändern - unter 100ms macht i.d.R. keinen Sinn, weil auch AAO etwas Zeit zur Verarbeitung braucht.


    Der Befehl "(SPLIT:800)" ist auch nicht als "genaue Script-Dauer sind 800ms" zu verstehen. Die Methode taugt nicht, um eine genau Stoppuhr zu bauen. Aber darum geht es hier ja auch gar nicht - es geht uns doch nur darum zu erkennen, ob der Benutzer schnell oder langsam oder "mittelschnell" den Drehknopf dreht. Ob das nun 1 Impuls mehr oder weniger pro Sekunde ist, spielt doch dabei gar keine Rolle.


    Ihr werdet beim Testen schnell feststellen, dass es mehr oder weniger unmöglich ist, kontinuierlich gleich schnell zu drehen. Und das wollen wir vom Benutzer auch gar nicht fordern. Wir wollen keine genauen Werte - nur Größenordnungen, die uns helfen grob die Drehgeschwindigkeit einzuschätzen.


    Je kürzer wir die Wartezeit beim Split wählen, desto "sensibler" ist die Anzeige. Um das "Umgreifen" der Finger während des längeren Drehens zu überbrücken, sollte die Zeit aber auch nicht zu kurz gewählt werden, weil durch die längere Zeit die Werte etwas "geglättet" werden. Ich habe mich für 800ms entschieden. Und das ist der nächste Parameter, an dem Ihr das Script für Euch feintunen könnt... :)


    Mit Hilfe der Variablen (L:Funatic_TriggerParallel) können wir jetzt also feststellen

    1. Ob der Drehknopf gerade gedreht wird (die Variable hat den Wert 0 falls nicht) und 
    2. Wie schnell der Drehknopf gedreht wird. Hohe Werte bedeutet schnell, niedrige Werte bedeutet langsam.

    Was uns jetzt noch fehlt ist das festlegen, ob die ermittelte Geschwindigkeit nun "schnell" oder "langsam" oder "durchwachsen" oder ... ist. Ich habe mich nur für "schnell" und "langsam" entschieden - und das - Ihr denkt es Euch schon - ist der nächste Wert, an dem Ihr für Euch feintunen könnt. Ich habe mich für 12 entschieden. Größer als 12 bezeichne ich als schnell, kleiner oder gleich 12 als langsam. Bedenkt bitte, dass der Wert auch davon abhängt, wie lange das Script läuft! Wenn wir bei o.g. Beispiel bleiben, dass wir alle 100ms das Script aufrufen, dann bekommen wir 10 Scripte parallel, wenn jedes Script 1000ms lang läuft. Lassen wir das Script nur 500ms lang laufen, dann schaffen wir es nur auf 5 parallele Scripte. Bedenkt dies, wenn Ihr über diese Werte nachdenkt!


    Für den weiteren Verlauf des Scripts sind "schnell" und "langsam" als Einstufung schlecht geeignet, da man damit nicht rechnen kann. 0 für langsam und 1 für schnell wäre deutlich geeigneter :)

    Wenn Ihr 3 Stufen wollt, dann wäre 0 für langsam, 1 für mittel und 2 für schnell geeignet. Bei 4 Stufen dann 0, 1, 2 und 3.


    Wie kommen wir jetzt da hin?

    Naja, bei nur 2 Unterscheidungen "langsam" und "schnell" ist das einfach (Variable (L:Funatic_TriggerRate) in den Variablen-Observer und schauen, was passiert, wenn ihr dreht):


    Code
    (L:Funatic_TriggerParallel)·++·(>L:Funatic_TriggerParallel)
    (L:Funatic_TriggerParallel)·12·>·if{·1·(>L:Funatic_TriggerRate)}·els{·0·(>L:Funatic_TriggerRate)}
    
    (SPLIT:800)
    
    (L:Funatic_TriggerParallel)·--·(>L:Funatic_TriggerParallel)


    Seht ihr es? Zeile 2 ist nicht optimal - die Variable wird VOR dem Ausführen des Scripts durch die Zahl ersetzt, das Erhöhen in Zeile 1 "verpassen" wir. Das ist nicht schlimm - es bedeutet nur, dass wir die Grenze statt bei 12 jetzt bei 13 ziehen...


    So wäre es besser:


    Code
    (L:Funatic_TriggerParallel)·++·d·(>L:Funatic_TriggerParallel)
    12·>·if{·1·(>L:Funatic_TriggerRate)}·els{·0·(>L:Funatic_TriggerRate)}
    
    (SPLIT:800)
    
    (L:Funatic_TriggerParallel)·--·(>L:Funatic_TriggerParallel)


    Wie macht man das (am einfachsten), wenn wir nicht nur "schnell/langsam" sondern 3 oder 4 oder gar mehr "Stufen" unterscheiden wollen?

    Glücklicherweise hat Oliver von AAO uns hier einen Befehl gebaut, den wir im RPN nutzen können (auch wenn es gar kein echter RPN Befehl ist - also genauso wie SPLIT oder WAIT oder SPEAK): nonlin.


    Nehmen wir als Beispiel 4 Stufen. Erste Stufe 0-4, zweite Stufe 4-10, dritte Stufe 10-17 und die vierte Stufe >17. Das kann man wie folgt programmieren:


    Code
    (L:Funatic_TriggerParallel)·++·(>L:Funatic_TriggerParallel)
    4·10·17·3·(L:Funatic_TriggerParallel)·nonlin·(>L:Funatic_TriggerRate)
    
    (SPLIT:800)
    
    (L:Funatic_TriggerParallel)·--·(>L:Funatic_TriggerParallel)

    Die AAO Doku sagt, dass "nonlin" die Position der Zahl liefert, deren Wert größer oder gleich (L:Funatic_TriggerParallel) ist. Da ist ein kleiner Fehler - entweder in der Doku oder in AAO, da gabe ich Oliver noch Bescheid. Denn "nonlin" prüft nicht auf größer oder gleich sondern nur auf größer. Das ist nichts weiter tragisches.


    Also, in obigem Code liefert "nonlin" bei (L:Funatic_TriggerParallel) <=4 den Wert 0, bei (L:Funatic_TriggerParallel) > 4 und <=10 den Wert 1, bei (L:Funatic_TriggerParallel) >10 und <=17 den Wert 2 und bei (L:Funatic_TriggerParallel) >17 den Wert 3. Ach wie praktisch... 0, 1, 2 und 3... für langsam, etwas schneller, noch etwas schneller und schnell ... :)


    Die Zahl 3 hinter den Schwellen 4 10 17 bedeutet, dass es 3 "Schwellen" gibt, also genau die 3 Zahlen davor die Schwellen definieren.


    In obigem Code haben wir jetzt wieder das Problem mit dem "Variable wird VOR der Ausführung durch den Wert ersetzt" erzeugt - wir hinken also wieder 1 Wert hinterher. Wie könnte man das am einfachsten umgehen?


    Der einfache Ansatz mit dem "d" vom Code zuvor funktioniert hier nicht - weil der Wert der Variablen (L:Funatic_TriggerParallel) vor dem "nonlin" stehen muss und nicht vor der ersten Schwelle!


    Einfacher Trick:

    Code
    4·10·17·3
    (L:Funatic_TriggerParallel)·++·d·(>L:Funatic_TriggerParallel)
    nonlin·(>L:Funatic_TriggerRate)
    
    (SPLIT:800)
    
    (L:Funatic_TriggerParallel)·--·(>L:Funatic_TriggerParallel)

    Ich habe die Schwellen vom "nonlin" VOR die Stelle geschrieben, an der ich die Variable berechne. DANN kommt das "d" um den Wert auf dem Stack aufzuheben und DANN erst der "nonlin" Befehl...


    Dass die Schwellen und die zugehörige Zähler und der Wert der Variablen, die hier untersucht wird, vor dem nonlin stehen, sieht auf dem Papier aus, als müssten sie auch im Code unmittelbar davor stehen. Das ist aber falsch. Die Zahlen müssen in der Reihenfolge auf dem Stack stehen bevor der nonlin Befehl ausgeführt wird. Aber was alles auf dem Stack passiert, bis der Befehl ausgeführt wird, ist völlig irrelevant. Nur zum Zeitpunkt der Ausführung muss der Stack mit den Anforderungen des Befehls übereinstimmen (das gilt natürlich für alle Befehle, nicht nur für den "nonlin" - und eigentlich haben wir das davor mit dem "d" auch schon so gemacht, nur ist es da nicht so aufgefallen...).



    So.. genug für heute. Ihr habt etwas zum ausprobieren - und ich noch ein wenig Zeit zum Fliegen. Die ganzen Erläuterungen möglichst verständlich schreiben kostet schon Zeit. Deshalb geht's nicht mehr heute sondern bei nächster Gelegenheit (vrmtl. morgen oder übermorgen) weiter...


    So long,

    Thomas

  • Drehdauer eines Drehknopfes ermitteln

    Wie schon zuvor kommt es uns hier nicht darauf an, einen genauen Wert zu ermitteln - sondern nur um grob abschätzen zu können, ob der Benutzer schon länger am Drehknopf dreht oder erst seit kurzem.

    Das mache ich mir recht einfach - ich zähle einfach Impulse. Nicht pro Zeit sondern absolut. Viele Impulse - lange Drehdauer, wenige Impulse, kurze Drehdauer. Eine schnelle Drehung erzeugt natürlich in einer kürzeren Zeit gleich viele Impulse, wie eine langsame Drehung. Aber das ist für unser Vorhaben doch gar nicht schlecht. Wenn beim schnellen Drehen früher eine "lange Drehung" erkannt wird als beim langsamen Drehen.


    Impulse zählen ist einfach - wir zählen einfach bei jedem Aufruf einen Zähler hoch. Wie beim Ermitteln der Drehgeschwindigkeit. Nur dürfen wir den Zähler am Ende des Scripts nicht wieder rückwärts zählen!


    (L:Funatic_TriggerCount)·++·(>L:Funatic_TriggerCount)


    Das Problem ist hier nicht das Zählen - also das Erkennen, ob der Benutzer schon lange zählt. Sondern das Erkennen, dass der Benutzer NICHT mehr dreht. Denn wenn er nicht mehr dreht, startet AAO kein Script, über das ich irgendeine Aktion (z.B. Zurücksetzen des Zählers auf 0) starten kann!


    Jetzt schielen wir mal kurz in den vorherigen Post:



    Dort haben wir doch eine Möglichkeit gefunden festzustellen, ob der Benutzer noch dreht oder nicht!

    Also bauen wir das Zählen und das Zurücksetzen in unser Script gleich ein:


    Code
    (L:Funatic_TriggerCount)·++·(>L:Funatic_TriggerCount)
    
    4·10·17·3
    (L:Funatic_TriggerParallel)·++·d·(>L:Funatic_TriggerParallel)
    nonlin·(>L:Funatic_TriggerRate)
    
    (SPLIT:800)
    
    (L:Funatic_TriggerParallel)·--·d·(>L:Funatic_TriggerParallel)
    0·==·if{·0·(>L:Funatic_TriggerCount)}


    Die Änderung zum Script vom vorherigen Post sind lediglich die erste und die letzte Zeile, sowie ein "d" in der vorletzten Zeile, damit wir die Variable (L:Funatic_TriggerParallel) auf 0 prüfen können.


    Es gibt aber noch mehr Gründe, eine Drehung als "ist beendet" (oder um genau zu sein als "eine neue Drehung hat begonnen") zu klassifizieren:

    1. Wenn der Benutzer auf einmal in die andere Richtung dreht! Das darf dann nicht mehr mit der "langen Drehung" der vorherigen Drehrichtung zusammen gebracht werden.
    2. Wenn der Benutzer die Drehgeschwindigkeit ändert. Da kann man sicherlich drüber diskutieren. Ich halte es jedoch für sinnvoll, wenn der Benutzer lange langsam dreht und dann schneller wird, dass wir dann nicht gleich in "lange schnell" wechseln sondern zuerst in "kurz schnell"...

    Fangen wir mal mit dem ersten Punkt an. Wir müssen uns also die Drehrichtung merken, damit wir vergleichen können, ob der Benutzer auf einmal andersrum dreht. Dazu habe ich die Variable (L:Funatic_TriggerCount) genutzt. Ich zähle die Variable hoch (also +1) wenn ich nach rechts drehe, und runter (-1) wenn ich nach links drehe.

    Wenn jetzt ein "Rechtsdrehen" Impuls kommt (rechts oder links wird von der Script-Variablen übermittelt! Also im Drehregler-Assignment +1 beim Rechtsdrehen und -1 beim Linksdrehen hinterlegen!) und die TriggerCount-Variable ist negativ, dann weiß ich, dass ich davor nach links gedreht habe. Kommt ein Linksdrehen Impuls und die Variable ist positiv, dann habe ich davor nach rechts gedreht und drehe jetzt nach links. Anderenfalls drehe ich in der gleichen Richtung weiter wie zuvor.


    Das Script könnte dann so aussehen ((L:Autopilot-Heading) ist die Script-Variable - also die so heißt, wie das Script heißt. Und die den Wert +1 bei einer Rechtsdrehung und -1 bei einer Linksdrehung enthält):

    Zeile 1 prüft also, ob die Script-Variable +1 enthält - also Rechtsdrehung. Falls ja, wird geprüft, ob unser TriggerCount negativ ist, ob wir also davor nach links gedreht haben. Falls dem so ist, dann setzen wir die TriggerCount-Variable auf 1, als auch (Zeile 2) die Parallel-Variable zurück auf 1 (wir fangen also mit der Ermittlung schnell/langsam auch wieder von vorn an).

    Haben wir davor auch schon nach rechts gedreht, erhöhen wir einfach unsere Parallel- und -Count-Zähler (Zeile 3+4).


    Drehen wir gerade nach links (Zeile 6) machen wir genau das gleiche wie zuvor - nur mit umgekehrter Richtung. Wir prüfen also zuerst, ob wir davor nach rechts gedreht haben (TriggerCount also positiv ist) und falls ja setzen wir unseren TriggerCount auf -1 (wir wollen ja linksrum negativ zählen) - und setzen auch unseren Parallelzähler wieder zurück auf 1 (Zeile 7). Haben wir davor schon nach links gedreht, zählen wir einfach weiter - den Count natürlich weiter ins negative... (Zeile 8+9).


    Wichtig: wir setzen unseren Parallel-Zähler zurück auf 1 - obwohl evtl. noch Scripte laufen und den Zähler am Scriptende um 1 erniedrigen werden. Dadurch könnte es passieren, dass der Parallel-Zähler <0 wird. Um das zu vermeiden brauchen wir in Zeile 18 jetzt das "0 max"...



    Fortsetzung im nächsten Post wegen 10.000 Zeichen...

  • ...


    Kommen wir jetzt zum 2. Teil: Zurücksetzen des "Läuft-schon-lange"-Zählers, wenn sich die Drehgeschwindigkeit ändert.

    Wir wollen den Zähler ja nicht zurück setzen, wenn der Benutzer jetzt 5 statt 4 Umdrehungen macht - sondern nur, wenn er von schnell nach langsam wechselt oder umgekehrt. Oder allgemein gesprochen: Wenn er die ermittelte "Stufe" wechselt. Diese "Drehgeschwindigkeits-Stufe" haben wir zuvor in der Variablen (L:Funatic_TriggerRate) gespeichert! Wir müssen also nur die neue "TriggerRate" mit der alten vergleichen und wenn sie nicht gleich sind, dann wissen wir, dass der Benutzer von schnell nach langsam oder umgekehrt gewechselt hat.


    Die Änderungen/Erweiterungen sind jetzt nur noch in Zeile 14+15. Statt direkt in die Variable (L:Funatic_TriggerRate) zu schreiben, prüfen wir zuvor, ob sich die Rate überhaupt geändert hat. Falls ja schreiben wir die neue Rate in die Variable - und setzen unseren TriggerCount wieder zurück auf den ersten Wert. Die Script-Variable hat netterweise schon die Werte +1 bzw. -1, die wir als Anfangswerte setzen wollen... :)


    Hat sich die Rate nicht geändert - müssen wir den Wert auch nicht in die Variable schreiben, er ist ja gleich. Den Fall brauchen wir also gar nicht abfragen.


    Beim Testen werdet ihr merken, dass es schwierig ist, die gleiche "TriggerRate", also die gleiche Drehgeschwindigkeitsstufe, einzuhalten. Das liegt daran, dass wir im Script 4 Stufen definiert haben! Nicht umsonst habe ich mich ja für nur 2 Stufen enschieden...


    Jetzt müssen wir noch aus den "TriggerCount"-Werten unsere Einstufung in Schwellen (also "kurz", "mittel", "lang" usw., bzw - damit wir damit besser rechnen können später - 0, 1, 2 usw.) machen. Wie das am einfachsten funktioniert, hatten wir ja schon bei der Drehgeschwindigkeit: Mit dem nonlin Befehl. Den gleichen können wir hier auch benutzen: Sagen wir mal <=8 sei kurz, >8 und <=15 langsam und >15 lang.

    Damit ergibt sich dann der folgende Code (Zeile 16):

    Wir haben jetzt noch die "Unschönheit", dass wir die "TriggerDuration" mit dem alten Wert von TriggerCount berechnen. Ich lass das aber jetzt mal so stehen, da wir beim nächsten Schritt, wenn wir aus diesen beiden Werten die "Heading Bug Increments" ermitteln, sowieso nochmal ran müssen.


    Die Variable (L:Funatic_TriggerDuration) brauchen wir langfristig nicht - die ist nur dazu da, dass man im Variablen-Observer etwas sehen kann ... ;)


    Ich empfehle als Verständnisübung, dass Ihr das Script mal umschreibt auf nur 2 Geschwindigkeitsstufen "schnell" und "langsam" sowie nur 2 Drehdauern "lang" und "kurz"...



    Gruß,

    Thomas


    PS: Ich hatte in der Zeile 16 ein "abs" vergessen. Das nimmt immer die positive Zahl. Da TriggerCount beim Linksdrehen auch negativ werden kann, ich aber nur den Zahlenwert und nicht das Vorzeichen vergleichen will, muss ich das mit "abs" eliminieren...

  • Abbilden einer Tabelle und Nachschlagen der Tabellenwerte

    Wir hatten uns ja am Anfang überlegt, dass man die "Heading Bug Increments" in einer Tabelle ablegen könnte, die z.B. wie folgt aussieht:



    Wie könnte man diese Tabelle in RPN abbilden?

    Dazu zählen wir die Zellen einfach mal durch - nach einem festen Muster (beginnend bei 0):



    Außerdem erinnern wir uns, dass wir langsam als 0 und schnell als 1 festgelegt haben, sowie kurz als 0, mittel als 1 und lang als 2.


    Die Formel Drehdauer + 3 * Drehgeschwindigkeit liefert uns jetzt genau die Zellennummer!

    Beispiel: lang langsam wäre also 2 + 3*0 = 2. Mittel schnell wäre 1 + 3*1 = 4. Usw.

    Allgemein lautet die Formel also Drehdauer + Anzahl Drehdauerstufen * Drehgeschwindigkeit.


    Mit dieser Zahl gehen wir jetzt in den "case" Befehl. Der macht in etwa das umgekehrte, was der nonlin gemacht hat. Nonlin hat den Wert mit der Schwelle verglichen und die Position der Schwelle zurückgegeben. Case nimmt die Position und liefert den Wert an der Position.


    4 10 1 3 2 case bedeutet folgendes (von rechts nach links lesen!): ich will den 2. Wert (Zählung beginnt bei 0!, also eigentlich den dritten Wert!) aus der Reihe, der 3 Zahlen davor. In diesem Beispiel würde dieser Befehl also 4 zurückgeben.


    Case funktioniert auch mit Kommazahlen, für unsere Zwecke reicht diese Beschreibung aber vollkommen aus.


    Jetzt nehmen wir uns wieder unsere Tabelle und schreiben die Werte der Felder 0 bis 5 in der Tabelle rückwärts (also von rechts nach links) auf:

    10 7 5 2 1 1

    Wir haben also 6 Werte

    Und wir wollen den Wert der z.B. 4. Stelle auslesen (also die 5. Zahl, weil wir bei 0 beginnen zu zählen - also den Wert 7):


    10 7 5 2 1 1 6 4 case


    Und wie kamen wir auf die 4? Drehdauer + 3 * Drehgeschwindigkeit. Tun wir mal so, als wären die Variablen (L:Drehdauer) und (L:Drehgeschwindigkeit).


    10 7 5 2 1 1 6 (L:Drehdauer) (L:Drehgeschwindigkeit) 3 * + case


    Btw - wir haben gerade den case Befehl mit einer Berechnung "unterbrochen". Siehe dazu meinen Post #9, kurz vor Ende...


    Wir können die Rechnung auch umdrehen, das spielt keine Rolle: Drehgeschwindigkeit * 3 + Drehdauer:

    10 7 5 2 1 1 6 (L:Drehgeschwindigkeit) 3 * (L:Drehdauer) + case


    Und für meine 2x2 Tabelle, die ich eingesetzt habe, sieht das also wie folgt aus:


    10 5 2 1 4 (L:Drehgeschwindigkeit) 2 * (L:Drehdauer) + case


    Damit haben wir also jetzt meinen "Heading Bug Increment" ermittelt. Nachdem wir jetzt wissen, um wie viel wir den Heading Bug erhöhen (oder erniedrigen! Drehrichtung beachten!) müssen, ist das Setzen des Heading-Bugs nicht mehr schwer.


    Jetzt müssen wir das Ganze nur noch zusammensetzen und vielleicht (nicht zwangsweise) ein wenig optimieren...



    Fangen wir mal mit den umfangreichen If-Statements am Anfang an:


    Code
    (L:Autopilot-Heading)··1·==·if{·(L:Funatic_TriggerCount)·0·<=·if{·1·(>L:Funatic_TriggerCount)··1·(>L:Funatic_TriggerParallel)}
    ·····························································els{·(L:Funatic_TriggerCount)·++·(>L:Funatic_TriggerCount) (L:Funatic_TriggerParallel)·++·(>L:Funatic_TriggerParallel)}}
    (L:Autopilot-Heading)·-1·==·if{·(L:Funatic_TriggerCount)·0·>=·if{·-1·(>L:Funatic_TriggerCount)·1·(>L:Funatic_TriggerParallel)}
    ·····························································els{·(L:Funatic_TriggerCount)·--·(>L:Funatic_TriggerCount) (L:Funatic_TriggerParallel)·++·(>L:Funatic_TriggerParallel)}}

    Es gibt genau 4 "Zweige", die das Script ausführen kann. Damit das deutlicher wird, habe ich mal die Zeilen anders umgebrochen.

    Jeder der 4 Zweige schreibt genau einen Wert sowohl in die Variable (L:Funatic_TriggerCount) als auch in (>L:Funatic_TriggerParallel).


    Statt nur in jedem der 4 Zweige das (>...) zu schreiben, könnte man das auch einfach einmal am Ende schreiben. Das funktioniert nur, weil alle 4 Zeilen das gleiche tun (nur mit unterschiedlichen Werten)!

    Code
    (L:Autopilot-Heading)··1·==·if{·(L:Funatic_TriggerCount)·0·<=·if{·1·1}
    ·····························································els{·(L:Funatic_TriggerCount)·++·(L:Funatic_TriggerParallel)·++}}
    (L:Autopilot-Heading)·-1·==·if{·(L:Funatic_TriggerCount)·0·>=·if{·-1·1·}
    ·····························································els{·(L:Funatic_TriggerCount)·--·(L:Funatic_TriggerParallel)·++}}
    (>L:Funatic_TriggerParallel)·(>L:Funatic_TriggerCount)

    Wichtig:

    1. Die Reihenfolge, in der die Variablenwerte angegeben werden, muss in allen 4 Zweigen gleich sein!
    2. Wir schreiben zuerst den TriggerCount auf den Stack und dann den TriggerParallel - beim Lesen erhalten wir jedoch zuerst den TriggerParallel und dann erst den TriggerCount. D.h. beim (>L:Funatic_TriggerParallel)·(>L:Funatic_TriggerCount) ist die Reihenfolge genau anders rum, als wenn ich das Schreiben in die Variable gleich nach dem Ermitteln des Wertes durchführe!

    Ob man diese "Optimierung" mag ist denke ich Geschmackssache. Es macht den Code etwas kürzer und übersichtlicher. Allerdings geht die Variablenzuweisung leichter unter. Geschwindigkeitsmäßig macht das glaub keinen Unterschied.


    Das 2·*·+·case·l0·* in meinem zu ursprünglich geposteten Script ist nichts anderes als das Drehgeschwindigkeit * 2 + Drehdauer. Das l0 * ist die Mulitplikation mit +1 oder -1 (je nach Drehrichtung) um den Heading Bug eben nach links oder rechts zu drehen.


    Mehr steckt in dem Script auch nicht drin...



    Vielleicht noch ein Hinweis zur Mehrfachbenutzung bei anderen Drehreglern, z.B. Altitude, Speed, Course...


    Natürlich kann man das Script jetzt x mal kopieren, jedesmal neue Variablen einführen usw. Ich gehe jedoch stark davon aus, dass wir in unserem Umfeld nicht mehrere Drehregler gleichzeitig drehen werden (würde ich Code für ein echtes Flugzeug schreiben dürfte ich davon nicht ausgehen! :) ). Deshalb würde ich tatsächlich nur 1 Script schreiben, die Aktion über die Script-Variable definieren (also z.b. +1/-1 = Heading, +2/-2 = Altitude, +3/-3 = Speed, +4/-4 = Course usw. Die Zahl gibt also nicht die Drehgeschwindigkeit an, sondern WAS ihr damit drehen wollt und +- gibt die Drehrichtung vor) und dann davon abhängig entweder den Heading-Bug, die Altitude, den Speed oder Course verändern. Dann braucht ihr nur 1 Script und auch nur 1x diesen Satz von 3 Variablen...

    Denkt daran, dass ihr dann aber mit ziemlicher Sicherheit auch pro Aktion eine eigene Tabelle zum Nachschlagen der "Increment"-Werte brauchen werdet.


    Ich werde das demnächst mal umsetzen und dann hier posten - heute jedoch nicht mehr.



    Und wie Anfangs gesagt noch ein Wort zu meinen Problemen mit dem FBW A320: Beim Drehen des Drehrads (in dem Fall für den Selected Speed) musste ich immer ewig drehen. Und mein Drehrad hat sich immer schneller gedreht, als die Speed-Anzeige im Cockpit gezählt hat. Irgendwas war da nicht in Ordnung. Deshalb habe ich dann mal folgenden Test gemacht:


    Im RPN Script Editor einfach mal


    1·(>K:A32NX.FCU_SPD_INC)

    1·(>K:A32NX.FCU_SPD_INC)

    1·(>K:A32NX.FCU_SPD_INC)

    1·(>K:A32NX.FCU_SPD_INC)

    1·(>K:A32NX.FCU_SPD_INC)

    1·(>K:A32NX.FCU_SPD_INC)

    1·(>K:A32NX.FCU_SPD_INC)

    1·(>K:A32NX.FCU_SPD_INC)

    1·(>K:A32NX.FCU_SPD_INC)

    1·(>K:A32NX.FCU_SPD_INC)


    eingegeben und dann auf "Test" geklickt.

    Damit wird also 10x Speed Increment ausgelöst. Die Selected Speed sollte dabei also um 10 Knoten erhöht werden.

    Das ist aber nicht passiert. Der Speed wurde nur um 5 oder 6 Knoten erhöht. Der FBW A320 hat tatsächlich einige Events verschluckt! Es wurde ja schon mehrfach berichtet, dass der FBW A320 CPU-lastig ist. Und eine Konsequenz dieser Tatsache ist, dass er tatsächlich für das Abarbeiten der Events länger braucht als z.B. die Cessna 172.

    Das wiederum hat zur Folge, dass bei schnell geschickten Events (wie z.B. von einem Drehknopf) manche verschluckt werden und der Drehknopf quasi "durchdreht".


    Deshalb empfehle ich, nicht das _INC und _DEC Event, sondern das Berechnen der neuen Position und das setzen mit _SET (oder wie auch immer das Event oder die Variable dazu dann heißt). Nachdem wir im obigen Script den Increment sowieso variabel ermitteln, müssen wir sowieso diesen Weg gehen.

    Doch Vorsicht: Wenn wir die aktuelle Heading auslesen, unser Increment addieren, dann das Event auslösen und in schneller Folge das nächste Script gestartet wird, dann kann es passieren, dass das Event noch gar nicht verarbeitet ist. Und das bedeutet, dass der 2. Aufruf des Scripts noch das selbe Heading aus dem Autopilot ausliest, wie der 1. Aufruf. Damit verhält sich auch dieser 2. Script-Aufruf wie ein "durchrutschen".

    Das bekommt ihr in den Griff, wenn ihr die aktuelle Heading in einer eigenen Variablen speichert (bei mir: (L:Funatic_Heading). Das Speichern des Wertes in dieser Variablen geht sehr schnell - und der nächste Script-Aufruf hat als Basis bereits das neue Heading. Und wenn der FBW A320 dann mal wieder ein Event verschluckt - dann ist das nicht weiter tragisch, weil der nächste Aufruf des Events ja dann schon den bereits erhöhten Heading-Wert setzt, das "durchgerutschte" Heading wird also quasi übersprungen...


    Ich gehe davon aus, dass das Problem nicht bei LVARs auftritt (sehr wohl, dass der FBW nicht jede Änderung mit bekommt, aber die Variable wird ihren gesetzten Wert wohl ziemlich sicher beibehalten, so dass der nächste Script-Aufruf mit dem richtigen Wert rechnen kann).



    Viele Grüße,

    Thomas


    PS: Das mit den 10 Speed_Inc gerade heute nochmal getestet - jetzt geht die Speed Anzeige um 13 Knoten hoch!? Das dafür aber jedesmal... :bonk:  rauf

    Ich gehe davon aus, dass sie das mit den "verschluckten Events" bemerkt und gefixt haben - und die 13 die automatische Beschleunigung innerhalb des FBW A320 sind (wieso habe ich das Script jetzt überhaupt geschrieben, wenn das der FBW jetzt von alleine macht??? Ok, die Cessna macht's nicht von alleine... :) )

  • So,


    hier noch wie versprochen das "Dynamischer Drehregler" Script für verschiedene Aktionen.

    Seht das bitte nur als Beispiel! Das kann man noch feintunen und um weitere Aktionen ergänzen - es soll einfach nur mal den Weg zeigen.


    Auf Optimierungen habe ich zugunsten der Verständlichkeit verzichtet.


    Bitte auch beachten: ich verwende die Register 0 bis 3, es gibt also den Text l0, l1, l2 und l3 - sehr wohl aber auch die Zahlen 10 und 12... also nicht verwechseln!




    Gruß,

    Thomas

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!