├── .gitignore ├── .jscsrc ├── CODE_OF_CONDUCT.md ├── LICENSE ├── examples └── simple.js ├── index.js ├── lib ├── adapters │ ├── gt02a.js │ ├── gt06.js │ ├── tk103.js │ └── tk510.js ├── device.js ├── functions.js └── server.js ├── package-lock.json ├── package.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "airbnb", 3 | "excludeFiles": ["node_modules/**", "bower_components/**"], 4 | "maxErrors": 1000 5 | } 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at gonzalo@freshworkstudio.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Freshwork Studio 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 | -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | //var gps = require("gps-tracking"); 2 | var gps = require('../index'); 3 | 4 | var options = { 5 | debug: true, 6 | port: 8090, 7 | device_adapter: 'TK103B' 8 | } 9 | 10 | var server = gps.server(options, function (device, connection) { 11 | 12 | device.on('login_request', function (device_id, msg_parts) { 13 | // Some devices sends a login request before transmitting their position 14 | // Do some stuff before authenticate the device... 15 | 16 | // Accept the login request. You can set false to reject the device. 17 | this.login_authorized(true) 18 | 19 | }) 20 | 21 | //PING -> When the gps sends their position 22 | device.on('ping', function (data) { 23 | 24 | //After the ping is received, but before the data is saved 25 | //console.log(data); 26 | return data 27 | 28 | }); 29 | 30 | }); 31 | 32 | server.setDebug(true); 33 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/server'); -------------------------------------------------------------------------------- /lib/adapters/gt02a.js: -------------------------------------------------------------------------------- 1 | /* Original code: https://github.com/cnberg/gps-tracking-nodejs/blob/master/lib/adapters/gt02a.js */ 2 | f = require('../functions'); 3 | 4 | exports.protocol = 'GT02A'; 5 | exports.model_name = 'GT02A'; 6 | exports.compatible_hardware = ['GT02A/supplier']; 7 | 8 | var adapter = function (device) { 9 | if (!(this instanceof adapter)) { 10 | return new adapter(device); 11 | } 12 | 13 | this.format = {'start': '(', 'end': ')', 'separator': ''}; 14 | this.device = device; 15 | this.__count = 1; 16 | 17 | /******************************************* 18 | PARSE THE INCOMING STRING FROM THE DECIVE 19 | You must return an object with a least: device_id, cmd and type. 20 | return device_id: The device_id 21 | return cmd: command from the device. 22 | return type: login_request, ping, etc. 23 | *******************************************/ 24 | this.parse_data = function (data) { 25 | data = this.bufferToHexString(data); 26 | console.log(data); 27 | var parts = { 28 | 'start': data.substr(0, 4) 29 | }; 30 | 31 | if (parts['start'] == '6868') { 32 | parts['length'] = parseInt(data.substr(4, 2), 16); 33 | parts['finish'] = data.substr(parts['length'] * 2 + 6, 4); 34 | 35 | if (parts['finish'] != '0d0a') { 36 | throw 'finish code incorrect!'; 37 | } 38 | parts['power'] = parseInt(data.substr(6, 2), 16); 39 | parts['gsm'] = parseInt(data.substr(8, 2), 16); 40 | parts['device_id'] = data.substr(10, 16); 41 | parts['count'] = data.substr(26, 4); 42 | parts['protocal_id'] = data.substr(30, 2); 43 | 44 | parts['data'] = data.substr(32, parts['length']); 45 | 46 | if (parts['protocal_id'] == '1a') { 47 | parts.cmd = 'login_request'; 48 | parts.action = 'login_request'; 49 | } else if (parts['protocal_id'] == '10') { 50 | parts.cmd = 'ping'; 51 | parts.action = 'ping'; 52 | } else { 53 | parts.cmd = 'noop'; 54 | parts.action = 'noop'; 55 | } 56 | } else if (parts['start'] == '7979') { 57 | parts['length'] = parseInt(data.substr(4, 4), 16); 58 | parts['finish'] = data.substr(8 + parts['length'] * 2, 4); 59 | 60 | parts['protocal_id'] = data.substr(8, 2); 61 | 62 | if (parts['finish'] != '0d0a') { 63 | throw 'finish code incorrect!'; 64 | } 65 | 66 | if (parts['protocal_id'] == '94') { 67 | parts['device_id'] = ''; 68 | parts.cmd = 'noop'; 69 | parts.action = 'noop'; 70 | } 71 | 72 | } else if (parts['start'] == '7878') { 73 | parts['length'] = parseInt(data.substr(4, 2), 16); 74 | parts['finish'] = data.substr(6 + parts['length'] * 2, 4); 75 | 76 | parts['protocal_id'] = data.substr(6, 2); 77 | 78 | if (parts['finish'] != '0d0a') { 79 | throw 'finish code incorrect!'; 80 | } 81 | 82 | if (parts['protocal_id'] == '8a') { 83 | parts['device_id'] = ''; 84 | parts.cmd = 'clock'; 85 | parts.action = 'clock'; 86 | } else { 87 | parts['device_id'] = ''; 88 | parts.cmd = 'noop'; 89 | parts.action = 'noop'; 90 | } 91 | } 92 | return parts; 93 | }; 94 | this.bufferToHexString = function (buffer) { 95 | var str = ''; 96 | for (var i = 0; i < buffer.length; i++) { 97 | if (buffer[i] < 16) { 98 | str += '0'; 99 | } 100 | str += buffer[i].toString(16); 101 | } 102 | return str; 103 | }; 104 | this.authorize = function () { 105 | this.send_comand('\u0054\u0068\u001a\u000d\u000a'); 106 | }; 107 | this.zeroPad = function (nNum, nPad) { 108 | return ('' + (Math.pow(10, nPad) + nNum)).slice(1); 109 | }; 110 | this.synchronous_clock = function () { 111 | var d = new Date(); 112 | 113 | var str = (d.getFullYear().toString().substr(2, 2)) + 114 | (this.zeroPad(d.getMonth() + 1, 2).toString()) + 115 | (this.zeroPad(d.getDate(), 2).toString()) + 116 | (this.zeroPad(d.getHours(), 2).toString()) + 117 | (this.zeroPad(d.getMinutes(), 2).toString()) + 118 | (this.zeroPad(d.getSeconds(), 2).toString()) + 119 | (this.zeroPad(this.__count, 4).toString()); 120 | 121 | this.__count++; 122 | 123 | var crc = require('/usr/lib/node_modules/crc/lib/index.js'); 124 | var crcResult = f.str_pad(crc.crc16(str).toString(16), 4, '0'); 125 | 126 | var buff = new Buffer(str + crcResult, 'hex'); 127 | this.send_comand('7878', buff); 128 | }; 129 | this.run_other = function (cmd, msg_parts) { 130 | switch (cmd) { 131 | case 'BP00': //Handshake 132 | this.device.send(this.format_data(this.device.uid + 'AP01HSO')); 133 | break; 134 | } 135 | }; 136 | 137 | this.request_login_to_device = function () { 138 | //@TODO: Implement this. 139 | }; 140 | 141 | this.receive_alarm = function (msg_parts) { 142 | //@TODO: implement this 143 | //My device have no support of this feature 144 | return alarm; 145 | }; 146 | 147 | this.dex_to_degrees = function (dex) { 148 | return parseInt(dex, 16) / 1800000; 149 | }; 150 | 151 | this.get_ping_data = function (msg_parts) { 152 | var str = msg_parts.data; 153 | 154 | var data = { 155 | 'date': str.substr(0, 12), 156 | 'latitude': this.dex_to_degrees(str.substr(12, 8)), 157 | 'longitude': this.dex_to_degrees(str.substr(20, 8)), 158 | 'speed': parseInt(str.substr(28, 2), 16), 159 | 'orientation': str.substr(30, 4), 160 | }; 161 | 162 | res = { 163 | latitude: data.latitude, 164 | longitude: data.longitude, 165 | speed: data.speed, 166 | orientation: data.orientation 167 | }; 168 | return res; 169 | }; 170 | 171 | /* SET REFRESH TIME */ 172 | this.set_refresh_time = function (interval, duration) { 173 | }; 174 | 175 | /* INTERNAL FUNCTIONS */ 176 | 177 | this.send_comand = function (cmd, data) { 178 | var msg = [cmd, data]; 179 | this.device.send(this.format_data(msg)); 180 | }; 181 | this.format_data = function (params) { 182 | /* FORMAT THE DATA TO BE SENT */ 183 | var str = this.format.start; 184 | if (typeof(params) == 'string') { 185 | str += params; 186 | } else if (params instanceof Array) { 187 | str += params.join(this.format.separator); 188 | } else { 189 | throw 'The parameters to send to the device has to be a string or an array'; 190 | } 191 | str += this.format.end; 192 | return str; 193 | }; 194 | }; 195 | exports.adapter = adapter; -------------------------------------------------------------------------------- /lib/adapters/gt06.js: -------------------------------------------------------------------------------- 1 | /* Original code: https://github.com/cnberg/gps-tracking-nodejs/blob/master/lib/adapters/gt06.js */ 2 | f = require('../functions'); 3 | crc = require('crc'); 4 | 5 | exports.protocol = 'GT06'; 6 | exports.model_name = 'GT06'; 7 | exports.compatible_hardware = ['GT06/supplier']; 8 | 9 | var adapter = function (device) { 10 | if (!(this instanceof adapter)) { 11 | return new adapter(device); 12 | } 13 | 14 | this.format = {'start': '(', 'end': ')', 'separator': ''}; 15 | this.device = device; 16 | this.__count = 1; 17 | 18 | /******************************************* 19 | PARSE THE INCOMING STRING FROM THE DECIVE 20 | You must return an object with a least: device_id, cmd and type. 21 | return device_id: The device_id 22 | return cmd: command from the device. 23 | return type: login_request, ping, etc. 24 | *******************************************/ 25 | this.parse_data = function (data) { 26 | data = data.toString('hex'); 27 | var parts = { 28 | 'start': data.substr(0, 4) 29 | }; 30 | 31 | if (parts['start'] == '7878') { 32 | parts['length'] = parseInt(data.substr(4, 2), 16); 33 | parts['finish'] = data.substr(6 + parts['length'] * 2, 4); 34 | 35 | parts['protocal_id'] = data.substr(6, 2); 36 | 37 | if (parts['finish'] != '0d0a') { 38 | throw 'finish code incorrect!'; 39 | } 40 | 41 | if (parts['protocal_id'] == '01') { 42 | parts['device_id'] = data.substr(8, 16); 43 | parts.cmd = 'login_request'; 44 | parts.action = 'login_request'; 45 | } else if (parts['protocal_id'] == '12') { 46 | parts['device_id'] = ''; 47 | parts['data'] = data.substr(8, parts['length'] * 2); 48 | parts.cmd = 'ping'; 49 | parts.action = 'ping'; 50 | } else if (parts['protocal_id'] == '13') { 51 | parts['device_id'] = ''; 52 | parts.cmd = 'heartbeat'; 53 | parts.action = 'heartbeat'; 54 | } else if (parts['protocal_id'] == '16' || parts['protocal_id'] == '18') { 55 | parts['device_id'] = ''; 56 | parts['data'] = data.substr(8, parts['length'] * 2); 57 | parts.cmd = 'alert'; 58 | parts.action = 'alert'; 59 | } else { 60 | parts['device_id'] = ''; 61 | parts.cmd = 'noop'; 62 | parts.action = 'noop'; 63 | } 64 | } else { 65 | parts['device_id'] = ''; 66 | parts.cmd = 'noop'; 67 | parts.action = 'noop'; 68 | } 69 | return parts; 70 | }; 71 | this.authorize = function () { 72 | //this.device.send("\u0078\u0078\u0005\u0001\u0000\u0001\u00d9\u00dc\u000d\u000a"); 73 | //return ; 74 | var length = '05'; 75 | var protocal_id = '01'; 76 | var serial = f.str_pad(this.__count, 4, 0); 77 | 78 | var str = length + protocal_id + serial; 79 | 80 | this.__count++; 81 | 82 | var crcResult = f.str_pad(crc.crc16(str).toString(16), 4, '0'); 83 | 84 | var buff = new Buffer('7878' + str + crcResult + '0d0a', 'hex'); 85 | var buff = new Buffer('787805010001d9dc0d0a', 'hex'); 86 | //发送原始数据 87 | this.device.send(buff); 88 | }; 89 | this.zeroPad = function (nNum, nPad) { 90 | return ('' + (Math.pow(10, nPad) + nNum)).slice(1); 91 | }; 92 | this.synchronous_clock = function (msg_parts) { 93 | 94 | }; 95 | this.receive_heartbeat = function (msg_parts) { 96 | var buff = new Buffer('787805130001d9dc0d0a', 'hex'); 97 | this.device.send(buff); 98 | }; 99 | this.run_other = function (cmd, msg_parts) { 100 | }; 101 | 102 | this.request_login_to_device = function () { 103 | //@TODO: Implement this. 104 | }; 105 | 106 | this.receive_alarm = function (msg_parts) { 107 | console.log(msg_parts); 108 | var str = msg_parts.data; 109 | 110 | var data = { 111 | 'date': str.substr(0, 12), 112 | 'set_count': str.substr(12, 2), 113 | 'latitude_raw': str.substr(14, 8), 114 | 'longitude_raw': str.substr(22, 8), 115 | 'latitude': this.dex_to_degrees(str.substr(14, 8)), 116 | 'longitude': this.dex_to_degrees(str.substr(22, 8)), 117 | 'speed': parseInt(str.substr(30, 2), 16), 118 | 'orientation': str.substr(32, 4), 119 | 'lbs': str.substr(36, 18), 120 | 'device_info': f.str_pad(parseInt(str.substr(54, 2)).toString(2), 8, 0), 121 | 'power': str.substr(56, 2), 122 | 'gsm': str.substr(58, 2), 123 | 'alert': str.substr(60, 4), 124 | }; 125 | 126 | data['power_status'] = data['device_info'][0]; 127 | data['gps_status'] = data['device_info'][1]; 128 | data['charge_status'] = data['device_info'][5]; 129 | data['acc_status'] = data['device_info'][6]; 130 | data['defence_status'] = data['device_info'][7]; 131 | console.log('alert'); 132 | console.log(data); 133 | }; 134 | 135 | this.dex_to_degrees = function (dex) { 136 | return parseInt(dex, 16) / 1800000; 137 | }; 138 | 139 | this.get_ping_data = function (msg_parts) { 140 | var str = msg_parts.data; 141 | 142 | var data = { 143 | 'date': str.substr(0, 12), 144 | 'set_count': str.substr(12, 2), 145 | 'latitude_raw': str.substr(14, 8), 146 | 'longitude_raw': str.substr(22, 8), 147 | 'latitude': this.dex_to_degrees(str.substr(14, 8)), 148 | 'longitude': this.dex_to_degrees(str.substr(22, 8)), 149 | 'speed': parseInt(str.substr(30, 2), 16), 150 | 'orientation': str.substr(32, 4), 151 | 'lbs': str.substr(36, 16), 152 | }; 153 | 154 | /* 155 | "device_info" : f.str_pad(parseInt(str.substr(54,2)).toString(2), 8, 0), 156 | "power" : str.substr(56,2), 157 | "gsm" : str.substr(58,2), 158 | "alert" : str.substr(60,4), 159 | data['power_status'] = data['device_info'][0]; 160 | data['gps_status'] = data['device_info'][1]; 161 | data['charge_status'] = data['device_info'][5]; 162 | data['acc_status']= data['device_info'][6]; 163 | data['defence_status'] = data['device_info'][7]; 164 | */ 165 | 166 | console.log(data); 167 | 168 | res = { 169 | latitude: data.latitude, 170 | longitude: data.longitude, 171 | speed: data.speed, 172 | orientation: data.orientation 173 | }; 174 | return res; 175 | }; 176 | 177 | /* SET REFRESH TIME */ 178 | this.set_refresh_time = function (interval, duration) { 179 | }; 180 | }; 181 | exports.adapter = adapter; -------------------------------------------------------------------------------- /lib/adapters/tk103.js: -------------------------------------------------------------------------------- 1 | /* */ 2 | f = require("../functions"); 3 | 4 | exports.protocol="GPS103"; 5 | exports.model_name="TK103"; 6 | exports.compatible_hardware=["TK103/supplier"]; 7 | 8 | var adapter = function(device){ 9 | if(!(this instanceof adapter)) return new adapter(device); 10 | 11 | this.format = {"start":"(","end":")","separator":""} 12 | this.device = device; 13 | 14 | /******************************************* 15 | PARSE THE INCOMING STRING FROM THE DECIVE 16 | You must return an object with a least: device_id, cmd and type. 17 | return device_id: The device_id 18 | return cmd: command from the device. 19 | return type: login_request, ping, etc. 20 | *******************************************/ 21 | this.parse_data = function(data){ 22 | data = data.toString(); 23 | var cmd_start = data.indexOf("B"); //al the incomming messages has a cmd starting with 'B' 24 | if(cmd_start > 13)throw "Device ID is longer than 12 chars!"; 25 | var parts={ 26 | "start" : data.substr(0,1), 27 | "device_id" : data.substring(1,cmd_start),//mandatory 28 | "cmd" : data.substr(cmd_start,4), //mandatory 29 | "data" : data.substring(cmd_start+4,data.length-1), 30 | "finish" : data.substr(data.length-1,1) 31 | }; 32 | switch(parts.cmd){ 33 | case "BP05": 34 | parts.action="login_request"; 35 | break; 36 | case "BR00": 37 | parts.action="ping"; 38 | break; 39 | case "BO01": 40 | parts.action="alarm"; 41 | break; 42 | default: 43 | parts.action="other"; 44 | } 45 | 46 | return parts; 47 | } 48 | this.authorize =function(){ 49 | this.send_comand("AP05"); 50 | } 51 | this.run_other = function(cmd,msg_parts){ 52 | switch(cmd){ 53 | case "BP00": //Handshake 54 | this.device.send(this.format_data(this.device.uid+"AP01HSO")); 55 | break; 56 | } 57 | } 58 | 59 | this.request_login_to_device = function(){ 60 | //@TODO: Implement this. 61 | } 62 | 63 | this.receive_alarm = function(msg_parts){ 64 | //@TODO: implement this 65 | 66 | //Maybe we can save the gps data too. 67 | //gps_data = msg_parts.data.substr(1); 68 | alarm_code = msg_parts.data.substr(0,1); 69 | alarm = false; 70 | switch(alarm_code.toString()){ 71 | case "0": 72 | alarm = {"code":"power_off","msg":"Vehicle Power Off"}; 73 | break; 74 | case "1": 75 | alarm = {"code":"accident","msg":"The vehicle suffers an acciden"}; 76 | break; 77 | case "2": 78 | alarm = {"code":"sos","msg":"Driver sends a S.O.S."}; 79 | break; 80 | case "3": 81 | alarm = {"code":"alarming","msg":"The alarm of the vehicle is activated"}; 82 | break; 83 | case "4": 84 | alarm = {"code":"low_speed","msg":"Vehicle is below the min speed setted"}; 85 | break; 86 | case "5": 87 | alarm = {"code":"overspeed","msg":"Vehicle is over the max speed setted"}; 88 | break; 89 | case "6": 90 | alarm = {"code":"gep_fence","msg":"Out of geo fence"}; 91 | break; 92 | } 93 | this.send_comand("AS01",alarm_code.toString()); 94 | return alarm 95 | } 96 | 97 | 98 | this.get_ping_data = function(msg_parts){ 99 | var str = msg_parts.data; 100 | var data = { 101 | "date" : str.substr(0,6), 102 | "availability" : str.substr(6,1), 103 | "latitude" : functions.minute_to_decimal(parseFloat(str.substr(7,9)),str.substr(16,1)), 104 | "longitude" : functions.minute_to_decimal(parseFloat(str.substr(17,9)),str.substr(27,1)), 105 | "speed" : parseFloat(str.substr(28,5)), 106 | "time" : str.substr(33,6), 107 | "orientation" : str.substr(39,6), 108 | "io_state" : str.substr(45,8), 109 | "mile_post" : str.substr(53,1), 110 | "mile_data" : parseInt(str.substr(54,8),16) 111 | }; 112 | var datetime = "20"+data.date.substr(0,2)+"/"+data.date.substr(2,2)+"/"+data.date.substr(4,2); 113 | datetime += " "+data.time.substr(0,2)+":"+data.time.substr(2,2)+":"+data.time.substr(4,2) 114 | data.datetime=new Date(datetime); 115 | res = { 116 | latitude : data.latitude, 117 | longitude : data.longitude, 118 | time : new Date(data.date+" "+data.time), 119 | speed : data.speed, 120 | orientation : data.orientation, 121 | mileage : data.mile_data 122 | } 123 | return res; 124 | } 125 | 126 | /* SET REFRESH TIME */ 127 | this.set_refresh_time = function(interval,duration){ 128 | //XXXXYYZZ 129 | //XXXX Hex interval for each message in seconds 130 | //YYZZ Total time for feedback 131 | //YY Hex hours 132 | //ZZ Hex minutes 133 | var hours = parseInt(duration/3600); 134 | var minutes = parseInt((duration-hours*3600)/60); 135 | var time = f.str_pad(interval.toString(16),4,'0')+ f.str_pad(hours.toString(16),2,'0')+ f.str_pad(minutes.toString(16),2,'0') 136 | this.send_comand("AR00",time); 137 | } 138 | 139 | /* INTERNAL FUNCTIONS */ 140 | 141 | this.send_comand = function(cmd,data){ 142 | var msg = [this.device.uid,cmd,data]; 143 | this.device.send(this.format_data(msg)); 144 | } 145 | this.format_data = function(params){ 146 | /* FORMAT THE DATA TO BE SENT */ 147 | var str = this.format.start; 148 | if(typeof(params) == "string"){ 149 | str+=params 150 | }else if(params instanceof Array){ 151 | str += params.join(this.format.separator); 152 | }else{ 153 | throw "The parameters to send to the device has to be a string or an array"; 154 | } 155 | str+= this.format.end; 156 | return str; 157 | } 158 | } 159 | exports.adapter = adapter; 160 | -------------------------------------------------------------------------------- /lib/adapters/tk510.js: -------------------------------------------------------------------------------- 1 | /* */ 2 | var f = require('../functions'); 3 | var crc = require('crc16-ccitt-node'); 4 | 5 | exports.protocol = 'GPSTK510'; 6 | exports.model_name = 'TK510'; 7 | exports.compatible_hardware = ['TK510/supplier']; 8 | 9 | var adapter = function (device) { 10 | if (!(this instanceof adapter)) return new adapter(device); 11 | 12 | this.format = {'start': '(', 'end': ')', 'separator': ''}; 13 | this.device = device; 14 | 15 | /******************************************* 16 | PARSE THE INCOMING STRING FROM THE DECIVE 17 | You must return an object with a least: device_id, cmd and type. 18 | return device_id: The device_id 19 | return cmd: command from the device. 20 | return type: login_request, ping, etc. 21 | *******************************************/ 22 | this.parse_data = function (data) { 23 | var parts = { 24 | 'device_id': data.toString('hex').substr(8, 14).replace(/f*$/, ''),//mandatory 25 | 'cmd': data.toString('hex').substr(22, 4), //mandatory 26 | 'data': data.toString('hex').substr(26).slice(0, -8), 27 | }; 28 | this.device_id_complete = data.toString('hex').substr(8, 14); 29 | switch (parts.cmd) { 30 | case '5000': 31 | parts.action = 'login_request'; 32 | break; 33 | case '9955': 34 | parts.action = 'ping'; 35 | break; 36 | case '9999': 37 | parts.action = 'alarm'; 38 | break; 39 | default: 40 | parts.action = 'other'; 41 | } 42 | 43 | return parts; 44 | }; 45 | this.authorize = function () { 46 | this.send_comand('4000', '01'); 47 | }; 48 | this.run_other = function (cmd, msg_parts) { 49 | switch (cmd) { 50 | case 'BP00': //Handshake 51 | this.device.send(this.format_data(this.device.uid + 'AP01HSO')); 52 | break; 53 | } 54 | }; 55 | 56 | this.request_login_to_device = function () { 57 | //@TODO: Implement this. 58 | }; 59 | 60 | this.receive_alarm = function (msg_parts) { 61 | //@TODO: implement this 62 | 63 | //Maybe we can save the gps data too. 64 | //gps_data = msg_parts.data.substr(1); 65 | alarm_code = msg_parts.data.substr(0, 2); 66 | alarm = {code: alarm_code, data: msg_parts.data.substr(2)}; 67 | switch (alarm_code.toString()) { 68 | case '01': 69 | alarm = {'code': 'sos', 'msg': 'Driver sends a S.O.S.'}; 70 | break; 71 | case '50': 72 | alarm = {'code': 'power_off', 'msg': 'Vehicle Power Off'}; 73 | break; 74 | case '71': 75 | alarm = {'code': 'accident', 'msg': 'The vehicle suffers an acciden'}; 76 | break; 77 | case '05': 78 | alarm = {'code': 'alarming', 'msg': 'The alarm of the vehicle is activated'}; 79 | break; 80 | case '11': 81 | alarm = {'code': 'overspeed', 'msg': 'Vehicle is over the max speed setted'}; 82 | break; 83 | case '13': 84 | alarm = {'code': 'gep_fence', 'msg': 'Out of geo fence'}; 85 | break; 86 | } 87 | //this.send_comand("AS01",alarm_code.toString()); 88 | return alarm; 89 | }; 90 | 91 | this.get_ping_data = function (msg_parts) { 92 | var data_parts = this.hex_to_ascii(msg_parts.data).split(','); 93 | var data = { 94 | 'time': data_parts[0], 95 | 'gps_status': data_parts[1], 96 | 'latitude_minutes': data_parts[2], 97 | 'latitude_orientation': data_parts[3], 98 | 'longitude_minutes': data_parts[4], 99 | 'longitude_orientation': data_parts[5], 100 | 'speed': data_parts[6], 101 | 'orientation': data_parts[7], 102 | 'date': data_parts[8], 103 | 'magnetic_variation': data_parts[9], 104 | 'direction': data_parts[10], 105 | 'checksum': data_parts[11] 106 | }; 107 | var datetime = '20' + data.date.substr(0, 2) + '/' + data.date.substr(2, 2) + '/' + data.date.substr(4, 2); 108 | datetime += ' ' + data.time.substr(0, 2) + ':' + data.time.substr(2, 2) + ':' + data.time.substr(4, 2); 109 | data.datetime = new Date(datetime); 110 | data.latitude = f.minute_to_decimal(data.latitude_minutes, data.latitude_orientation); 111 | data.longitude = f.minute_to_decimal(data.longitude_minutes, data.longitude_orientation); 112 | return data; 113 | }; 114 | 115 | /* SET REFRESH TIME */ 116 | this.set_refresh_time = function (interval, duration) { 117 | //XXXXYYZZ 118 | //XXXX Hex interval for each message in seconds 119 | //YYZZ Total time for feedback 120 | //YY Hex hours 121 | //ZZ Hex minutes 122 | var hours = parseInt(duration / 3600); 123 | var minutes = parseInt((duration - hours * 3600) / 60); 124 | var time = f.str_pad(interval.toString(16), 4, '0') + f.str_pad(hours.toString(16), 2, '0') + f.str_pad(minutes.toString(16), 2, '0'); 125 | this.send_comand('AR00', time); 126 | }; 127 | 128 | /* INTERNAL FUNCTIONS */ 129 | 130 | this.checksum = function (msg) { 131 | return crc.getCrc16(new Buffer(msg, 'hex')).toString(16); 132 | }; 133 | 134 | this.send_comand = function (cmd, data) { 135 | if (typeof data === 'undefined') data = ''; 136 | var l = data.length / 2 + 17; 137 | var msg = '4040' + this.pad_hex(l.toString(16), 4) + this.device_id_complete + cmd.substr(0, 4) + data; 138 | var checksum = this.checksum(msg); 139 | msg += checksum + '0d0a'; 140 | 141 | var msg = new Buffer(msg, 'hex'); 142 | this.device.send(msg); 143 | }; 144 | 145 | this.pad_hex = function (string, length) { 146 | var str = '' + string; 147 | while (str.length < length) str = '0' + str; 148 | return str; 149 | }; 150 | 151 | this.hex_to_ascii = function (str1) { 152 | var hex = str1.toString(); 153 | var str = ''; 154 | for (var n = 0; n < hex.length; n += 2) { 155 | str += String.fromCharCode(parseInt(hex.substr(n, 2), 16)); 156 | } 157 | return str; 158 | }; 159 | 160 | }; 161 | exports.adapter = adapter; 162 | -------------------------------------------------------------------------------- /lib/device.js: -------------------------------------------------------------------------------- 1 | util = require('util'); 2 | EventEmitter = require('events').EventEmitter; 3 | util.inherits(Device, EventEmitter); 4 | 5 | function Device(adapter, connection, gpsServer) { 6 | /* Inherits EventEmitter class */ 7 | EventEmitter.call(this); 8 | 9 | var _this = this; 10 | 11 | this.connection = connection; 12 | this.server = gpsServer; 13 | this.adapter = adapter.adapter(this); 14 | 15 | this.uid = false; 16 | this.ip = connection.ip; 17 | this.port = connection.port; 18 | this.name = false; 19 | this.loged = false; 20 | 21 | init(); 22 | /* init */ 23 | function init() { 24 | 25 | } 26 | 27 | /**************************************** 28 | RECEIVING DATA FROM THE DEVICE 29 | ****************************************/ 30 | this.on('data', function (data) { 31 | var msgParts = _this.adapter.parse_data(data); 32 | 33 | if (this.getUID() === false && typeof (msgParts.device_id) === 'undefined') { 34 | throw 'The adapter doesn\'t return the device_id and is not defined'; 35 | } 36 | 37 | if (msgParts === false) { //something bad happened 38 | _this.do_log('The message (' + data + ') can\'t be parsed. Discarding...'); 39 | return; 40 | } 41 | 42 | if (typeof (msgParts.cmd) === 'undefined') { 43 | throw 'The adapter doesn\'t return the command (cmd) parameter'; 44 | } 45 | 46 | //If the UID of the devices it hasn't been setted, do it now. 47 | if (this.getUID() === false) { 48 | this.setUID(msgParts.device_id); 49 | } 50 | 51 | /************************************ 52 | EXECUTE ACTION 53 | ************************************/ 54 | _this.make_action(msgParts.action, msgParts); 55 | }); 56 | 57 | this.make_action = function (action, msgParts) { 58 | //If we're not loged 59 | if (action !== 'login_request' && !_this.loged) { 60 | _this.adapter.request_login_to_device(); 61 | _this.do_log(_this.getUID() + ' is trying to \'' + action + '\' but it isn\'t loged. Action wasn\'t executed'); 62 | return false; 63 | } 64 | 65 | switch (action) { 66 | case 'login_request': 67 | _this.login_request(msgParts); 68 | break; 69 | case 'ping': 70 | _this.ping(msgParts); 71 | break; 72 | case 'alarm': 73 | _this.receive_alarm(msgParts); 74 | break; 75 | case 'other': 76 | _this.adapter.run_other(msgParts.cmd, msgParts); 77 | break; 78 | } 79 | }; 80 | 81 | /**************************************** 82 | LOGIN & LOGOUT 83 | ****************************************/ 84 | this.login_request = function (msgParts) { 85 | _this.do_log('I\'m requesting to be loged.'); 86 | _this.emit('login_request', this.getUID(), msgParts); 87 | }; 88 | 89 | this.login_authorized = function (val, msgParts) { 90 | if (val) { 91 | this.do_log('Device ' + _this.getUID() + ' has been authorized. Welcome!'); 92 | this.loged = true; 93 | this.adapter.authorize(msgParts); 94 | } else { 95 | this.do_log('Device ' + _this.getUID() + ' not authorized. Login request rejected'); 96 | } 97 | }; 98 | 99 | this.logout = function () { 100 | this.loged = false; 101 | this.adapter.logout(); 102 | }; 103 | 104 | /**************************************** 105 | RECEIVING GPS POSITION FROM THE DEVICE 106 | ****************************************/ 107 | this.ping = function (msgParts) { 108 | var gpsData = this.adapter.get_ping_data(msgParts); 109 | if (gpsData === false) { 110 | //Something bad happened 111 | _this.do_log('GPS Data can\'t be parsed. Discarding packet...'); 112 | return false; 113 | } 114 | 115 | /* Needs: 116 | latitude, longitude, time 117 | Optionals: 118 | orientation, speed, mileage, etc */ 119 | 120 | _this.do_log('Position received ( ' + gpsData.latitude + ',' + gpsData.longitude + ' )'); 121 | gpsData.from_cmd = msgParts.cmd; 122 | _this.emit('ping', gpsData, msgParts); 123 | 124 | }; 125 | 126 | /**************************************** 127 | RECEIVING ALARM 128 | ****************************************/ 129 | this.receive_alarm = function (msgParts) { 130 | //We pass the message parts to the adapter and they have to say wich type of alarm it is. 131 | var alarmData = _this.adapter.receive_alarm(msgParts); 132 | /* Alarm data must return an object with at least: 133 | alarm_type: object with this format: 134 | {'code':'sos_alarm','msg':'SOS Alarm activated by the driver'} 135 | */ 136 | _this.emit('alarm', alarmData.code, alarmData, msgParts); 137 | }; 138 | 139 | /**************************************** 140 | SET REFRESH TIME 141 | ****************************************/ 142 | this.set_refresh_time = function (interval, duration) { 143 | _this.adapter.set_refresh_time(interval, duration); 144 | }; 145 | 146 | /* adding methods to the adapter */ 147 | this.adapter.get_device = function () { 148 | return device; 149 | }; 150 | 151 | this.send = function (msg) { 152 | this.emit('send_data', msg); 153 | this.connection.write(msg); 154 | this.do_log('Sending to ' + _this.getUID() + ': ' + msg); 155 | }; 156 | 157 | this.do_log = function (msg) { 158 | _this.server.do_log(msg, _this.getUID()); 159 | }; 160 | 161 | /**************************************** 162 | SOME SETTERS & GETTERS 163 | ****************************************/ 164 | this.getName = function () { 165 | return this.name; 166 | }; 167 | 168 | this.setName = function (name) { 169 | this.name = name; 170 | }; 171 | 172 | this.getUID = function () { 173 | return this.uid; 174 | }; 175 | 176 | this.setUID = function (uid) { 177 | this.uid = uid; 178 | }; 179 | 180 | } 181 | 182 | module.exports = Device; 183 | -------------------------------------------------------------------------------- /lib/functions.js: -------------------------------------------------------------------------------- 1 | /***************************************** 2 | FUNCTIONS 3 | ******************************************/ 4 | exports.rad = function (x) { 5 | return x * Math.PI / 180; 6 | }; 7 | 8 | /* 9 | @param p1: {lat:X,lng:Y} 10 | @param p2: {lat:X,lng:Y} 11 | */ 12 | exports.get_distance = function (p1, p2) { 13 | var R = 6378137; // Earth’s mean radius in meter 14 | var dLat = exports.rad(p2.lat - p1.lat); 15 | var dLong = exports.rad(p2.lng - p1.lng); 16 | var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + 17 | Math.cos(exports.rad(p1.lat)) * Math.cos(exports.rad(p2.lat)) * 18 | Math.sin(dLong / 2) * Math.sin(dLong / 2); 19 | var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 20 | var d = R * c; 21 | return d; // returns the distance in meter 22 | }; 23 | 24 | exports.send = function (socket, msg) { 25 | socket.write(msg); 26 | console.log('Sending to ' + socket.name + ': ' + msg); 27 | }; 28 | 29 | exports.parse_data = function (data) { 30 | data = data.replace(/(\r\n|\n|\r)/gm, ''); //Remove 3 type of break lines 31 | var cmd_start = data.indexOf('B'); //al the incomming messages has a cmd starting with 'B' 32 | if (cmd_start > 13)throw 'Device ID is longer than 12 chars!'; 33 | var parts = { 34 | 'start': data.substr(0, 1), 35 | 'device_id': data.substring(1, cmd_start), 36 | 'cmd': data.substr(cmd_start, 4), 37 | 'data': data.substring(cmd_start + 4, data.length - 1), 38 | 'finish': data.substr(data.length - 1, 1) 39 | }; 40 | return parts; 41 | }; 42 | 43 | exports.parse_gps_data = function (str) { 44 | var data = { 45 | 'date': str.substr(0, 6), 46 | 'availability': str.substr(6, 1), 47 | 'latitude': gps_minute_to_decimal(parseFloat(str.substr(7, 9))), 48 | 'latitude_i': str.substr(16, 1), 49 | 'longitude': gps_minute_to_decimal(parseFloat(str.substr(17, 9))), 50 | 'longitude_i': str.substr(27, 1), 51 | 'speed': str.substr(28, 5), 52 | 'time': str.substr(33, 6), 53 | 'orientation': str.substr(39, 6), 54 | 'io_state': str.substr(45, 8), 55 | 'mile_post': str.substr(53, 1), 56 | 'mile_data': parseInt(str.substr(54, 8), 16) 57 | }; 58 | return data; 59 | }; 60 | 61 | exports.send_to = function (socket, cmd, data) { 62 | if (typeof(socket.device_id) == 'undefined')throw 'The socket is not paired with a device_id yet'; 63 | var str = gps_format.start; 64 | str += socket.device_id + gps_format.separator + cmd; 65 | if (typeof(data) != 'undefined') str += gps_format.separator + data; 66 | str += gps_format.end; 67 | send(socket, str); 68 | //Example: (||) - separator: | ,start: (, end: ) 69 | }; 70 | 71 | exports.minute_to_decimal = function (pos, pos_i) { 72 | if (typeof(pos_i) === 'undefined') pos_i = 'N'; 73 | var dg = parseInt(pos / 100); 74 | var minutes = pos - (dg * 100); 75 | var res = (minutes / 60) + dg; 76 | return (pos_i.toUpperCase() === 'S' || pos_i.toUpperCase() === 'W') ? res * -1 : res; 77 | }; 78 | 79 | // Send a message to all clients 80 | exports.broadcast = function (message, sender) { 81 | clients.forEach(function (client) { 82 | if (client === sender) return; 83 | client.write(message); 84 | }); 85 | process.stdout.write(message + '\n'); 86 | }; 87 | 88 | exports.data_to_hex_array = function (data) { 89 | var arr = []; 90 | for (var i = 0; i < data.length; i++)arr.push(data[i].toString(16)); 91 | return arr; 92 | }; 93 | 94 | /* RETRUN AN INTEGER FROM A HEX CHAR OR integer */ 95 | exports.hex_to_int = function (hex_char) { 96 | return parseInt(hex_char, 16); 97 | }; 98 | 99 | exports.sum_hex_array = function (hex_array) { 100 | var sum = 0; 101 | for (var i in hex_array)sum += exports.hex_to_int(hex_array[i]); 102 | return sum; 103 | }; 104 | 105 | exports.hex_array_to_hex_str = function (hex_array) { 106 | var str = ''; 107 | for (var i in hex_array) { 108 | var char; 109 | if (typeof(hex_array[i]) === 'number') char = hex_array[i].toString(16); 110 | else char = hex_array[i].toString(); 111 | str += exports.str_pad(char, 2, '0'); 112 | } 113 | return str; 114 | }; 115 | 116 | exports.str_pad = function (input, length, string) { 117 | string = string || '0'; 118 | input = input + ''; 119 | return input.length >= length ? input : new Array(length - input.length + 1).join(string) + input; 120 | }; 121 | 122 | exports.crc_itu_get_verification = function (hex_data) { 123 | var crc16 = require('crc-itu').crc16; 124 | if (typeof(hex_data) === 'String') str = hex_data; 125 | else str = exports.hex_array_to_hex_str(hex_data); 126 | return crc16(str, 'hex'); 127 | }; 128 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | util = require('util'); 2 | EventEmitter = require('events').EventEmitter; 3 | net = require('net'); 4 | extend = require('node.extend'); 5 | functions = require('./functions'); 6 | Device = require('./device'); 7 | 8 | util.inherits(Server, EventEmitter); 9 | 10 | function Server(opts, callback) { 11 | if (!(this instanceof Server)) { 12 | return new Server(opts, callback); 13 | } 14 | 15 | EventEmitter.call(this); 16 | var defaults = { 17 | debug: false, 18 | port: 8080, 19 | device_adapter: false, 20 | }; 21 | 22 | //Merge default options with user options 23 | this.opts = extend(defaults, opts); 24 | 25 | var _this = this; 26 | this.devices = []; 27 | 28 | this.server = false; 29 | this.availableAdapters = { 30 | TK103: './adapters/tk103', 31 | TK510: './adapters/tk510', 32 | GT02A: './adapters/gt02a', 33 | GT06: './adapters/gt06', 34 | }; 35 | 36 | /**************************** 37 | SOME FUNCTIONS 38 | *****************************/ 39 | /* */ 40 | this.setAdapter = function (adapter) { 41 | if (typeof adapter.adapter !== 'function') { 42 | throw 'The adapter needs an adapter() method to start an instance of it'; 43 | } 44 | 45 | this.device_adapter = adapter; 46 | }; 47 | 48 | this.getAdapter = function () { 49 | return this.device_adapter; 50 | }; 51 | 52 | this.addAdaptar = function (model, Obj) { 53 | this.availableAdapters.push(model); 54 | }; 55 | 56 | this.init = function (cb) { 57 | //Set debug 58 | _this.setDebug(this.opts.debug); 59 | 60 | /***************************** 61 | DEVICE ADAPTER INITIALIZATION 62 | ******************************/ 63 | if (_this.opts.device_adapter === false) 64 | throw 'The app don\'t set the device_adapter to use. Which model is sending data to this server?'; 65 | 66 | if (typeof _this.opts.device_adapter === 'string') { 67 | 68 | //Check if the selected model has an available adapter registered 69 | if (typeof this.availableAdapters[this.opts.device_adapter] === 'undefined') 70 | throw 'The class adapter for ' + this.opts.device_adapter + ' doesn\'t exists'; 71 | 72 | //Get the adapter 73 | var adapterFile = (this.availableAdapters[this.opts.device_adapter]); 74 | 75 | this.setAdapter(require(adapterFile)); 76 | 77 | } else { 78 | //IF THE APP PASS THE ADEPTER DIRECTLY 79 | _this.setAdapter(this.opts.device_adapter); 80 | } 81 | 82 | _this.emit('before_init'); 83 | if (typeof cb === 'function') cb(); 84 | _this.emit('init'); 85 | 86 | /* FINAL INIT MESSAGE */ 87 | console.log('\n=================================================\nGPS LISTENER running at port ' + _this.opts.port + '\nEXPECTING DEVICE MODEL: ' + _this.getAdapter().model_name + '\n=================================================\n'); 88 | }; 89 | 90 | this.addAdaptar = function (model, Obj) { 91 | this.adapters.push(model); 92 | }; 93 | 94 | this.do_log = function (msg, from) { 95 | //If debug is disabled, return false 96 | if (this.getDebug() === false) return false; 97 | 98 | //If from parameter is not set, default is server. 99 | if (typeof from === 'undefined') { 100 | from = 'SERVER'; 101 | } 102 | 103 | msg = '#' + from + ': ' + msg; 104 | console.log(msg); 105 | 106 | }; 107 | 108 | /**************************************** 109 | SOME SETTERS & GETTERS 110 | ****************************************/ 111 | this.setDebug = function (val) { 112 | this.debug = (val === true); 113 | }; 114 | 115 | this.getDebug = function () { 116 | return this.debug; 117 | }; 118 | 119 | //Init app 120 | this.init(function () { 121 | /************************************* 122 | AFTER INITIALIZING THE APP... 123 | *************************************/ 124 | _this.server = net.createServer(function (connection) { 125 | //Now we are listening! 126 | 127 | //We create an new device and give the an adapter to parse the incomming messages 128 | connection.device = new Device(_this.getAdapter(), connection, _this); 129 | _this.devices.push(connection); 130 | 131 | //Once we receive data... 132 | connection.on('data', function (data) { 133 | connection.device.emit('data', data); 134 | }); 135 | 136 | // Remove the device from the list when it leaves 137 | connection.on('end', function () { 138 | _this.devices.splice(_this.devices.indexOf(connection), 1); 139 | connection.device.emit('disconnected'); 140 | }); 141 | 142 | callback(connection.device, connection); 143 | 144 | connection.device.emit('connected'); 145 | }).listen(opts.port); 146 | }); 147 | 148 | /* Search a device by ID */ 149 | this.find_device = function (deviceId) { 150 | for (var i in this.devices) { 151 | var dev = this.devices[i].device; 152 | if (dev.uid === deviceId) { 153 | return dev; 154 | } 155 | } 156 | 157 | return false; 158 | }; 159 | 160 | /* SEND A MESSAGE TO DEVICE ID X */ 161 | this.send_to = function (deviceId, msg) { 162 | var dev = this.find_device(deviceId); 163 | dev.send(msg); 164 | }; 165 | 166 | return this; 167 | } 168 | 169 | exports.server = Server; 170 | exports.version = require('../package').version; 171 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gps-tracking", 3 | "version": "1.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "base64-js": { 8 | "version": "1.3.1", 9 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", 10 | "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" 11 | }, 12 | "buffer": { 13 | "version": "5.4.0", 14 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.0.tgz", 15 | "integrity": "sha512-Xpgy0IwHK2N01ncykXTy6FpCWuM+CJSHoPVBLyNqyrWxsedpLvwsYUhf0ME3WRFNUhos0dMamz9cOS/xRDtU5g==", 16 | "requires": { 17 | "base64-js": "^1.0.2", 18 | "ieee754": "^1.1.4" 19 | } 20 | }, 21 | "crc": { 22 | "version": "3.8.0", 23 | "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", 24 | "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", 25 | "requires": { 26 | "buffer": "^5.1.0" 27 | } 28 | }, 29 | "crc16-ccitt-node": { 30 | "version": "1.0.6", 31 | "resolved": "https://registry.npmjs.org/crc16-ccitt-node/-/crc16-ccitt-node-1.0.6.tgz", 32 | "integrity": "sha512-f+nOcz4xOxesVRZbs8UzR18kwKORovMxKb2AJKh5f3HcJe9oaNQh7zOOH65IELXP2J9jeO6ND+xuHHIW3MZoKQ==" 33 | }, 34 | "function-bind": { 35 | "version": "1.1.1", 36 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 37 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 38 | }, 39 | "has": { 40 | "version": "1.0.3", 41 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 42 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 43 | "requires": { 44 | "function-bind": "^1.1.1" 45 | } 46 | }, 47 | "ieee754": { 48 | "version": "1.1.13", 49 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 50 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" 51 | }, 52 | "is": { 53 | "version": "3.3.0", 54 | "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", 55 | "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==" 56 | }, 57 | "node.extend": { 58 | "version": "2.0.2", 59 | "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", 60 | "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", 61 | "requires": { 62 | "has": "^1.0.3", 63 | "is": "^3.2.1" 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gps-tracking", 3 | "version": "1.1.0", 4 | "description": "Let you work with some GPS trackers that connects through tcp.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/freshworkstudio/gps-tracking-nodejs.git" 12 | }, 13 | "keywords": [ 14 | "gps", 15 | "tracker", 16 | "Tk103", 17 | "car", 18 | "gps" 19 | ], 20 | "author": "Gonzalo De-Spirito (http://www.freshworkstudio.com/)", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/freshworkstudio/gps-tracking-nodejs/issues" 24 | }, 25 | "homepage": "https://github.com/freshworkstudio/gps-tracking-nodejs", 26 | "dependencies": { 27 | "crc": "^3.5.0", 28 | "crc16-ccitt-node": "^1.0.6", 29 | "node.extend": "^2.0.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | ![NODE.JS GPS Tracker Server](https://user-images.githubusercontent.com/1103494/31578284-95673986-b0f4-11e7-81dd-2fefd3fb0478.jpg) 3 | ![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square) 4 | 5 | GPS TRACKING SERVER | Node.js 6 | ============== 7 | 8 | This package let you easily create listeners for your GPS tracking devices. You can add your custom implementations to handle more protocols. 9 | 10 | - [Installation](#Installation) 11 | - [Usage](#usage) 12 | - [Adapters](#adapters) 13 | - [Examples](#examples) 14 | - [GPS Emulator](#gps-emulator) 15 | 16 | # Installation 17 | With package manager [npm](http://npmjs.org/): 18 | 19 | npm install gps-tracking 20 | 21 | #### Currently supported models 22 | - TK103 23 | - TK510 24 | - GT06 25 | - GT02A 26 | * You can add your own adapters easily as commented below 27 | 28 | # Usage 29 | Once you have installed the package, you can use it like: 30 | 31 | ``` javascript 32 | var gps = require("gps-tracking"); 33 | 34 | var options = { 35 | 'debug' : true, 36 | 'port' : 8090, 37 | 'device_adapter' : "TK103" 38 | } 39 | 40 | var server = gps.server(options,function(device,connection){ 41 | 42 | device.on("login_request",function(device_id,msg_parts){ 43 | 44 | // Some devices sends a login request before transmitting their position 45 | // Do some stuff before authenticate the device... 46 | 47 | // Accept the login request. You can set false to reject the device. 48 | this.login_authorized(true); 49 | 50 | }); 51 | 52 | 53 | //PING -> When the gps sends their position 54 | device.on("ping",function(data){ 55 | 56 | //After the ping is received, but before the data is saved 57 | //console.log(data); 58 | return data; 59 | 60 | }); 61 | 62 | }); 63 | ``` 64 | 65 | ### Step by step 66 | 67 | 1) [Install Node](https://nodejs.org/) 68 | 69 | 2) Create a folder for your project 70 | 71 | 3) Copy the example code above in a .js file like server.js 72 | 73 | 4) Install the package in the project folder 74 | ``` bash 75 | cd /path/to/my/project 76 | npm install gps-tracking 77 | ``` 78 | 5) Run your server 79 | ``` bash 80 | node server.js 81 | ``` 82 | # Overview 83 | With this package you are going to create a tcp server that listens on a open port of your server/computer for a specific gps device model. 84 | For example, you are going to listen on port 8090 for 'TK103 gps-trackers'. 85 | 86 | If you want to listen for different kind of trackers, you have to create another tcp server. You can do this in a different node.js program in the same server, but you have to listen in a different port. 87 | 88 | So, you can listen on port 8090 for TK103 devices and listen on 8091 for TK102 devices (or any gps-tracker you want) 89 | 90 | # Options 91 | #### debug 92 | Enables console.log messages. 93 | ``` javascript 94 | "debug":false, 95 | ``` 96 | #### port 97 | The port to listen to. Where the packages of the device will arrive. 98 | ``` javascript 99 | "port": 8080, 100 | ``` 101 | 102 | #### device_adapter 103 | Which device adapter will be used to parse the incoming packets. 104 | ``` javascript 105 | "device_adapter": false, 106 | // If false, the server will throw an error. 107 | // At the moment, the modules comes with only one adater: TK103. 108 | "device_adapter": "TK103" 109 | // You can create your own adapter. 110 | 111 | //FOR USING A CUSTOM DEVICE ADAPTER 112 | "device_adapter": require("./my_custom_adapter") 113 | ``` 114 | 115 | # Events 116 | Once you create a server, you can access to the connection and the device object connected. Both objects emits events you can listen on your app. 117 | ```javascript 118 | var server = gps.server(options,function(device,connection){ 119 | //conection = net.createServer(...) object 120 | //device = Device object 121 | } 122 | ``` 123 | 124 | #### connection events 125 | Available events: 126 | - end 127 | - data 128 | - close 129 | - timeout 130 | - drain 131 | 132 | You can [check the documentation of node.js net object here](http://nodejs.org/api/net.html#net_net_createserver_options_connectionlistener). 133 | 134 | ``` javascript 135 | //Example: 136 | var server = gps.server(opts,function(device,connection){ 137 | connection.on("data",function(res){ 138 | //When raw data comes from the device 139 | }); 140 | }); 141 | ``` 142 | 143 | ### Device object events 144 | Every time something connects to the server, a net connection and a new device object will be created. 145 | The Device object is your interface to send & receive packets/commands. 146 | 147 | ``` javascript 148 | var server = gps.server(opts, function(device, connection){ 149 | /* Available device variables: 150 | ---------------------------- 151 | device.uid -> Set when the first packet is parsed 152 | device.name -> You can set a custon name for this device. 153 | device.ip -> IP of the device 154 | device.port --> Device port 155 | */ 156 | 157 | /****************************** 158 | LOGIN 159 | ******************************/ 160 | device.on("login_request", function(device_id, msg_parts){ 161 | //Do some stuff before authenticate the device... 162 | // This way you can prevent from anyone to send their position without your consent 163 | this.login_authorized(true); //Accept the login request. 164 | }); 165 | 166 | device.on("login",function() { 167 | console.log("Hi! i'm " + device.uid); 168 | }); 169 | 170 | device.on("login_rejected",function(){ 171 | 172 | }); 173 | 174 | 175 | /****************************** 176 | PING - When the gps sends their position 177 | ******************************/ 178 | device.on("ping",function(data){ 179 | //After the ping is received 180 | //console.log(data); 181 | console.log("I'm here now: " + gps_data.latitude + ", " + gps_data.longitude); 182 | return data; 183 | }); 184 | 185 | 186 | /****************************** 187 | ALARM - When the gps sends and alarm 188 | ******************************/ 189 | device.on("alarm", function(alarm_code, alarm_data, msg_data) { 190 | console.log("Help! Something happend: " + alarm_code + " (" + alarm_data.msg + ")"); 191 | //call_me(); 192 | }); 193 | 194 | 195 | /****************************** 196 | MISC 197 | ******************************/ 198 | device.on("handshake",function(){ 199 | 200 | }); 201 | 202 | }); 203 | 204 | server.setDebug(true); 205 | ``` 206 | 207 | # Adapters 208 | If you want to create a new adapter, you have to create and exports an adapter function. 209 | You can base your new adapter on one of these nativaly supported adapters: https://github.com/freshworkstudio/gps-tracking-nodejs/tree/master/lib/adapters 210 | 211 | `youradapter.js` 212 | ```javascript 213 | exports.protocol="GPS103"; 214 | exports.model_name="TK103"; 215 | exports.compatible_hardware=["TK103/supplier"]; 216 | 217 | var adapter = function(device){ 218 | //Code that parses and respond to commands 219 | } 220 | exports.adapter = adapter; 221 | ``` 222 | #### Functions you have to implement 223 | ##### function parse_data(data) 224 | You receive the data and you have to return an object with: 225 | 226 | ```javascript 227 | return { 228 | 'device_id': 'string', 229 | // ID of the device. Mandatory 230 | 231 | 'cmd': 'string', 232 | //'string' Represents what the device is trying to do. You can send some of the available commands or a custom string. Mandatory 233 | 234 | 'data': 'string' 235 | //Aditional data in the packet. Mandatory 236 | } 237 | ``` 238 | #### Available commands (What the device is trying to do?) 239 | ``` javscript 240 | 'cmd':'login_request' // The device is trying to login. 241 | 'cmd':'alarm' // (login_request, ping, alarm) 242 | 'cmd':'ping' //The device is sending gps_data 243 | 244 | //Or send custom string 245 | 'cmd':'other_command' //You can catch this custom command in you app. 246 | ``` 247 | Example: 248 | ```javascript 249 | var adapter = function(device){ 250 | function parse_data(data){ 251 | // Example implementation 252 | // 253 | // Packet from device: 254 | // #ID_DEVICE_XXX#TIME#LOG_ME_IN_PLEASE#MORE_DATA(GPS,LBS,ETC)# 255 | 256 | //Do some stuff... 257 | return { 258 | "device_id" : 'ID_DEVICE_XXX',//mandatory 259 | "cmd" : 'login_request', //mandatory 260 | "data" : 'MORE_DATA(GPS,LBS,ETC)' //Mandatory 261 | 262 | //optional parameters. Anything you want. 263 | "optional_params": '', 264 | "more_optional_parameters":'...', 265 | } 266 | } 267 | } 268 | ``` 269 | 270 | 271 | ### Full example (device_adapter implementation) 272 | This is the implementation for TK103. 273 | Example data: 274 | 275 | #### Login request from TK103 276 | Packet: 277 | (012341234123BP05000012341234123140607A3330.4288S07036.8518W019.2230104172.3900000000L00019C2C) 278 | 279 | So, 280 | Start String = "(" 281 | Device ID = "012341234123" 282 | Command = "BP05" --> "login_request" 283 | Custom Data = "000012341234123140607A3330.4288S07036.8518W019.2230104172.3900000000L00019C2C" 284 | Finish String = ")" 285 | 286 | ```javascript 287 | /* */ 288 | 289 | /* */ 290 | // some functions you could use like this 291 | // f = require('gps-tracking/functions'). There are optionals 292 | f = require("../functions"); 293 | 294 | exports.protocol="GPS103"; 295 | exports.model_name="TK103"; 296 | exports.compatible_hardware=["TK103/supplier"]; 297 | 298 | var adapter = function(device){ 299 | if(!(this instanceof adapter)) return new adapter(device); 300 | 301 | this.format = {"start":"(","end":")","separator":""} 302 | this.device = device; 303 | 304 | /******************************************* 305 | PARSE THE INCOMING STRING FROM THE DECIVE 306 | You must return an object with a least: device_id, cmd and type. 307 | return device_id: The device_id 308 | return cmd: command from the device. 309 | return type: login_request, ping, etc. 310 | *******************************************/ 311 | this.parse_data = function(data){ 312 | data = data.toString(); 313 | var cmd_start = data.indexOf("B"); //al the incomming messages has a cmd starting with 'B' 314 | if(cmd_start > 13)throw "Device ID is longer than 12 chars!"; 315 | var parts={ 316 | "start" : data.substr(0,1), 317 | "device_id" : data.substring(1,cmd_start),//mandatory 318 | "cmd" : data.substr(cmd_start,4), //mandatory 319 | "data" : data.substring(cmd_start+4,data.length-1), 320 | "finish" : data.substr(data.length-1,1) 321 | }; 322 | switch(parts.cmd){ 323 | case "BP05": 324 | parts.action="login_request"; 325 | break; 326 | case "BR00": 327 | parts.action="ping"; 328 | break; 329 | case "BO01": 330 | parts.action="alarm"; 331 | break; 332 | default: 333 | parts.action="other"; 334 | } 335 | 336 | return parts; 337 | } 338 | this.authorize =function(){ 339 | this.send_comand("AP05"); 340 | } 341 | this.run_other = function(cmd,msg_parts){ 342 | switch(cmd){ 343 | case "BP00": //Handshake 344 | this.device.send(this.format_data(this.device.uid+"AP01HSO")); 345 | break; 346 | } 347 | } 348 | 349 | this.request_login_to_device = function(){ 350 | //@TODO: Implement this. 351 | } 352 | 353 | this.receive_alarm = function(msg_parts){ 354 | //@TODO: implement this 355 | 356 | //gps_data = msg_parts.data.substr(1); 357 | alarm_code = msg_parts.data.substr(0,1); 358 | alarm = false; 359 | switch(alarm_code.toString()){ 360 | case "0": 361 | alarm = {"code":"power_off","msg":"Vehicle Power Off"}; 362 | break; 363 | case "1": 364 | alarm = {"code":"accident","msg":"The vehicle suffers an acciden"}; 365 | break; 366 | case "2": 367 | alarm = {"code":"sos","msg":"Driver sends a S.O.S."}; 368 | break; 369 | case "3": 370 | alarm = {"code":"alarming","msg":"The alarm of the vehicle is activated"}; 371 | break; 372 | case "4": 373 | alarm = {"code":"low_speed","msg":"Vehicle is below the min speed setted"}; 374 | break; 375 | case "5": 376 | alarm = {"code":"overspeed","msg":"Vehicle is over the max speed setted"}; 377 | break; 378 | case "6": 379 | alarm = {"code":"gep_fence","msg":"Out of geo fence"}; 380 | break; 381 | } 382 | this.send_comand("AS01",alarm_code.toString()); 383 | return alarm 384 | } 385 | 386 | 387 | this.get_ping_data = function(msg_parts){ 388 | var str = msg_parts.data; 389 | var data = { 390 | "date" : str.substr(0,6), 391 | "availability" : str.substr(6,1), 392 | "latitude" : functions.minute_to_decimal(parseFloat(str.substr(7,9)),str.substr(16,1)), 393 | "longitude" : functions.minute_to_decimal(parseFloat(str.substr(17,9)),str.substr(27,1)), 394 | "speed" : parseFloat(str.substr(28,5)), 395 | "time" : str.substr(33,6), 396 | "orientation" : str.substr(39,6), 397 | "io_state" : str.substr(45,8), 398 | "mile_post" : str.substr(53,1), 399 | "mile_data" : parseInt(str.substr(54,8),16) 400 | }; 401 | var datetime = "20"+data.date.substr(0,2)+"/"+data.date.substr(2,2)+"/"+data.date.substr(4,2); 402 | datetime += " "+data.time.substr(0,2)+":"+data.time.substr(2,2)+":"+data.time.substr(4,2) 403 | data.datetime=new Date(datetime); 404 | res = { 405 | latitude : data.latitude, 406 | longitude : data.longitude, 407 | time : new Date(data.date+" "+data.time), 408 | speed : data.speed, 409 | orientation : data.orientation, 410 | mileage : data.mile_data 411 | } 412 | return res; 413 | } 414 | 415 | /* SET REFRESH TIME */ 416 | this.set_refresh_time = function(interval,duration){ 417 | //XXXXYYZZ 418 | //XXXX Hex interval for each message in seconds 419 | //YYZZ Total time for feedback 420 | //YY Hex hours 421 | //ZZ Hex minutes 422 | var hours = parseInt(duration/3600); 423 | var minutes = parseInt((duration-hours*3600)/60); 424 | var time = f.str_pad(interval.toString(16),4,'0')+ f.str_pad(hours.toString(16),2,'0')+ f.str_pad(minutes.toString(16),2,'0') 425 | this.send_comand("AR00",time); 426 | } 427 | 428 | /* INTERNAL FUNCTIONS */ 429 | 430 | this.send_comand = function(cmd,data){ 431 | var msg = [this.device.uid,cmd,data]; 432 | this.device.send(this.format_data(msg)); 433 | } 434 | this.format_data = function(params){ 435 | /* FORMAT THE DATA TO BE SENT */ 436 | var str = this.format.start; 437 | if(typeof(params) == "string"){ 438 | str+=params 439 | }else if(params instanceof Array){ 440 | str += params.join(this.format.separator); 441 | }else{ 442 | throw "The parameters to send to the device has to be a string or an array"; 443 | } 444 | str+= this.format.end; 445 | return str; 446 | } 447 | } 448 | exports.adapter = adapter; 449 | 450 | 451 | ``` 452 | # Examples 453 | ### DEMO SERVER APP 454 | You can check a basic demo app [here](https://github.com/freshworkstudio/gps-tracking-demo) 455 | 456 | # GPS Emulator 457 | We created a brand new gps emulator so you can start testing your app in a breeze. 458 | You can check the code of the emulator in [this repo](https://github.com/freshworkstudio/gps-tracking-emulator). 459 | [https://github.com/freshworkstudio/gps-tracking-emulator](https://github.com/freshworkstudio/gps-tracking-emulator) 460 | 461 | 462 | #### Stay tuned - Contributions 463 | We are adding support for multiple devices and protocols. 464 | We highly appreciate your contributions to the project. 465 | Please, just throw me an email at gonzalo@freshworkstudio.com if you have questions/suggestions. 466 | 467 | #### Why NodeJS? 468 | NodeJS appears to be the perfect solution to receive the data for your multiple GPS devices thanks to the amazing performance an ease of use. Actually, it's extremely fast and it's easy to understand. 469 | --------------------------------------------------------------------------------