wiki:FACT++/JavaScript

Version 1 (modified by tbretz, 5 months ago) (diff)

--

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

JavaScript references can be found her:

Be aware that only the language related features are available not the ones specific for web development.

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

A full reference to the additional JavaScript elements is available here.

The following will contain an example script and some explanations.

Basics

Include

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

include('test3.js');
include("test2.js");

Output

There are two kinds of output: to the local console (*out*) or as log-message of the script (*print*).

console.out("This message will be shown on the local console of dimctrl.");
dim.print("This message will appear as log-message in the global log-streams.")

Arguments

Arguments 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].

// Arguments are accessible as $ and arg (both are exchangable)

// Loop over all arguments by property name
for (var name in $)
{
    // "Print" a message to the console and the global log-file
    // (Corresponds, currently, to the batch script ">" command)
    console.out("Arguments: " + name + "=" + $[name]);
}

// Loop over all arguments by index
for (var i=0; i<arg.length; i++)
{
    console.out("Arguments: " + i + "=" + arg[i]);
}

Sleep

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

Exit

If you want to exit a script you can call exit.

// check the state of the FAD_CONTROL
var error = true;
if (error)
{
    console.out("An error occuredl");
    exit();
}

Exceptions

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

// Check if the user provided a required argument
try
{
    if (!$['test'])
        throw new Error("argument test not defined");
}
catch (e)
{
    // These are the contents available to the error object
    console.out(e.name);
    console.out(e.message);
    console.out(e.stack);
    console.out(e);
}

// To define your own error
function MyError(message) 
{
    this.name = "MyError";
    this.message = message || "Default Message";
}
MyError.prototype = new Error();

try 
{
    throw MyError();
}
catch (e)
{
    console.out("This exception is of type MyError: "+(e instanceof MyError));
}

You 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 http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi

String formating

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

// Implicit format conversion if possible
console.out(String.format("%s %s", [ "This is a string", 11 ]))
console.out(String.format("Int: %d %d %d", [ "12", 13, 14.5 ]));
console.out(String.format("Float: %f %f", [ "12.3", 13.6 ]));
// 'This is a string 11'
// 'Int: 12 13 14'
// 'Float: 12 14'

var arr = [ "12.3", 13.6 ];
console.out(String.format("Array: %s", [ arr ]));
// 'Array: ["12.3",13.6]'

var obj = { test:"test", id:12 };
console.out(String.format("Object: %s", [ obj ]));
console.out("Object: %s".$(obj));
// 'Object: {"test":"test","id":12}'
// 'Object: {"test":"test","id":12}'

// First character of a string
console.out(dim.format("%c", [ "Test" ]));
// 'T'

// Formating as in C for ints
console.out(String.format("%5d",  [ 12 ]));
console.out(String.format("%05d", [ 12 ]));
console.out(String.format("%-5d", [ 12 ]));
// '   12'
// '00012'
// '12   '

// An extension is available which rounds the
// intergers to given precision (here the outpu will be 120)
console.out(String.format("%5.2d",  [ 123 ]));
console.out(String.format("%05.2d", [ 123 ]));
console.out(String.format("%-5.2d", [ 123 ]));
// '  120'
// '00120'
// '120  '

// Formating as in C for floats
// (output in floating point notation with fixed number of digits after comma)
console.out(String.format("%5.2f",  [ 1.1 ]));
console.out(String.format("%-5.2f", [ 1.1 ]));
// ' 1.10'
// '1.10 '

// Formating as in C for exponentials
// (output as exponential with fixed number of digits after comma)
console.out(String.format("%10.2e",  [ 1.1 ]));
console.out(String.format("%-10.2e", [ 1.1 ]));
// '   1.10e+0'
// '1.10e+0   '

// Formating with fixed precision (number of valid digits)
// (In this example number of valid digits is three)
console.out(String.format("%5.3p",  [ 1.12345 ]));
console.out(String.format("%-5.3p", [ 1.12345 ]));
// ' 1.12'
// '1.12 '

// A special modifier is available to allow output with a different base
// (output of hex numbers are like in C)
console.out(String.format("%20x",    [  45054 ]));
console.out(String.format("%20#16x", [  45054 ]));
console.out(String.format("%20#2x",  [ "45054" ]));
// '                affe'
// '                affe'
// '    1010111111111110'

// Used with the d-modifier, the bases for interpretation is changes
console.out(String.format("%6#2d",    [ "111" ]));
console.out(String.format("%6#16d",   [ "affe" ]));
console.out(String.format("%6.2#16d", [ "affe" ]));
// '     7'
// ' 45054'
// ' 45000'

