Board index FlightGear Development Nasal

Running a Nasal function from another application

Nasal is the scripting language of FlightGear.

Running a Nasal function from another application

Postby ludomotico » Mon Nov 18, 2013 9:44 pm

Is there any way to run an existing Nasal function from another application? I don't mean reading or modifying a property, but running a Nasal function that an aircraft provides from an external application using telnet, for example.

Thank you very much in advance!
User avatar
ludomotico
 
Posts: 1269
Joined: Tue Apr 24, 2012 2:01 pm
Version: nightly
OS: Windows 10

Re: Running a Nasal function from another application

Postby Hooray » Mon Nov 18, 2013 9:46 pm

Yes, you can do that - see for example: http://wiki.flightgear.org/Telnet_usage ... r_programs
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Running a Nasal function from another application

Postby ludomotico » Mon Nov 18, 2013 10:11 pm

Thanks for your answer, Hooray.

I know I can read and write elements of the property tree, but I'm not sure if I can run a more complex Nasal command.

For example, imagine a remote panel. All light switches I checked can be operated writing on a property, and this can be done easily using telnet. But the autopilot buttons are different because they make a lot of test and conditions. Take the b1900b. To set the autopilot in the HDG mode you can run this simple nasal code:

FCP65_input("hdg");

or run this pseudocode in the remote panel:

tmp = Lmode.getValue();
if(tmp != "HDG") tmp="HDG" else tmp = "ROLL";
Lmode.setValue(tmp);
Larmed.setValue("");
appr_enabled.setValue(0);
nav_enabled.setValue(0);

I prefer to run the first simple command and not mess with the autopilot logic in the remote panel, but I can't see any way to do this.

One nice way to do this would be declaring nasal functions in the protocol files, but I don't know if including nasal code inside the protocol files is possible. For example, this would be "FGDATA/Protocol/remoteautopilot.xml"

Code: Select all
<PropertyList>
<nasal>
if (some_property_changes) {
    FCP65_input("hdg");
}
</nasal>
 <generic>

  <output>
   <line_separator>newline</line_separator>
   <var_separator>:</var_separator>

   <chunk>
    <name>speed</name>
    <format>%f</format>
    <type>float</type>
    <node>/instrumentation/airspeed-indicator/indicated-speed-kt</node>
   </chunk>

....
</PropertyList>
User avatar
ludomotico
 
Posts: 1269
Joined: Tue Apr 24, 2012 2:01 pm
Version: nightly
OS: Windows 10

Re: Running a Nasal function from another application

Postby adrian » Mon Nov 18, 2013 10:32 pm

Yes, it's possible to run any Nasal function or script via telnet. For an example, see this file line 208 and below.
adrian
 
Posts: 362
Joined: Wed Sep 15, 2010 3:15 pm

Re: Running a Nasal function from another application

Postby ludomotico » Mon Nov 18, 2013 11:02 pm

Thanks adrian, this seems exactly what I need.

I'm using these lines, but they are not working

Code: Select all
set /nasal/myap/script FCS.FCP65_input("hdg");
run nasal /nasal/myap


According to your example the paths seem correct, so I guess it has something to do with namespaces. I'll test a simpler function after supper.
User avatar
ludomotico
 
Posts: 1269
Joined: Tue Apr 24, 2012 2:01 pm
Version: nightly
OS: Windows 10

Re: Running a Nasal function from another application

Postby Hooray » Thu Nov 21, 2013 4:05 am

frankly, while it does work this way, the whole approach is a bit messy, i.e. using "set" to write a Nasal script to a property was never explicitly designed for - it just happened to work "by accident". It would be cleaner to expose a real "Nasal shell" via telnet. Once the telnet/props interface is re-implemented in scripting space it should be pretty straightforward to directly write code into a buffer and use compile(). But for now that should suffice, and work.
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Running a Nasal function from another application

Postby adrian » Thu Nov 21, 2013 9:59 am

Hooray wrote in Thu Nov 21, 2013 4:05 am:frankly, while it does work this way, the whole approach is a bit messy, i.e. using "set" to write a Nasal script to a property was never explicitly designed for - it just happened to work "by accident".

Why not? After all, Nasal is just plain text. Write plain text to a memory location, then run it through an interpreter on demand. Seems pretty well designed, if a little cumbersome.

Hooray wrote in Thu Nov 21, 2013 4:05 am: It would be cleaner to expose a real "Nasal shell" via telnet. Once the telnet/props interface is re-implemented in scripting space it should be pretty straightforward to directly write code into a buffer and use compile(). But for now that should suffice, and work.

Would be, could be, should be. But who's got the time or the incentive to write this? Especially if the method above works well enough.
adrian
 
Posts: 362
Joined: Wed Sep 15, 2010 3:15 pm

Re: Running a Nasal function from another application

