├── .eslintrc ├── .gitignore ├── CHANGELOG.md ├── README.md ├── UNLICENSE ├── example.js ├── package.json ├── test.js └── tk102.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module" 5 | }, 6 | 7 | "env": { 8 | "node": true 9 | }, 10 | 11 | "rules": { 12 | "array-bracket-spacing": [2, "never"], 13 | "brace-style": [2, "1tbs", { 14 | "allowSingleLine": true 15 | }], 16 | "camelcase": [2, { 17 | "properties": "never" 18 | }], 19 | "comma-spacing": [2, { 20 | "before": false, 21 | "after": true 22 | }], 23 | "comma-style": [2, "last"], 24 | "comma-dangle": [2, "never"], 25 | "complexity": [1, 8], 26 | "computed-property-spacing": [2, "never"], 27 | "consistent-return": 1, 28 | "curly": [2, "all"], 29 | "default-case": 2, 30 | "dot-notation": [1, { 31 | "allowKeywords": true 32 | }], 33 | "dot-location": [2, "property"], 34 | "eol-last": 2, 35 | "eqeqeq": 2, 36 | "func-style": 0, 37 | "guard-for-in": 0, 38 | "handle-callback-err": [2, "^(e|er|err|error)[0-9]{1,2}?$"], 39 | "indent": [2, 2, { 40 | "SwitchCase": 1 41 | }], 42 | "keyword-spacing": 2, 43 | "key-spacing": [2, { 44 | "beforeColon": false, 45 | "afterColon": true 46 | }], 47 | "lines-around-comment": [2, { 48 | "beforeBlockComment": true, 49 | "afterBlockComment": true, 50 | "beforeLineComment": true, 51 | "afterLineComment": false, 52 | "allowBlockStart": true, 53 | "allowBlockEnd": false 54 | }], 55 | "linebreak-style": [2, "unix"], 56 | "max-nested-callbacks": [1, 3], 57 | "new-cap": 0, 58 | "newline-after-var": [2, "always"], 59 | "no-alert": 2, 60 | "no-caller": 2, 61 | "no-catch-shadow": 2, 62 | "no-delete-var": 2, 63 | "no-div-regex": 2, 64 | "no-duplicate-case": 2, 65 | "no-else-return": 2, 66 | "no-empty": 2, 67 | "no-empty-character-class": 2, 68 | "no-eval": 2, 69 | "no-extend-native": 2, 70 | "no-extra-semi": 2, 71 | "no-fallthrough": 2, 72 | "no-floating-decimal": 2, 73 | "no-func-assign": 2, 74 | "no-implied-eval": 2, 75 | "no-inline-comments": 2, 76 | "no-invalid-regexp": 2, 77 | "no-label-var": 2, 78 | "no-labels": 2, 79 | "no-lone-blocks": 2, 80 | "no-lonely-if": 2, 81 | "no-mixed-requires": 0, 82 | "no-mixed-spaces-and-tabs": 2, 83 | "no-multi-spaces": 2, 84 | "no-multi-str": 2, 85 | "no-multiple-empty-lines": [2, { 86 | "max": 2 87 | }], 88 | "no-native-reassign": 2, 89 | "no-nested-ternary": 2, 90 | "no-new-func": 2, 91 | "no-new-object": 2, 92 | "no-new-wrappers": 2, 93 | "no-octal-escape": 2, 94 | "no-octal": 2, 95 | "no-path-concat": 2, 96 | "no-param-reassign": 0, 97 | "no-process-env": 0, 98 | "no-proto": 2, 99 | "no-redeclare": 2, 100 | "no-reserved-keys": 0, 101 | "no-return-assign": [2, "always"], 102 | "no-self-compare": 2, 103 | "no-sequences": 2, 104 | "no-shadow": 2, 105 | "no-shadow-restricted-names": 2, 106 | "no-spaced-func": 0, 107 | "no-sparse-arrays": 1, 108 | "no-sync": 1, 109 | "no-ternary": 0, 110 | "no-throw-literal": 2, 111 | "no-trailing-spaces": 2, 112 | "no-undef": 2, 113 | "no-undef-init": 2, 114 | "no-undefined": 2, 115 | "no-underscore-dangle": 2, 116 | "no-unexpected-multiline": 2, 117 | "no-unneeded-ternary": 2, 118 | "no-unreachable": 2, 119 | "no-unused-vars": 1, 120 | "no-use-before-define": 2, 121 | "no-useless-concat": 2, 122 | "no-warning-comments": 1, 123 | "no-with": 2, 124 | "no-wrap-func": 0, 125 | "object-curly-spacing": [2, "always", { 126 | "objectsInObjects": false, 127 | "arraysInObjects": false 128 | }], 129 | "one-var": [2, "never"], 130 | "operator-assignment": [2, "always"], 131 | "operator-linebreak": [2, "none", { 132 | "overrides": { 133 | "+": "before" 134 | } 135 | }], 136 | "padded-blocks": [2, "never"], 137 | "quote-props": [2, "consistent"], 138 | "quotes": [2, "single", "avoid-escape"], 139 | "radix": 2, 140 | "semi": 2, 141 | "semi-spacing": [2, { 142 | "before": false, 143 | "after": true 144 | }], 145 | "space-before-blocks": [2, "always"], 146 | "space-before-function-paren": [2, "always"], 147 | "space-in-parens": [2, "never"], 148 | "space-infix-ops": 2, 149 | "space-unary-ops": [2, { 150 | "words": true, 151 | "nonwords": false 152 | }], 153 | "spaced-comment": [2, "always"], 154 | "use-isnan": 2, 155 | "valid-typeof": 2, 156 | "vars-on-top": 2, 157 | "wrap-regex": 0, 158 | "yoda": [2, "never"] 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | .*_history 5 | npm-debug.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #### 1.3.4 (2021-07-17) 2 | 3 | #### 1.3.3 (2021-07-17) 4 | 5 | #### 1.3.2 (2021-07-17) 6 | 7 | ##### Chores 8 | 9 | * **package:** 10 | * Update dotest dev dep ([50de1e95](https://github.com/fvdm/nodejs-tk102/commit/50de1e958b194756e2dcb2a8ab8862ef03b507ac)) 11 | * Update dev deps ([e590be47](https://github.com/fvdm/nodejs-tk102/commit/e590be47c702824524122ce6d25fa92dbc5a2f63)) 12 | * Replaced test runner and dev deps by dotest ([37bbf7a0](https://github.com/fvdm/nodejs-tk102/commit/37bbf7a0f61d70a04a1da579377c0865c07e3a91)) 13 | * **develop:** 14 | * Added gitignore config ([e352157f](https://github.com/fvdm/nodejs-tk102/commit/e352157f3e8bf1f0c1dfe5cb29f293a82bb4814b)) 15 | * Added bitHound config ([c702663b](https://github.com/fvdm/nodejs-tk102/commit/c702663bbe7cf7c01a7135974835d440b4df4a05)) 16 | 17 | ##### Documentation Changes 18 | 19 | * **badges:** 20 | * Removed broken npm badge ([c54d9d0f](https://github.com/fvdm/nodejs-tk102/commit/c54d9d0f4cdf4a6553db5376cc4a692b8ccba81c)) 21 | * Add Gemnasium dependencies status ([e3d1ef9b](https://github.com/fvdm/nodejs-tk102/commit/e3d1ef9b56ee8f344169f81b4211f208c3986fe1)) 22 | * Add npm version for changelog ([fba6684e](https://github.com/fvdm/nodejs-tk102/commit/fba6684e7dc95e59861544b08960d48b7c42ade8)) 23 | * Deprecation message ([4cc2bd95](https://github.com/fvdm/nodejs-tk102/commit/4cc2bd95524d7b71b4970af86c22a94843dd52b2)) 24 | * **readme:** 25 | * Added links to gpsObject subs ([f6c15076](https://github.com/fvdm/nodejs-tk102/commit/f6c150767bf922038af767d53b410ceba8784a62)) 26 | * Fix legacy tables ([71086334](https://github.com/fvdm/nodejs-tk102/commit/71086334a8bd20484113463c15ee3937edb3a9ee)) 27 | 28 | ##### Other Changes 29 | 30 | * always run both test commands ([1235f254](https://github.com/fvdm/nodejs-tk102/commit/1235f254af0dacbaf0b93ef479888e58e0684cfb)) 31 | * added Tonic example code ([400cc9aa](https://github.com/fvdm/nodejs-tk102/commit/400cc9aa30ea886da9fe11139e69b44086dc121c)) 32 | * added eslint to npm test ([a139e59c](https://github.com/fvdm/nodejs-tk102/commit/a139e59c76812f62763f5f8270069bde21e656fe)) 33 | * added dotest dev dependency ([e2b7d3c8](https://github.com/fvdm/nodejs-tk102/commit/e2b7d3c800b41e2496ded9de2b0f6e028b8a2df2)) 34 | * added contributors ([605c0c5f](https://github.com/fvdm/nodejs-tk102/commit/605c0c5fcbb3ae085b32d6f8fee4d712b785dac7)) 35 | * fixed text typo ([3f383e3f](https://github.com/fvdm/nodejs-tk102/commit/3f383e3f6d1df207c8a5fcea3e7f95b042a4f15c)) 36 | 37 | ##### Refactors 38 | 39 | * **package:** Minimum supported node v4.0 ([19725445](https://github.com/fvdm/nodejs-tk102/commit/197254450b513b8c20a888cfaa156604735ab243)) 40 | 41 | ##### Code Style Changes 42 | 43 | * **comments:** 44 | * Minor tweaks ([bdcbc9f5](https://github.com/fvdm/nodejs-tk102/commit/bdcbc9f507e6618754dcc0b01d2a4620e67d1b5f)) 45 | * Add JSDoc to functions ([80529698](https://github.com/fvdm/nodejs-tk102/commit/805296982910624a48c908d5d1065fca4b466396)) 46 | 47 | ##### Tests 48 | 49 | * **package:** Change dev deps to dotest ([e3eab190](https://github.com/fvdm/nodejs-tk102/commit/e3eab19079d3a45386dce01b588c074eeeb0dab2)) 50 | * **config:** 51 | * Update Travis CI node versions ([88613cc6](https://github.com/fvdm/nodejs-tk102/commit/88613cc60dd130481cc4b1ff1f846745be713633)) 52 | * Travis CI update node versions ([affe971b](https://github.com/fvdm/nodejs-tk102/commit/affe971b85b1ab48718254a5dab972a789310c3d)) 53 | * Use dynamic node versions on Travis CI ([336660b0](https://github.com/fvdm/nodejs-tk102/commit/336660b0d40fa0befaa6dec267379c69ac8a1b94)) 54 | * **lint:** Update eslint to ES6 ([2813b1c7](https://github.com/fvdm/nodejs-tk102/commit/2813b1c76072fb3b2078eb938eaaa5d43df3972c)) 55 | * add node v6 to Travis config ([d5870eb5](https://github.com/fvdm/nodejs-tk102/commit/d5870eb55bf0a1b6169ae81371c13058a1d73203)) 56 | * fixed syntax typo ([cd7ea876](https://github.com/fvdm/nodejs-tk102/commit/cd7ea876fb758fd7980016c167916f28820b9ff8)) 57 | * added basic .closeServer method check ([2b8a10ac](https://github.com/fvdm/nodejs-tk102/commit/2b8a10ac01fd092f1e075b264f3511020d129fea)) 58 | * removed redundant checks ([951b19ee](https://github.com/fvdm/nodejs-tk102/commit/951b19ee6457f38f2aea9626184f201a69fc1c49)) 59 | * updated eslint config ([1c4afcee](https://github.com/fvdm/nodejs-tk102/commit/1c4afcee15fde1a3602d30656a0477999a68826c)) 60 | * fixed horrible typos ([5e265705](https://github.com/fvdm/nodejs-tk102/commit/5e265705dfac524d017eef0c459d5358be188431)) 61 | * removed old node from Travis config ([63c43563](https://github.com/fvdm/nodejs-tk102/commit/63c4356385092060fe405be5603e5090171ef757)) 62 | * replaced script with dotest module ([7620fb92](https://github.com/fvdm/nodejs-tk102/commit/7620fb9278aa3cdc2685b65696cafdd5aedd559a)) 63 | 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TK102 GPS server - DEPRECATED 2 | ================ 3 | 4 | This package is no longer being maintained. 5 | 6 | --- 7 | 8 | Receive and parse GPS data from Xexun TK102 trackers. 9 | 10 | The Xexun TK102 is a GPS device that can send coordinates over TCP to 11 | a server via GPRS. This Node.js script creates a TCP server that listens 12 | for GPRMC data, parse it and send the data to your post-process function. 13 | The parsed data is provided in a clean easy to use object, so you can 14 | easily store it in a database or push to a websocket server, etc. 15 | 16 | 17 | - [Example](#example) 18 | - [Prepare device](#prepare-device) 19 | - [Installation](#installation) 20 | - [Settings](#settings) 21 | - [Events](#events) 22 | - [Notes](#notes) 23 | 24 | 25 | Example 26 | ------- 27 | 28 | ```js 29 | var server = require ('tk102'); 30 | 31 | // start server 32 | server.createServer ({ 33 | port: 1337 34 | }); 35 | 36 | // incoming data, i.e. update a map 37 | server.on ('track', function (gps) { 38 | updateMap (gps.geo.latitude, gps.geo.longitude); 39 | }); 40 | ``` 41 | 42 | 43 | Prepare device 44 | -------------- 45 | 46 | Assuming your simcard has enough SMS and data credits and the TK102 is 47 | configured for your provider's APN, simply send `adminip123456 IP PORT` 48 | where obviously `IP` is the server's IP, `PORT` is the port to listen on 49 | and `123456` is your admin password. :) It cannot take hostnames as it 50 | has no dns features on board. 51 | 52 | Activate sending coordinates: **t030s003n123456** 53 | 54 | This tells the device to send its location AFTER each **30** seconds and 55 | no more than **3** times. 30 seconds is the minimum. 56 | Send `t030s***n123456` to go on for infinity. 57 | 58 | * **s** (seconds) can also be **m** (minutes) or **h** (hours) 59 | * To end tracking send `notn123456` 60 | 61 | 62 | Installation 63 | ------------ 64 | 65 | Stable: `npm install tk102` 66 | 67 | Develop: `npm install fvdm/nodejs-tk102#develop` 68 | 69 | 70 | Settings 71 | -------- 72 | 73 | name | type | default | description 74 | :-----------|:--------|:--------|:---------------------------------- 75 | ip | string | 0.0.0.0 | Listen on IP, `0.0.0.0` is all IPs 76 | port | integer | 0 | Listen on port, `0` is random (see [listening](#listening) event) 77 | connections | integer | 10 | Maximum simultaneous connections 78 | timeout | integer | 10 | Idle time out in seconds 79 | 80 | ```js 81 | server.createServer ({ 82 | ip: '1.2.3.4', 83 | port: 0 84 | }); 85 | ``` 86 | 87 | 88 | Events 89 | ------ 90 | 91 | The server emits the following events about the server status and incoming GPS pushes. 92 | 93 | 94 | ### track 95 | **( gpsObject )** 96 | 97 | The data push from the device. 98 | 99 | ```js 100 | server.on ('track', function (gps) { 101 | { raw: '1203301642,0031698765432,GPRMC,144219.000,A,5213.0327,N,00516.7759,E,0.63,179.59,300312,,,A*6D,F,imei:123456789012345,123', 102 | datetime: '2012-03-30 16:42', 103 | phone: '0031698765432', 104 | gps: { date: '2012-03-30', time: '14:42:19.000', signal: 'full', fix: 'active' }, 105 | geo: { latitude: 52.130326, longitude: 5.167759, bearing: 179 }, 106 | speed: { knots: 0.63, kmh: 1.167, mph: 0.725 }, 107 | imei: '123456789012345' } 108 | }) 109 | ``` 110 | 111 | property | description 112 | :------------------------|:-------------------------------------------------- 113 | raw | the input string without trailing whitespace 114 | datetime | the device 24h clock 115 | phone | the admin phonenumber that initiated this tracking 116 | imei | device IMEI 117 | [gps](#gpsobjectgps) | information about the GPS signal 118 | [geo](#gpsobjectgeo) | geographical position and direction 119 | [speed](#gpsobjectspeed) | travel speed 120 | 121 | 122 | #### gpsObject.gps 123 | 124 | property | description 125 | :-----------|:--------------------------------------- 126 | date | date as received from GPS 127 | time | time in 24h UTC as received from GPS 128 | signal | signal strength, either _full_ or _low_ 129 | fix | GPS fix, either _active_ or _invalid_ 130 | 131 | 132 | #### gpsObject.geo 133 | 134 | property | description 135 | :-----------|:-------------------- 136 | latitude | position latitude 137 | longitude | position longitude 138 | bearing | direction in degrees 139 | 140 | 141 | #### gpsObject.speed 142 | 143 | property | description 144 | :-----------|:---------------------------------- 145 | knots | speed in knots per hour (original) 146 | kmh | speed in kilometer per hour 147 | mph | speed in miles per hour 148 | 149 | 150 | ### data 151 | **( rawString )** 152 | 153 | The raw unprocessed inbound data. 154 | 155 | ```js 156 | server.on ('data', function (raw) { 157 | console.log ('Incoming data: '+ raw); 158 | }); 159 | ``` 160 | 161 | 162 | ### listening 163 | **( listeningObject )** 164 | 165 | Very useful to find out random port (0). 166 | 167 | ```js 168 | server.on ('listening', function (listen) { 169 | // listen = { port: 56751, family: 2, address: '0.0.0.0' } 170 | }); 171 | ``` 172 | 173 | 174 | ### connection 175 | **( socket )** 176 | 177 | Emitted when a connection is established with the server, includes the socket basics. 178 | 179 | ```js 180 | server.on ('connection', function (socket) { 181 | console.log ('Connection from '+ socket.remoteAddress); 182 | }); 183 | ``` 184 | 185 | 186 | ### disconnect 187 | **( socket )** 188 | 189 | Emitted when a connection is ended, includes the socket basics. 190 | 191 | ```js 192 | server.on ('disconnect', function (socket) { 193 | console.log ('Disconnected device '+ socket.remoteAddress); 194 | }); 195 | ``` 196 | 197 | 198 | ### timeout 199 | **( socket )** 200 | 201 | Emitted when a connection expires, includes the socket basics. 202 | 203 | ```js 204 | server.on ('timeout', function (socket) { 205 | console.log ('Time-out from '+ socket.remoteAddress); 206 | }); 207 | ``` 208 | 209 | 210 | ### fail 211 | **( Error )** 212 | 213 | Emitted when data cannot be parsed. 214 | Useful for debugging device issues. 215 | 216 | `Error` is an `instanceof Error` with .stack trace. 217 | 218 | ```js 219 | server.on ('fail', function (err) { 220 | console.log (err); 221 | }); 222 | ``` 223 | 224 | 225 | ### error 226 | **( Error )** 227 | 228 | Emitted when a server related error occured. 229 | 230 | `Error` is an `instanceof Error` with .stack trace. 231 | 232 | 233 | #### Messages 234 | 235 | error | description 236 | :------------------------|:--------------------------------- 237 | Server error | Catch server failures 238 | Socket error | Catch communication failures 239 | IP or port not available | This catches EADDRNOTAVAIL errors 240 | 241 | 242 | ```js 243 | server.on ('error', function (err) { 244 | console.log (err); 245 | }); 246 | ``` 247 | 248 | 249 | ### log 250 | **( name, data )** 251 | 252 | Emitted on any of the above events. 253 | Useful for debugging and logging. 254 | 255 | argument | type | description 256 | :--------|:-------|:------------------------ 257 | name | string | Event name, i.e. `track` 258 | value | mixed | Data from the event 259 | 260 | ```js 261 | server.on ('log', function (name, value) { 262 | console.log ('Event: ' + name); 263 | console.log (value); 264 | }); 265 | ``` 266 | 267 | 268 | Notes 269 | ----- 270 | 271 | There is no security built in, anyone could push data to your server. 272 | 273 | The parsed object is based on a lot of testing in the field and 274 | incomplete documentation, I hope I got it right. There are many 275 | variations of the TK102 tracker available with each different data 276 | formats. It may be possible your device or even mine is one of the 277 | clones and incompatible with this module. 278 | 279 | If you can, please provide multiple responses from the [data](#data) 280 | event in a [Github issue](https://github.com/fvdm/nodejs-tk102/issues) 281 | (public) or contact me [directly](https://frankl.in/contact), your data 282 | is handled confidentially. Preferably leave the device tracking with an 283 | interval of 30 seconds or less for about 3 to 5 minutes outdoors or near 284 | a window. This way I can figure out the data format and various states 285 | the tracker reports about itself to the server. 286 | 287 | 288 | Unlicense 289 | --------- 290 | 291 | This is free and unencumbered software released into the public domain. 292 | 293 | Anyone is free to copy, modify, publish, use, compile, sell, or 294 | distribute this software, either in source code form or as a compiled 295 | binary, for any purpose, commercial or non-commercial, and by any 296 | means. 297 | 298 | In jurisdictions that recognize copyright laws, the author or authors 299 | of this software dedicate any and all copyright interest in the 300 | software to the public domain. We make this dedication for the benefit 301 | of the public at large and to the detriment of our heirs and 302 | successors. We intend this dedication to be an overt act of 303 | relinquishment in perpetuity of all present and future rights to this 304 | software under copyright law. 305 | 306 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 307 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 308 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 309 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 310 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 311 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 312 | OTHER DEALINGS IN THE SOFTWARE. 313 | 314 | For more information, please refer to 315 | 316 | 317 | Author 318 | ------ 319 | 320 | [Franklin](https://fvdm.com) 321 | | [Buy me a coffee](https://fvdm.com/donating) 322 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | /* 2 | Start with: node example.js 3 | Telnet to: telnet 127.0.0.1 1337 4 | Copy/paste: 1203292316,0031698765432,GPRMC,211657.000,A,5213.0247,N,00516.7757,E,0.00,273.30,290312,,,A*62,F,imei:123456789012345,123 5 | */ 6 | 7 | var tk102 = require ('tk102'); 8 | var net = require ('net'); 9 | 10 | var gps = '1203292316,0031698765432,GPRMC,211657.000,A,5213.0247,N,00516.7757,E,0.00,273.30,290312,,,A*62,F,imei:123456789012345,123'; 11 | 12 | // fancy console log 13 | function output (data) { 14 | console.log ('\nIncoming GPS data:\n'); 15 | console.dir (data, { 16 | colors: String (process.env.TERM) .match (/color$/) 17 | }); 18 | } 19 | 20 | // report only track event to console 21 | tk102.on ('track', output); 22 | 23 | // wait for server to be ready 24 | tk102.on ('listening', function (lst) { 25 | var client; 26 | 27 | console.log ('TK102 server is ready'); 28 | 29 | // Send data with telnet 30 | client = net.connect (lst.port, function () { 31 | console.log ('Connected to TK102 server'); 32 | console.log ('Sending GPS data string for processing'); 33 | 34 | client.write (gps + '\r\n'); 35 | client.end (); 36 | 37 | console.log ('CTRL+C to exit'); 38 | }); 39 | }); 40 | 41 | // start server 42 | tk102.createServer ({ 43 | port: 1337 44 | }); 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Franklin", 4 | "email": "info@fvdm.com", 5 | "url": "https://fvdm.com" 6 | }, 7 | "name": "tk102", 8 | "description": "Unofficial Xexun TK102 GPS server", 9 | "version": "1.3.4", 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/fvdm/nodejs-tk102.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/fvdm/nodejs-tk102/issues" 16 | }, 17 | "main": "tk102.js", 18 | "dependencies": {}, 19 | "devDependencies": { 20 | "dotest": "^2.3.0" 21 | }, 22 | "engines": { 23 | "node": ">=4.0.0" 24 | }, 25 | "keywords": [ 26 | "gps", 27 | "tk102", 28 | "server", 29 | "tracker" 30 | ], 31 | "license": "Unlicense", 32 | "scripts": { 33 | "test": "dotest" 34 | }, 35 | "tonicExampleFilename": "example.js" 36 | } 37 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Name: tk102 - test.js 3 | Description: Test script for TK102 GPS server for node.js 4 | Source: https://github.com/fvdm/nodejs-tk102 5 | License: Unlicense / Public Domain (see UNLICENSE file) 6 | */ 7 | 8 | var dotest = require ('dotest'); 9 | var app = require ('./'); 10 | 11 | // Str to work with 12 | var input = '1203292316,0031698765432,GPRMC,211657.000,A,5213.0247,N,00516.7757,E,0.00,273.30,290312,,,A*62,F,imei:123456789012345,123'; 13 | 14 | 15 | // module 16 | dotest.add ('Module', function () { 17 | dotest.test () 18 | .isObject ('fail', 'exports', app) 19 | .isObject ('fail', '.settings', app && app.settings) 20 | .isFunction ('fail', '.event', app && app.event) 21 | .isFunction ('fail', '.createServer', app && app.createServer) 22 | .isFunction ('fail', '.closeServer', app && app.closeServer) 23 | .isFunction ('fail', '.fixGeo', app && app.fixGeo) 24 | .isFunction ('fail', '.checksum', app && app.checksum) 25 | .isFunction ('fail', '.parse', app && app.parse) 26 | .done (); 27 | }); 28 | 29 | // checksum valid 30 | dotest.add ('checksum valid', function () { 31 | var data = app.checksum (input); 32 | 33 | dotest.test () 34 | .isExactly ('fail', 'data', data, true) 35 | .done (); 36 | }); 37 | 38 | // checksum invalid 39 | dotest.add ('checksum invalid', function () { 40 | var data = app.checksum (input.toLowerCase ()); 41 | 42 | dotest.test () 43 | .isExactly ('fail', 'data', data, false) 44 | .done (); 45 | }); 46 | 47 | // parser valid 48 | dotest.add ('parse valid', function () { 49 | var data = app.parse (input); 50 | 51 | dotest.test () 52 | .isObject ('fail', 'data', data) 53 | .isExactly ('fail', 'data.raw', data && data.raw, input) 54 | .isExactly ('fail', 'data.checksum', data && data.checksum, true) 55 | .isExactly ('fail', 'data.phone', data && data.phone, '0031698765432') 56 | .isExactly ('fail', 'data.imei', data && data.imei, '123456789012345') 57 | .isExactly ('fail', 'data.datetime', data && data.datetime, '2012-03-29 23:16') 58 | .isObject ('fail', 'data.gps', data && data.gps) 59 | .isExactly ('fail', 'data.gps.date', data && data.gps && data.gps.date, '2012-03-29') 60 | .isExactly ('fail', 'data.gps.time', data && data.gps && data.gps.time, '21:16:57.000') 61 | .isExactly ('fail', 'data.gps.signal', data && data.gps && data.gps.signal, 'full') 62 | .isExactly ('fail', 'data.gps.fix', data && data.gps && data.gps.fix, 'active') 63 | .isObject ('fail', 'data.geo', data && data.geo) 64 | .isExactly ('fail', 'data.geo.latitude', data && data.geo && data.geo.latitude, 52.217078) 65 | .isExactly ('fail', 'data.geo.longitude', data && data.geo && data.geo.longitude, 5.279595) 66 | .isExactly ('fail', 'data.geo.bearing', data && data.geo && data.geo.bearing, 273) 67 | .isObject ('fail', 'data.speed', data && data.speed) 68 | .isExactly ('fail', 'data.speed.knots', data && data.speed && data.speed.knots, 0) 69 | .done (); 70 | }); 71 | 72 | // parser fail 73 | dotest.add ('parse fail', function () { 74 | var data = app.parse ('invalid input'); 75 | 76 | dotest.test () 77 | .isNull ('fail', 'data', data) 78 | .done (); 79 | }); 80 | 81 | 82 | // Start the tests 83 | dotest.run (); 84 | -------------------------------------------------------------------------------- /tk102.js: -------------------------------------------------------------------------------- 1 | /* 2 | Name: tk102 3 | Description: TK102 GPS server for Node.js 4 | Author: Franklin (https://fvdm.com) 5 | Source: https://github.com/fvdm/nodejs-tk102 6 | License: Unlicense (Public Domain, see UNLICENSE file) 7 | */ 8 | 9 | var net = require ('net'); 10 | var EventEmitter = require ('events') .EventEmitter; 11 | var tk102 = new EventEmitter (); 12 | 13 | // device data 14 | var specs = [ 15 | function (raw) { 16 | // 1203292316,0031698765432,GPRMC,211657.000,A,5213.0247,N,00516.7757,E,0.00,273.30,290312,,,A*62,F,imei:123456789012345,123 17 | var result = null; 18 | var str = []; 19 | var datetime = ''; 20 | var gpsdate = ''; 21 | var gpstime = ''; 22 | 23 | try { 24 | raw = raw.trim (); 25 | str = raw.split (','); 26 | 27 | if (str.length === 18 && str [2] === 'GPRMC') { 28 | datetime = str [0] .replace (/([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})/, function (s, y, m, d, h, i) { 29 | return '20' + y + '-' + m + '-' + d + ' ' + h + ':' + i; 30 | }); 31 | 32 | gpsdate = str [11] .replace (/([0-9]{2})([0-9]{2})([0-9]{2})/, function (s, d, m, y) { 33 | return '20' + y + '-' + m + '-' + d; 34 | }); 35 | 36 | gpstime = str [3] .replace (/([0-9]{2})([0-9]{2})([0-9]{2})\.([0-9]{3})/, function (s0, h, i, s, ms) { 37 | return h + ':' + i + ':' + s + '.' + ms; 38 | }); 39 | 40 | result = { 41 | raw: raw, 42 | datetime: datetime, 43 | phone: str [1], 44 | gps: { 45 | date: gpsdate, 46 | time: gpstime, 47 | signal: str [15] === 'F' ? 'full' : 'low', 48 | fix: str [4] === 'A' ? 'active' : 'invalid' 49 | }, 50 | geo: { 51 | latitude: tk102.fixGeo (str [5], str [6]), 52 | longitude: tk102.fixGeo (str [7], str [8]), 53 | bearing: parseInt (str [10], 10) 54 | }, 55 | speed: { 56 | knots: Math.round (str [9] * 1000) / 1000, 57 | kmh: Math.round (str [9] * 1.852 * 1000) / 1000, 58 | mph: Math.round (str [9] * 1.151 * 1000) / 1000 59 | }, 60 | imei: str [16] .replace ('imei:', ''), 61 | checksum: tk102.checksum (raw) 62 | }; 63 | } 64 | } catch (e) { 65 | result = null; 66 | } 67 | 68 | return result; 69 | } 70 | ]; 71 | 72 | 73 | // defaults 74 | tk102.settings = { 75 | ip: '0.0.0.0', 76 | port: 0, 77 | connections: 10, 78 | timeout: 10 79 | }; 80 | 81 | 82 | /** 83 | * Emit an event 84 | * and duplicate to 'log' event 85 | * 86 | * @param {string} name Event name 87 | * @param {string} value Event value 88 | * @return {void} 89 | */ 90 | 91 | tk102.event = function (name, value) { 92 | tk102.emit (name, value); 93 | tk102.emit ('log', name, value); 94 | }; 95 | 96 | 97 | /** 98 | * Catch uncaught exceptions (server kill) 99 | * 100 | * @param {Error} err Error cause 101 | * @return {void} 102 | */ 103 | 104 | process.on ('uncaughtException', function (err) { 105 | var error = new Error ('uncaught exception'); 106 | 107 | error.error = err; 108 | console.log (error); 109 | tk102.event ('error', error); 110 | }); 111 | 112 | 113 | /** 114 | * Create server 115 | * 116 | * @param {object} [vars] Override default settings 117 | * @param {string} [vars.ip='0.0.0.0'] Listen on IP 118 | * @param {number} [vars.port=0] Listen on port, `0` = random 119 | * @param {number} [vars.connections=10] Max server connections 120 | * @param {number} [vars.timeout=10] Socket timeout in seconds 121 | * @return {object} tk102 The server 122 | */ 123 | 124 | tk102.createServer = function (vars) { 125 | var key; 126 | 127 | // override settings 128 | if (typeof vars === 'object' && Object.keys (vars) .length >= 1) { 129 | for (key in vars) { 130 | tk102.settings [key] = vars [key]; 131 | } 132 | } 133 | 134 | // start server 135 | tk102.server = net.createServer (); 136 | 137 | // maximum number of slots 138 | tk102.server.maxConnections = tk102.settings.connections; 139 | 140 | // server started 141 | tk102.server.on ('listening', function () { 142 | tk102.event ('listening', tk102.server.address ()); 143 | }); 144 | 145 | // inbound connection 146 | tk102.server.on ('connection', function (socket) { 147 | var connection = tk102.server.address (); 148 | 149 | connection.remoteAddress = socket.remoteAddress; 150 | connection.remotePort = socket.remotePort; 151 | 152 | tk102.event ('connection', connection); 153 | socket.setEncoding ('utf8'); 154 | 155 | if (tk102.settings.timeout > 0) { 156 | socket.setTimeout (parseInt (tk102.settings.timeout * 1000, 10)); 157 | } 158 | 159 | socket.on ('timeout', function () { 160 | tk102.event ('timeout', connection); 161 | socket.destroy (); 162 | }); 163 | 164 | socket.on ('data', function (data) { 165 | var gps = {}; 166 | var err = null; 167 | 168 | data = data.trim (); 169 | tk102.event ('data', data); 170 | 171 | if (data !== '') { 172 | gps = tk102.parse (data); 173 | 174 | if (gps) { 175 | tk102.event ('track', gps); 176 | } else { 177 | err = new Error ('Cannot parse GPS data from device'); 178 | err.reason = err.message; 179 | err.input = data; 180 | err.connection = connection; 181 | 182 | tk102.event ('fail', err); 183 | } 184 | } 185 | }); 186 | 187 | socket.on ('close', function (hadError) { 188 | connection.hadError = hadError; 189 | tk102.event ('disconnect', connection); 190 | }); 191 | 192 | // error 193 | socket.on ('error', function (error) { 194 | var err = new Error ('Socket error'); 195 | 196 | err.reason = error.message; 197 | err.socket = socket; 198 | err.settings = tk102.settings; 199 | 200 | tk102.event ('error', err); 201 | }); 202 | }); 203 | 204 | tk102.server.on ('error', function (error) { 205 | var err = new Error ('Server error'); 206 | 207 | if (error === 'EADDRNOTAVAIL') { 208 | err = new Error ('IP or port not available'); 209 | } 210 | 211 | err.reason = error.message; 212 | err.input = tk102.settings; 213 | 214 | tk102.event ('error', err); 215 | }); 216 | 217 | // Start listening 218 | tk102.server.listen (tk102.settings.port, tk102.settings.ip); 219 | 220 | return tk102; 221 | }; 222 | 223 | 224 | /** 225 | * Graceful close server 226 | * 227 | * @callback callback 228 | * @param {function} callback `(err)` 229 | * @return {void} 230 | */ 231 | 232 | tk102.closeServer = function (callback) { 233 | if (!tk102.server) { 234 | callback (new Error ('server not started')); 235 | return; 236 | } 237 | 238 | tk102.server.close (callback); 239 | }; 240 | 241 | 242 | /** 243 | * Parse GPRMC string 244 | * 245 | * @param {string} raw Command text from device 246 | * @return {object} data Parsed data 247 | */ 248 | 249 | tk102.parse = function (raw) { 250 | var data = null; 251 | var i = 0; 252 | 253 | while (data === null && i < specs.length) { 254 | data = specs [i] (raw); 255 | i++; 256 | } 257 | 258 | return data; 259 | }; 260 | 261 | 262 | /** 263 | * Clean geo positions, with 6 decimals 264 | * 265 | * @param {string} one Geo position 266 | * @param {string} two Geo direction: N W S E 267 | * @return {float} Decimal geo position 268 | */ 269 | 270 | tk102.fixGeo = function (one, two) { 271 | var minutes = one.substr (-7, 7); 272 | var degrees = parseInt (one.replace (minutes, ''), 10); 273 | 274 | one = degrees + (minutes / 60); 275 | one = parseFloat ((two === 'S' || two === 'W' ? '-' : '') + one); 276 | 277 | return Math.round (one * 1000000) / 1000000; 278 | }; 279 | 280 | 281 | /** 282 | * Check checksum in raw string 283 | * 284 | * @param {string} raw Command text from device 285 | * @return {boolean} check Checksum result 286 | */ 287 | 288 | tk102.checksum = function (raw) { 289 | var str = raw.trim () .split (/[,*#]/); 290 | var strsum = parseInt (str [15], 10); 291 | var strchk = str.slice (2, 15) .join (','); 292 | var check = 0; 293 | var i; 294 | 295 | for (i = 0; i < strchk.length; i++) { 296 | check ^= strchk.charCodeAt (i); 297 | } 298 | 299 | check = parseInt (check.toString (16), 10); 300 | return (check === strsum); 301 | }; 302 | 303 | 304 | // ready 305 | module.exports = tk102; 306 | --------------------------------------------------------------------------------