├── .changeset ├── README.md └── config.json ├── .github └── workflows │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── ConnectionStateMachine.png ├── LICENSE ├── README.md ├── branch-policy.md ├── errorTCP-APRS ├── exitKISSMode ├── gulpfile.js ├── monitorPort ├── monitorSerial-APRS ├── monitorTCP-APRS ├── monitorTCPServer-APRS ├── monitorWS-APRS ├── package.json ├── sharePortToTCP ├── sonar-project.properties ├── src ├── AddressBuilder.js ├── AprsDataConnection.js ├── AprsDataEndpoint.js ├── KISSConnection.js ├── KISSFrameBuilder.js ├── KISSFrameEndpoint.js ├── Request.js ├── SerialKISSFrameEndpoint.js ├── ServerSocketKISSFrameEndpoint.js ├── SocketKISSFrameEndpoint.js ├── WebSocketAprsDataBrowserEndpoint.js ├── WebSocketAprsDataEndpoint.js ├── aprs-data-endpoint-states.js ├── aprs-formats │ └── index.js ├── aprs-info-parser.js ├── aprs-processor.js ├── ax25-utils.js ├── browser.js ├── connection-machine-states.js ├── crc-generator.js ├── exceptions.js ├── index.js ├── info-lex.js ├── kiss-frame-parser.js ├── kiss-framing-transform.js ├── kiss-framing.js ├── mic-e-format │ ├── address-encoding.js │ └── index.js ├── showSampleFrames.js ├── showUndecodedSampleFrames.js ├── showports.js ├── spec │ ├── AprsDataEndpointSpec.js │ ├── RequestSpec.js │ ├── ServerSocketKISSFrameEndpointSpec.js │ ├── aprs-info-parser-spec.js │ ├── aprs-phg-spec.js │ ├── aprs-processor-spec.js │ ├── buffer-slice-spec.js │ ├── crc-spec.js │ ├── frame-builder-spec.js │ ├── frame-parser-spec.js │ ├── helpers │ │ └── sample-frames.js │ ├── info-lex-spec.js │ ├── kiss-frame-builder-spec.js │ ├── latitude-re-spec.js │ ├── latitude-spec.js │ ├── message-text-re-spec.js │ ├── sample-frames.js │ └── support │ │ └── jasmine.json ├── tnc-simulator.js └── validateFrame.js └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | id-token: write 17 | pull-requests: write 18 | steps: 19 | - name: Checkout Repo 20 | uses: actions/checkout@v3 21 | 22 | - name: Setup Node.js 20 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: 20 26 | 27 | - name: Install Dependencies 28 | run: yarn 29 | 30 | - name: Create Release Pull Request 31 | uses: changesets/action@v1 32 | with: 33 | publish: npm publish --provenance --access public 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | nohup.out 3 | **/.fuse_hidden* 4 | node_modules/** 5 | docs/gen 6 | docs/gen/** 7 | /npm-debug.log 8 | /.nyc_output 9 | /.scannerwork 10 | /package-lock.json 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # utils-for-aprs 2 | 3 | ## 3.0.4 4 | 5 | ### Patch Changes 6 | 7 | - 10b6486: Update to publishing workflow. 8 | 9 | ## 3.0.3 10 | 11 | ### Patch Changes 12 | 13 | - a8fa0e7: No real change, just added automated publishing to npm with provenance. 14 | 15 | ## 3.0.2 16 | 17 | ### Patch Changes 18 | 19 | - 9e66753: Add in the changeset action. 20 | - f360152: Update dependencies to remove some vulnerabilities. Any that remain are in the dev dependencies. They are results of including gulp-jsdoc3, which hasn't been updated for quite a while. At some point, we'll look at how to generate documentation in some modern way, probably removing gulp entirely, but they are no harm at the present time. 21 | -------------------------------------------------------------------------------- /ConnectionStateMachine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trasukg/utils-for-aprs/cf7c445f17e9f17ac7e67e154e0fa6697d3bcdcf/ConnectionStateMachine.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Utilities for APRS under Node.js 2 | 3 | APRS is the Advanced Packet Reporting System, designed by Bob Bruninga, WB4APR. 4 | 5 | It uses the Un-numbered Information (UI) packets of the AX.25 protocol to allow 6 | for stations to broadcast small information packets and messages to the other 7 | stations in the surrounding area (typically the local area covered by a digital 8 | repeater, or digipeater). 9 | 10 | This module is a set of utilities for dealing with "RF" packets, in other words 11 | the part of APRS that involves connecting a computer to a packet modem and 12 | then to a radio, in order to monitor and send APRS information over the air 13 | in a local area. Currently these utilities don't deal with 'APRS-IS', 14 | the internet server part of APRS. That may happen in the future, but right now 15 | the focus is on making it possible to deploy a portable station that uses 16 | modern techniques to provide an APRS client. 17 | 18 | # What Can You Do With This Library? 19 | 20 | This library is intended as a building block for applications that use APRS, but 21 | includes a few utilities as well. Longer term, the utilities that prove useful 22 | will be split out into separate projects that include 'utils-for-aprs' as a 23 | dependency, but for now, they're here, and they're useful for testing the library's 24 | functionality. 25 | 26 | Briefly, the library includes a few things: 27 | 28 | ## Endpoints For KISS Frame Devices 29 | 30 | An Endpoint is an interface between JavaScript in Node and some external device, 31 | like a TNC or a DireWolf sound-card interface. 32 | 33 | There are three endpoint classes provided: 34 | * _SerialPortKISSFrameEndpoint_ - This endpoint attaches to a serial-port TNC, 35 | including a TNC connected through a USB-Serial converter. 36 | * _SocketKISSFrameEndpoint_ - This endpoint attaches to KISS-over-TCP/IP devices 37 | like DireWolf's KISS TNC Network option on port 8001 (not the AWGPE interface). 38 | * _ServerSocketKISSFrameEndpoint_ - This endpoint establishes a KISS-over-TCP _server_, 39 | much the same way that DireWolf establishes a server on port 8001. 40 | Clients that understand KISS-over-TCP (like APRSIS32, YAAC, or APRX) can connect to this 41 | server port. The server can accept an unlimited number of connections at once, and 42 | each connection is available separately to the JavaScript code. 43 | 44 | Each endpoint provides one or more 'KISSConnection' objects to your JavaScript code. 45 | Your code can listen for KISS frames coming in to the connection from the outside, 46 | device and send KISS frames out the connection to the device. 47 | 48 | ## APRS Info Packet Utilities 49 | 50 | There is a utility function, 'APRSInfoParser()', that takes KISS frames and decodes 51 | both the AX25 and APRS Info portions of them into JavaScript objects. As well, 52 | there are a few useful utility functions, for instance to convert an array of 53 | callsign objects in a path to a TNC2-style path string for output and display. 54 | 55 | ## Utilities and Tests 56 | 57 | The following scripts can be run from the command line. The source code for each 58 | of them gives some idea of how to use the 'utils-for-aprs' library. 59 | 60 | * _sharePortToTCP_ - Connects to a serial-connected tnc and establishes a server 61 | socket, allowing more than one client to share the TNC and radio. In addition to 62 | receiving packets from the radio, each client also receives any packets that were 63 | sent by other clients. 64 | node sharePortToTCP /dev/ttyUSB0 8001 65 | * _monitorTCP-APRS_ - Acts as a client to a server endpoint. It displays any 66 | packets that are recieved, allowing you to monitor an RF connection easily. 67 | node monitorTCP-APRS raspberrypi:8001 68 | * _monitorTCPServer-APRS_ - Sets up a server endpoint that can be connected to 69 | by a client like APRSIS32, YAAC, or APRX, so you can observe packets sent by a 70 | client. 71 | node monitorTCPServer-APRS 8001 72 | * There are a few other scripts at the top level of the project folder, but the 73 | ones listed are the major ones. 74 | 75 | ## Getting Started 76 | 77 | To experiment with the utilities and work on the library code, do the following: 78 | 1 - Install node.js, as per [The node website](https://nodejs.org/). 79 | 2 - Checkout the project folder as follows: 80 | git clone https://github.com/trasukg/utils-for-aprs.git 81 | 2 - 'cd' into the 'utils-for-aprs' folder. 82 | 3 - Install the required npm packages 83 | npm install 84 | 4 - Run the serial interface with 85 | node sharePortToTCP 86 | 5 - In a different terminal window, run the monitor: 87 | node monitorTCP-APRS localhost: 88 | 89 | Enjoy! 90 | 91 | This library and utilities are licensed under the Apache Software License, version 2. 92 | In fact, if we can build a community around APRS on Node.js, I'd love to see 93 | the project go through incubation at the ASF. 94 | 95 | Cheers, 96 | 97 | Greg Trasuk, VA3TSK 98 | 99 | The phrase APRS is a registered trademark of Bob Bruninga WB4APR. 100 | 101 | # Release Notes 102 | 103 | 2.0.0 - Nov 11, 2016 - Endpoints are working as is port sharing and monitoring 104 | 2.1.0 - Jan 6, 2017 - AddressBuilder and KISSFrameBuilder let you construct 105 | KISS frames for transmittal. 106 | 2.1.1 - Fixed a minor format error in this README file. 107 | 2.2.1 - Added support for Browserify and Web Socket endpoints. 108 | 2.2.2 - Added required dependency on 'ws'. 109 | 2.2.3 - Added required dependency on 'bluebird'. 110 | 2.2.4 - Fixed a problem with ServerSocketKISSFrameEndpoint. 111 | 2.2.5 - Fixed formatting problem in package.JSON 112 | 2.2.6 - Refactored etc to remove bugs flagged by SonarQube. 113 | 2.2.7 - Made 'serialport' library optional, and lazy-loaded. 114 | 2.2.8 - Updated many dependencies to remove security vulnerabilities exposed 115 | by 'npm audit'. They're not all gone, because one of the updates required is 116 | to serialport, but serialport's move from 4.x to 5.x (7.x is current) 117 | had breaking changes to the 118 | interface that will take some time to adopt. 119 | 2.2.9 - Fixed a framing error that caused some transmitted frames to not 120 | be recognized by the receiving device. 121 | 2.2.10 - Eliminated usage of 'new Buffer(...)'. 122 | 3.0.0 - Updated dependencies, including adoption of the new serialport options format for the SerialKISSFrameEndpoint. 123 | -------------------------------------------------------------------------------- /branch-policy.md: -------------------------------------------------------------------------------- 1 | 2 | # Branch Policy 3 | 4 | utils-for-aprs uses trunk-based development. Features are developed on short-lived 5 | feature branches and then quickly merged to 'main'. 6 | 7 | For release, 8 | * Create a release branch locally 9 | git checkout -b release/2.2.11 10 | * Remove the '-beta' from the version number 11 | * Stage and commit 12 | git add . 13 | git commit 14 | * Tag with the version 15 | git tag 2.2.11 16 | * Bump the version to the next '-beta' 17 | * Stage and commit 18 | git add . 19 | git commit 20 | * Push the release branch and tags 21 | git push --set-upstream origin release/2.2.11 22 | git push --tags 23 | * On Github, create a pull request to merge the release branch to 'main' 24 | * Delete the release branch (it was only there to get the version updates on the remote for a pull request). 25 | 26 | # Trying out changesets 27 | 28 | With changesets, we'll end up just using the main branch, and doing pull requests to it, ignoring version 29 | numbers, but adding changesets. So if there's any changesets present, we'll know that it's a beta version. 30 | 31 | What should happen is that once we push to the main branch (i.e. through a PR merge), and changesets should 32 | maintain a PR (separate from any PR that we have) that rolls up the other PRs into a versioned change. 33 | 34 | Just for the first pass, we'll do it without publishing to npm, so any publish step will be done by 35 | pulling the release tag, running npm build and publish manually. 36 | -------------------------------------------------------------------------------- /errorTCP-APRS: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | /* 23 | This is a command-line utility to monitor and print out APRS packets from a 24 | TCP KISS device, like an instance of the DireWolf sound card modem. 25 | */ 26 | var path=require('path'); 27 | var util=require('util'); 28 | var ax25utils=require('./src/index.js').ax25utils; 29 | var SocketKISSFrameEndpoint=require('./src/index.js').SocketKISSFrameEndpoint; 30 | var APRSProcessor=require('./src/index.js').APRSProcessor; 31 | 32 | console.log("process.argv=" + process.argv); 33 | 34 | if (process.argv.length != 3) { 35 | console.log("Usage: node %s :", path.basename(process.argv[1])); 36 | return; 37 | } 38 | 39 | var res=/([^\:]+):([0-9]+)/.exec(process.argv[2]); 40 | if(!res) { 41 | console.log("Usage: node %s :", path.basename(process.argv[1])); 42 | } 43 | var host=res[1]; 44 | var port=res[2]; 45 | 46 | /* 47 | The pipeline is sort of like this: 48 | Endpoint -> APRSProcessor -> Console 49 | */ 50 | 51 | //Create the endpoint 52 | var endpoint=new SocketKISSFrameEndpoint(); 53 | endpoint.host=host; 54 | endpoint.port=port; 55 | var aprsProcessor=new APRSProcessor(); 56 | 57 | // When we get data on the aprsProcessor, show it on the console. 58 | aprsProcessor.on('aprsData', function(frame) { 59 | frame.receivedAt=new Date(); 60 | 61 | console.log( "[" + frame.receivedAt + "]" + ax25utils.addressToString(frame.source) + 62 | '->' + ax25utils.addressToString(frame.destination) + 63 | ' (' + ax25utils.repeaterPathToString(frame.repeaterPath) + ')' + 64 | frame.info); 65 | }); 66 | aprsProcessor.on('error', function(err, frame) { 67 | console.log("Got error event:" + err); 68 | console.log("Frame is:" + JSON.stringify(frame)); 69 | }); 70 | 71 | // The endpoint provides de-escaped KISS frames. Pass them on to the aprsProcessor 72 | 73 | 74 | // Log interesting events... 75 | endpoint.on('connect', function(connection) { 76 | console.log("Connected to port " + endpoint.port); 77 | connection.on('data', function(frame) { 78 | // Attempt to crash the server 79 | throw new Error("Crash!!!"); 80 | }); 81 | connection.on('disconnect', function() { 82 | console.log('Lost connection'); 83 | }); 84 | }); 85 | 86 | 87 | 88 | // Turn on the endpoint. It will attempt to connect in a persistent fashion. 89 | endpoint.enable(); 90 | -------------------------------------------------------------------------------- /exitKISSMode: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | const {SerialPort} = require('serialport'); 23 | 24 | var path=require('path'); 25 | var util=require('util'); 26 | var framing=require('./kiss-framing.js'); 27 | 28 | console.log("process.argv=" + process.argv); 29 | 30 | if (process.argv.length != 3) { 31 | console.log("Usage: node %s ", path.basename(process.argv[1])); 32 | return; 33 | } 34 | 35 | //Open the serial port 36 | var port=new SerialPort( 37 | { 38 | path: process.argv[2], 39 | baudRate: 1200, 40 | } ); 41 | // On open, install a handler that prints the data. 42 | port.on('open', function() { 43 | console.log("Port opened"); 44 | port.on('data', function(data) { 45 | console.log("Data instanceof Buffer=" + (data instanceof Buffer)); 46 | var output=""; 47 | for(var i=0; i < data.length; i++) { 48 | output=output+ data[i].toString(16) + " "; 49 | } 50 | console.log(output); 51 | }); 52 | var outputBuf=Buffer.alloc(2); 53 | outputBuf[0]=0xc0; 54 | outputBuf[1]=0xff; 55 | 56 | port.write(outputBuf); 57 | }); 58 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var gulp = require('gulp'); 21 | var browserSync = require('browser-sync').create(); 22 | 23 | // Static server 24 | gulp.task('show-docs', function() { 25 | browserSync.init({ 26 | server: { 27 | baseDir: "./docs/gen" 28 | } 29 | }); 30 | }); 31 | 32 | var jsdoc = require('gulp-jsdoc3'); 33 | 34 | gulp.task('doc', function (cb) { 35 | gulp.src(['README.md', 'src/**/*.js'], {read: false}) 36 | .pipe(jsdoc(cb)); 37 | }); 38 | 39 | const jasmine = require('gulp-jasmine'); 40 | 41 | gulp.task('test', () => 42 | gulp.src('src/spec/**/*.js') 43 | // gulp-jasmine works on filepaths so you can't have any plugins before it 44 | .pipe(jasmine()) 45 | ); 46 | -------------------------------------------------------------------------------- /monitorPort: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | var serialport=require('serialport'); 23 | var SerialPort=serialport.SerialPort; 24 | var path=require('path'); 25 | var util=require('util'); 26 | const { TncFrameParser } = require('./src/kiss-framing-transform'); 27 | const { Transform } = require('node:stream'); 28 | 29 | console.log("process.argv=" + process.argv); 30 | 31 | if (process.argv.length != 3) { 32 | console.log("Usage: node %s ", path.basename(process.argv[1])); 33 | return; 34 | } 35 | 36 | //Open the serial port 37 | var port=new SerialPort( 38 | { 39 | path: process.argv[2], 40 | baudRate: 1200, 41 | /* parser: framing.tncFrameParser() */ 42 | } ); 43 | // On open, install a handler that prints the data. 44 | port.on('open', function() { 45 | console.log("Port opened"); 46 | var tncFrameParser = new TncFrameParser(); 47 | port.pipe(tncFrameParser); 48 | tncFrameParser.on('data', function(data) { 49 | //console.log("Data instanceof Buffer=" + (data instanceof Buffer)); 50 | var output="["; 51 | for(var i=0; i < data.length; i++) { 52 | if (i != 0) { 53 | output=output+", "; 54 | } 55 | output=output+ "0x" + data[i].toString(16); 56 | } 57 | output=output+"],"; 58 | console.log(output); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /monitorSerial-APRS: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | /* 23 | This is a command-line utility to monitor and print out APRS packets from a 24 | TCP KISS device, like an instance of the DireWolf sound card modem. 25 | */ 26 | var path=require('path'); 27 | var util=require('util'); 28 | var SerialKISSFrameEndpoint=require('./src/SerialKISSFrameEndpoint.js'); 29 | var APRSProcessor=require("./src/aprs-processor.js"); 30 | 31 | console.log("process.argv=" + process.argv); 32 | 33 | if (process.argv.length != 3) { 34 | console.log("Usage: node %s ", path.basename(process.argv[1])); 35 | return; 36 | } 37 | 38 | /* 39 | The pipeline is sort of like this: 40 | Endpoint -> APRSProcessor -> Console 41 | */ 42 | 43 | //Create the endpoint 44 | var endpoint=new SerialKISSFrameEndpoint( { 45 | path: process.argv[2], 46 | baudRate: 1200 47 | }); 48 | 49 | var aprsProcessor=new APRSProcessor(); 50 | 51 | // When we get data on the aprsProcessor, show it on the console. 52 | aprsProcessor.on('aprsData', function(frame) { 53 | console.log(frame); 54 | }); 55 | aprsProcessor.on('error', function(err) { 56 | console.log("Got eror event:" + err); 57 | }); 58 | 59 | // Log interesting events... 60 | endpoint.on('connect', function(connection) { 61 | console.log("Connected to port " + endpoint.port); 62 | // The endpoint provides de-escaped KISS frames. Pass them on to the aprsProcessor 63 | connection.on('data', function(frame) { 64 | console.log("Got frame " + frame); 65 | aprsProcessor.data(frame); 66 | }); 67 | }); 68 | // Log interesting events... 69 | endpoint.on('error', function(err) { 70 | console.log("Error on endpoint:" + err); 71 | }); 72 | 73 | endpoint.on('disconnect', function() { 74 | console.log('Lost connection'); 75 | }); 76 | 77 | // Turn on the endpoint. It will attempt to connect in a persistent fashion. 78 | console.log("Enabling the endpoint"); 79 | endpoint.enable(); 80 | setTimeout(function() { 81 | console.log("Timeout was triggered."); 82 | }, 5000) 83 | -------------------------------------------------------------------------------- /monitorTCP-APRS: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | /* 23 | This is a command-line utility to monitor and print out APRS packets from a 24 | TCP KISS device, like an instance of the DireWolf sound card modem. 25 | */ 26 | var path=require('path'); 27 | var util=require('util'); 28 | var ax25utils=require('utils-for-aprs').ax25utils; 29 | var SocketKISSFrameEndpoint=require('utils-for-aprs').SocketKISSFrameEndpoint; 30 | var APRSProcessor=require('utils-for-aprs').APRSProcessor; 31 | 32 | console.log("process.argv=" + process.argv); 33 | 34 | if (process.argv.length != 3) { 35 | console.log("Usage: node %s :", path.basename(process.argv[1])); 36 | return; 37 | } 38 | 39 | var res=/([^\:]+):([0-9]+)/.exec(process.argv[2]); 40 | if(!res) { 41 | console.log("Usage: node %s :", path.basename(process.argv[1])); 42 | } 43 | var host=res[1]; 44 | var port=res[2]; 45 | 46 | /* 47 | The pipeline is sort of like this: 48 | Endpoint -> APRSProcessor -> Console 49 | */ 50 | 51 | //Create the endpoint 52 | var endpoint=new SocketKISSFrameEndpoint(); 53 | endpoint.host=host; 54 | endpoint.port=port; 55 | var aprsProcessor=new APRSProcessor(); 56 | 57 | // When we get data on the aprsProcessor, show it on the console. 58 | aprsProcessor.on('aprsData', function(frame) { 59 | frame.receivedAt=new Date(); 60 | 61 | console.log( "[" + frame.receivedAt + "]" + ax25utils.addressToString(frame.source) + 62 | '->' + ax25utils.addressToString(frame.destination) + 63 | ' (' + ax25utils.repeaterPathToString(frame.repeaterPath) + ')' + 64 | frame.info); 65 | }); 66 | aprsProcessor.on('error', function(err, frame) { 67 | console.log("Got error event:" + err); 68 | console.log("Frame is:" + JSON.stringify(frame)); 69 | }); 70 | 71 | // The endpoint provides de-escaped KISS frames. Pass them on to the aprsProcessor 72 | 73 | 74 | // Log interesting events... 75 | endpoint.on('connect', function(connection) { 76 | console.log("Connected to port " + endpoint.port); 77 | connection.on('data', function(frame) { 78 | aprsProcessor.data(frame); 79 | }); 80 | connection.on('disconnect', function() { 81 | console.log('Lost connection'); 82 | }); 83 | }); 84 | 85 | 86 | 87 | // Turn on the endpoint. It will attempt to connect in a persistent fashion. 88 | endpoint.enable(); 89 | -------------------------------------------------------------------------------- /monitorTCPServer-APRS: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | /* 23 | This is a command-line utility to monitor and print out APRS packets from a 24 | TCP KISS device, like an instance of the DireWolf sound card modem. 25 | */ 26 | var path=require('path'); 27 | var util=require('util'); 28 | var ServerSocketKISSFrameEndpoint= 29 | require('utils-for-aprs').ServerSocketKISSFrameEndpoint; 30 | var APRSProcessor=require('utils-for-aprs').APRSProcessor; 31 | 32 | console.log("process.argv=" + process.argv); 33 | 34 | if (process.argv.length != 3) { 35 | console.log("Usage: node %s ", path.basename(process.argv[1])); 36 | return; 37 | } 38 | 39 | /* 40 | The pipeline is sort of like this: 41 | Endpoint -> APRSProcessor -> Console 42 | */ 43 | 44 | //Create the endpoint 45 | var endpoint=new ServerSocketKISSFrameEndpoint(); 46 | endpoint.host="localhost"; 47 | endpoint.port=process.argv[2]; 48 | var aprsProcessor=new APRSProcessor(); 49 | 50 | // When we get data on the aprsProcessor, show it on the console. 51 | aprsProcessor.on('aprsData', function(frame) { 52 | console.log(frame); 53 | }); 54 | aprsProcessor.on('error', function(err) { 55 | console.log("Got eror event:" + err); 56 | }); 57 | 58 | // The endpoint provides de-escaped KISS frames. Pass them on to the aprsProcessor 59 | 60 | 61 | // Log interesting events... 62 | endpoint.on('connect', function(connection) { 63 | console.log("Connected to port " + endpoint.port); 64 | connection.on('data', function(frame) { 65 | aprsProcessor.data(frame); 66 | }); 67 | connection.on('disconnect', function() { 68 | console.log('Lost connection'); 69 | }); 70 | }); 71 | 72 | 73 | 74 | // Turn on the endpoint. It will attempt to connect in a persistent fashion. 75 | endpoint.enable(); 76 | -------------------------------------------------------------------------------- /monitorWS-APRS: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | /* 23 | This is a command-line utility to monitor and print out APRS packets from a 24 | TCP KISS device, like an instance of the DireWolf sound card modem. 25 | */ 26 | var path=require('path'); 27 | var util=require('util'); 28 | var ax25utils=require('utils-for-aprs').ax25utils; 29 | var WebSocketAprsDataEndpoint=require('utils-for-aprs').WebSocketAprsDataEndpoint; 30 | console.log("process.argv=" + process.argv); 31 | 32 | if (process.argv.length != 3) { 33 | console.log("Usage: node %s ", path.basename(process.argv[1])); 34 | return; 35 | } 36 | 37 | var url=process.argv[2]; 38 | /* 39 | The pipeline is sort of like this: 40 | Endpoint -> Console 41 | */ 42 | 43 | //Create the endpoint 44 | var endpoint=new WebSocketAprsDataEndpoint(url); 45 | 46 | // When we get data on the endpoint, show it on the console. 47 | endpoint.on('aprsData', function(frame) { 48 | frame.receivedAt=new Date(); 49 | 50 | console.log( "[" + frame.receivedAt + "]" + ax25utils.addressToString(frame.source) + 51 | '->' + ax25utils.addressToString(frame.destination) + 52 | ' (' + ax25utils.repeaterPathToString(frame.repeaterPath) + ')' + 53 | frame.info); 54 | }); 55 | endpoint.on('error', function(err, frame) { 56 | console.log("Got error event:" + err); 57 | console.log("Frame is:" + JSON.stringify(frame)); 58 | }); 59 | 60 | // Log interesting events... 61 | endpoint.on('connect', function(connection) { 62 | console.log("Connected to " + endpoint.url); 63 | connection.on('disconnect', function() { 64 | console.log('Lost connection'); 65 | }); 66 | }); 67 | 68 | // Turn on the endpoint. It will attempt to connect in a persistent fashion. 69 | endpoint.enable(); 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "utils-for-aprs", 3 | "version": "3.0.4", 4 | "description": "A suite of utilities for Advanced Packet Reporting System over a KISS TNC.", 5 | "main": "src/index.js", 6 | "browser": "src/browser.js", 7 | "scripts": { 8 | "test": "nyc --reporter=text jasmine" 9 | }, 10 | "author": "Greg Trasuk", 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "@trasukg/state-machine": "1.0.1", 14 | "bluebird": "3.7.2", 15 | "lex": "1.7.9", 16 | "sprintf-js": "1.1.2", 17 | "ws": "8.18.0" 18 | }, 19 | "keywords": [ 20 | "amateur radio", 21 | "aprs", 22 | "ax.25", 23 | "KISS" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "git@github.com:trasukg/utils-for-aprs.git" 28 | }, 29 | "devDependencies": { 30 | "@changesets/cli": "^2.27.9", 31 | "gulp": "^5.0.0", 32 | "gulp-jasmine": "4.0.0", 33 | "gulp-jsdoc3": "3.0.0", 34 | "serialport": "10.5.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sharePortToTCP: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Licensed to the Apache Software Foundation (ASF) under one 5 | or more contributor license agreements. See the NOTICE file 6 | distributed with this work for additional information 7 | regarding copyright ownership. The ASF licenses this file 8 | to you under the Apache License, Version 2.0 (the 9 | "License"); you may not use this file except in compliance 10 | with the License. You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, 15 | software distributed under the License is distributed on an 16 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | KIND, either express or implied. See the License for the 18 | specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | /* 23 | This is a command-line utility to share out a serial port TNC to one or more 24 | KISS-TCP client programs. 25 | 26 | For instance, you could have a serial TNC shared between APRX and a client 27 | program like APRSIS32. 28 | */ 29 | var path=require('path'); 30 | var util=require('util'); 31 | var ServerSocketKISSFrameEndpoint= 32 | require('./src').ServerSocketKISSFrameEndpoint; 33 | var SerialKISSFrameEndpoint=require('./src').SerialKISSFrameEndpoint; 34 | 35 | if (process.argv.length != 4) { 36 | console.log("Usage: node %s ", path.basename(process.argv[1])); 37 | return; 38 | } 39 | 40 | /* 41 | The pipeline is sort of like this: 42 | SerialKISSFrameEndpoint Endpoint -> sharePortToTCP -> ServerSocketKISSFrameEndpoint 43 | */ 44 | 45 | var device=process.argv[2]; 46 | var port=process.argv[3]; 47 | 48 | //Create the server socket endpoint 49 | var serverEndpoint=new ServerSocketKISSFrameEndpoint("0.0.0.0", port); 50 | var serialEndpoint=new SerialKISSFrameEndpoint(device, {baudRate: 1200}); 51 | 52 | /* All connections are put into a Set, which lets us use forEach() 53 | */ 54 | var allConnections=new Set(); 55 | 56 | // Log interesting events... 57 | serverEndpoint.on('connect', function(connection) { 58 | console.log("TCP Endpoint received a connection from " + 59 | connection.socket.remoteAddress + ":" + connection.socket.remotePort); 60 | allConnections.add(connection); 61 | connection.on('data', function(frame) { 62 | relayToAllBut(connection,frame); 63 | }); 64 | connection.on('close', function() { 65 | allConnections.delete(connection); 66 | console.log("TCP connection from " + 67 | connection.socket.remoteAddress + ":" + connection.socket.remotePort + 68 | " was closed"); 69 | }); 70 | console.log("Returning from connect()..."); 71 | }); 72 | 73 | serverEndpoint.on('listening', function() { 74 | console.log("KISS TCP server established - listening for connections on port " + [port]); 75 | }); 76 | 77 | serverEndpoint.on('error', function(err) { 78 | console.log("Error on server endpoint:" + err); 79 | }); 80 | 81 | serialEndpoint.on('error', function(err) { 82 | console.log("Error on serial TNC endpoint:" + err); 83 | }); 84 | 85 | serialEndpoint.on('connect', function(connection) { 86 | console.log("Connected to serial TNC on " + device ); 87 | allConnections.add(connection); 88 | connection.on('data', function(frame) { 89 | relayToAllBut(connection, frame); 90 | }); 91 | connection.on('disconnect', function() { 92 | allConnections.delete(connection); 93 | console.log('Serial TNC connection was closed'); 94 | }); 95 | }); 96 | 97 | var relayToAllBut=function(source, frame) { 98 | console.log("Got frame from " + source); 99 | 100 | allConnections.forEach(function(connection){ 101 | if (connection === source) { 102 | //console.log("Skipping destination " + connection); 103 | } else { 104 | connection.data(frame); 105 | } 106 | }); 107 | 108 | } 109 | 110 | /* Turn on the endpoints. 111 | - Serial Endpoint will attempt to connect to TNC 112 | - ServerSocketKISSFrameEndpoint will open the server port and start 113 | accepting connections 114 | */ 115 | serverEndpoint.enable(); 116 | serialEndpoint.enable(); 117 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # must be unique in a given SonarQube instance 2 | sonar.projectKey=my:utils-for-aprs 3 | # this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1. 4 | sonar.projectName=utils-for-aprs 5 | sonar.projectVersion=1.1 6 | 7 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 8 | # This property is optional if sonar.modules is set. 9 | sonar.sources=src 10 | 11 | # Encoding of the source code. Default is default system encoding 12 | #sonar.sourceEncoding=UTF-8 13 | -------------------------------------------------------------------------------- /src/AddressBuilder.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var AddressBuilder=function(callsign) { 21 | var res=/([A-Z0-9]{2,6})(-([0-9]{1,2}))?/.exec(callsign); 22 | if (!res) { 23 | throw new exceptions.FormatError("callsign"); 24 | } 25 | this.callsign=res[1]; 26 | if(res[3]) { 27 | this.ssid=parseInt(res[3]); 28 | } else { 29 | this.ssid=0; 30 | } 31 | this.valueForHasBeenRepeated=false; 32 | this.extensionBit=false; 33 | } 34 | 35 | AddressBuilder.prototype.build=function() { 36 | var address={ 37 | "callsign": this.callsign, 38 | "ssid": this.ssid, 39 | "hasBeenRepeated":this.valueForHasBeenRepeated, 40 | "rr":3, 41 | "extensionBit": this.extensionBit 42 | } 43 | return address; 44 | } 45 | 46 | AddressBuilder.prototype.hasBeenRepeated=function(val) { 47 | this.valueForHasBeenRepeated=(val===undefined)?true:val; 48 | return this; 49 | } 50 | 51 | AddressBuilder.prototype.withExtensionBit=function(val) { 52 | this.extensionBit= (val===undefined)?true:val; 53 | return this; 54 | } 55 | 56 | module.exports=function(arg) { 57 | return new AddressBuilder(arg); 58 | } 59 | -------------------------------------------------------------------------------- /src/AprsDataConnection.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var util=require('util'); 21 | var EventEmitter=require('events'); 22 | var Escaper=require('./kiss-framing.js').Escaper; 23 | 24 | /** 25 | This class is a base class for some class that knows how to write data 26 | to an AprsData connection. 27 | @alias module:utils-for-aprs.AprsDataConnection 28 | @fires module:utils-for-aprs.AprsDataConnection#data 29 | @fires module:utils-for-aprs.AprsDataConnection#close 30 | @class 31 | */ 32 | module.exports=function() { 33 | 34 | } 35 | 36 | util.inherits(module.exports, EventEmitter); 37 | 38 | /** 39 | Write APRS data to the connection. 40 | @param data An object containing an APRS packet. 41 | @alias module:utils-for-aprs.AprsDataConnection.data 42 | */ 43 | module.exports.prototype.aprsData=function(aprsData) { 44 | 45 | } 46 | 47 | /** 48 | Incoming Data Event. The event payload is a buffer that contains a de-escaped 49 | KISS frame. 50 | @event module:utils-for-aprs.AprsDataConnection#aprsData 51 | @type {Object} 52 | */ 53 | 54 | /** 55 | Close event. The connection is being closed. 56 | @event module:utils-for-aprs.AprsDataConnection#close 57 | @type {void} 58 | */ 59 | -------------------------------------------------------------------------------- /src/AprsDataEndpoint.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var util=require('util'); 21 | var StateMachine=require('@trasukg/state-machine'); 22 | var EventEmitter=require('events'); 23 | var AprsDataConnection=require('./AprsDataConnection.js'); 24 | var Request=require('./Request'); 25 | 26 | // 'connection-machine-states' contains a state machine description that has the 27 | // persistent connection behaviour that we want. 28 | var states= require('./aprs-data-endpoint-states.js'); 29 | 30 | /** 31 | This is an "Endpoint" that attempts to make a connection to a TCP KISS 32 | device, e.g. an instance of the DireWolf soundcard modem. 33 | 34 | Once enabled (by calling 'enable()'), the endpoint attempts to make a connection 35 | to the host and port that have been set into its properties. 36 | 37 | It will emit 'connect' and 'disconnect' events when it makes and/or loses a 38 | connection. If the initial connection fails, or the connection is lost, the 39 | endpoint waits for a certain period of time (default 5s, configured by retryTime), 40 | and then attempts to reconnect. 41 | 42 | The 'connect' event delivers a KISSConnection object that represents the 43 | connection. It scans the input for properly-framed KISS packets, removing any 44 | byte-stuffing as required. When a KISS frame is received, the endpoint emits a 45 | 'data' event, with the received frame as the argument. A KISS frame can be sent 46 | down the connection by calling the connection's 'data' method. 47 | 48 | @alias module:utils-for-aprs.AprsDataEndpoint 49 | @extends EventEmitter 50 | @fires AprsDataEndpoint#connection 51 | @constructor 52 | */ 53 | var AprsDataEndpoint=function() { 54 | EventEmitter.apply(this); 55 | 56 | /** 57 | Enable the endpoint. Tells the endpoint to open its actual connection and 58 | begin trying to make connections. If a connection fails, the endpoint will 59 | typically wait for five seconds and then try again, so long as the endpoint is 60 | enabled. 61 | */ 62 | this.enable=function() { 63 | /* Note - this function is here for documentation purposes, but is actually 64 | replaced by the state machine setup, to be an event on the state machine. 65 | */ 66 | }; 67 | 68 | /** 69 | Disable the endpoint. Tells the endpoint to close its actual connection and 70 | cease trying to make connections. Connections currently in process will 71 | normall be closed. 72 | */ 73 | this.disable=function() { 74 | /* Note - this function is here for documentation purposes, but is actually 75 | replaced by the state machine setup, to be an event on the state machine. 76 | */ 77 | }; 78 | StateMachine.call(this, states, 'Idle'); 79 | 80 | this._outstandingRequests=new Map(); 81 | this._nextMessageId=1; 82 | }; 83 | 84 | util.inherits(AprsDataEndpoint, EventEmitter); 85 | 86 | /** 87 | This function should open the physical connection. It is called on 88 | entry to the Connecting state. On successful opening, it should call 89 | 'self.connectionSucceeded()' and on failure, it should call 'self.connectionFailed()', 90 | as expected by the state machine. 91 | 92 | @abstract 93 | */ 94 | AprsDataEndpoint.prototype.openConnection=function() { 95 | console.log("Whoops - the abstract openConnection got called!"); 96 | } 97 | 98 | /** 99 | 100 | (implemented by subclasses) 101 | 102 | The connection machine state table calls this function when the 103 | Connected state is entered. It should create a KISSConnection object that 104 | is wrapped around the actual connection, and then emit a 'connect' event that 105 | passes the KISSConnection object as its argument. Clients can then subscribe 106 | to either 'data' events on the KISSConnection to receive frames. They can 107 | also call 'data(frame)' on the KISSConnection to send a frame. When the 108 | connection gets closed, it will emit a 'closed' event, so the client knows to 109 | stop using it. 110 | 111 | When data arrives on the underlying port, it should be passed through the 112 | kissFrameParser, such that the connection object emits a 'data' event that 113 | contains the unescaped KISS frame, with the command header stripped. 114 | 115 | @abstract 116 | */ 117 | AprsDataEndpoint.prototype.emitConnect=function() { 118 | this.emit('connect', undefined); 119 | } 120 | 121 | /** 122 | (Implemented by subclasses) 123 | Close the connection and emit a 'disconnect' event. 124 | @abstract 125 | */ 126 | AprsDataEndpoint.prototype.closeConnectionAndEmitDisconnect=function() { 127 | this.emit('disconnect'); 128 | } 129 | 130 | /** 131 | Called by the state machine states to trigger a timer that will call 132 | the timeout() method after a fixed time span (5000ms). 133 | */ 134 | AprsDataEndpoint.prototype.triggerWait=function() { 135 | // The closures will be called in the context of the socket, so store the current 136 | // value of 'this' for use in the closures. 137 | var self=this; 138 | setTimeout(function() { 139 | self.timeout(); 140 | }, 5000) 141 | } 142 | 143 | /** 144 | (implemented by subclass) 145 | 146 | Called to send data down the connected pipe. 147 | data is an object that will be converted to JSON. 148 | */ 149 | AprsDataEndpoint.prototype._send=function(data) { 150 | console.log("Sending data: " + JSON.stringify(data)); 151 | } 152 | 153 | /** Initiate a request, storing the request data, etc. */ 154 | AprsDataEndpoint.prototype.initiateRequest=function(data) { 155 | data.msgId=this._nextMessageId++; 156 | var request=new Request(data); 157 | // Write the request out to the connected peer. */ 158 | var self=this; 159 | // Add to list of outstanding requests. 160 | self._outstandingRequests.set(data.msgId, request); 161 | return request.send( 162 | function(data) {self.send(data); }, 163 | function() { 164 | console.log('Finished request ' + data.msgId); 165 | self._outstandingRequests.delete(data.msgId); 166 | }); 167 | } 168 | 169 | /** 170 | This method should be called by the subclass whenever data is received from 171 | the opposite end of the connection. It is used to manage answers to requests. 172 | */ 173 | AprsDataEndpoint.prototype._incoming_message = function (message) { 174 | // See if this is a reply message 175 | console.log("_incoming_message called with " + JSON.stringify(message)); 176 | if (message.replyTo) { 177 | // Look up the request that goes with it. 178 | var request=this._outstandingRequests.get(message.replyTo); 179 | if (request) { 180 | request.reply(message); 181 | /* The promise.tap(...) added in initiateRequest() will remove the 182 | request from the list of outstanding requests, when the request's promise 183 | resolves. 184 | */ 185 | } 186 | } 187 | }; 188 | 189 | /** 190 | Connection event 191 | @event module:utils-for-aprs.AprsDataEndpoint#connection 192 | @type {AprsDataConnection} 193 | */ 194 | 195 | // Export the endpoint constructor. 196 | module.exports=AprsDataEndpoint; 197 | -------------------------------------------------------------------------------- /src/KISSConnection.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var util=require('util'); 21 | var EventEmitter=require('events'); 22 | var Escaper=require('./kiss-framing.js').Escaper; 23 | 24 | /** 25 | This class is a base class for some class that knows how to write data 26 | to a KISS Connection. 27 | @alias module:utils-for-aprs.KISSConnection 28 | @fires module:utils-for-aprs.KISSConnection#data 29 | @fires module:utils-for-aprs.KISSConnection#close 30 | @class 31 | @param bufferLength The length of the output buffer. Defaults to 1024 if 32 | undefined. 33 | */ 34 | module.exports=function(bufferLength) { 35 | this.escaper=new Escaper(bufferLength?bufferLength:1024); 36 | } 37 | 38 | util.inherits(module.exports, EventEmitter); 39 | 40 | /** 41 | Write data to the connection. 42 | @param data A Buffer containing a KISS frame. Should be unescaped, and 43 | typically starts with the 'data' command. 44 | @alias module:utils-for-aprs.KISSConnection.data 45 | */ 46 | module.exports.prototype.data=function(data) { 47 | var buffer=this.escaper.escape(data); 48 | this.write(buffer); 49 | this.flush(); 50 | } 51 | 52 | /** 53 | Incoming Data Event. The event payload is a buffer that contains a de-escaped 54 | KISS frame. 55 | @event module:utils-for-aprs.KISSConnection#data 56 | @type {Buffer} 57 | */ 58 | 59 | /** 60 | Close event. The connection is being closed. 61 | @event module:utils-for-aprs.KISSConnection#close 62 | @type {void} 63 | */ 64 | -------------------------------------------------------------------------------- /src/KISSFrameBuilder.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | const validateAddress=function(address) { 21 | var callsignLength=address.callsign.length; 22 | if (callsignLength>7) { 23 | throw new exceptions.FormatError('Callsigns need to be 7 characters or less' 24 | + ' (' + address.callsign + ')' + ' for KISS frames.'); 25 | } 26 | if (typeof(address.ssid) === 'string' || address.ssid <0 || address.ssid>15) { 27 | throw new exceptions.FormatError('SSID needs to be integer 0-15 for KISS frame.') 28 | } 29 | } 30 | 31 | const SPACE_IN_CALLSIGN=' '.charCodeAt(0)<<1; 32 | 33 | const writeAddress=function(buffer, writeIndex, address){ 34 | validateAddress(address); 35 | var callsignLength=address.callsign.length; 36 | // We're going to casually ignore Unicode callsigns for now (are they even a thing?) 37 | var i=0; 38 | while(i ':' ['{' ] 165 | */ 166 | var parseMessage=function() { 167 | this.frame.dataType='message'; 168 | formats.parseAddressee.call(this); 169 | if (this.lexer.current.token !== InfoLexer.COLON) { 170 | //console.log('lexer.theRest=' + this.lexer.theRest()); 171 | throw new exceptions.FormatError("Message format is incorrect - should be ':' after addressee"); 172 | } 173 | formats.parseMessageText.call(this); 174 | } 175 | 176 | /** 177 | Objects: 178 | ';' (positionWithTimestamp) 179 | */ 180 | var parseObject=function() { 181 | this.frame.dataType='object'; 182 | formats.parseObjectName.call(this); 183 | if (this.lexer.current.token === InfoLexer.STAR) { 184 | this.frame.killed=false; 185 | } else if (this.lexer.current.token !== InfoLexer.UNDERSCORE) { 186 | //console.log('lexer.theRest=' + this.lexer.theRest()); 187 | throw new exceptions.FormatError("Object format is incorrect - should be " + 188 | "* or _ after name"); 189 | } 190 | parsePositionWithTimestampData.call(this); 191 | } 192 | 193 | var parseStationCapability=function() { 194 | this.frame.dataType='stationCapabilities'; 195 | var rs=/IGATE,MSG_CNT=(\d+),LOC_CNT=(\d+)/.exec(this.lexer.theRest()); 196 | if (!rs) { 197 | throw new exceptions.FormatError("Unknown station capability:" + 198 | this.lexer.theRest()); 199 | } 200 | this.frame.capability='IGATE'; 201 | this.frame.messageCount=parseInt(rs[1]); 202 | this.frame.localStationCount=parseInt(rs[2]); 203 | } 204 | 205 | var parseThirdPartyTraffic=function() { 206 | // Transfer the source, destination and path to forwardingXYZ 207 | this.frame.forwardingSource=this.frame.source; 208 | this.frame.forwardingRepeaterPath=this.frame.repeaterPath; 209 | this.frame.forwardingDestination=this.frame.destination; 210 | delete this.frame.destination; 211 | delete this.frame.source; 212 | delete this.frame.repeaterPath; 213 | 214 | // Get the lexer started (this isn't done by the parse() function). 215 | this.lexer.advance(); 216 | 217 | parseThirdPartyHeader.call(this); 218 | parseThirdPartyData.call(this); 219 | }; 220 | 221 | var parseThirdPartyHeader=function() { 222 | this.frame.source=formats.parseTNC2Callsign.call(this); 223 | if (this.lexer.current.token !== InfoLexer.GREATER_THAN) { 224 | throw new exceptions.FormatError("Third-party header format is incorrect: " + 225 | "should include '>'"); 226 | } 227 | this.lexer.advance(); 228 | this.frame.destination=formats.parseTNC2Callsign.call(this); 229 | this.frame.repeaterPath=[]; 230 | /* 231 | The third-party header must include network ID and receiving gateway stn. 232 | We treat this as a repeaterlist, and just keep parsing until the list no 233 | longer continues (i.e. the next token isn't a comma). 234 | */ 235 | parseRepeaterPathList.call(this); 236 | if (this.lexer.current.token !== InfoLexer.COLON) { 237 | throw new exceptions.FormatError("Expected ':' after third-party header"); 238 | } 239 | 240 | }; 241 | 242 | var parseRepeaterPathList=function() { 243 | if(this.lexer.current.token===InfoLexer.COMMA) { 244 | this.lexer.advance(); 245 | this.frame.repeaterPath.push(formats.parseTNC2Callsign.call(this)); 246 | parseRepeaterPathList.call(this); 247 | } 248 | } 249 | var parseThirdPartyData=function() { 250 | this.frame.info=this.lexer.theRest(); 251 | parseInfo.call(this); 252 | }; 253 | 254 | var dataTypeParsers={ 255 | 62 : parseStatus, 256 | 84 : parseTelemetry, 257 | 47 : parsePositionWithTimestampNoMessaging, 258 | 64 : parsePositionWithTimestampWithMessaging, 259 | 61 : parsePositionWithoutTimestampWithMessaging, 260 | 33 : parsePositionWithoutTimestampNoMessaging, 261 | 96 : parseCurrentMicEData, 262 | 39 : parseCurrentMicEData, 263 | 58 : parseMessage, 264 | 59 : parseObject, 265 | 60 : parseStationCapability, 266 | 125 : parseThirdPartyTraffic 267 | }; 268 | 269 | var parseInfo=function() { 270 | this.frame.dataTypeChar=this.lexer.advanceFixed(1).charCodeAt(0); 271 | var parser=dataTypeParsers[this.frame.dataTypeChar]; 272 | if (parser === undefined) { 273 | throw new exceptions.InfoError(sprintf("%d (%s)", 274 | this.frame.dataTypeChar, String.fromCharCode(this.frame.dataTypeChar))); 275 | } 276 | parser.call(this); 277 | }; 278 | 279 | APRSInfoParser.prototype.parse=function(frame) { 280 | this.frame=frame; 281 | // Prime the lexical analyzer for the rest of the frame. 282 | this.lexer.setInput(frame.info); 283 | parseInfo.call(this); 284 | this.frame=undefined; 285 | } 286 | 287 | 288 | -------------------------------------------------------------------------------- /src/aprs-processor.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | /* 21 | This is a processor for KISS-formatted APRS data. 22 | It can either deal with raw bytes that contain KISS-formatted data, or you 23 | can feed it KISS frames that have already been decoded for framing and de-escaped. 24 | 25 | Either way, it emits an 'aprsData' event on a good frame decode, and an 'error' 26 | event on a failed decode. 27 | */ 28 | 29 | var util=require('util'); 30 | var EventEmitter=require('events'); 31 | 32 | var KISSFrameParser=require("./kiss-frame-parser.js"); 33 | var APRSInfoParser=require("./aprs-info-parser.js"); 34 | 35 | /** 36 | @module utils-for-aprs 37 | */ 38 | 39 | /** 40 | @constructor APRSProcessor 41 | @alias module:utils-for-aprs/APRSProcessor 42 | Creates a new APRSProcessor object 43 | 44 | @fires APRSProcessor#aprsData 45 | */ 46 | var APRSProcessor=function() { 47 | this.frameParser=new KISSFrameParser(); 48 | this.aprsParser=new APRSInfoParser(); 49 | /** 50 | Process a KISS frame. 51 | The frame should be de-escaped, but includes the KISS data frame 52 | command (i.e. it comes straight from the 'data' event of a 53 | KISSConnection). 54 | 55 | If the frame decodes successfully, the APRSProcessor will emit an 'aprsData' 56 | event. Otherwise it will emit an 'error' event. 57 | 58 | Just a reminder - Node sees an unhandled 'error' event as an 59 | exception (i.e. if a component emits 'error' and nobody handles it, 60 | the event loop throws Error). 61 | If that exception is thrown in the event handler for 62 | (for instance) a TCP connection, that will potentially terminate the 63 | entire server. So make sure you listen for 'error'! 64 | */ 65 | this.data=function(data) { 66 | var frame; 67 | this.frameParser.setInput(data); 68 | try { 69 | frame=this.frameParser.parseFrame(); 70 | 71 | } catch(err) { 72 | this.emit('error', err); 73 | return; 74 | } 75 | try { 76 | this.aprsParser.parse(frame); 77 | /** 78 | aprsData event 79 | @event APRSProcessor#aprsData 80 | @type {object} 81 | */ 82 | this.emit('aprsData', frame); 83 | } catch(err) { 84 | frame.dataType='undecoded'; 85 | frame.undecodeReason=err.message; 86 | this.emit('aprsData', frame); 87 | } 88 | }; 89 | } 90 | 91 | util.inherits(APRSProcessor, EventEmitter); 92 | 93 | module.exports=APRSProcessor; 94 | -------------------------------------------------------------------------------- /src/ax25-utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var sprintf=require("sprintf-js").sprintf; 21 | 22 | var addressToString=function(address) { 23 | if (address === undefined) { 24 | return "??"; 25 | } 26 | return (address.ssid===0 || address.ssid ==='')? 27 | address.callsign:sprintf("%s-%s", address.callsign, address.ssid); 28 | } 29 | exports.addressToString=addressToString; 30 | 31 | exports.repeaterPathToString=function(repeaterPath) { 32 | if (repeaterPath===undefined || repeaterPath.length===0) { 33 | return ""; 34 | } 35 | var path=""; 36 | for(i in repeaterPath) { 37 | if(i>0) { 38 | path=path+","; 39 | } 40 | path=path+addressToString(repeaterPath[i]); 41 | if (repeaterPath[i].hasBeenRepeated) { 42 | path=path+"*"; 43 | } 44 | } 45 | return path; 46 | } 47 | -------------------------------------------------------------------------------- /src/browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | /** 21 | @module utils-for-aprs 22 | */ 23 | exports.APRSProcessor=require("./aprs-processor"); 24 | exports.ax25utils=require("./ax25-utils"); 25 | /* Not in the browser 26 | exports.framing=require('./kiss-framing.js'); 27 | exports.tncSimulator=require('./tnc-simulator.js'); 28 | exports.SocketKISSFrameEndpoint=require('./SocketKISSFrameEndpoint.js'); 29 | exports.newKISSFrame=require('./KISSFrameBuilder.js'); 30 | */ 31 | exports.addressBuilder=require('./AddressBuilder'); 32 | exports.validateFrame=require('./validateFrame') 33 | /* Not in browser 34 | exports.ServerSocketKISSFrameEndpoint=require('./ServerSocketKISSFrameEndpoint.js'); 35 | */ 36 | /*No serial in the browser... 37 | exports.SerialKISSFrameEndpoint=require('./SerialKISSFrameEndpoint.js'); 38 | */ 39 | exports.WebSocketAprsDataEndpoint=require('./WebSocketAprsDataBrowserEndpoint'); 40 | -------------------------------------------------------------------------------- /src/connection-machine-states.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | module.exports = { 21 | Idle: { 22 | enable: 'Connecting', 23 | error: 'Idle', 24 | timeout: 'Idle', 25 | disable: 'Idle' 26 | }, 27 | Connecting: { 28 | connectionSucceeded: 'Connected', 29 | connectionFailed: 'WaitingRetry', 30 | error: 'WaitingRetry', 31 | disable: ['Idle', function() {this.closeConnection(); }], 32 | onEntry: function() { this.openConnection(); }, 33 | }, 34 | Connected: { 35 | disable: 'Idle', 36 | error: 'WaitingRetry', 37 | onExit: function() { this.closeConnectionAndEmitDisconnect(); }, 38 | onEntry: function() { this.emitConnect(); } 39 | }, 40 | WaitingRetry: { 41 | disable: 'Idle', 42 | error: 'WaitingRetry', 43 | timeout: 'Connecting', 44 | onEntry: function() { this.triggerWait(); }, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/crc-generator.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | /* Note - interesting though this generator function is, it turns 21 | out to be not required for a KISS TNC. The TNC generates and checks 22 | the CRC, and doesn't pass the value on to the host. 23 | */ 24 | 25 | var CRCGenerator=function() { 26 | this.sr=0xffff; 27 | } 28 | 29 | CRCGenerator.prototype.data=function(byte) { 30 | for(var i=0; i <8; i++) { 31 | var leftmostBit=(this.sr & 0x8000)>>15; 32 | this.sr=(this.sr<<1) & 0xffff; 33 | if (leftmostBit ^ (byte&0x01)) { 34 | this.sr=this.sr ^ 0x1021; 35 | } 36 | byte=byte>>>1; 37 | } 38 | } 39 | 40 | CRCGenerator.prototype.crc=function() { 41 | return this.sr ^ 0xffff; 42 | } 43 | 44 | CRCGenerator.prototype.reset=function() { 45 | this.sr=0xffff; 46 | } 47 | 48 | exports.CRCGenerator=CRCGenerator; 49 | -------------------------------------------------------------------------------- /src/exceptions.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var util=require("util"); 21 | var sprintf=require("sprintf-js").sprintf; 22 | 23 | function FrameError(msg) { 24 | this.detail=msg; 25 | this.message="Framing Error: " + msg; 26 | } 27 | util.inherits(FrameError, Error); 28 | 29 | exports.FrameError=FrameError; 30 | 31 | function InfoError(msg) { 32 | this.detail=msg; 33 | this.message=sprintf("Unknown data type: %s", msg); 34 | } 35 | util.inherits(InfoError, Error); 36 | exports.InfoError=InfoError; 37 | 38 | function FormatError(msg) { 39 | this.detail=msg; 40 | this.message=sprintf("Format error: %s", msg); 41 | } 42 | util.inherits(FormatError, Error); 43 | exports.FormatError=FormatError; 44 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | /** 21 | @module utils-for-aprs 22 | */ 23 | exports.APRSProcessor=require("./aprs-processor.js"); 24 | exports.ax25utils=require("./ax25-utils.js"); 25 | exports.framing=require('./kiss-framing.js'); 26 | exports.tncSimulator=require('./tnc-simulator.js'); 27 | exports.SocketKISSFrameEndpoint=require('./SocketKISSFrameEndpoint.js'); 28 | exports.newKISSFrame=require('./KISSFrameBuilder.js'); 29 | exports.addressBuilder=require('./AddressBuilder.js'); 30 | exports.validateFrame=require('./validateFrame.js') 31 | exports.ServerSocketKISSFrameEndpoint=require('./ServerSocketKISSFrameEndpoint.js'); 32 | exports.SerialKISSFrameEndpoint=require('./SerialKISSFrameEndpoint.js'); 33 | exports.WebSocketAprsDataEndpoint=require('./WebSocketAprsDataEndpoint.js'); 34 | -------------------------------------------------------------------------------- /src/info-lex.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var util=require("util"); 21 | var Lexer=require("lex"); 22 | 23 | module.exports=InfoLexer; 24 | 25 | function InfoLexer() { 26 | Lexer.call(this); 27 | 28 | this.theRest=function() { 29 | return this.input.slice(this.index); 30 | }; 31 | 32 | this.addRule(/#(\d{1,3})/, function(lexeme, n) { 33 | return { 34 | token: InfoLexer.SEQ_NUMBER, 35 | tval: parseInt(n, 10) 36 | }; 37 | }); 38 | this.addRule(/MIC/, function() { 39 | return { 40 | token: InfoLexer.MIC 41 | }; 42 | }); 43 | this.addRule(/,/, function() { 44 | return { 45 | token: InfoLexer.COMMA 46 | }; 47 | }); 48 | this.addRule(/-/, function() { 49 | return { 50 | token: InfoLexer.DASH 51 | }; 52 | }); 53 | this.addRule(/:/, function() { 54 | return { 55 | token: InfoLexer.COLON 56 | }; 57 | }); 58 | this.addRule(/\*/, function() { 59 | return { 60 | token: InfoLexer.STAR 61 | }; 62 | }); 63 | this.addRule(/_/, function() { 64 | return { 65 | token: InfoLexer.UNDERSCORE 66 | }; 67 | }); 68 | this.addRule(/>/, function() { 69 | return { 70 | token: InfoLexer.GREATER_THAN 71 | }; 72 | }); 73 | this.addRule(/\d{1,3}/, function(lexeme) { 74 | return { 75 | token: InfoLexer.INT, 76 | tval: parseInt(lexeme) 77 | }; 78 | }); 79 | this.addRule(/[01]{8}/, function(lexeme) { 80 | return { 81 | token: InfoLexer.BINARY_OCTET, 82 | tval: parseInt(lexeme,2) 83 | }; 84 | }); 85 | this.addRule(/[a-zA-Z0-9]+/ , function(lexeme) { 86 | return { 87 | token: InfoLexer.CSTEXT, 88 | tval: lexeme 89 | } 90 | }); 91 | this.advance=function() { 92 | this.current=this.lex(); 93 | } 94 | 95 | // Advance and read a fixed-length field of n characters. 96 | this.advanceFixed=function(n) { 97 | this.token=InfoLexer.FIXED_WIDTH; 98 | this.tval=this.input.slice(this.index, this.index+n); 99 | this.index += n; 100 | return this.tval; 101 | } 102 | 103 | // Peek at a possible fixed-width field. 104 | this.peek=function(n) { 105 | return this.input.slice(this.index, this.index+n); 106 | } 107 | } 108 | 109 | util.inherits(InfoLexer,Lexer); 110 | 111 | 112 | InfoLexer.SEQ_NUMBER=0; 113 | InfoLexer.MIC=1; 114 | InfoLexer.COMMA=2; 115 | InfoLexer.INT=3; 116 | InfoLexer.BINARY_OCTET=4; 117 | InfoLexer.FIXED_WIDTH=5; 118 | InfoLexer.COLON=6; 119 | InfoLexer.STAR=7; 120 | InfoLexer.UNDERSCORE=8; 121 | InfoLexer.DASH=9; 122 | InfoLexer.GREATER_THAN=10; 123 | InfoLexer.CSTEXT=11; 124 | -------------------------------------------------------------------------------- /src/kiss-frame-parser.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var exceptions=require("./exceptions.js"); 21 | var sprintf=require("sprintf-js").sprintf; 22 | var CRCGenerator=require("./crc-generator.js").CRCGenerator; 23 | 24 | /* 25 | The KISSFrameParser takes a KISS frame in internal byte buffer format 26 | and decodes the information in it, to yield a JSON object that is 27 | a little more programmer-friendly. 28 | 29 | The internal byte buffer format follows the AX25 KISS frame format 30 | as laid out in http://www.ax25.net/kiss.aspx, with the following 31 | other considerations: 32 | 33 | - The data is not escaped. There is no need to do any special handling 34 | on the framing characters (below). The endpoints handle the escaping and 35 | de-escaping when the internal buffer is read from or written to a 36 | KISSConnection 37 | - Since the framing characters are not used, the endpoint can only 38 | recognize the end of frame by the buffer length. In other words, when 39 | you pass a buffer to the endpoint, it writes-out the entire buffer. So 40 | when you construct a buffer, you need to slice(...) it to the 41 | correct content length. Similarly, when receiving a KISS frame from 42 | a KISSConnection, the frame is the complete buffer - there's no TFEND 43 | character. 44 | - The KISS command byte is included at the beginning of the buffer. 45 | Usually this will be '0', to indicate a data frame, but others can be 46 | used. 47 | 48 | Abbreviation Description Hex value 49 | FEND Frame End C0 50 | FESC Frame Escape DB 51 | TFEND Transposed Frame End DC 52 | TFESC Transposed Frame Escape DD 53 | */ 54 | function KISSFrameParser() { 55 | this.crcgen=new CRCGenerator(); 56 | } 57 | 58 | KISSFrameParser.prototype.setInput=function(buf) { 59 | if (! (buf instanceof Buffer)) { 60 | throw new TypeError("Input must be a Buffer"); 61 | } 62 | this.inputBuffer=buf; 63 | this.currentIndex=0; 64 | this.nextByte=this.inputBuffer[0]; 65 | this.crcgen.reset(); 66 | } 67 | 68 | /* TODO: How do we calculate this? */ 69 | KISSFrameParser.prototype.addToFCS=function(byte) { 70 | this.crcgen.data(byte); 71 | } 72 | 73 | /* Advance by one byte, incorporating the last-seen data into the 74 | frame check code. 75 | */ 76 | KISSFrameParser.prototype.advance=function() { 77 | /* Don't advance if we're already past the end. */ 78 | if (this.nextByte !== undefined) { 79 | this.currentIndex++; 80 | this.nextByte=this.inputBuffer[this.currentIndex]; 81 | } 82 | } 83 | 84 | KISSFrameParser.prototype.parseAddress=function() { 85 | var address={}; 86 | var callsign=""; 87 | for(var i=0; i <6; i++) { 88 | if (this.nextByte === undefined) { 89 | throw new exceptions.FrameError("Incomplete address at " + this.currentIndex); 90 | } 91 | callsign=callsign+String.fromCharCode((this.nextByte)>>1); 92 | this.advance(); 93 | } 94 | if (this.nextByte === undefined) { 95 | throw new exceptions.FrameError("Incomplete address at " + this.currentIndex); 96 | } 97 | address.callsign=callsign.trim(); 98 | address.ssid=this.nextByte>>1 & 0x0f; 99 | address.hasBeenRepeated=((this.nextByte & 0x80) !== 0); 100 | address.rr=this.nextByte>>5 & 0x03; 101 | address.extensionBit=((this.nextByte & 0x01) !== 0); 102 | this.advance(); 103 | return address; 104 | } 105 | 106 | KISSFrameParser.prototype.parseKISSDataFrameCommand=function() { 107 | if (this.nextByte === undefined) { 108 | throw new exceptions.FrameError("Empty Frame"); 109 | } 110 | if (this.nextByte!==0) { 111 | throw new 112 | exceptions.FrameError("Expected data frame command(0), but got " + 113 | "0x" + this.nextByte.toString(16)); 114 | } 115 | this.advance(); 116 | } 117 | 118 | KISSFrameParser.prototype.parseUIFrameControlField=function() { 119 | if (this.nextByte === undefined) { 120 | throw new exceptions.FrameError("Frame ended before control field"); 121 | } 122 | if ((this.nextByte & 0xef) !== 0x03) { 123 | throw new 124 | exceptions.FrameError("Expected UI frame control, but got " + 125 | "0x" + this.nextByte.toString(16)); 126 | } 127 | this.advance(); 128 | } 129 | 130 | KISSFrameParser.prototype.parseRepeaterPath=function() { 131 | var repeaterPath=[]; 132 | var address=this.parseAddress(); 133 | repeaterPath.push(address); 134 | while(!address.extensionBit) { 135 | address=this.parseAddress(); 136 | repeaterPath.push(address); 137 | } 138 | return repeaterPath; 139 | } 140 | 141 | KISSFrameParser.prototype.parseFrame=function(inputBuffer) { 142 | if(inputBuffer) { 143 | this.setInput(inputBuffer); 144 | } 145 | var frame={}; 146 | if (this.nextByte===undefined) { 147 | return undefined; 148 | } 149 | this.parseKISSDataFrameCommand(); 150 | frame.destination=this.parseAddress(); 151 | frame.source=this.parseAddress(); 152 | frame.repeaterPath=(!frame.source.extensionBit)? 153 | this.parseRepeaterPath():[]; 154 | // At this time we only recognize UI (APRS) frames. 155 | // This would be where we deal with other types of frames if required later. 156 | this.parseUIFrameControlField(); 157 | frame.protocol=this.parseProtocol(); 158 | frame.info=this.parseInfo(); 159 | return frame; 160 | } 161 | 162 | KISSFrameParser.prototype.parseProtocol=function() { 163 | var protocol=this.nextByte; 164 | this.advance(); 165 | return protocol; 166 | } 167 | 168 | KISSFrameParser.prototype.parseInfo=function() { 169 | var info=this.inputBuffer.slice(this.currentIndex).toString('utf8'); 170 | this.currentIndex += info.length; 171 | return info; 172 | } 173 | 174 | module.exports=KISSFrameParser; 175 | -------------------------------------------------------------------------------- /src/kiss-framing-transform.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | /* This function returns a parser function that emits a 21 | data event when a properly framed KISS frame is received. While processing 22 | the input events, it de-escapes the TNC data stream. 23 | 24 | The KISS protocol document recommends no arbitrary limit on packet size. 25 | It also recommends that packets at least 1024 bytes long should be accomodated. 26 | 27 | Initially, we're focussing on APRS, so we're probably talking less than. 28 | also we're running on a machine that isn't 29 | fundamentally memory-limited (at least in the context of this program, 30 | even an old RPi with 512MB is pretty big), so we'll use 1024 bytes, which 31 | ought to be plenty. 32 | 33 | If it ever comes down to wanting "no limits", the thing to do is to separate 34 | into 'data' event and 'packet-ended' event. But realistically, APRS packets 35 | are going to be small. 36 | */ 37 | 38 | /* TODO: In order to work with later versions of serialport, this needs to be 39 | rewritten as a Transform stream 40 | */ 41 | 42 | const { Transform } = require('node:stream'); 43 | 44 | class TncFrameParser extends Transform { 45 | 46 | #stateMachine=unescapeStateMachine(1024); 47 | 48 | _transform(chunk, encoding, callback){ 49 | for (var i=0; i { 52 | // console.log(' ..writing output'); 53 | this.push(output)}, chunk[i]); 54 | } 55 | callback(); 56 | }; 57 | } 58 | 59 | var FEND=0xc0; 60 | var FESC=0xdb; 61 | var TFEND=0xdc; 62 | var TFESC=0xdd; 63 | 64 | var unescapeStateMachine=function(bufferLength) { 65 | 66 | var outputBuffer=Buffer.alloc(bufferLength); 67 | var contentLength=0; 68 | var process; 69 | 70 | var ignore=function(emitter,c) { 71 | if (c===FEND) { 72 | process=plain; 73 | } 74 | } 75 | var output=function(c) { 76 | outputBuffer[contentLength++]=c; 77 | if (contentLength>bufferLength) { 78 | // Overflow is an error and will be silently ignored. 79 | // ignore all data til FEND 80 | contentLength=0; 81 | process=ignore; 82 | } 83 | }; 84 | 85 | var idle=function(emitter, c) { 86 | switch(c) { 87 | case FEND: 88 | break; 89 | default: 90 | process=plain; 91 | output(c); 92 | } 93 | }; 94 | 95 | var plain=function(emitter, c) { 96 | switch(c) { 97 | case FESC: 98 | process=escaped; 99 | break; 100 | case FEND: 101 | if (contentLength>0) emitter(outputBuffer.subarray(0,contentLength)); 102 | contentLength=0; 103 | break; 104 | default: 105 | output(c); 106 | } 107 | } 108 | 109 | var escaped=function(emitter, c) { 110 | switch(c) { 111 | case TFESC: 112 | output(FESC); 113 | break; 114 | case TFEND: 115 | output(FEND); 116 | break; 117 | } 118 | process=plain; 119 | } 120 | 121 | process=idle; 122 | 123 | return function(emitter, c) { 124 | process(emitter, c); 125 | } 126 | } 127 | 128 | /** 129 | This machine takes an unescaped frame and escapes it. 130 | Calling it a state machine is probably a little generous, as it only has one 131 | state, but the pattern is the same as the 'unescapeStateMachine'. 132 | 133 | @constructor 134 | */ 135 | var Escaper=function(bufferLength) { 136 | 137 | var outputBuffer=Buffer.alloc(bufferLength); 138 | var contentLength=0; 139 | 140 | var output=function(c) { 141 | outputBuffer[contentLength++]=c; 142 | if (contentLength>bufferLength) { 143 | // Overflow is an error and will be silently ignored. 144 | // Note that this _will_ have the effect of sending garbage down the 145 | // wire, but the framing protocol will recover. 146 | contentLength=0; 147 | } 148 | }; 149 | 150 | var process=function(c) { 151 | switch(c) { 152 | case FESC: 153 | output(FESC); 154 | output(TFESC); 155 | break; 156 | case FEND: 157 | output(FESC); 158 | output(TFEND); 159 | break; 160 | default: 161 | output(c); 162 | } 163 | } 164 | 165 | 166 | this.escape=function(buffer) { 167 | contentLength=0; 168 | // Frame is preceded and followed by FEND, as per KISS spec. 169 | output(FEND); 170 | for (var i=0; ibufferLength) { 72 | // Overflow is an error and will be silently ignored. 73 | // ignore all data til FEND 74 | contentLength=0; 75 | process=ignore; 76 | } 77 | }; 78 | 79 | var idle=function(emitter, c) { 80 | switch(c) { 81 | case FEND: 82 | break; 83 | default: 84 | process=plain; 85 | output(c); 86 | } 87 | }; 88 | 89 | var plain=function(emitter, c) { 90 | switch(c) { 91 | case FESC: 92 | process=escaped; 93 | break; 94 | case FEND: 95 | if (contentLength>0) emitter.emit('data', outputBuffer.slice(0,contentLength)); 96 | contentLength=0; 97 | break; 98 | default: 99 | output(c); 100 | } 101 | } 102 | 103 | var escaped=function(emitter, c) { 104 | switch(c) { 105 | case TFESC: 106 | output(FESC); 107 | break; 108 | case TFEND: 109 | output(FEND); 110 | break; 111 | } 112 | process=plain; 113 | } 114 | 115 | process=idle; 116 | 117 | return function(emitter, c) { 118 | process(emitter, c); 119 | } 120 | } 121 | 122 | /** 123 | This machine takes an unescaped frame and escapes it. 124 | Calling it a state machine is probably a little generous, as it only has one 125 | state, but the pattern is the same as the 'unescapeStateMachine'. 126 | 127 | @constructor 128 | */ 129 | var Escaper=function(bufferLength) { 130 | 131 | var outputBuffer=Buffer.alloc(bufferLength); 132 | var contentLength=0; 133 | 134 | var output=function(c) { 135 | outputBuffer[contentLength++]=c; 136 | if (contentLength>bufferLength) { 137 | // Overflow is an error and will be silently ignored. 138 | // Note that this _will_ have the effect of sending garbage down the 139 | // wire, but the framing protocol will recover. 140 | contentLength=0; 141 | } 142 | }; 143 | 144 | var process=function(c) { 145 | switch(c) { 146 | case FESC: 147 | output(FESC); 148 | output(TFESC); 149 | break; 150 | case FEND: 151 | output(FESC); 152 | output(TFEND); 153 | break; 154 | default: 155 | output(c); 156 | } 157 | } 158 | 159 | 160 | this.escape=function(buffer) { 161 | contentLength=0; 162 | // Frame is preceded and followed by FEND, as per KISS spec. 163 | output(FEND); 164 | for (var i=0; i= 60) { 101 | minutes -= 60; 102 | } 103 | 104 | var hundredthMinutes=encodedData.charCodeAt(2); 105 | 106 | var longitude=deg + (minutes + hundredthMinutes/100)/60; 107 | longitude=east?longitude:-longitude; 108 | this.frame.position.coords.longitude=longitude; 109 | 110 | var speed=(encodedData.charCodeAt(3)-28) * 10; 111 | var dc=encodedData.charCodeAt(4) - 28; 112 | // Double bitwise-not converts float to ints in JavaScript 113 | speed=speed + ~~(dc/10); 114 | speed=(speed>=800)?speed-800:speed; 115 | 116 | var course=(speed%10) * 100; 117 | course=course + encodedData.charCodeAt(5) - 28; 118 | course=(course>400)?course-400:course; 119 | 120 | this.frame.position.coords.heading=course; 121 | // Convert to m/s 122 | this.frame.position.coords.speed=speed*0.514444; 123 | 124 | this.frame.position.symbolTableId=encodedData.charAt(7); 125 | this.frame.position.symbolId=encodedData.charAt(6); 126 | } 127 | 128 | /* 129 | Parse the message bits out of the destination address. 130 | Note - this is a pretty crazy encoding! 131 | See APRS101 page 45. 132 | */ 133 | var parseMessage=function() { 134 | var destAddress=this.frame.destination.callsign; 135 | var messageBits=0; 136 | var custom=0; 137 | for(var i=0; i < 3; i++) { 138 | messageBits=messageBits<<1; 139 | custom=custom<<1; 140 | var encoding=addressEncoding[destAddress.charAt(i)]; 141 | custom=custom | (encoding.messageIsCustom?1:0); 142 | messageBits=messageBits | encoding.message; 143 | } 144 | this.frame.micEMessageBits=messageBits; 145 | // If all the 'custom' bits are not either 0 or 1, message is undefined. 146 | switch(custom) { 147 | case 0: 148 | this.frame.micEMessageType="standard"; 149 | this.frame.micEMessage=standardMessages[messageBits]; 150 | break; 151 | case 7: 152 | this.frame.micEMessageType="custom"; 153 | this.frame.micEMessage=customMessages[messageBits]; 154 | break; 155 | default: 156 | this.frame.micEMessageType='undefined'; 157 | } 158 | } 159 | 160 | var standardMessages=[ 161 | "Emergency", 162 | "Priority", 163 | "Special", 164 | "Committed", 165 | "Returning", 166 | "In Service", 167 | "En Route", 168 | "Off Duty" 169 | ]; 170 | 171 | var customMessages=[ 172 | "Emergency", 173 | "Custom-6", 174 | "Custom-5", 175 | "Custom-4", 176 | "Custom-3", 177 | "Custom-2", 178 | "Custom-1", 179 | "Custom-0" 180 | ]; 181 | -------------------------------------------------------------------------------- /src/showSampleFrames.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var KISSFrameParser=require("./kiss-frame-parser.js"); 21 | var sampleFrames=require("./spec/sample-frames.js").sampleFrames; 22 | var ax25utils=require("./ax25-utils.js"); 23 | 24 | var parser=new KISSFrameParser(); 25 | var APRSParser=require("./aprs-info-parser.js"); 26 | var aprsParser=new APRSParser(); 27 | 28 | var parsedAprs=0; 29 | 30 | sampleFrames.forEach(function(item,index) { 31 | if (item.length===0) { return; } 32 | parser.setInput(Buffer.from(item)); 33 | var frame=parser.parseFrame(); 34 | console.log("[%d] %s -> %s via %s : %s", 35 | index, 36 | ax25utils.addressToString(frame.source), 37 | ax25utils.addressToString(frame.destination), 38 | ax25utils.repeaterPathToString(frame.repeaterPath), 39 | frame.info.toString("utf8")); 40 | try { 41 | aprsParser.parse(frame); 42 | console.log("Frame is " + JSON.stringify(frame, null, 2)); 43 | parsedAprs++; 44 | } catch(err) { 45 | //console.log(err); 46 | } 47 | }); 48 | 49 | console.log("APRS Parser worked on %d frames of %d", parsedAprs, sampleFrames.length); 50 | -------------------------------------------------------------------------------- /src/showUndecodedSampleFrames.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var KISSFrameParser=require("./kiss-frame-parser.js"); 21 | var sampleFrames=require("./spec/sample-frames.js").sampleFrames; 22 | var ax25utils=require("./ax25-utils.js"); 23 | 24 | var parser=new KISSFrameParser(); 25 | var APRSParser=require("./aprs-info-parser.js"); 26 | var aprsParser=new APRSParser(); 27 | 28 | var parsedAprs=0; 29 | var undecodedErrors={}; 30 | 31 | sampleFrames.forEach(function(item, index) { 32 | if (item.length===0) { return; } 33 | parser.setInput(Buffer.from(item)); 34 | var frame=parser.parseFrame(); 35 | try { 36 | aprsParser.parse(frame); 37 | //console.log("Frame is " + JSON.stringify(frame, null, 2)); 38 | parsedAprs++; 39 | } catch(err) { 40 | if (undecodedErrors[err]===undefined) { 41 | undecodedErrors[err]=0; 42 | } 43 | undecodedErrors[err]=undecodedErrors[err]+1; 44 | console.log("[%d] %s -> %s via %s : %s", 45 | index, 46 | ax25utils.addressToString(frame.source), 47 | ax25utils.addressToString(frame.destination), 48 | ax25utils.repeaterPathToString(frame.repeaterPath), 49 | frame.info.toString("utf8")); 50 | console.log("...couldn't decode APRS: %s", err); 51 | } 52 | 53 | }); 54 | 55 | console.log("APRS Parser worked on %d frames of %d", parsedAprs, sampleFrames.length); 56 | console.log(undecodedErrors); 57 | -------------------------------------------------------------------------------- /src/showports.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var serialport = require('serialport'); 21 | var SerialPort = serialport.SerialPort; 22 | 23 | // list serial ports: 24 | serialport.list(function (err, ports) { 25 | ports.forEach(function(port) { 26 | console.log(port.comName); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/spec/AprsDataEndpointSpec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var AprsDataEndpoint=require('../AprsDataEndpoint'); 21 | var Promise=require('bluebird'); 22 | 23 | describe("An AprsDataEndpoint object", function() { 24 | var UUT; 25 | 26 | beforeEach(function() { 27 | UUT=new AprsDataEndpoint(); 28 | /* This looks odd, but is here to alleviate a problem when running 'gulp test' 29 | where something else is installing a jasmine clock. 30 | https://stackoverflow.com/questions/39600819/conflict-between-zone-js-and-jasmines-clock 31 | */ 32 | jasmine.clock().uninstall(); 33 | jasmine.clock().install(); 34 | }); 35 | 36 | afterEach(function() { 37 | jasmine.clock().uninstall(); 38 | }); 39 | 40 | it('has an openConnection function', function() { 41 | expect(UUT.openConnection).toBeDefined(); 42 | }); 43 | 44 | it('Starts in an idle state', function() { 45 | spyOn(UUT, 'openConnection'); 46 | expect(UUT.currentState.name).toBe('Idle'); 47 | }); 48 | 49 | it('goes to Connecting and opens the port when we enable()', function() { 50 | spyOn(UUT, 'openConnection'); 51 | expect(UUT.currentState.name).toBe('Idle'); 52 | UUT.enable(); 53 | expect(UUT.currentState.name).toBe('Connecting'); 54 | expect(UUT.openConnection).toHaveBeenCalled(); 55 | }); 56 | 57 | it('goes to Connected if the port opens', function() { 58 | spyOn(UUT, 'openConnection'); 59 | expect(UUT.currentState.name).toBe('Idle'); 60 | UUT.enable(); 61 | expect(UUT.currentState.name).toBe('Connecting'); 62 | expect(UUT.openConnection).toHaveBeenCalled(); 63 | UUT.connectionSucceeded(); 64 | expect(UUT.currentState.name).toBe('Connected'); 65 | }); 66 | 67 | it('goes to WaitingRetry if the port doesnt open', function() { 68 | spyOn(UUT, 'openConnection'); 69 | expect(UUT.currentState.name).toBe('Idle'); 70 | UUT.enable(); 71 | expect(UUT.currentState.name).toBe('Connecting'); 72 | expect(UUT.openConnection).toHaveBeenCalled(); 73 | UUT.connectionFailed(); 74 | expect(UUT.currentState.name).toBe('WaitingRetry'); 75 | }); 76 | 77 | it('tries opening again upon timeout', function() { 78 | spyOn(UUT, 'openConnection'); 79 | expect(UUT.currentState.name).toBe('Idle'); 80 | UUT.enable(); 81 | expect(UUT.currentState.name).toBe('Connecting'); 82 | expect(UUT.openConnection).toHaveBeenCalled(); 83 | UUT.connectionFailed(); 84 | expect(UUT.currentState.name).toBe('WaitingRetry'); 85 | UUT.openConnection.calls.reset(); 86 | UUT.timeout(); 87 | expect(UUT.currentState.name).toBe('Connecting'); 88 | expect(UUT.openConnection).toHaveBeenCalled(); 89 | }); 90 | 91 | it('fires a "connect" event if the port opens', function() { 92 | spyOn(UUT, 'openConnection'); 93 | var eventMethod=jasmine.createSpy(); 94 | UUT.on('connect', eventMethod); 95 | expect(UUT.currentState.name).toBe('Idle'); 96 | UUT.enable(); 97 | expect(UUT.currentState.name).toBe('Connecting'); 98 | expect(UUT.openConnection).toHaveBeenCalled(); 99 | UUT.connectionSucceeded(); 100 | expect(UUT.currentState.name).toBe('Connected'); 101 | expect(eventMethod).toHaveBeenCalled(); 102 | }); 103 | 104 | describe('A connected AprsDataEndpoint', function() { 105 | var UUT; 106 | 107 | beforeEach(function() { 108 | UUT=new AprsDataEndpoint(); 109 | /* This looks odd, but is here to alleviate a problem when running 'gulp test' 110 | where something else is installing a jasmine clock. 111 | https://stackoverflow.com/questions/39600819/conflict-between-zone-js-and-jasmines-clock 112 | */ 113 | jasmine.clock().uninstall(); 114 | jasmine.clock().install(); 115 | UUT.enable(); 116 | UUT.connectionSucceeded(); 117 | }); 118 | 119 | afterEach(function() { 120 | jasmine.clock().uninstall(); 121 | }); 122 | 123 | it('Has a request() method', function() { 124 | expect(UUT.request).toBeDefined(); 125 | }); 126 | 127 | it('Sends a request message when we call request() method', function(done) { 128 | spyOn(UUT, 'send'); 129 | Promise.resolve().then(function() { 130 | var promise=UUT.request({ command: "config?", msgId: 1}); 131 | expect(promise instanceof Promise).toBeTruthy(); 132 | }).then(function() { 133 | expect(UUT.send).toHaveBeenCalled(); 134 | }).finally(done); 135 | }); 136 | 137 | it('Completes request message when there is a reply', function(done) { 138 | spyOn(UUT, 'send'); 139 | var responseHandler=jasmine.createSpy(); 140 | var expectedResponse={ replyTo: 1, response: "Blah blah"}; 141 | Promise.resolve().then(function() { 142 | var promise=UUT.request({ command: "config?", msgId: 1}); 143 | expect(promise instanceof Promise).toBeTruthy(); 144 | promise.then(responseHandler); 145 | }).then(function() { 146 | expect(UUT.send).toHaveBeenCalled(); 147 | }).then(function() { 148 | UUT._incoming_message(expectedResponse); 149 | }).then(function() { 150 | jasmine.clock().tick(100); 151 | }).then(function() { 152 | expect(responseHandler).toHaveBeenCalledWith(expectedResponse); 153 | expect(UUT._outstandingRequests.size).toBe(0); 154 | }) 155 | .finally(done); 156 | }); 157 | 158 | 159 | }) 160 | }); 161 | -------------------------------------------------------------------------------- /src/spec/RequestSpec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var Request=require('../Request'); 21 | 22 | describe("A Request object", function() { 23 | var UUT; 24 | var sender=jasmine.createSpy(); 25 | var completer=jasmine.createSpy(); 26 | 27 | beforeEach(function() { 28 | jasmine.clock().install(); 29 | }); 30 | 31 | afterEach(function() { 32 | jasmine.clock().uninstall(); 33 | }); 34 | 35 | var testData={ request: "Hi", messageId:1 }; 36 | 37 | it('is created with an object for the request', function() { 38 | UUT=new Request(testData); 39 | }); 40 | 41 | it('calls the sender function when we call send()', function() { 42 | UUT=new Request(testData); 43 | UUT.send(sender); 44 | expect(sender).toHaveBeenCalledWith(testData); 45 | }); 46 | 47 | it('resolves the promise on reply()', function(done) { 48 | UUT=new Request(testData); 49 | var responseFunction=function(data) { 50 | console.log("got data " + JSON.stringify(data)); 51 | done(); 52 | }; 53 | 54 | UUT.send(sender,completer).then(responseFunction).catch(function(err) { 55 | console.log("Caught error " + err); 56 | }); 57 | expect(sender).toHaveBeenCalledWith(testData); 58 | testData.replyTo=testData.messageId; 59 | UUT.reply(testData); 60 | expect(completer).toHaveBeenCalled(); 61 | }); 62 | 63 | it("times out if there's no reply in time", function(done) { 64 | UUT=new Request(testData); 65 | var catchFunction=function(err) { 66 | console.log("got err " + JSON.stringify(err)); 67 | done(); 68 | } 69 | UUT.send(sender,completer).catch(catchFunction); 70 | expect(sender).toHaveBeenCalledWith(testData); 71 | jasmine.clock().tick(15000); 72 | expect(completer).toHaveBeenCalled(); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /src/spec/ServerSocketKISSFrameEndpointSpec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var ServerSocketKISSFrameEndpoint=require('../ServerSocketKISSFrameEndpoint'); 21 | var Promise=require('bluebird'); 22 | var EventEmitter=require('events'); 23 | 24 | describe("A ServerSocketKISSFrameEndpoint object", function() { 25 | var UUT; 26 | 27 | beforeEach(function() { 28 | UUT=new ServerSocketKISSFrameEndpoint(); 29 | }); 30 | 31 | afterEach(function() { 32 | }); 33 | 34 | it('extends EventEmitter', function() { 35 | expect(UUT instanceof EventEmitter).toBeTruthy(); 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /src/spec/aprs-phg-spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var KISSFrameParser=require("../kiss-frame-parser.js"); 21 | var APRSInfoParser=require("../aprs-info-parser.js"); 22 | var exceptions=require("../exceptions.js"); 23 | var sampleFrames=require("./sample-frames.js").sampleFrames; 24 | var ax25utils=require("../ax25-utils.js"); 25 | 26 | var aprsParser=new APRSInfoParser(); 27 | 28 | describe("The APRS info parser", function() { 29 | 30 | it("sample 11 should have phg and altitude", 31 | function() { 32 | var input=Buffer.from(sampleFrames[11]); 33 | var parser=new KISSFrameParser(); 34 | parser.setInput(input); 35 | var frame=parser.parseFrame(); 36 | console.log("Info field is [%s]", frame.info); 37 | aprsParser.parse(frame); 38 | // Should get back a position in the same form that html5 would return it. 39 | expect(frame.dataType).toBe('positionWithoutTimestamp'); 40 | expect(frame.hasMessaging).toBeFalsy(); 41 | expect(frame.position).toBeDefined(); 42 | expect(frame.position.coords).toBeDefined(); 43 | expect(frame.position.coords.latitude).toBeCloseTo(43.58533333333333,5); 44 | expect(frame.position.coords.longitude).toBeDefined(); 45 | expect(frame.position.coords.accuracy).toBeDefined(); 46 | expect(frame.position.timestamp).toBeUndefined(); 47 | // The two following fields are extensions to the html5 Position object, 48 | // for APRS. 49 | expect(frame.position.symbolTableId).toBeDefined(); 50 | expect(frame.position.symbolId).toBeDefined(); 51 | expect(frame.position.power).toEqual(16); 52 | expect(frame.position.height).toEqual(20); 53 | expect(frame.position.gain).toEqual(3); 54 | expect(frame.position.directivity).toBeUndefined(); 55 | expect(frame.comment).toBe("Mississauga City Centre"); 56 | }); 57 | it("takes the 5th sample (MIC-E Data) and parses it", 58 | function() { 59 | var input=Buffer.from(sampleFrames[5]); 60 | var parser=new KISSFrameParser(); 61 | parser.setInput(input); 62 | var frame=parser.parseFrame(); 63 | console.log("To, Info field is [%s][%s]", 64 | ax25utils.addressToString(frame.destination), 65 | frame.info); 66 | aprsParser.parse(frame); 67 | // Should get back a position in the same form that html5 would return it. 68 | expect(frame.dataType).toBe('micEData'); 69 | expect(frame.hasMessaging).toBeFalsy(); 70 | expect(frame.position).toBeDefined(); 71 | expect(frame.position.coords.latitude).toBeCloseTo(43.460167,5); 72 | expect(frame.position.coords.longitude).toBeDefined(); 73 | expect(frame.position.coords.accuracy).toBeCloseTo(26,0); 74 | expect(frame.position.coords.speed).toBeCloseTo(0,1); 75 | expect(frame.position.timestamp).toBeUndefined(); 76 | // The two following fields are extensions to the html5 Position object, 77 | // for APRS. 78 | expect(frame.position.symbolTableId).toBeDefined(); 79 | expect(frame.position.symbolId).toBeDefined(); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /src/spec/aprs-processor-spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var KISSFrameParser=require("../kiss-frame-parser.js"); 21 | var APRSProcessor=require("../aprs-processor.js"); 22 | var sampleFrames=require("./sample-frames.js").sampleFrames; 23 | 24 | var Uut=new APRSProcessor(); 25 | 26 | describe("The APRS processor", function() { 27 | it("takes gets called with frame data and emits events.", function() { 28 | var input=Buffer.from(sampleFrames[0]); 29 | var wasCalled=false; 30 | var receivedPacket=null; 31 | 32 | Uut.on('aprsData', function(packet) { 33 | wasCalled=true; 34 | receivedPacket=packet; 35 | }); 36 | Uut.data(input); 37 | expect(wasCalled).toBeTruthy(); 38 | expect(receivedPacket).toBeDefined(); 39 | expect(receivedPacket.dataType).toBe('status'); 40 | expect(receivedPacket.statusText).toBe('Burlington Amateur Radio Club'); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/spec/buffer-slice-spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | describe('The Buffer class', function() { 21 | it('can be allocated and copied', function() { 22 | var buf1=Buffer.alloc(256); 23 | Buffer.from(buf1); 24 | }) 25 | }); 26 | -------------------------------------------------------------------------------- /src/spec/crc-spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var CRCGenerator=require("../crc-generator.js").CRCGenerator; 21 | 22 | describe("The CRCGenerator", function() { 23 | it("processes 'ABC' to generate 0xf4f9", function() { 24 | var gen=new CRCGenerator(); 25 | gen.data(65); 26 | gen.data(66); 27 | gen.data(67); 28 | expect(gen.crc().toString(16)).toBe(0xf4f9.toString(16)); 29 | }); 30 | 31 | }) 32 | -------------------------------------------------------------------------------- /src/spec/frame-builder-spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var exceptions=require("../exceptions.js"); 21 | 22 | describe("The Address builder", function() { 23 | var addressBuilder=require("../index.js").addressBuilder; 24 | it("is in the main library under 'addressBuilder'", function() { 25 | expect(addressBuilder).toBeDefined(); 26 | }); 27 | it("Builds an address", function() { 28 | var address=addressBuilder("VE3GXV").build(); 29 | 30 | expect(address).toEqual( 31 | { 32 | "callsign":"VE3GXV", 33 | "ssid":0, 34 | "hasBeenRepeated":false, 35 | "rr":3,"extensionBit":false 36 | } 37 | ); 38 | }); 39 | it("Builds an address with SSID", function() { 40 | expect(addressBuilder("VE3GXV-0").build()).toEqual( 41 | { 42 | "callsign":"VE3GXV", 43 | "ssid":0, 44 | "hasBeenRepeated":false, 45 | "rr":3,"extensionBit":false 46 | } 47 | ); 48 | expect(addressBuilder("VE3GXV-1").build()).toEqual( 49 | { 50 | "callsign":"VE3GXV", 51 | "ssid":1, 52 | "hasBeenRepeated":false, 53 | "rr":3,"extensionBit":false 54 | } 55 | ); 56 | }); 57 | it("supports hasBeenRepeated()", function() { 58 | expect(addressBuilder("VE3GXV-0").hasBeenRepeated().build()).toEqual( 59 | { 60 | "callsign":"VE3GXV", 61 | "ssid":0, 62 | "hasBeenRepeated":true, 63 | "rr":3,"extensionBit":false 64 | } 65 | ); 66 | expect(addressBuilder("VE3GXV-0").hasBeenRepeated(true).build()).toEqual( 67 | { 68 | "callsign":"VE3GXV", 69 | "ssid":0, 70 | "hasBeenRepeated":true, 71 | "rr":3,"extensionBit":false 72 | } 73 | ); 74 | expect(addressBuilder("VE3GXV-0").hasBeenRepeated(false).build()).toEqual( 75 | { 76 | "callsign":"VE3GXV", 77 | "ssid":0, 78 | "hasBeenRepeated":false, 79 | "rr":3,"extensionBit":false 80 | } 81 | ); 82 | }); 83 | it("supports withExtensionBit", function() { 84 | expect(addressBuilder("VE3GXV-0").withExtensionBit().build()).toEqual( 85 | { 86 | "callsign":"VE3GXV", 87 | "ssid":0, 88 | "hasBeenRepeated":false, 89 | "rr":3, 90 | "extensionBit":true 91 | } 92 | ); 93 | expect(addressBuilder("VE3GXV-0").withExtensionBit(true).build()).toEqual( 94 | { 95 | "callsign":"VE3GXV", 96 | "ssid":0, 97 | "hasBeenRepeated":false, 98 | "rr":3,"extensionBit":true 99 | } 100 | ); 101 | expect(addressBuilder("VE3GXV-0").withExtensionBit(false).build()).toEqual( 102 | { 103 | "callsign":"VE3GXV", 104 | "ssid":0, 105 | "hasBeenRepeated":false, 106 | "rr":3,"extensionBit":false 107 | } 108 | ); 109 | }); 110 | }); 111 | 112 | describe("The frame validator", function() { 113 | var addressBuilder=require("../index.js").addressBuilder; 114 | var validateFrame=require("../index.js").validateFrame; 115 | it("is in the main library under 'validateFrame'", function() { 116 | expect(validateFrame).toBeDefined(); 117 | }); 118 | it("Rejects a frame with no source", function() { 119 | var badFrame={ 120 | destination: addressBuilder("VE3GXV").build() 121 | } 122 | 123 | expect(function() { validateFrame(badFrame)} ).toThrowError(/Source address/); 124 | }); 125 | it("Rejects a frame with no destination", function() { 126 | var badFrame={ 127 | source: addressBuilder("VE3GXV").build() 128 | } 129 | 130 | expect(function() { validateFrame(badFrame)} ).toThrowError(/Destination address/); 131 | }); 132 | it("Rejects a frame with no more than 8 repeater path steps", function() { 133 | var badFrame={ 134 | source: addressBuilder("VE3GXV").build(), 135 | destination: addressBuilder("APZTSK").build(), 136 | repeaterPath: [ 137 | addressBuilder("A1").build(), 138 | addressBuilder("A2").build(), 139 | addressBuilder("A3").build(), 140 | addressBuilder("A4").build(), 141 | addressBuilder("A5").build(), 142 | addressBuilder("A6").build(), 143 | addressBuilder("A7").build(), 144 | addressBuilder("A8").build(), 145 | addressBuilder("A9").build() 146 | ] 147 | } 148 | 149 | expect(function() { validateFrame(badFrame)} ).toThrowError(/Repeater path/); 150 | }); 151 | it("Patches the extension bits as appropriate when there's no repeat path", 152 | function() { 153 | var frame={ 154 | source: addressBuilder("VE3GXV").build(), 155 | destination: addressBuilder("APZTSK").withExtensionBit().build(), 156 | }; 157 | validateFrame(frame); 158 | expect(frame.source.extensionBit).toBe(true); 159 | expect(frame.destination.extensionBit).toBe(false); 160 | }); 161 | it("Patches the extension bits as appropriate when is a single repeat path", 162 | function() { 163 | var frame={ 164 | source: addressBuilder("VE3GXV").build(), 165 | destination: addressBuilder("APZTSK").withExtensionBit().build(), 166 | repeaterPath: [ 167 | addressBuilder("A1").build() 168 | ] 169 | }; 170 | validateFrame(frame); 171 | expect(frame.source.extensionBit).toBe(false); 172 | expect(frame.destination.extensionBit).toBe(false); 173 | expect(frame.repeaterPath[0].extensionBit).toBe(true); 174 | }); 175 | it("Patches the extension bits as appropriate when is a multi repeat path", 176 | function() { 177 | var frame={ 178 | source: addressBuilder("VE3GXV").build(), 179 | destination: addressBuilder("APZTSK").withExtensionBit().build(), 180 | repeaterPath: [ 181 | addressBuilder("A1").withExtensionBit().build(), 182 | addressBuilder("A2").build() 183 | ] 184 | }; 185 | validateFrame(frame); 186 | expect(frame.source.extensionBit).toBe(false); 187 | expect(frame.destination.extensionBit).toBe(false); 188 | expect(frame.repeaterPath[0].extensionBit).toBe(false); 189 | expect(frame.repeaterPath[1].extensionBit).toBe(true); 190 | }); 191 | }); 192 | -------------------------------------------------------------------------------- /src/spec/frame-parser-spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var KISSFrameParser=require("../kiss-frame-parser.js"); 21 | var exceptions=require("../exceptions.js"); 22 | var sampleFrames=require("./sample-frames.js").sampleFrames; 23 | 24 | describe("The AX.25 KISS Frame Parser", function() { 25 | describe("has a context object that we can load a Buffer into", function() { 26 | var parser=new KISSFrameParser(); 27 | var sampleBuf=Buffer.from([0x0, 0x84, 0x8a, 0x82, 0x86, 0x9e, 0x9c, 0x60, 0xac, 0x8a, 0x66, 0x8e, 0xb0, 0xac, 0x60, 0xac, 0x8a, 0x66, 0xb2, 0x82, 0xa0, 0xe0, 0xac, 0x8a, 0x66, 0x96, 0xae, 0xae, 0xe2, 0xae, 0x92, 0x88, 0x8a, 0x64, 0x40, 0x63, 0x3, 0xf0, 0x21, 0x34, 0x33, 0x33, 0x35, 0x2e, 0x31, 0x32, 0x4e, 0x2f, 0x30, 0x37, 0x39, 0x33, 0x38, 0x2e, 0x39, 0x38, 0x57, 0x2d, 0x50, 0x48, 0x47, 0x34, 0x31, 0x33, 0x30, 0x2f, 0x41, 0x3d, 0x30, 0x30, 0x30, 0x35, 0x32, 0x32, 0x2f, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x73, 0x73, 0x61, 0x75, 0x67, 0x61, 0x20, 0x43, 0x69, 0x74, 0x79, 0x20, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x65, 0xd]); 28 | parser.setInput(sampleBuf); 29 | 30 | it("and we can read out the length", function() { 31 | expect(parser.inputBuffer.length).toBe(99); 32 | }); 33 | it("and it has a currentIndex count that gets set to 0 on input.", function(){ 34 | expect(parser.currentIndex).toBe(0); 35 | }); 36 | it("Can read the source and data addresses", function() { 37 | parser.parseKISSDataFrameCommand(); 38 | var dest=parser.parseAddress(); 39 | var src=parser.parseAddress(); 40 | expect(dest.callsign).toBe("BEACON"); 41 | expect(src.callsign).toBe("VE3GXV"); 42 | }) 43 | }); 44 | describe("can parse a callsign as per AX25 spec, reading", function() { 45 | var parser=new KISSFrameParser(); 46 | var sampleBuf=Buffer.from([0x9c, 0x94, 0x6e, 0xa0, 0x40, 0x40, 0xe0]); 47 | parser.setInput(sampleBuf); 48 | var address=parser.parseAddress(); 49 | it("the callsign correctly", function() { 50 | expect(address.callsign).toBe("NJ7P"); 51 | }); 52 | it("the ssid correctly", function() { 53 | expect(address.ssid).toBe(0); 54 | }); 55 | }); 56 | describe("can parse a callsign as per AX25 spec, reading", function() { 57 | var parser=new KISSFrameParser(); 58 | var sampleBuf=Buffer.from([0x9c, 0x94, 0x6e, 0xa0, 0x40, 0x40, 0xe2]); 59 | parser.setInput(sampleBuf); 60 | var address=parser.parseAddress(); 61 | it("the callsign correctly", function() { 62 | expect(address.callsign).toBe("NJ7P"); 63 | }); 64 | it("the ssid correctly", function() { 65 | expect(address.ssid).toBe(1); 66 | }); 67 | }); 68 | it("throws an exception if the buffer is too short.", function() { 69 | var parser=new KISSFrameParser(); 70 | var sampleBuf=Buffer.from([0x9c, 0x94, 0x6e, 0xa0, 0x40]); 71 | parser.setInput(sampleBuf); 72 | 73 | expect(function() { 74 | parser.parseAddress(); 75 | }).toThrowError(exceptions.FrameError); 76 | }); 77 | 78 | describe("parses a destination with repeat path", function() { 79 | var parser=new KISSFrameParser(); 80 | var sampleBuf=Buffer.from([0x9c, 0x94, 0x6e, 0xa0, 0x40, 0x40, 0xe2, 81 | 0x9c, 0x6e, 0x98, 0x8a, 0x9a, 0x40, 0x60, 82 | 0x9c, 0x6e, 0x9e, 0x9e, 0x40, 0x40, 0xe3]); 83 | parser.setInput(sampleBuf); 84 | var repeaterPath=parser.parseRepeaterPath(); 85 | it("gets the length right", function() { 86 | expect(repeaterPath.length).toBe(3); 87 | }); 88 | it("reads the first callsign correctly", function() { 89 | expect(repeaterPath[0].callsign).toBe("NJ7P"); 90 | }); 91 | it("reads the second callsign correctly", function() { 92 | expect(repeaterPath[1].callsign).toBe("N7LEM"); 93 | }); 94 | it("reads the third callsign correctly", function() { 95 | expect(repeaterPath[2].callsign).toBe("N7OO"); 96 | }); 97 | }); 98 | 99 | describe("parses a complete frame, giving us an object that contains the frame." + 100 | " When complete, we can abandon or re-use the input buffer", 101 | function() { 102 | var parser=new KISSFrameParser(); 103 | parser.setInput(Buffer.from(sampleFrames[0])); 104 | var frame=parser.parseFrame(); 105 | 106 | it("decodes the source address correctly", function() { 107 | expect(frame.source.callsign).toBe("VE3NDQ"); 108 | expect(frame.source.ssid).toBe(10); 109 | }); 110 | it("decodes the destination address correctly", function() { 111 | expect(frame.destination.callsign).toBe("APTT4"); 112 | expect(frame.destination.ssid).toBe(0); 113 | }); 114 | 115 | it("decodes the repeater path correctly", function() { 116 | expect(frame.repeaterPath.length).toBe(1); 117 | expect(frame.repeaterPath[0].callsign).toBe("WIDE1"); 118 | }); 119 | 120 | it("decodes the information subfield", function() { 121 | expect(frame.info).toBeDefined(); 122 | }); 123 | }); 124 | 125 | }); 126 | -------------------------------------------------------------------------------- /src/spec/info-lex-spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var InfoLexer=require("../info-lex.js"); 21 | 22 | describe("The InfoLex lexer", function() { 23 | it("matches a sequence number", function() { 24 | var lexer=new InfoLexer(); 25 | lexer.setInput("#012"); 26 | var tok=lexer.lex(); 27 | expect(tok.token).toBe(InfoLexer.SEQ_NUMBER); 28 | expect(tok.tval).toBe(12); 29 | }); 30 | describe("matches MIC followed by a comma", function() { 31 | var lexer=new InfoLexer(); 32 | lexer.setInput("MIC,"); 33 | it("matches a MIC", function() { 34 | var tok=lexer.lex(); 35 | expect(tok.token).toBe(InfoLexer.MIC); 36 | }); 37 | it("matches a COMMA", function() { 38 | var tok=lexer.lex(); 39 | expect(tok.token).toBe(InfoLexer.COMMA); 40 | }); 41 | 42 | }); 43 | }) 44 | -------------------------------------------------------------------------------- /src/spec/kiss-frame-builder-spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var KISSFrameParser=require("../kiss-frame-parser.js"); 21 | var exceptions=require("../exceptions.js"); 22 | var sampleFrames=require("./sample-frames.js").sampleFrames; 23 | var addressBuilder=require("../index.js").addressBuilder; 24 | 25 | function formatBuffer(data) { 26 | 27 | var output="["; 28 | for(var i=0; i < data.length; i++) { 29 | if (i !== 0) { 30 | output=output+", "; 31 | } 32 | output=output+ "0x" + data[i].toString(16); 33 | } 34 | output=output+"],"; 35 | return output; 36 | } 37 | 38 | describe("The KISS Frame builder", function() { 39 | var newKISSFrame=require("../index.js").newKISSFrame; 40 | it("is in the main library under 'newKISSFrame'", function() { 41 | expect(newKISSFrame).toBeDefined(); 42 | }); 43 | it("Builds a sample frame", function() { 44 | var expectedFrame=Buffer.from([0x0, 0x84, 0x8a, 0x82, 0x86, 0x9e, 0x9c, 0x60, 0xac, 0x8a, 0x66, 0x8e, 0xb0, 0xac, 0x60, 0xac, 0x8a, 0x66, 0xb2, 0x82, 0xa0, 0xe0, 0xac, 0x8a, 0x66, 0x96, 0xae, 0xae, 0xe2, 0xae, 0x92, 0x88, 0x8a, 0x64, 0x40, 0x63, 0x3, 0xf0, 0x21, 0x34, 0x33, 0x33, 0x35, 0x2e, 0x31, 0x32, 0x4e, 0x2f, 0x30, 0x37, 0x39, 0x33, 0x38, 0x2e, 0x39, 0x38, 0x57, 0x2d, 0x50, 0x48, 0x47, 0x34, 0x31, 0x33, 0x30, 0x2f, 0x41, 0x3d, 0x30, 0x30, 0x30, 0x35, 0x32, 0x32, 0x2f, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x73, 0x73, 0x61, 0x75, 0x67, 0x61, 0x20, 0x43, 0x69, 0x74, 0x79, 0x20, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x65, 0xd]); 45 | var frame={ 46 | "destination": addressBuilder("BEACON").build(), 47 | "source": addressBuilder("VE3GXV").build(), 48 | "repeaterPath":[ 49 | addressBuilder('VE3YAP').hasBeenRepeated().build(), 50 | addressBuilder('VE3KWW-1').hasBeenRepeated().build(), 51 | addressBuilder('WIDE2-1').withExtensionBit(true).build() 52 | ], 53 | "protocol":240, 54 | "info":"!4335.12N/07938.98W-PHG4130/A=000522/Mississauga City Centre\r" 55 | }; 56 | var actualFrame=newKISSFrame().fromFrame(frame).build(); 57 | 58 | //var parser=new KISSFrameParser(); 59 | //var decodedFrame=parser.parseFrame(actualFrame); 60 | //console.log("Frame is " + JSON.stringify(frame) + "\n"); 61 | //console.log("KISSFrame is " + formatBuffer(actualFrame) + "\n"); 62 | //console.log("Decoded frame is " + JSON.stringify(decodedFrame)); 63 | expect(actualFrame).toEqual(expectedFrame); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/spec/latitude-re-spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var latRe=/^(\d{2})([\d\ ]{2}).([\d ]{2})([NS])$/; 21 | 22 | describe("The regexp for latitude", function() { 23 | it("matches a fully specified latitude", function() { 24 | expect("4532.28N".match(latRe)).toBeTruthy(); 25 | }); 26 | it("matches a latitude with 10th minutes", function() { 27 | expect("4532.2 N".match(latRe)).toBeTruthy(); 28 | }); 29 | it("matches a latitude with no decimal minutes", function() { 30 | expect("4532. N".match(latRe)).toBeTruthy(); 31 | }); 32 | it("matches a latitude with 10s of minutes", function() { 33 | expect("453 . N".match(latRe)).toBeTruthy(); 34 | }); 35 | it("matches a latitude with no minutes", function() { 36 | expect("45 . N".match(latRe)).toBeTruthy(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/spec/latitude-spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var APRSInfoParser=require("../aprs-info-parser.js"); 21 | var formats=require("../aprs-formats"); 22 | 23 | var parser=new APRSInfoParser(); 24 | parser.frame={ 25 | position: { 26 | coords: {} 27 | } 28 | }; 29 | 30 | describe("Latitude parser", function() { 31 | it("parses degrees alone.", function() { 32 | parser.lexer.setInput("49 . N"); 33 | formats.parseLatitude.call(parser); 34 | expect(parser.frame.position.coords.latitude).toBeCloseTo(49,0.1); 35 | }); 36 | it("parses a latitude with 10s of minutes", function() { 37 | parser.lexer.setInput("493 . N"); 38 | formats.parseLatitude.call(parser); 39 | expect(parser.frame.position.coords.latitude).toBeCloseTo(49.5,0.1); 40 | }); 41 | it("parses a latitude with minutes", function() { 42 | parser.lexer.setInput("4935. N"); 43 | formats.parseLatitude.call(parser); 44 | expect(parser.frame.position.coords.latitude).toBeCloseTo(49.5833,4); 45 | }); 46 | it("parses a latitude with minutes and 10th minutes", function() { 47 | parser.lexer.setInput("4935.4 N"); 48 | formats.parseLatitude.call(parser); 49 | expect(parser.frame.position.coords.latitude).toBeCloseTo(49.59,4); 50 | }); 51 | it("parses a fully specified latitude", function() { 52 | parser.lexer.setInput("4935.49N"); 53 | formats.parseLatitude.call(parser); 54 | expect(parser.frame.position.coords.latitude).toBeCloseTo(49.5915,4); 55 | }); 56 | it("parses a fully specified southern latitude", function() { 57 | parser.lexer.setInput("4935.49S"); 58 | formats.parseLatitude.call(parser); 59 | expect(parser.frame.position.coords.latitude).toBeCloseTo(-49.5915,4); 60 | }); 61 | it("calculates accurace for a 'minute' arc", function() { 62 | parser.lexer.setInput("4935. S"); 63 | formats.parseLatitude.call(parser); 64 | /* 65 | 1 minute of arc is 1 nm at the equator, but since the APRS ambiguity 66 | defines a box in degrees, the html5 accuracy must be sqrt(2)*mins, 67 | converted to meters. 68 | */ 69 | var expectedAccuracy=1.414214*1852; 70 | expect(parser.frame.position.coords.accuracy).toBeCloseTo(expectedAccuracy,1); 71 | });}); 72 | -------------------------------------------------------------------------------- /src/spec/message-text-re-spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | describe('The message text regular expresssion', function() { 21 | var mtre=/^([^{]*)([{]([^}]*))?(([}])(.*))?$/; 22 | it('accepts "Hello"', function() { 23 | result=mtre.exec("Hello"); 24 | expect(result).toBeDefined(); 25 | expect(result[1]).toBe('Hello'); 26 | }); 27 | it('accepts "Hello{m0001"', function() { 28 | result=mtre.exec("Hello{m0001"); 29 | expect(result).toBeDefined(); 30 | expect(result[1]).toBe('Hello'); 31 | expect(result[2]).toBe('{m0001'); 32 | expect(result[3]).toBe('m0001'); 33 | }) 34 | it('accepts "Hello{m0001}m0000"', function() { 35 | result=mtre.exec("Hello{m0001}m0000"); 36 | expect(result).toBeDefined(); 37 | expect(result[1]).toBe('Hello'); 38 | expect(result[2]).toBe('{m0001'); 39 | expect(result[3]).toBe('m0001'); 40 | expect(result[4]).toBe('}m0000'); 41 | expect(result[5]).toBe('}'); 42 | expect(result[6]).toBe('m0000'); 43 | }) 44 | }); 45 | -------------------------------------------------------------------------------- /src/spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "spec_dir": "spec", 4 | "spec_files": [ 5 | "**/*[sS]pec.js" 6 | ], 7 | "stopSpecOnExpectationFailure": false, 8 | "random": false 9 | } 10 | -------------------------------------------------------------------------------- /src/tnc-simulator.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | /** 21 | This simulator function sets up an ongoing background timer that gets a 22 | buffer from the packetBufferSource, then calls the deliveryFunction with it. 23 | 24 | It starts out at an initial rate for some initial interval, then switches to 25 | a sustained rate. 26 | */ 27 | var tncSimulator=function(packetBufferSource, deliveryFunction, initialInterval, 28 | initialRate, sustainedRate) { 29 | 30 | var count=0; 31 | var cancelFlag=false; 32 | 33 | var timeoutInInitialState=function() { 34 | if (cancelFlag) return; 35 | deliveryFunction(packetBufferSource()); 36 | count=count+1; 37 | if (count > initialInterval*initialRate) { 38 | console.log("timeoutInInitialState: next timeout is " + (1000/sustainedRate) + "ms"); 39 | setTimeout(timeoutInSustainedState, 1000/sustainedRate); 40 | } else { 41 | console.log("timeoutInInitialState: next timeout is " + (1000/initialRate) + "ms"); 42 | setTimeout(timeoutInInitialState, 1000/initialRate); 43 | } 44 | } 45 | var timeoutInSustainedState=function() { 46 | if (cancelFlag) { 47 | return; 48 | } 49 | deliveryFunction(packetBufferSource()); 50 | console.log("timeoutInSustainedState: next timeout is " + (1000/sustainedRate) + "ms"); 51 | setTimeout(timeoutInSustainedState, 1000/sustainedRate); 52 | } 53 | timeoutInInitialState(); 54 | return function() { 55 | cancelFlag=true; 56 | } 57 | } 58 | module.exports=tncSimulator; 59 | -------------------------------------------------------------------------------- /src/validateFrame.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | var exceptions=require('./exceptions.js'); 21 | 22 | /** 23 | This function validates the JSON representation of frames to ensure that 24 | they can be used by the KISSFrameBuilder. 25 | */ 26 | module.exports=function(frame) { 27 | if (!frame.source) { 28 | throw new exceptions.FormatError("Source address is missing."); 29 | } 30 | if (!frame.destination) { 31 | throw new exceptions.FormatError("Destination address is missing."); 32 | } 33 | if (!frame.protocol) { 34 | frame.protocol=240; 35 | } 36 | if(!frame.repeaterPath) { 37 | frame.repeaterPath=[]; 38 | } 39 | /* AX.25 allows up to 8 repeater path entries. */ 40 | if (frame.repeaterPath.length>8) { 41 | throw new exceptions.FormatError("Repeater path is longer than 8 entries.") 42 | } 43 | /* 44 | Setup the address constraints - in the AX.25 packet, the last address in 45 | the block needs to have the 'extension' bit set to true, and all the others 46 | need to be false. 47 | The address block is 48 | [path]* 49 | So, if there are addresses in the repeater path, the src needs the extension 50 | bit false, and the last path entry needs to have extension true. 51 | If there are no addresses in the repeater path, the src needs extension=true. 52 | */ 53 | frame.destination.extensionBit=false; 54 | frame.source.extensionBit=(frame.repeaterPath.length===0) 55 | if (frame.repeaterPath.length>0) { 56 | for (var i=0;i