Changes between Initial Version and Version 1 of FACT++/JavaScript


Ignore:
Timestamp:
11/09/18 16:38:14 (7 years ago)
Author:
tbretz
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • FACT++/JavaScript

    v1 v1  
     1[[TOC]]
     2
     3The !JavaScript engine of dimctrl is fully based on Google's V8 which you find for example in their Browser. The advantage is two-fold. It is basically a well maintained product, and it can be embedded into our own structure, e.g. any script can be terminated at any time without the need to foresee that case in the script.
     4
     5!JavaScript references can be found her:
     6
     7* [http://www.w3schools.com/jsref/default.asp w3schools] (//!JavaScript objects//)
     8* [https://developer.mozilla.org/en-US/docs/JavaScript/Reference Mozilla]
     9* [http://de.selfhtml.org/javascript/sprache/index.htm SelfHTML] (//german//)
     10* [http://www.tutorialspoint.com/javascript/index.htm Tutorialspoint]
     11* [http://www.ecma-international.org/ecma-262/5.1/ Official language reference]
     12* [http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/ ECMA Strict mode and more]
     13* Variaous Wikipedia articles in German and English
     14* Google ;)
     15
     16Be aware that only the language related features are available not the ones specific for web development.
     17
     18Our scripts should use the so called strict-mode which forbids a few common mistakes. A reasonably good documentation of it can be found here https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/Strict_mode.
     19
     20**A full reference to the additional !JavaScript elements is available [https://www.fact-project.org/dimctrl here].**
     21
     22The following will contain an example script and some explanations.
     23
     24== Basics ==
     25
     26=== Include ===
     27
     28With the following code you can include code from other scripts. This might, e.g., help to write a little library. Note that the code included as it would be written at this place of the file.
     29
     30{{{#!java
     31include('test3.js');
     32include("test2.js");
     33}}}
     34
     35=== Output ===
     36
     37There are two kinds of output: to the local console (*out*) or as log-message of the script (*print*).
     38
     39{{{#!java
     40console.out("This message will be shown on the local console of dimctrl.");
     41dim.print("This message will appear as log-message in the global log-streams.")
     42}}}
     43
     44=== Arguments ===
     45
     46Arguments can be passed to the script when the script is started. This can be done from dimctrl's console or when a script is started either through a Dim Command or dimctrl's commandline. Each argument can have a name (e.g. declination=20). In this case it will be accessible by by either $!['declination'] or arg!['declinatioin']. Arguments can also be passed without name. In this case they can be accessed by their index (which refers to all unnamed arguments), e.g. $![2] or arg![2].
     47
     48{{{#!java
     49// Arguments are accessible as $ and arg (both are exchangable)
     50
     51// Loop over all arguments by property name
     52for (var name in $)
     53{
     54    // "Print" a message to the console and the global log-file
     55    // (Corresponds, currently, to the batch script ">" command)
     56    console.out("Arguments: " + name + "=" + $[name]);
     57}
     58
     59// Loop over all arguments by index
     60for (var i=0; i<arg.length; i++)
     61{
     62    console.out("Arguments: " + i + "=" + arg[i]);
     63}
     64}}}
     65
     66=== Sleep ===
     67
     68If there are infinite loop or loops which take a lot of time, it is suggested that you give some CPU time back to the system. Otherwise you produce a 100% CPU load for just doing nothing (checking something much more often than really necessary). This is done with v8.sleep(N) with N being the number of milliseconds to sleep. v8.sleep() is an abbreviation for v8.sleep(1), which is usually enough.
     69
     70=== Exit ===
     71
     72If you want to exit a script you can call exit.
     73
     74{{{#!java
     75// check the state of the FAD_CONTROL
     76var error = true;
     77if (error)
     78{
     79    console.out("An error occuredl");
     80    exit();
     81}
     82}}}
     83
     84=== Exceptions ===
     85
     86Not only can some !JavaScript command throw exceptions which you might or might not want to handle, your !JavaScript can also throw exception. This has the advantage that the error-message is closely related to the exit of the program. This should usually be used if you have to terminate because, e.g., the user forgot an argument.
     87
     88{{{#!java
     89// Check if the user provided a required argument
     90try
     91{
     92    if (!$['test'])
     93        throw new Error("argument test not defined");
     94}
     95catch (e)
     96{
     97    // These are the contents available to the error object
     98    console.out(e.name);
     99    console.out(e.message);
     100    console.out(e.stack);
     101    console.out(e);
     102}
     103
     104// To define your own error
     105function MyError(message)
     106{
     107    this.name = "MyError";
     108    this.message = message || "Default Message";
     109}
     110MyError.prototype = new Error();
     111
     112try
     113{
     114    throw MyError();
     115}
     116catch (e)
     117{
     118    console.out("This exception is of type MyError: "+(e instanceof MyError));
     119}
     120}}}
     121
     122You could also throw any other object or just a string, but in this case no stack trace is available. More information about exceptions in V8 can be found at
     123http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
     124
     125=== String formating ===
     126
     127For string formating, similar to C, String.format is available taking the format string as the first argument and an array with the data as second argument, e.g. String.format("%d %d", [11,12]). A similar functions is available as member function of the String object, calld $. This allows a very compact syntax constructing a basic string object: "%d %f".$(12, 13.5) which is a shortcut for String("%d %f").$(12, 13.5). Especially, the first notation should be used with care, because it is not immediately apparent how it works.
     128
     129{{{#!java
     130// Implicit format conversion if possible
     131console.out(String.format("%s %s", [ "This is a string", 11 ]))
     132console.out(String.format("Int: %d %d %d", [ "12", 13, 14.5 ]));
     133console.out(String.format("Float: %f %f", [ "12.3", 13.6 ]));
     134// 'This is a string 11'
     135// 'Int: 12 13 14'
     136// 'Float: 12 14'
     137
     138var arr = [ "12.3", 13.6 ];
     139console.out(String.format("Array: %s", [ arr ]));
     140// 'Array: ["12.3",13.6]'
     141
     142var obj = { test:"test", id:12 };
     143console.out(String.format("Object: %s", [ obj ]));
     144console.out("Object: %s".$(obj));
     145// 'Object: {"test":"test","id":12}'
     146// 'Object: {"test":"test","id":12}'
     147
     148// First character of a string
     149console.out(dim.format("%c", [ "Test" ]));
     150// 'T'
     151
     152// Formating as in C for ints
     153console.out(String.format("%5d",  [ 12 ]));
     154console.out(String.format("%05d", [ 12 ]));
     155console.out(String.format("%-5d", [ 12 ]));
     156// '   12'
     157// '00012'
     158// '12   '
     159
     160// An extension is available which rounds the
     161// intergers to given precision (here the outpu will be 120)
     162console.out(String.format("%5.2d",  [ 123 ]));
     163console.out(String.format("%05.2d", [ 123 ]));
     164console.out(String.format("%-5.2d", [ 123 ]));
     165// '  120'
     166// '00120'
     167// '120  '
     168
     169// Formating as in C for floats
     170// (output in floating point notation with fixed number of digits after comma)
     171console.out(String.format("%5.2f",  [ 1.1 ]));
     172console.out(String.format("%-5.2f", [ 1.1 ]));
     173// ' 1.10'
     174// '1.10 '
     175
     176// Formating as in C for exponentials
     177// (output as exponential with fixed number of digits after comma)
     178console.out(String.format("%10.2e",  [ 1.1 ]));
     179console.out(String.format("%-10.2e", [ 1.1 ]));
     180// '   1.10e+0'
     181// '1.10e+0   '
     182
     183// Formating with fixed precision (number of valid digits)
     184// (In this example number of valid digits is three)
     185console.out(String.format("%5.3p",  [ 1.12345 ]));
     186console.out(String.format("%-5.3p", [ 1.12345 ]));
     187// ' 1.12'
     188// '1.12 '
     189
     190// A special modifier is available to allow output with a different base
     191// (output of hex numbers are like in C)
     192console.out(String.format("%20x",    [  45054 ]));
     193console.out(String.format("%20#16x", [  45054 ]));
     194console.out(String.format("%20#2x",  [ "45054" ]));
     195// '                affe'
     196// '                affe'
     197// '    1010111111111110'
     198
     199// Used with the d-modifier, the bases for interpretation is changes
     200console.out(String.format("%6#2d",    [ "111" ]));
     201console.out(String.format("%6#16d",   [ "affe" ]));
     202console.out(String.format("%6.2#16d", [ "affe" ]));
     203// '     7'
     204// ' 45054'
     205// ' 45000'
     206
     207// It doesn't make sense to use format for conversion from
     208// one basis to another one. This is better done this way
     209console.out(parseInt("affe", 16).toString(2));
     210}}}
     211
     212== States ==
     213
     214=== Change state of local state machine ===
     215
     216The internal state machine can be modified with user defined states. States are allowed in the range [10;255] and are used as follows.
     217
     218{{{#!java
     219// Bring state machine into state 15
     220dimctrl.setState(15);
     221
     222// Define state name for state 15 and 16
     223dimctrl.defineState(15, "NewState1", "This states is for testing purposes only.");
     224dimctrl.defineState(16, "NewState2", "This states is for testing purposes only.");
     225
     226// Would both throw an exception
     227//dimctrl.newState(16, "NewStateX");
     228//dimctrl.newState(17, "NewState2");
     229
     230// Switch to new state by name
     231dimctrl.setState("NewState2");
     232
     233// Switch back to state 15
     234dimctrl.setState("NewState1");
     235
     236// And back to 16
     237dimctrl.setState(16);
     238}}}
     239
     240
     241=== Access state of another state machine ===
     242
     243To get the state of a state machine in the network, the state-function exists. It returns an object which contains name, index and chang time of the state. If the server is disconnected all three wil be undefined. Note that if you check first whether it is connected and access the state later, it might have disconnected in the meantime. If you have to avoid that, just copy the result of the call to state to a new variable. This is especially important if you access the data members *index* and *name* which correspond to the value of the state and the state's name. If both are accessed and you want to  make sure that they do not change between accessing one and the other, you have to make a copy first!
     244
     245{{{#!java
     246// Wait until the server MAGIC_WEATHER is connected
     247var state;
     248while (1)
     249{
     250    state = dim.state("MAGIC_WEATHER");
     251    if (state)
     252       break
     253
     254    // Sleep 1 millisecond
     255    v8.sleep(1);
     256}
     257
     258// Now print a message containing the state which was detected.
     259dim.console("Magic Weather connected. It's state is " + state.name + " [" + state.index + "] since "+state.time);
     260}}}
     261
     262=== Wait for a state ===
     263
     264As in the simple batch processor, waiting for a state is possible. Although this will block your script, the script termination is still possible.
     265
     266{{{#!java
     267// Wait 500ms for FAD_CONTROL to change its state to 1
     268// In case of a time-out print "timeout"
     269// If the server disconnects or is not connected, an exception
     270// is thrown. Catch the exception
     271try
     272{
     273    // Will throw in case of timeout
     274    dim.wait("FAD_CONTROL", 1, 500);
     275   
     276    if (!dim.wait("FAD_CONTROL", "TakingData", -500))
     277        dim.console("timeout");
     278}
     279catch (e)
     280{
     281    exit();
     282}
     283}}}
     284
     285=== State callbacks ===
     286
     287If a state changes, a callback function which is executed every time the state changed is installed. Access is identical than to the events retrieved with get(). The callback must not be blocking and should not take too long because it blocks any processing of further events.
     288Because of the possibility to block the processing of events, it is usually not a good idea to use callbacks. Callbacks are however ideal if it is strictly necessary to catch all state changes.
     289
     290{{{#!java
     291dim.onchange['FAD_CONTROL'] = function (arg)
     292{
     293     if (!this.counter)
     294          this.counter = 0;
     295     else
     296          this.counter++;
     297
     298     dim.console("Server: "+arg.server);
     299     dim.console("Index: "+arg.index);
     300     dim.console("Name: "+arg.name);
     301     dim.console("Time: "+arg.time);
     302     dim.consoel("Comment: "+arg.comment);
     303     dim.console("Counter: "+this.counter);
     304}
     305}}}
     306
     307== Services ==
     308
     309=== Access of a service ===
     310
     311To access a Dim service, you have to create a subscription first. This tells the server to send all updates to dimctrl. The following code block contains an example how to subscribe, access and close (unsubscribe) services. When a service is subscribed a handle to the service is returned. This handle contains a data-member (*name*) to access the name of the service, and two functions (*get* and *close*). *close* is obviously used to unsubscribe from the service. It is strongly recommended to do so if a service is not needed to avoid load on the server and the network. Note that subscribing to a service can take a macroscopic time (~1s). With the get function you retrieve the current context of the service. Note that the internal buffer always contains only the latest service update received. So if you do not access that before the next updates arrives you have lost it. After subscribing to a service, it might take a while until the first data was retrieved. You can wait for that checking the return value of get for being defined with the !-operator. To be able to access all members of the returned structure consistently, it is suggested to make a copy. The structure returned by get will return *format*, *names*, *counter*, *time*, *qos* and *data* (See example for the explanation).  *data* itself is an array containing the data retrieved splitted according to the format string received. If the format it just a single item, then the data can be accessed through the *data* data-member directly, otherwise it needs to be indexed. The dim-network contains information which allows to access data of services through names (as in the FITS files). If this has already been received, access will be possible via name. Arrays will be accessible as arrays, e.g. data!["BoardRate"]![20]. The counter can be used to check if a new service update has been received or whether you have missed one update.
     312
     313Note that when a server is not disconnected or disconnects a default service is emitted by dim. In FACT++ the default service is an empty event. Such events are returned without the data element (data==undefined). To distinguish this for a valid value with zero size, in this case the data element will be valid but null (data==null). That means, data==undefined means that the service is not available or the server not connected, data==null means that a service object has been received but contains no data (zero size).
     314
     315Subscribing twice to the same service is possible, but just a handle to the existing object will be returned.
     316
     317{{{#!java
     318// Subscribe to the the services (returns a handle to each of them)
     319var w = new Subscription("MAGIC_WEATHER/DATA");
     320var x = new Subscription("TNG_WEATHER/DUST");
     321var y = new Subscription("TNG_WEATHER/CLIENT_LIST");
     322
     323// Name which corresponds to handle
     324console.out(w.name);
     325
     326// Wait until a valid service object is in the internal buffer
     327while (!w.get())
     328    v8.sleep(100);
     329
     330// Make sure that the service description for this service is available
     331// This allows to access the service values by name (access by index
     332// is always possible)
     333while (!w.get().obj)
     334    v8.sleep(100);
     335
     336console.out("have data");
     337
     338// get the current service data
     339var d = w.get();
     340
     341// Here is a summary:
     342//    d.obj===undefined: no data received yet
     343//    d.obj!==undefined, d.obj.length==0: valid names are available, received data empty (d.data===null)
     344//    obj!==undefined, obj.length>0: valid names are available, data received
     345//
     346//    d.data===undefined: no data received yet
     347//    d.data===null: event received, but contains no data
     348//    d.data.length>0: event received, contains data
     349
     350console.out("Format: "+d.format); // Dim format string
     351console.out("Counter: "+d.counter); // How many service object have been received so far?
     352console.out("Time: "+d.time); // Which time is attached to the data?
     353console.out("QoS: "+d.qos); // Quality-of-Service parameter
     354console.out("Length: "+d.data.length); // Number of entries in data array
     355console.out("Data: "+d.data); // Print array
     356
     357// Or to plot the whole contents, you can do
     358console.out(JSON.stringify(d));
     359console.out(JSON.stringify(d.data));
     360console.out(JSON.stringify(d.obj));
     361
     362// Loop over all service properties by name
     363for (var name in d.obj)
     364{
     365    console.out("obj." + name + "=" + d.obj[name]);
     366}
     367
     368// Loop over all service properties by index
     369for (var i=0; i<d.data.length; i++)
     370{
     371    console.out(data["+ i +"]="+ d.data[i]);
     372}
     373
     374// Note that in case of formats like F:160, the entries in data
     375// might be arrays themselves
     376
     377var cnt = d.counter;
     378
     379// Print counter and time
     380console.out("Time: "+d.counter+" - "+d.time);
     381
     382// Wait until at least one new event has been received
     383while (cnt==d.counter)
     384{
     385    v8.sleep(1000);
     386    d = w.get();
     387}
     388
     389// Print counter and time of new event (usually counter+1, but this is
     390// not guranteed) We can have missed service objects
     391console.out("Time: "+d.counter+" - "+d.time);
     392
     393// Access the Wind property of the weather data
     394console.out("Wind: "+d.obj.v);
     395console.out("Wind: "+d.obj['v']);
     396
     397// Get the dust and client_list
     398var xx = x.get();
     399var yy = y.get();
     400
     401// data contains only a single value. No array indexing required
     402console.out("Dust: "+xx.data);
     403console.out("CL:   "+yy.data);
     404
     405// Service is still open
     406console.out(w.isOpen);
     407
     408// Close the subscription (unsubscribe from the dim-service)
     409// Tells you if the service was still subscribed or not
     410var rc = w.close();
     411
     412// Service is not subscribed anymore
     413console.out(w.isOpen);
     414}}}
     415
     416=== Service callbacks ===
     417
     418Also services allow callbacks, whenever a new event is received. Access is identical than to the events retrieved with get(). The callback must not be blocking and should not take too long because it blocks any processing of further events. Because of the possibility to block the processing of events, it is usually not a good idea to use callbacks. Callbacks are however ideal if it is strictly necessary to catch all events.
     419
     420{{{#!java
     421var s = new Subscription("FEEDBACK/CALIBRATION");
     422s.onchange = function(arg)
     423{
     424    console.out("Name: "+arg.name);
     425    console.out("Counter: "+arg.counter);
     426    console.out("Format: "+arg.format);
     427    console.out("QoS: "+arg.qos);
     428    console.out("Time: "+arg.time);
     429    console.out("Data: "+arg.data);
     430}
     431}}}
     432
     433=== Sending a dim command ===
     434
     435Of course, not only services can be accessed but also dim commands can be sent. To do this, you can compile the string yourself or let the !JavaScript engine do that for you. See example (note that this is not a working example because the program does not exist).
     436
     437{{{#!java
     438// Example how to send a dim command
     439
     440var fuenf = 5;
     441var sieben = 7.8;
     442
     443// Make sure that the necessary format information was already received
     444while (!dim.send("SMART_FACT"))
     445     v8.sleep();
     446
     447dim.send("SMART_FACT/PRINT");
     448dim.send('TEST/DO_SOMETHING 5 7.87 "test"');
     449dim.send("TEST/DO_SOMETHING", fuenf, sieben, "test");
     450}}}
     451
     452== Database ==
     453
     454The following shows a complete example how to access a database and shows all data-member or functions available. First a connection to
     455a database has to be opened (and should be closed if not needed anymore). With *query* a query is sent and the result returned.
     456*table* will contain the table name, *length* the number of rows. The array *cols* contains the name of the columns. Rows are accessed by indexing. Columns in one row can be either indexed or accessed by their names, e.g. result![5]![3] or result![5]!['name_of_col_3']. In case of
     457errors (mainly during connection problems or when sending a query) an exception is thrown. Exceptions can be cached and evaluated or, if not handled, will terminate the script execution. *close* will return if the connection was still open or already closed.
     458
     459{{{#!java
     460try
     461{
     462    var db = new Database("user:password@127.0.0.1/FACT");
     463
     464    console.out("User: "+db.user);
     465    console.out("Server: "+db.server+":"+db.port);
     466    console.out("Database: "+db.database);
     467   
     468    var res = db.query("SELECT * FROM Configuration");
     469    console.out("Table: "+ res.table + " [N=" + res.length+"]");
     470   
     471    for (var i=0; i<res.cols.length; i++)
     472        console.out("Col "+i+": "+res.cols[i]);
     473   
     474    for (var i=0; i<res.length; i++)
     475    {
     476       var str = "";
     477       for (var col in res[i])
     478           str += " "+col+"="+res[i][col];       
     479       console.out(str);
     480   }
     481 
     482   console.out("Close: "+db.close());
     483   console.out("Close: "+db.close());
     484   console.out("Query: "+db.query("test"));
     485
     486}
     487catch (e)
     488{
     489    console.out("SQL Exception caught: "+e);
     490}
     491}}}
     492
     493== Astrometry ==
     494
     495For simple access and conversion to important values some astrometry functions are available. Note that currently the observer's location is build in and cannot be changed. Calculations are based on libnova. The precision should be enough for the anticipated purpose. It is not meant for super high precision calculations.
     496
     497{{{#!java
     498// Get the current equatorial position of the moon
     499var moon = Sky.moon();
     500
     501// To do the calculation for a custom date you can do
     502// var date = new Date();
     503// var moon = Sky.moon(date);
     504
     505console.out("Equatorial coordinates: ra="+moon.ra+"h dec="+moon.dec);
     506
     507// Print the time for which the position was calculated
     508console.out(moon.time);
     509
     510// Convert the position to local coordinates
     511var loc = moon.toLocal();
     512
     513// Get the current equatorial position of the moon
     514var moon = Sky.moon();
     515console.out("Celestial coordinates: zd="+loc.zd+"deg az="+loc.az);
     516
     517// Print the time for which the position was calculated
     518console.out(moon.time);
     519
     520// Initialize new equatorial coordinates
     521var radec = Sky(moon.ra, moon.dec);
     522console.out(radec.ra+" "+radec.dec);
     523
     524// Convert them to the celestial sphere
     525var zdaz = radec.toLocal();
     526console.out(zdaz.zd+" "+zdaz.az+" "+zdaz.time);
     527
     528// Or for a different time you can do
     529// var zdaz = radec.toLocal(date);
     530
     531// Initialize celestial coordinates
     532var celest = Local(zdaz.zd, zdaz.az);
     533console.out(celest.zd+" "+celest.az);
     534
     535// Convert them to equatorial coordinates
     536var equ = celest.toSky();
     537console.out(equ.ra+" "+equ.dec+" "+equ.time);
     538
     539// And for a custom date
     540// var equ = celest.toSky(date);
     541}}}
     542
     543
     544== !JavaScript ==
     545
     546=== Some !JavaScript specials ===
     547
     548{{{#!java
     549// Note that in JavaScript the following two notations are identical
     550
     551var object = {name:"Thomas", surname:"Mustermann" };
     552
     553console.out(object.name);
     554console.out(object['name']);
     555}}}
     556
     557
     558=== Conversion ===
     559
     560Sometimes conversion form s string to typed variables is necessary, e.g. when doing a calculation. This is done by:
     561
     562{{{#!java
     563// Java function to convert strings to float and integer
     564var f0 = parseFloat("12.5");
     565var i1 = parseInt("12");
     566
     567// To convert an object to text you can do
     568var loc = Local(12, 13);
     569console.out(JSON.stringify(loc));
     570
     571var obj = { test: 12.5 };
     572console.out(JSON.stringify(obj));
     573}}}