JavaScript
Aim
To provide a scriptable plugin that allows users to tailor the functioning of OpenCPN without getting into the complexity of writing a plugin.
How does it work?
The plugin presents a console window comprising a script pane and a results pane and some buttons. You enter your JavaScript in the script pane (or load it from a file) and click on the Run button. The script is compiled into byte code and executed and any result is displayed. At its simplest, enter, say
(4+8)/3
and the result 4 is displayed. But you could also enter, say
function fib(n) {
if (n == 0) { return 0; }
if (n == 1) { return 1; }
return fib(n-1) + fib(n-2);
}
function build(n) {
var res = [];
for (i = 0; i <n; i++) {
res.push(fib(i));
}
return(res.join(' '));
}
print("Fibonacci says: ", build(20), “\n");
Which displays
Fibonacci says: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
This illustrates how functions can be defined and called, including recursively. So we have a super calculator!
I have been developing interfaces (APIs) to OpenCPN and it is here that the fun starts. As an example, the following statement takes the supplied NMEA sentence, adds a checksum (replacing any existing one) and pushes it out over the OpenCPN connections
OCPNpushNMEA(“$OCRMB,A,0.000,L,,Yarmouth,5030.530,N,00120.030,W,
15.386,82.924,0.000,5030.530,S,00120.030,E,V");
A challenge has been how to arrange for the processing of OpenCPN events. I have developed a way of specifying a function to handle a particular event, so we can write, for example
OCPNonNMEAsentence(handleNMEA);
function handleNMEA(result){
if(result.OK){
print(“Received: “, result.value, “\n”);
}
else print(“Bad checksum\n”);
}
This listens out for the next NMEA sentence and displays it on receipt if the checksum is OK.
Structures and methods
JavaScript supports the use of structures and methods. I have created structures to represent positions, waypoints and routes. You can write something like:
myposition = new Position(50.234, 3.231);
// let’s change the longitude
myposition.longitude = -1.674;
print(“My position is “, myPosition.formatted, ‘\n”);
// displays //My position is 50º 14.040’N 001º 40.440’W
mypos.NMEA returns the string 5014.04,N,140.44,W, which is the very odd way positions are represented in NMEA sentences.
This structure includes methods, as illustrated here:
sentence = "$OCRMB,A,0.000,L,,UK-S:Y,5030.530,N,00120.030,W,15.386,82.924,
0.000,5030.530,S,00120.030,E,V,A*69";
myPosition.NMEAdecode(sentence,1); // decodes NMEA sentence and sets
print(mypos.formatted, “\n"); // displays 50º 30.530’N 001º 20.030’W
myPosition.latest(); // sets myPosition to the latest available from OpenCPN
print("My latest position is", myPosition.formatted, "\n");
// might display My latest position is 50º 23.50’N 001º 41.234’W
A simple application
As an illustrative application, I note that @arnolddemaa was having problems capturing magnetic variation from HDG sentences and inserting into RMC sentences. Here is a solution which captures the variation from HDG sentences and inserts it into any RMC sentences that do not already have the variation. It changes the RMC sender identity so that the originals can be blocked by OpenCPN filtering. It works by splitting the sentence into an array of fields. Note how it ends by setting up to process the next NMEA sentence.
// insert magnetic variation into RMC sentence
var vardegs = ""; // where we will save the latest variation
var varEW = "";
OCPNonNMEAsentence(processNMEA);
function processNMEA(result){
if (result.OK){
sentence = result.value;
switch (sentence.slice(3,6)){
case “HDG:" // capture variation
splut = sentence.split(",");
vardegs = splut[4]; varEW = splut[5];
break;
case "RMC":
splut = sentence.split(",");
if (splut[10] == "") { // if no variation already
splut[10] = vardegs; splut[11] = varEW;
splut[0] = “$JSRMC”;
}
result = splut.join(“,"); // put sentence together
OCPNpushNMEA(result);
break;
}
}
OCPNonNMEAsentence(processNMEA);
};
OpenCPN Messaging
I have implemented APIs to handle OpenCPN messages. Different messages can be directed to message-specific functions. For example:
//request route list
routeRequest = '{"mode": "Not track"}' // JSON needed to get route
OCPNonMessageName(handleRL, “OCPN_ROUTELIST_RESPONSE”);
OCPNsendMessage("OCPN_ROUTELIST_REQUEST", routeRequest);
function handleRL(routeListJS){ //handle receipt of the route list
routeList = JSON.parse(routeListJS);
// notice how easy it is to parse the JSON into a structure
// for illustration, here we extract the GUID of the first route
firstGUID = routeList[0].GUID;
}
Probing OpenCPN
I have found the plugin an excellent way of probing OpenCPN functionality, particularly as I can evolve the script in the light of what I get, iteratively. Wondering what a route list looks like? This will show you:
OCPNonMessageName(handleRL, “OCPN_ROUTELIST_RESPONSE”);
OCPNsendMessage("OCPN_ROUTELIST_REQUEST", JSON.stringify({"mode": "Not track”}));
function handleRL(routeListJS){ // handle route list response
print(routeListJS, “\n”);
}