ggserver
Version:
GeoGate is an opensource GPS tracking server framework
255 lines (213 loc) • 9.53 kB
JavaScript
/*
* Copyright 2014 Fulup Ar Foll.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This Adaptator receive an NMEA position and return AIS target within a
* range of XXXNM. It is design for with OpenCPN and VirtualLoupDeMer, but
* should work with any chart system that can send it possition in NMEA on
* the same channel as it read AIS.
*
* Client may send either a GPRMC NMEA value or AIS/JSON data
*
* $GPRMC,171417,A,4737.1061,N,00245.6595,W,1.0969,58.544,080516,,*02
*
* {"aistype":24,"mmsi":163994842,"part":0,"shipname":"maitai"}
* {"aistype":24,"mmsi":163994842,"part":1,"callsign":"FG9196","cargo":95,"dimA":10,"dimB":2,"dimC":3,"dimD":3}
* {"aistype":18,"mmsi":163994842,"lon":-2.4529008333333334,"lat":47.273095000000003,"cog":1300,"sog":160,"dsc":false,"accuracy":true,"second":1}
*
*/
'use strict';
var Debug = require("../lib/_Debug");
var TrackerCmd = require("../lib/_TrackerCmd");
var TcpClient = require("../lib/_TcpClient");
// use localdev tree if available
var AisEncode;
if (process.env.GEOGATE !== 'dev')
AisEncode = require('ggencoder').AisEncode;
else AisEncode = require("../../encoder/ApiExport").AisEncode;
// use localdev tree if available
var NmeaDecode;
if (process.env.GEOGATE !== 'dev')
NmeaDecode = require('ggencoder').NmeaDecode;
else NmeaDecode = require("../../encoder/ApiExport").NmeaDecode;
// Adapter is an object own by a given device controller that handle data connection
function DevAdapter (controller) {
this.id = controller.svc;
this.uid = "//" + controller.svcopts.adapter + "/" + controller.svc + ":" + controller.svcopts.port;;
this.info = 'ocpvlm';
this.control = 'tcpsock'; // this wait for AIS clients to connect via TCP
this.gateway = controller.gateway;
this.debug = controller.svcopts.debug;// inherit debug from controller
this.controller= controller; // keep a link to device controller and TCP socket
this.count = 0; // index for incomming client
this.nbclient = 0;
this.distance = controller.svcopts.distance*1852 || 30*1852;
this.maxclient = controller.svcopts.maxclient || 30;
this.Debug (1,"uid=%s distance=%dNM", this.uid, this.distance/1852);
HookBackendEvent(this, controller.gateway.backend);
};
// Import debug method
DevAdapter.prototype.Debug = Debug;
// hook user event handler to receive a copy of messages
function HookBackendEvent (adapter, backend, socket) {
// Events successful process by tracker adapter
function EventAisData (aismsg, aisnmea){
adapter.Debug (9, "devid=%s aistype=%d nmea=%s", aismsg.mmsi, aismsg.aistype, aisnmea);
adapter.BroadcastAisData (aismsg, aisnmea);
};
backend.event.on("ais-data" ,EventAisData);
};
DevAdapter.prototype.DistanceMove = function (device, now) {
var old=device.stamp;
var R = 6371; // Radius of the earth in km
var dLat = (now.lat - old.lat) * Math.PI / 180; // deg2rad below
var dLon = (now.lon - old.lon) * Math.PI / 180;
var a =
0.5 - Math.cos(dLat)/2 +
Math.cos(old.lat * Math.PI / 180) * Math.cos(now.lat * Math.PI / 180) *
(1 - Math.cos(dLon))/2;
var d= R * 2 * Math.asin(Math.sqrt(a));
d= Math.round (d*1000);
this.Debug (9, "Distance devid:%s [%s] moved %dm", device.devid, device.name, d);
return (d); // return distance in Meter
};
DevAdapter.prototype.BroadcastAisData = function (aismsg, aisnmea) {
var aisOut;
// transform AIS string into a buffer linefeed ended
var msgbuf = new Buffer (aisnmea + "\n");
// if we have AIS TCP device let's send a copy of AISpos to each of them
for (var devid in this.gateway.activeClients) {
var device = this.gateway.activeClients[devid];
// if not tcpdevice with a known position ignore
if (!device.socket || !device.stamp) continue;
// if target is close enough from device send vessel position
if (aismsg.vlm || this.DistanceMove (device, aismsg) <= this.distance) try {
device.socket.write (msgbuf);
} catch (err) {
this.Debug (1, '### Hoops BroadcastPos lost aisdevice: %s [err=%s]', device.uid, err);
device.LogoutDev();
}
}
};
// we got a new JsonAisClient add it to client list for broadcast
DevAdapter.prototype.ClientConnect = function (socket) {
// protect from deny of service by overloading service
if (this.nbclient >= this.maxclient) {
this.Debug (3, 'MaxClient==%d Reached', this.nbclient);
socket.write ("VlmOpc Too Many Clients [please retry later]\n");
return -1;
}
this.nbclient ++;
socket.id=this.count ++;
socket.uid="opcvlm2ais://" + socket.remoteAddress +':'+ socket.remotePort;
this.Debug (4, "New client [%s]=%s", socket.id, socket.uid);
socket.device = new TcpClient (socket);
socket.lineidx = 0; // index within buffer
socket.linebuf = new Buffer (256); // intermediary buffer
socket.write ("VlmOpc Ready\n");
};
// aisproxy quit remove it from out list
DevAdapter.prototype.ClientQuit = function (socket) {
this.Debug (4, 'Quit aisproxy client: %s id=%j', socket.uid, socket.id);
this.nbclient --;
socket.device.LogoutDev ();
};
// browser talking, ignore data
DevAdapter.prototype.ParseLine = function(socket, line) {
this.Debug (4, '%s data=%s', socket.uid, line);
var data;
var device= socket.device;
// looks like NMEA packet
if (line[0] === '$') {
// implement a fake login for NMEA only client
if (!device.logged) {
data =
{devid: socket.id
,cmd : TrackerCmd.GetFrom.LOGIN
,name : socket.uid
};
device.ProcessData (data);
}
data= new NmeaDecode (line);
if (!data.valid) {
socket.write ("Adapter: [" + this.info + "] NMEA Invalid RCM position --> " + line + '\n');
return;
}
// send parsed data to unique device attached to NMEA adapter
data.cmd = TrackerCmd.GetFrom.TRACK;
device.ProcessData (data);
if (this.debug > 5) socket.write ('NMEA Posi OK\n');
// Might be AIS/JSON
} else {
try {
var aismsg = JSON.parse (line);
} catch(e) {
socket.write ("Adapter: [" + this.info + "] Invalid JSON position --> " + line + '\n');
return;
}
// Encode AIS/Json on AIS/NMEA binary
var aisnmea = new AisEncode (aismsg);
if (! aisnmea.valid) {
this.Debug (1, "Invalid JSON/AIS=%j", aismsg);
return;
}
// implement a fake login at 1st AIS packet
if (!device.logged) {
data =
{devid: aismsg.mmsi
,cmd : TrackerCmd.GetFrom.LOGIN
,name : socket.uid
};
device.ProcessData (data);
}
switch (aismsg.aistype) {
case 3:
case 18:
// Update Static and include last known position of client vessel
aismsg.cmd = TrackerCmd.GetFrom.TRACK;
device.ProcessData (aismsg);
this.BroadcastAisData (aismsg, aisnmea.nmea); // Simulate an AIS input to broacast position to other vessels.
if (this.debug > 5) socket.write ('AIS Posi OK\n');
break;
case 5:
case 24:
this.BroadcastAisData (aismsg, aisnmea.nmea); // Simulate an AIS input to broacast position to other vessels.
if (this.debug > 5) socket.write ('AIS Static OK\n');
break;
default:
// ignore any other data
return;
}
}
};
DevAdapter.prototype.ParseBuffer = function(socket, buffer) {
this.Debug (9, "request=[%s]", buffer);
// split buffer multiple lines if any and remove \r\n
for (var idx=0; idx < buffer.length; idx++) {
switch (buffer [idx]) {
case 0x0A: // new line \n
var status = this.ParseLine (socket, socket.linebuf.toString ('ascii',0, socket.lineidx));
socket.lineidx=0;
break;
case 0x0D: break; // cariage return \r
default:
socket.linebuf[socket.lineidx] = buffer [idx];
socket.lineidx++;
}
}
};
// This adapter does not send command
DevAdapter.prototype.SendCommand = function(device, action, arg1) {return (0);};
module.exports = DevAdapter; // http://openmymind.net/2012/2/3/Node-Require-and-Exports/