gpsd-fake
Version:
Fakes a instance of gpsd and gives random data to clients.
226 lines (192 loc) • 8.46 kB
JavaScript
/*
* Copyright 2016 Lukas Metzger <developer@lukas-metzger.com>.
*
* 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.
*/
var net = require('net');
var fs = require('fs');
var path = require('path');
var Vector = require('victor');
module.exports = function (options) {
//Build the config based on user options and defaults
var defaultConfig = {
port: 2947,
configFile: path.resolve(__dirname, "config.json")
}
var config = Object.assign({}, defaultConfig, options)
//Validation
if (!fs.existsSync(config.configFile)) {
console.error('The config file "' + config.configFile + '" does not exist.\r\nAborting.')
return
}
//Inject the config from the config file
var fileConfig = require(path.resolve(config.configFile))
config = Object.assign({}, config, fileConfig)
//Get tmp file if supplied
var backup = {}
if (config.tmpFile) {
//Resolve relative tmpfile path and create empty file if not exists
config.tmpFile = path.resolve(config.tmpFile)
if (!fs.existsSync(config.tmpFile)) {
fs.writeFileSync(config.tmpFile, "{}")
}
backup = require(config.tmpFile)
}
////////////////////////////////////////////////////////////////////////////////
//Socket handling
////////////////////////////////////////////////////////////////////////////////
//List with active sockets
var sockets = [];
//Create server
var server = net.createServer(function(socket) {
//Add new socket to socket list
sockets.push(socket);
//If socket is closed remove from list
socket.on("close", function() {
var index = sockets.indexOf(socket);
sockets.splice(index, 1);
});
//On error remove from list
socket.on("error", function() {
var index = sockets.indexOf(socket);
sockets.splice(index, 1);
});
//If some data is received assume it the watch enable command and send some responses
socket.on("data", function() {
socket.write('{"class":"DEVICES","devices":[{"class":"DEVICE","path":"/dev/pts/4","driver":"NMEA0183","activated":"2016-08-20T10:00:12.934Z","flags":1,"native":0,"bps":4800,"parity":"N","stopbits":1,"cycle":1.00}]}' + "\r\n");
socket.write('{"class":"WATCH","enable":true,"json":true,"nmea":false,"raw":0,"scaled":false,"timing":false,"split24":false,"pps":false}' + "\r\n");
socket.write('{"class":"DEVICE","path":"/dev/pts/4","driver":"NMEA0183","activated":"2016-08-20T10:12:11.296Z","native":0,"bps":4800,"parity":"N","stopbits":1,"cycle":1.00}' + "\r\n");
});
socket.write('{"class":"VERSION","release":"3.16","rev":"3.16","proto_major":3,"proto_minor":11}' + "\r\n");
});
//Tell user that new client connected
server.on("connection", function() {
console.log('New gpsd client connected')
})
//Listen to Port
server.listen(config.port, function () {
console.log('Fake gpsd server listening at', config.port)
});
////////////////////////////////////////////////////////////////////////////////
//GPS Simulation
////////////////////////////////////////////////////////////////////////////////
var movement;
var position;
var goal;
var direction;
//1km long latitude
var distancePerSecond = 0.011134839 * config.speed / 3600;
//Get initial movement vector
if(backup.movement) {
movement = Vector.fromArray(backup.movement);
} else {
movement = new Vector(distancePerSecond, 0).rotateDeg(360*Math.random());
}
//Get initial position
if(backup.position) {
position = Vector.fromArray(backup.position);
} else {
position = new Vector();
position.x = (config.area.lat.max-config.area.lat.min)*Math.random() + config.area.lat.min;
position.y = (config.area.lon.max-config.area.lon.min)*Math.random() + config.area.lon.min;
}
//Get initial goal
if(backup.goal) {
goal = Vector.fromArray(backup.goal);
} else {
goal = new Vector();
goal.x = (config.area.lat.max-config.area.lat.min)*Math.random() + config.area.lat.min;
goal.y = (config.area.lon.max-config.area.lon.min)*Math.random() + config.area.lon.min;
}
//Get initial direction
if(backup.direction) {
direction = backup.direction;
} else {
direction = Math.random() > 0.5 ? 1 : -1;
}
setInterval(function() {
//Rotate random ammount in current direction
movement.rotateDeg(Math.pow(Math.random(), 4) * 1 * direction);
//Add current movement vector to position
position.add(movement);
//If goal is reached, use new goal
if(position.distance(goal) < distancePerSecond*300) {
goal = new Vector();
goal.x = (config.area.lat.max-config.area.lat.min)*Math.random() + config.area.lat.min;
goal.y = (config.area.lon.max-config.area.lon.min)*Math.random() + config.area.lon.min;
}
//If course is very wrong correct it
if(Math.acos(goal.clone().subtract(position).dot(movement)) > Math.PI/2) {
var newMov = goal.clone().subtract(position).normalize().multiply(new Vector(distancePerSecond, distancePerSecond));
newMov.rotateDeg(Math.pow(Math.random(), 4) * -5 * direction);
movement = newMov;
}
//Generate data package
var data = {
class: "TPV",
device: "/dev/pts/4",
mode: 3,
time: new Date().toISOString(),
ept: 0.005,
lat: position.x,
lon: position.y,
alt: 1000,
epx: 2.234,
epy: 2.454,
epv: 5.345,
track: movement.verticalAngleDeg(),
speed: config.speed/3.6,
climb: 0.000
};
//Send messages to each socket
sockets.forEach(function(socket) {
//Send TPV data
socket.write(JSON.stringify(data) + "\r\n");
//Send some sattelite state
socket.write('{"class":"SKY","device":"/dev/pts/4","xdop":0.55,"ydop":0.72,"vdop":0.90,"tdop":1.00,"hdop":1.00,"gdop":2.09,"pdop":1.30,"satellites":[{"PRN":82,"el":37,"az":123,"ss":30,"used":false},{"PRN":67,"el":46,"az":40,"ss":29,"used":false},{"PRN":68,"el":68,"az":178,"ss":28,"used":false},{"PRN":74,"el":7,"az":296,"ss":0,"used":false},{"PRN":75,"el":9,"az":344,"ss":0,"used":false},{"PRN":69,"el":18,"az":203,"ss":0,"used":false},{"PRN":84,"el":23,"az":314,"ss":0,"used":false},{"PRN":83,"el":0,"az":0,"ss":0,"used":false}]}' + "\r\n");
});
//Backup data to disk
if(config.tmpFile) {
fs.writeFileSync(config.tmpFile, JSON.stringify({
position: position.toArray(),
movement: movement.toArray(),
goal: goal.toArray(),
direction: direction
}));
}
}, 1000);
//Every 30 seconds
setInterval(function() {
//change current direction with chance of 80 percent
if(Math.random() > 0.7) {
var random = Math.random();
if(random < 0.2) {
direction = -1;
} else if(random < 0.4) {
direction = 1;
} else {
var newMov = goal.clone().subtract(position).normalize().multiply(new Vector(distancePerSecond, distancePerSecond));
newMov.rotateDeg(Math.pow(Math.random(), 4) * -5 * direction);
movement = newMov;
}
}
//turn randomly
if(Math.random() > 0.95) {
if(Math.random() < 0.5) {
movement.rotateDeg(-90);
} else {
movement.rotateDeg(90);
}
}
}, 30000);
}