// It doesn't make sense to use format for conversion from
// one basis to another one. This is better done this way
console.out(parseInt("affe", 16).toString(2));

States

Change state of local state machine

The internal state machine can be modified with user defined states. States are allowed in the range [10;255] and are used as follows.

// Bring state machine into state 15
dimctrl.setState(15);

// Define state name for state 15 and 16
dimctrl.defineState(15, "NewState1", "This states is for testing purposes only.");
dimctrl.defineState(16, "NewState2", "This states is for testing purposes only.");

// Would both throw an exception
//dimctrl.newState(16, "NewStateX");
//dimctrl.newState(17, "NewState2");

// Switch to new state by name
dimctrl.setState("NewState2");

// Switch back to state 15
dimctrl.setState("NewState1");

// And back to 16
dimctrl.setState(16);

Access state of another state machine

To 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!

// Wait until the server MAGIC_WEATHER is connected
var state;
while (1)
{
    state = dim.state("MAGIC_WEATHER");
    if (state)
       break

    // Sleep 1 millisecond
    v8.sleep(1);
}

// Now print a message containing the state which was detected.
dim.console("Magic Weather connected. It's state is " + state.name + " [" + state.index + "] since "+state.time);

Wait for a state

As in the simple batch processor, waiting for a state is possible. Although this will block your script, the script termination is still possible.

// Wait 500ms for FAD_CONTROL to change its state to 1
// In case of a time-out print "timeout"
// If the server disconnects or is not connected, an exception
// is thrown. Catch the exception
try
{
    // Will throw in case of timeout
    dim.wait("FAD_CONTROL", 1, 500);
    
    if (!dim.wait("FAD_CONTROL", "TakingData", -500))
        dim.console("timeout");
}
catch (e)
{
    exit();
}

State callbacks

If 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. 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 state changes.

dim.onchange['FAD_CONTROL'] = function (arg)
{
     if (!this.counter)
          this.counter = 0;
     else
          this.counter++;

     dim.console("Server: "+arg.server);
     dim.console("Index: "+arg.index);
     dim.console("Name: "+arg.name);
     dim.console("Time: "+arg.time);
     dim.consoel("Comment: "+arg.comment);
     dim.console("Counter: "+this.counter);
}

Services

Access of a service

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

Note 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).

Subscribing twice to the same service is possible, but just a handle to the existing object will be returned.

// Subscribe to the the services (returns a handle to each of them)
var w = new Subscription("MAGIC_WEATHER/DATA");
var x = new Subscription("TNG_WEATHER/DUST");
var y = new Subscription("TNG_WEATHER/CLIENT_LIST");

// Name which corresponds to handle
console.out(w.name);

// Wait until a valid service object is in the internal buffer
while (!w.get())
    v8.sleep(100);

// Make sure that the service description for this service is available
// This allows to access the service values by name (access by index
// is always possible)
while (!w.get().obj)
    v8.sleep(100);

console.out("have data");

// get the current service data
var d = w.get();

// Here is a summary:
//    d.obj===undefined: no data received yet
//    d.obj!==undefined, d.obj.length==0: valid names are available, received data empty (d.data===null)
//    obj!==undefined, obj.length>0: valid names are available, data received
//
//    d.data===undefined: no data received yet
//    d.data===null: event received, but contains no data
//    d.data.length>0: event received, contains data

console.out("Format: "+d.format); // Dim format string
console.out("Counter: "+d.counter); // How many service object have been received so far?
console.out("Time: "+d.time); // Which time is attached to the data?
console.out("QoS: "+d.qos); // Quality-of-Service parameter
console.out("Length: "+d.data.length); // Number of entries in data array
console.out("Data: "+d.data); // Print array

// Or to plot the whole contents, you can do
console.out(JSON.stringify(d));
console.out(JSON.stringify(d.data));
console.out(JSON.stringify(d.obj));

// Loop over all service properties by name
for (var name in d.obj)
{
    console.out("obj." + name + "=" + d.obj[name]);
}

