Skip to content

Instantly share code, notes, and snippets.

@antipole2
Created April 6, 2021 11:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save antipole2/d12d22176dca90f5e9d89b6b57fafaf5 to your computer and use it in GitHub Desktop.
Save antipole2/d12d22176dca90f5e9d89b6b57fafaf5 to your computer and use it in GitHub Desktop.
Cause remove device to follow OpenCPN route navigation
// This script sends any active route out as WPT and RTE sentences so that iNavX shows the up-to-date active route.
// If the route or its routepoints are updated, iNavX will update to reflect this
// V2.0 - major rewrite to utilise latest plugin capabilities - much simplified
Position = require("Position"); // load the required constructors
Route = require("Route");
// here we define enduring variables we need outside the functions
var debug = false; // for debug prints
const prefix = "$JS"; // NMEA identifier
const repeatInterval = 20; // repeat after this number of seconds
var activeRoutepointName = ""; // to hold the active routepoint name, else ""
var activeRouteName = ""; // the name of the active route
var lastRoutePoint; // these two are needed to fix up RMB sentences and synthesise a BOD sentence…
var nextRoutePoint; // … which is needed so iNavX can work out which leg of the route we are on
var bearingToDest; // bearing to destination WP from where WP is activated
var fromHere; // position we are navigating from
var variation; // magnetic variation
var workingPosition = new Position(); // creates a working position object
var route = new Route(); // a working route object
OCPNonNMEAsentence(processNMEA); // start processing NMEA sentences
listenOut();
consoleHide(); // start listening
function listenOut(){
if (debug) print("Listening out\n");
// this is where we start the action
APRgpx = OCPNgetARPgpx(); // get Active Route Point as GPX
if (debug) print(APRgpx, "\n");
if (APRgpx.length > 0){
// we have an Active Route Point. Need to extract the routepoint name and save for later
routepointPart = /<name>.*<\/name>/.exec(APRgpx);
routepointName = routepointPart[0].slice(6, -7);
if (routepointName != activeRoutepointName){
// active routepoint has changed
bearingToDest = ""; // invalidate any previous info
lastRoutePoint = ""; nextRoutePoint = "";
activeRoutepointName = routepointName;
// we need to remember our position for navigation from here to next routepoint
here = OCPNgetNavigation();
fromHere = here.position; // saves the lat and long
variation = here.variation; //save magnetic variation
// now get the route leg
OCPNonMessageName(handleAR, "OCPN_ACTIVE_ROUTELEG_RESPONSE");
OCPNsendMessage("OCPN_ACTIVE_ROUTELEG_REQUEST");
}
else { // still on same leg - send same stuff
formSentences();
}
}
else {
// print("No active route routepoint in GPX\n");
activeRoutepointName = "";
}
onSeconds(listenOut, repeatInterval); // Do it again
}
function handleAR(activeRouteJS){
// we have received the active route, if there is one
activeRoute = JSON.parse(activeRouteJS);
if (debug) print("Active route:\n", activeRoute, "\n");
// NB the JSON returned creates an array
if (!activeRoute[0].error ){
// we have an active route
routeGUID = activeRoute[0].active_route_GUID;
route.get(routeGUID);
if (debug) print("Route is:\n", route, "\n");
activeRouteName = route.Name; // attribute in here has capitalised name!
lastRoutePoint = ""; nextRoutePoint = ""; // clear out previous route info
formSentences();
}
else throw("Active route point but active route returned had error");
}
function formSentences(){
// we work through the route points, sending out WPL sentences and noting which is the next and last
for (i in route.waypoints){ // push out the WPT sentences
workingPosition.latitude = route.waypoints[i].position.latitude; // convert from position as in JSOn to our way of doing it
workingPosition.longitude = route.waypoints[i].position.longitude;
sentence = prefix + "WPL" + "," + workingPosition.NMEA + "," + route.waypoints[i].markName;
if (debug) print(sentence, "\n");
OCPNpushNMEA(sentence);
if (route.waypoints[i].markName == activeRoutepointName){
activeWPL = sentence; // remember for later]
if (debug) print("Remembering:", activeWPL, "\n");
if (i > 0) {
// not the first
lastRoutePoint = route.waypoints[i-1].markName;
nextRoutePoint = route.waypoints[i].markName;
}
else {
// Still to reach first point
lastRoutePoint = ""; // no last point
nextRoutePoint = route.waypoints[i].markName;
}
}
}
// next we build an array of lists of routepoints to go in each RTE sentence as space permits
available = 79 - 12 - route.name.length - 3; // space available in RTE for routepoint names
spaceLeft = available;
var wpLists = [""]; // create our array of routepoint groups to go in an RTE sentence
listIndex = 0;
for (i in route.waypoints){
wpName = route.waypoints[i].markName;
wpNameLength = wpName.length;
if (spaceLeft >= wpNameLength){
wpLists[listIndex] += (wpName + ",");
spaceLeft -= (wpNameLength+1); //allow for comma
continue;
}
else{
// no more space in this one
wpLists[listIndex] = wpLists[listIndex].slice(0,-1); // drop trailing comma
wpLists.push(""); // new array member starts empty
listIndex += 1; spaceLeft = available;
i -= 1; // don't forget this last routepoint still to be fitted in next time
}
}
// we may have a trailing comma after last one
lastOne = wpLists[listIndex];
lastChar = lastOne.charAt(lastOne.length - 1);
if (lastChar == ",") lastOne = lastOne.slice(0,-1); // drop it
wpLists[listIndex] = lastOne;
arrayCount = wpLists.length;
for (i in wpLists) { // send out the RTE sentences
sentence = prefix + "RTE," + arrayCount + "," + (i*1+1) + ",c," + route.name + "," + wpLists[i];
if (debug) print(sentence, "\n");
OCPNpushNMEA(sentence);
}
// Now to send a BOD sentence
if (bearingToDest != ""){ // can only do this if we have acquired a bearing - else wait until we have it
bearingToDest = bearingToDest*1; // very odd - have to force this to be number for next bit
bearingToDestM = bearingToDest + variation;
sentence = prefix + "BOD" + "," + bearingToDest.toFixed(2) + "," + "T" + "," + bearingToDestM.toFixed(2) + "," + "M" + "," + nextRoutePoint + "," + lastRoutePoint;
if (debug) print(sentence, "\n");
OCPNpushNMEA(sentence);
if (activeWPL != "") OCPNpushNMEA(activeWPL); // repeat the active WPL fer th BOD, as per MacENC
}
}
function processNMEA(input){ // we need to un-abbreviate the routepoint name in APB sentences
if(input.OK && (activeRouteName != "")){ // only bother if have active routepoint
switch (input.value.slice(0,6)) {
case "$OCRMB":
{
if (debug) print("Have RMB with nextRoutePoint:", nextRoutePoint, "\n");
if (nextRoutePoint == "") break; // we cannot act until we have this
splut = input.value.split(",", 20);
shortWPname = splut[5];
if (activeRoutepointName.startsWith(shortWPname)){ // we check this really is the right one
splut[0] = prefix + "RMB"; // give it our branding
splut[4] = lastRoutePoint; // and add the origin WP name
splut[5] = nextRoutePoint; // the full destination WP Name
bearingToDest = splut[11]; // remember the bearing
result = splut.join(","); // put it back together
OCPNpushNMEA(result);
}
break;
}
case "$OCAPB":
{
// print("APB received\n");
splut = input.value.split(",", 20);
splut[0] = prefix + "APB"; // give it our branding
splut[10] = nextRoutePoint; // the full destination WP Name
result = splut.join(","); // put it back together
OCPNpushNMEA(result);
break;
}
default:
break;
}
}
OCPNonNMEAsentence(processNMEA); // Listen out for another NMEA sentence
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment