├── .gitignore ├── LICENSE ├── README.md ├── config.json.example ├── package.json ├── screenshots ├── latest.jpg └── track.jpg ├── server.js ├── static ├── bower.json └── script.js └── views └── index.jade /.gitignore: -------------------------------------------------------------------------------- 1 | /config.json 2 | /database 3 | /GeoServer.iml 4 | /static/bower_components 5 | /node_modules 6 | /.idea 7 | /.DS_Store 8 | # Created by .ignore support plugin (hsz.mobi) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Michael Kleinhenz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Tracker Service for TK102b style GPS Trackers 2 | 3 | This is a simple service backend and HTML query frontend for TK102b style GPS trackers like [this](http://www.gearbest.com/car-gps-tracker/pp_320284.html). 4 | 5 | ![Single Tracking Point](/screenshots/latest.jpg?raw=true "Single Tracking Point") 6 | 7 | ![Interval Tracking](/screenshots/track.jpg?raw=true "Interval Tracking") 8 | 9 | # Installing 10 | 11 | Unpack the distribution on your server. Then install the dependencies: 12 | 13 | ``` 14 | npm install 15 | cd static && bower install 16 | ``` 17 | 18 | You will need node, npm and bower already installed. Use Google on how to install these. 19 | 20 | # Launching the service 21 | 22 | Set the configuration parameters in config.json (an example is provided). You can get a Google Maps API Key 23 | from the Google Developers Console (use Google). Make sure you have enabled the "Maps API for JavaScript" for 24 | your API key. Then launch the server with 25 | 26 | ``` 27 | node server.js 28 | ``` 29 | 30 | Make sure the `httpPort` and `trackerPort` are accessible from outside your machine. Then configure 31 | the GPS tracker to use the service. Send the following SMS messages to the tracker (wait for a confirmation 32 | SMS after each command), replacing `` with your tracker's password ("123456" on new devices): 33 | 34 | ``` 35 | begin 36 | ``` 37 | 38 | This initializes the tracker. 39 | 40 | ``` 41 | timezone 2 42 | ``` 43 | 44 | Replace `2` with `1` for winter time. 45 | 46 | ``` 47 | admin 48 | ``` 49 | 50 | Make **absolutely sure** you use the correct phone number like it is transmitted by your phone carrier. Test this by 51 | calling yourself and note the displayed number. The value sometimes starts with the country code (for example `+49`), 52 | sometimes just the number is transmitted. 53 | 54 | ``` 55 | adminip 56 | ``` 57 | 58 | This configures the IP address and port of your tracking server. 59 | 60 | ``` 61 | gprs 62 | ``` 63 | 64 | This switches from SMS reporting to GPRS reporting to the IP set above. 65 | 66 | ``` 67 | t001m***n 68 | ``` 69 | 70 | This sets the frequency of tracker messages to 1 minute. Change the expression to any interval you like. Refer to the tracker 71 | documentation on the format. It is similar to the crontab format. 72 | 73 | The last command starts the tracking. If the tracker's battery goes dead or the server crashes or is unavailable, the 74 | tracker might give up on sending tracking info. In this case, send the interval message again to restart the tracking. 75 | 76 | # Accessing the Frontend 77 | 78 | The GPS tracker will start to send tracking data messages to the service. You can see the last received tracking message by 79 | visiting your server on the `httpPort` port. The frontend provides multi tracker support and the feature to see the latest 80 | as well as interval tracking information (plotted as a polyline on the map with the messages as nodes). -------------------------------------------------------------------------------- /config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "username": "admin", 3 | "password": "admin", 4 | "trackerPort": 8844, 5 | "httpPort": 8080, 6 | "databasePath": "database", 7 | "googleMapsAPIKey": "APIKEY" 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tk102b-server", 3 | "version": "1.0.0", 4 | "private": true, 5 | "devDependencies": {}, 6 | "dependencies": { 7 | "basic-auth": "^1.0.3", 8 | "express": "^4.13.4", 9 | "jade": "^1.11.0", 10 | "nedb": "^1.8.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /screenshots/latest.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelkleinhenz/tk102b-server/e7f789d0b0f6bc1849d7782fc911acb3b3aad622/screenshots/latest.jpg -------------------------------------------------------------------------------- /screenshots/track.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelkleinhenz/tk102b-server/e7f789d0b0f6bc1849d7782fc911acb3b3aad622/screenshots/track.jpg -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 2 | var config = require(process.env.CONFIGFILE || "./config.json"); 3 | 4 | var net = require('net'); 5 | var Datastore = require('nedb'); 6 | 7 | var db = new Datastore({ filename: config.databasePath, autoload: true }); 8 | 9 | var server = net.createServer(function(socket) { 10 | 11 | console.log(new Date() + "Client connected: " + socket.remoteAddress); 12 | 13 | socket.on('data', function (data) { 14 | var re = /\(([0-9]{12})BR00([0-9]{2})([0-9]{2})([0-9]{2})([AV])([0-9]{2})([0-9]{2}\.[0-9]{4})([NS])([0-9]{3})([0-9]{2}\.[0-9]{4})([EW])([0-9]{3}\.[0-9])([0-9]{2})([0-9]{2})([0-9]{3}\.[0-9]{12}L[0-9]{8}).*\)/; 15 | var m; 16 | if ((m = re.exec(data)) !== null) { 17 | if (m.index === re.lastIndex) { 18 | re.lastIndex++; 19 | } 20 | var doc = { 21 | type: "trackerinfo", 22 | timestamp: new Date(), 23 | trackerId: m[1], 24 | trackerDate: new Date("20" + m[2] + "." + m[3] + "." + m[4] + " " + m[13] + ":" + m[14]), 25 | trackingState: m[5], 26 | latitudeDegree: parseInt(m[6]), 27 | latitudePoint: parseFloat(m[7]), 28 | latitudeFlag: m[8], 29 | longitudeDegree: parseInt(m[9]), 30 | longitudePoint: parseFloat(m[10]), 31 | longitudeFlag: m[11], 32 | speed: parseFloat(m[10]), 33 | origData: m[0], 34 | unknown: m[15] 35 | }; 36 | console.log(doc.timestamp + " - " + doc.origData); 37 | db.insert(doc, function (err, newDoc) { 38 | if (err) 39 | console.log("Database Error: " + err.message); 40 | }); 41 | } 42 | }); 43 | 44 | socket.on('close', function () { 45 | console.log(new Date() + " - Client disconnected: " + socket.remoteAddress); 46 | }); 47 | 48 | socket.on('error', function (err) { 49 | console.log(new Date() + " - Error: " + socket.remoteAddress + " - " + err.message); 50 | }); 51 | 52 | }); 53 | 54 | server.listen(config.trackerPort, '0.0.0.0'); 55 | 56 | var express = require('express'); 57 | var basicAuth = require('basic-auth'); 58 | var app = express(); 59 | var router = express.Router(); 60 | 61 | app.set('view engine', 'jade'); 62 | app.use(express.static('static')); 63 | 64 | var auth = function (req, res, next) { 65 | var user = basicAuth(req); 66 | if (!user || !user.name || !user.pass) { 67 | res.set('WWW-Authenticate', 'Basic realm=Authorization Required'); 68 | res.sendStatus(401); 69 | } else 70 | if (user && user.name === config.username && user.pass === config.password) { 71 | next(); 72 | } else { 73 | res.set('WWW-Authenticate', 'Basic realm=Authorization Required'); 74 | res.sendStatus(401); 75 | } 76 | }; 77 | 78 | router.get('/latest', auth, function(req, res) { 79 | db.find({ type: "trackerinfo" }).sort({ timestamp: -1 }).limit(1).exec(function (err, docs) { 80 | if (err) 81 | res.send(500, err.message); 82 | else 83 | if (docs[0]) 84 | res.json(docs[0]); 85 | else 86 | res.send(404); 87 | }); 88 | }); 89 | 90 | router.get('/latest/:trackerId', auth, function(req, res) { 91 | db.find({ $and: [ { trackerId: req.param("trackerId") }, { type: "trackerinfo" } ]}).sort({ timestamp: -1 }).limit(1).exec(function (err, docs) { 92 | if (err) 93 | res.send(500, err.message); 94 | else 95 | if (docs[0]) 96 | res.json(docs[0]); 97 | else 98 | res.send(404); 99 | }); 100 | }); 101 | 102 | router.get('/range/:trackerId/:start/:end', auth, function(req, res) { 103 | var tsS = new Date(parseInt(req.param("start"))); 104 | var tsE = new Date(parseInt(req.param("end"))); 105 | db.find({ $and: [{ trackerId: req.param("trackerId") }, { trackerDate: { $gte: tsS } }, { trackerDate: { $lte: tsE } }] }).sort({ trackerDate: 1 }).exec(function (err, docs) { 106 | if (err) 107 | res.send(500, err.message); 108 | else 109 | if (docs) 110 | res.json(docs); 111 | else 112 | res.send(404); 113 | }); 114 | }); 115 | 116 | router.get('/trackerlist', auth, function(req, res) { 117 | db.find({}, { trackerId: 1 }).exec(function (err, docs) { 118 | if (err) 119 | res.send(500, err.message); 120 | else 121 | if (docs) { 122 | var list = []; 123 | for (var i=0; i0) { 74 | for (var i=0; i'+ 105 | ''+ 106 | '
'+ 107 | '