Postby Philosopher » Thu Nov 21, 2013 2:44 pm

ludomotico wrote in Mon Nov 18, 2013 11:02 pm:I'm using these lines, but they are not working

Have you tried:
  • Making sure the property shows up correctly?
  • A simpler Nasal statement (maybe just print())?

Also note that if you want to run the scripting in a specific namespace, you can set a .../module property with the name of the namespace (otherwise it runs in a temporary one).

Just an idea I was thinking of (pretty much how the telnet system works now, except mine would work via a generic protocol): use a script to set up a bunch of command nodes as bindings (bindings have their command stored as a node) and have a "run" property to run that command. (So instead of a telnet run command, you would set a property to True instead.)
Philosopher
 
Posts: 1593
Joined: Sun Aug 12, 2012 7:29 pm

Re: Running a Nasal function from another application

Postby Hooray » Thu Nov 21, 2013 3:04 pm

completely agreed P. - a while ago, I actually started extending the telnet/props interface to allow commands to be implemented as Nasal hashes, but that was prior to the canvas/cppbind days. These days, it would indeed be better to simply wrap some lower-level interface like the generic protocol system (or ideally FGInterface/FGProtocol) and re-implement telnet/httpd support on top of it. I think, I'm going to use this as a test-case for the CppBind tutorial, and add some related examples to the article.

Unifying code that isn't performance-critical is a good thing, and helps us get rid of old/unmaintained code over time.
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Running a Nasal function from another application

Postby adrian » Thu Nov 21, 2013 3:35 pm

Guys, wait a second. None of you noticed this, but the reason my example works is that I've actually patched a bug in the props server - and completely forgot about it.
Ludomotico, you'll have to excuse me. You could have tried to run your nasal scripts for a good couple of years if I hadn't been inadvertently reminded by Hooray that the official code is bugged. I'll post the patch here, in case you don't want to wait for someone to actually merge it into the official tree.
If you patch your source an recompile, you'll be able to run Nasal scripts like I've shown in my example.


Code: Select all
diff --git a/src/Network/props.cxx b/src/Network/props.cxx
index 58905e2..56928f4 100644
--- a/src/Network/props.cxx
+++ b/src/Network/props.cxx
@@ -447,23 +447,47 @@ PropsChannel::foundTerminator()
                             node->setStringValue( tokens[3].c_str() );
                        }
                     }