// Loop over all service properties by index
for (var i=0; i<d.data.length; i++)
{
    console.out(data["+ i +"]="+ d.data[i]);
}

// Note that in case of formats like F:160, the entries in data
// might be arrays themselves

var cnt = d.counter;

// Print counter and time
console.out("Time: "+d.counter+" - "+d.time);

// Wait until at least one new event has been received
while (cnt==d.counter)
{
    v8.sleep(1000);
    d = w.get();
}

// Print counter and time of new event (usually counter+1, but this is
// not guranteed) We can have missed service objects
console.out("Time: "+d.counter+" - "+d.time);

// Access the Wind property of the weather data
console.out("Wind: "+d.obj.v);
console.out("Wind: "+d.obj['v']);

// Get the dust and client_list
var xx = x.get();
var yy = y.get();

// data contains only a single value. No array indexing required
console.out("Dust: "+xx.data);
console.out("CL:   "+yy.data);

// Service is still open
console.out(w.isOpen);

// Close the subscription (unsubscribe from the dim-service)
// Tells you if the service was still subscribed or not
var rc = w.close();

// Service is not subscribed anymore
console.out(w.isOpen);

Service callbacks

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

var s = new Subscription("FEEDBACK/CALIBRATION");
s.onchange = function(arg) 
{
    console.out("Name: "+arg.name);
    console.out("Counter: "+arg.counter);
    console.out("Format: "+arg.format);
    console.out("QoS: "+arg.qos);
    console.out("Time: "+arg.time);
    console.out("Data: "+arg.data);
}

Sending a dim command

Of 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).

// Example how to send a dim command

var fuenf = 5;
var sieben = 7.8;

// Make sure that the necessary format information was already received
while (!dim.send("SMART_FACT"))
     v8.sleep();

dim.send("SMART_FACT/PRINT");
dim.send('TEST/DO_SOMETHING 5 7.87 "test"');
dim.send("TEST/DO_SOMETHING", fuenf, sieben, "test");

Database

The following shows a complete example how to access a database and shows all data-member or functions available. First a connection to a database has to be opened (and should be closed if not needed anymore). With *query* a query is sent and the result returned. *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 errors (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.

try
{
    var db = new Database("user:password@127.0.0.1/FACT");

    console.out("User: "+db.user);
    console.out("Server: "+db.server+":"+db.port);
    console.out("Database: "+db.database);
    
    var res = db.query("SELECT * FROM Configuration");
    console.out("Table: "+ res.table + " [N=" + res.length+"]");
    
    for (var i=0; i<res.cols.length; i++)
        console.out("Col "+i+": "+res.cols[i]);
    
    for (var i=0; i<res.length; i++)
    {
       var str = "";
       for (var col in res[i])
           str += " "+col+"="+res[i][col];        
       console.out(str);
   }
  
   console.out("Close: "+db.close());
   console.out("Close: "+db.close());
   console.out("Query: "+db.query("test"));

}
catch (e)
{
    console.out("SQL Exception caught: "+e);
}

Astrometry

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

// Get the current equatorial position of the moon
var moon = Sky.moon();

// To do the calculation for a custom date you can do
// var date = new Date();
// var moon = Sky.moon(date); 

console.out("Equatorial coordinates: ra="+moon.ra+"h dec="+moon.dec);

// Print the time for which the position was calculated
console.out(moon.time);

// Convert the position to local coordinates
var loc = moon.toLocal();

// Get the current equatorial position of the moon
var moon = Sky.moon();
console.out("Celestial coordinates: zd="+loc.zd+"deg az="+loc.az);

// Print the time for which the position was calculated
console.out(moon.time);

// Initialize new equatorial coordinates 
var radec = Sky(moon.ra, moon.dec);
console.out(radec.ra+" "+radec.dec);

// Convert them to the celestial sphere
var zdaz = radec.toLocal();
console.out(zdaz.zd+" "+zdaz.az+" "+zdaz.time);

// Or for a different time you can do
// var zdaz = radec.toLocal(date);

// Initialize celestial coordinates
var celest = Local(zdaz.zd, zdaz.az);
console.out(celest.zd+" "+celest.az);

// Convert them to equatorial coordinates
var equ = celest.toSky();
console.out(equ.ra+" "+equ.dec+" "+equ.time);

// And for a custom date
// var equ = celest.toSky(date);

JavaScript

Some JavaScript specials

// Note that in JavaScript the following two notations are identical

var object = {name:"Thomas", surname:"Mustermann" };

console.out(object.name);
console.out(object['name']);

Conversion

Sometimes conversion form s string to typed variables is necessary, e.g. when doing a calculation. This is done by:

// Java function to convert strings to float and integer
var f0 = parseFloat("12.5");
var i1 = parseInt("12");

// To convert an object to text you can do
var loc = Local(12, 13);
console.out(JSON.stringify(loc));

var obj = { test: 12.5 };
console.out(JSON.stringify(obj));