' + 108 | 'Seen: ' + new Date(data.trackerDate).toLocaleString() + '
' + 109 | 'Database Timestamp: ' + new Date(data.timestamp).toLocaleString() + '
' + 110 | 'Tracker Id: ' + data.trackerId + '
' + 111 | '

'+ 112 | '
'+ 113 | ''; 114 | var infowindow = new google.maps.InfoWindow({ 115 | content: contentString 116 | }); 117 | if (latestMarker!=null) 118 | latestMarker.setMap(null); 119 | latestMarker = new google.maps.Marker({ 120 | position: myLatLng, 121 | map: map, 122 | title: new Date(data.trackerDate).toLocaleString() 123 | }); 124 | latestMarker.addListener('click', function() { 125 | infowindow.open(map, latestMarker); 126 | }); 127 | setTimeout(function() { 128 | infowindow.open(map, latestMarker); 129 | }, 1000); 130 | } 131 | }); 132 | } 133 | 134 | function geoRound(inValue) { 135 | return Math.round(inValue * 10000000) / 10000000 136 | } 137 | 138 | jQuery(function() { 139 | jQuery('#dtpStart').datetimepicker({ 140 | onShow: function (ct) { 141 | this.setOptions({ 142 | maxDate: jQuery('#dtpEnd').val() ? jQuery('#dtpEnd').val() : false 143 | }) 144 | }, 145 | timepicker: true 146 | }); 147 | jQuery('#dtpEnd').datetimepicker({ 148 | onShow: function (ct) { 149 | this.setOptions({ 150 | minDate: jQuery('#dtpStart').val() ? jQuery('#dtpStart').val() : false 151 | }) 152 | }, 153 | timepicker: true 154 | }); 155 | 156 | $.get("/api/trackerlist", function(data) { 157 | for (var i=0; i" + data[i] + "") 159 | }); 160 | 161 | }); 162 | -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Tracker Info 5 | script(type="text/javascript" src="bower_components/jquery/dist/jquery.min.js") 6 | script(type="text/javascript" src="bower_components/datetimepicker/build/jquery.datetimepicker.full.min.js") 7 | link(type="text/css" rel="stylesheet" href="bower_components/datetimepicker/jquery.datetimepicker.css") 8 | style(type="text/css"). 9 | html, body { height: 100%; margin: 0; padding: 0; font-family: sans-serif} 10 | #map { height: 90%; } 11 | body(style="width:100%;height:100%;margin:0;padding:0") 12 | div(style="height:10%;display:table-row") 13 | div(style="padding:1em;display:table-cell;width:60%") 14 | span Start:  15 | input(id="dtpStart" type="text") 16 | span       17 | span End:  18 | input(id="dtpEnd" type="text") 19 | span       20 | span Tracker:  21 | select(class="trackerId" id="rangeTracker") 22 | span    23 | button(id="plot") Show 24 | div(style="padding:1em;display:table-cell;width:40%;text-align:right") 25 | span Show latest from tracker:  26 | select(class="trackerId" id="showTracker") 27 | span    28 | button(id="show") Show 29 | div(id="map") 30 | script(type="text/javascript" src="script.js") 31 | script(async defer src="https://maps.googleapis.com/maps/api/js?key=" + apiKey + "&callback=initMap") 32 | --------------------------------------------------------------------------------