-                    if ( !globals->get_commands()
-                             ->execute(tokens[1].c_str(), &args) )
-                    {
-                        SG_LOG( SG_NETWORK, SG_ALERT,
-                                "Command " << tokens[1] << " failed.");
-                        if ( mode == PROMPT ) {
-                            tmp += "*failed*";
-                            push( tmp.c_str() );
-                            push( getTerminator() );
-                        }
-                    } else {
-                        if ( mode == PROMPT ) {
-                            tmp += "<completed>";
-                            push( tmp.c_str() );
-                            push( getTerminator() );
-                        }
-                    }
+                    // how do we get the property for nasal script?
+                    if ( tokens[1] == "nasal" ) {
+                  SGPropertyNode *node_s;
+                  node_s = globals->get_props()->getNode( tokens[2].c_str() );
+                                     
+                  if ( !globals->get_commands()
+                         ->execute(tokens[1].c_str(), node_s) )
+                  {
+                     SG_LOG( SG_NETWORK, SG_ALERT,
+                           "Command " << tokens[1] << " failed.");
+                     if ( mode == PROMPT ) {
+                        tmp += "*failed*";
+                        push( tmp.c_str() );
+                        push( getTerminator() );
+                     }
+                  } else {
+                     if ( mode == PROMPT ) {
+                        tmp += "<completed>";
+                        push( tmp.c_str() );
+                        push( getTerminator() );
+                     }
+                  }
+               } else {
+                  if ( !globals->get_commands()
+                         ->execute(tokens[1].c_str(), &args) )
+                  {
+                     SG_LOG( SG_NETWORK, SG_ALERT,
+                           "Command " << tokens[1] << " failed.");
+                     if ( mode == PROMPT ) {
+                        tmp += "*failed*";
+                        push( tmp.c_str() );
+                        push( getTerminator() );
+                     }
+                  } else {
+                     if ( mode == PROMPT ) {
+                        tmp += "<completed>";
+                        push( tmp.c_str() );
+                        push( getTerminator() );
+                     }
+                  }
+               }
                 } else {
                     if ( mode == PROMPT ) {
                         tmp += "no command specified";
adrian
 
Posts: 362
Joined: Wed Sep 15, 2010 3:15 pm

Re: Running a Nasal function from another application

Postby adrian » Thu Nov 21, 2013 3:38 pm

Alas, I also remember posting something about this on devel, but I think everyone ignored it. Perhaps if you open a bug, we can gather a few people and get it patched for good.
adrian
 
Posts: 362
Joined: Wed Sep 15, 2010 3:15 pm

Re: Running a Nasal function from another application

Postby Philosopher » Thu Nov 21, 2013 3:50 pm

How about this one? IMHO it's a little cleaner (didn't test, though):

Code: Select all
diff --git a/src/Network/props.cxx b/src/Network/props.cxx
index 58905e2..5c2c3a8 100644
--- a/src/Network/props.cxx
+++ b/src/Network/props.cxx
@@ -404,6 +404,7 @@ PropsChannel::foundTerminator()
                 string tmp;
                 if ( tokens.size() >= 2 ) {
                     SGPropertyNode args;
+                    SGPropertyNode* arg_ptr = &arg;
                     if ( tokens[1] == "reinit" ) {
                         for ( unsigned int i = 2; i < tokens.size(); ++i ) {
                             cout << "props: adding subsystem = " << tokens[i]
@@ -446,9 +447,11 @@ PropsChannel::foundTerminator()
                             node = args.getNode("file", 0, true);
                             node->setStringValue( tokens[3].c_str() );
                        }
+                    } else if ( tokens[1] == "nasal" ) {
+                        arg_ptr = globals->get_props()->getNode( tokens[2].c_str() );
                     }
                     if ( !globals->get_commands()
-                             ->execute(tokens[1].c_str(), &args) )
+                             ->execute(tokens[1].c_str(), arg_ptr) )
                     {
                         SG_LOG( SG_NETWORK, SG_ALERT,
                                 "Command " << tokens[1] << " failed.");


Anyways, no need to file a bug report - a simple merge request should do, as long as you get the attention of one of the developers.

Otherwise, it would also make sense to support passing a property name to get the args from anyways, as sort of a default (i.e. no special case for "nasal", and other commands would automatically support it; so instead of "run play-audio-message <path> <file>", it could be "run play-audio-message <path-to-command-parent-node>", where <path-to-command-parent-node> would contain a <path> and a <file>; or one could just support a "binding" command that always takes its argument out of the property tree, so that the former would stay the same).
Philosopher
 
Posts: 1593
Joined: Sun Aug 12, 2012 7:29 pm

Re: Running a Nasal function from another application

Postby ludomotico » Thu Nov 21, 2013 4:38 pm

I was getting crashes from the simulator with my two little lines and I was planning to investigate further during this weekend. Thanks for looking into this :)

Philosopher, your code needs an extra 's' at line 407: SGPropertyNode* arg_ptr = &args;

After applying your fixes (with the extra 's'!) to the current git version of flightgear, the two lines of code that I tested worked perfectly. On a b1900d, open the autopilot dialog. Then run this commands from a telnet connection (you will see how the lateral mode changes)

Code: Select all
set /nasal/myap/script FCS.FCP65_input("hdg");
run nasal /nasal/myap


Thank you very much! I will test the system during this weekend, but I'd really like this bug solved for all users. I don't know how to issue a merge request, but I will investigate.
User avatar
ludomotico
 
Posts: 1269
Joined: Tue Apr 24, 2012 2:01 pm
Version: nightly
OS: Windows 10

Re: Running a Nasal function from another application

Postby Hooray » Thu Nov 21, 2013 4:43 pm

Guys, running Nasal code via telnet should still work, even without the patch, it may just involve a helper script in $FG_ROOT/Nasal instead of using the awkward "run nasal /foo/bar" method - I haven't tried it in a while, but the approach should be documented somewhere in the wiki. If that also doesn't work any longer, it's a bug which should be fixed. Expecting the "run" method to properly deal with arbitrary Nasal code is a bit far-fetched. I wasn't aware that you were doing it that way.

The telnet interface is clearly one of the most-flexible and most-straightforward interfacing options, but its RPC interface is really crude and simple, see the full thread started by mfranz: http://thread.gmane.org/gmane.games.fli ... ocus=41538

You are better of by using a simple "rpc.nas" script that registers a listener to map property commands to aircraft specific commands.
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Running a Nasal function from another application

Postby ludomotico » Thu Nov 21, 2013 4:52 pm

The thing with an extra nasal file is that you have to instruct the users to add a nasal file to some system directory, and I know some of them "don't know how to do it". In addition, you will have another nasal function loaded even if the user is not currently using the external panel. I'm not sure if this is going to have any impact on the performance.

So it was just looking for something that needed "as less configuration as possible from the user side". I'll check the script in the nasal directory, maybe it is really a better solution even with the extra configuration steps.

FYI, all of this is to add autopilot support to some panels in FlightGearMap: http://wiki.flightgear.org/FlightGearMap
User avatar
ludomotico
 
Posts: 1269
Joined: Tue Apr 24, 2012 2:01 pm
Version: nightly
OS: Windows 10

Next

Return to Nasal

Who is online

Users browsing this forum: No registered users and 0 guests