├── .gitignore ├── LICENSE ├── README.md ├── SharedEndpoint.js ├── package.json ├── setTxDelay ├── share-tnc.js ├── share-tnc.service └── spec ├── ControlCodeSpec.js └── support └── jasmine.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /package-lock.json 3 | nohup.out 4 | **/.fuse_hidden* 5 | node_modules/** 6 | docs/gen 7 | docs/gen/** 8 | -------------------------------------------------------------------------------- /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 | # share-tnc 2 | 3 | Share a serial TNC using KISS over TCP/IP 4 | 5 | This is a command-line utility that lets you share a serial (typically USB-serial) 6 | KISS TNC to a TCP/IP port. The port can be used by any program (e.g. aprx, APRSIS32, etc) 7 | that can use the KISS-over-TCP protocol. 8 | 9 | # Hardware/Software Environment 10 | 11 | share-tnc uses 'node.js' and the 'serialport' npm module. It has been tested most 12 | heavily on a Raspberry Pi running Raspbian Jessie, but has been casually tested on 13 | Windows 10, Linux Mint and MacOS Sierra, and appears to work fine. Please file a 14 | report if you experience difficulties. 15 | 16 | share-tnc does not attempt to put the TNC into KISS mode. You should have your TNC 17 | configured to go into KISS mode automatically on bootup and stay there. 18 | 19 | # Installation 20 | 21 | _Note:_ 'serialport' doesn't like to installed in the usual way with 'sudo' 22 | (see [here](https://github.com/EmergingTechnologyAdvisors/node-serialport#sudo--root) 23 | for more information). If you need to use 'sudo' to install globally, use 24 | the following: 25 | 26 | sudo npm install -g --unsafe-perm share-tnc 27 | 28 | If you don't need 'sudo', then 29 | 30 | npm install -g share-tnc 31 | 32 | ## Side-note: Installing Node.js on the Raspberry Pi 33 | 34 | The version of Node that you'll get with 'apt-get install node' is unfortunately very 35 | old. Your best bet is to install from the 'nodejs.org' binary downloads. 36 | - Go to https://nodejs.org/en/download/ and download the _ARMv6_ package 37 | - Unpack it, and then copy all the directories in the package to /usr/local 38 | 39 | Example: 40 | curl -O https://nodejs.org/dist/v6.9.5/node-v6.9.5-linux-armv6l.tar.xz 41 | tar xf node-v6.9.5-linux-armv6l.tar.xz 42 | cp -r node-v6.9.5-linux-armv6l/bin /usr/local 43 | cp -r node-v6.9.5-linux-armv6l/include /usr/local 44 | cp -r node-v6.9.5-linux-armv6l/share /usr/local 45 | cp -r node-v6.9.5-linux-armv6l/lib /usr/local 46 | 47 | # Usage 48 | 49 | ## Command Line - Sharing the TNC 50 | 51 | share-tnc [--baud ] 52 | 53 | e.g. 54 | 55 | share-tnc /dev/ttyUSB0 8001 --baud 1200 56 | 57 | You can also share out a KISS-over-TCP connection like DireWolf (since DireWolf 58 | only supports one connection at a time): 59 | 60 | share-tnc : 61 | 62 | e.g. 63 | 64 | share-tnc localhost:8001 8002 65 | 66 | ### Multiple Connections 67 | 68 | If more than one client connects to share-tnc's server port, the system simulates 69 | what would happen if each client had a radio and TNC to itself, but could hear 70 | the other clients. When a client sends a packet, that packet is not echoed back to 71 | the client (because you wouldn't hear while you're transmitting), 72 | but it _is_ sent to every other client. Packets received on the 73 | physical radio/tnc are relayed to every client that's connected to share-tnc. 74 | 75 | Note that this behaviour may confuse clients that are trying 76 | to use the same callsign-ssid 77 | combo, and may give undesired on-air results. For instance if you had two APRS clients 78 | configured to use VA3ZZZ-1, they would both acknowledge a message packet that was 79 | received. So, don't do that. If you have more than one APRS client hooked up, they 80 | should generally have different callsigns or ssids. 81 | 82 | ## Command Line - Monitoring the On-Air APRS Traffic 83 | 84 | watch-aprs : 85 | 86 | e.g. 87 | 88 | watch-aprs raspberrypi:8001 89 | 90 | 'watch-aprs' isn't specific to the 'share-tnc' package. It will work with any 91 | KISS-over-TCP server (e.g. DireWolf). 92 | 93 | # Installing as a 'systemd' Service 94 | 95 | There is a sample 'module definition' in the module library as 'share-tnc.service'. 96 | Typical usage would be something like: 97 | 98 | - Ensure you have a directory called /usr/local/lib/systemd/system 99 | - Copy 'share-tnc.service' into /usr/local/lib/systemd/system 100 | - Edit /usr/local/lib/systemd/system/share-tnc.service to reflect the device port, 101 | baud rate and KISS-TCP port that you want to use. 102 | - sudo systemctl enable share-tnc 103 | - sudo systemctl start share-tnc 104 | 105 | ## Logging 106 | 107 | share-tnc doesn't attempt to manage its own logging. It simply outputs to the 108 | console. When started by 'systemd', this output will be logged to systemd's journal. 109 | You can view the log output with: 110 | 111 | sudo journalctl -u share-tnc 112 | 113 | # Contributing 114 | 115 | To contribute, please fork https://github.com/trasukg/share-tnc and then submit 116 | pull requests, or open an issue. 117 | 118 | See also https://github.com/trasukg/utils-for-aprs for the underlying components 119 | used in this utility. 120 | 121 | # License 122 | 123 | This software is licensed under the Apache Software License 2.0 124 | 125 | The phrase APRS is a registered trademark of Bob Bruninga WB4APR. 126 | 127 | # Release Notes 128 | 129 | 1.0.0 - February 8, 2017 - First Release 130 | 1.0.1 - March 10, 2017 - Fixed a bug where the only baud rate actually used was 1200 131 | 1.0.2 - April 3, 2017 - Updated to require the latest utils-for-aprs, which should 132 | now include the 'ws' dependency properly. 133 | 1.0.3 - April 3, 2017 - Updated utils-for-aprs to call out the 'bluebird' dependency. 134 | 1.0.4 - April 3, 2017 - Updated to latest utils-for-aprs 135 | 1.0.5 - January 11, 2018 - Added capability to share a KISS-over-TCP port. 136 | 1.0.6 - February 17, 2019 - Updated to latest utils-for-aprs to fix a framing 137 | issue. 138 | 1.0.7 - August 9, 2020 - Update to latest serialport, so it compiles under the 139 | latest nodejs on raspbian. 140 | -------------------------------------------------------------------------------- /SharedEndpoint.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 | The class is a state machine that sets up a shared endpoint. 22 | */ 23 | 24 | const util=require('util'); 25 | 26 | const EventEmitter=require('events'); 27 | const StateMachine=require('@trasukg/state-machine'); 28 | const utilsForAprs=require("utils-for-aprs"); 29 | const SerialKISSFrameEndpoint=utilsForAprs.SerialKISSFrameEndpoint; 30 | const ServerSocketKISSFrameEndpoint=utilsForAprs.ServerSocketKISSFrameEndpoint; 31 | const SocketKISSFrameEndpoint=utilsForAprs.SocketKISSFrameEndpoint; 32 | 33 | const states={ 34 | Idle: { 35 | enable: "Enabled", 36 | disable: "Idle" 37 | }, 38 | Enabled: { 39 | onEntry: function() { 40 | /* Turn on the endpoints. 41 | - Target Endpoint will attempt to connect to TNC 42 | - ServerSocketKISSFrameEndpoint will open the server port and start 43 | accepting connections 44 | */ 45 | this.targetEndpoint.enable(); 46 | this.serverEndpoint.enable(); 47 | }, 48 | onExit: function() { 49 | /* Turn off the endpoints. 50 | */ 51 | this.targetEndpoint.disable(); 52 | this.serverEndpoint.disable(); 53 | }, 54 | enable: "Enabled", 55 | disable: "Idle" 56 | } 57 | }; 58 | 59 | const SharedEndpoint=function(options) { 60 | EventEmitter.apply(this); 61 | StateMachine.call(this, states, "Idle"); 62 | 63 | 64 | /* All connections are put into a Set, which lets us use forEach() 65 | */ 66 | this.allConnections=new Set(); 67 | 68 | //Create the server socket endpoint 69 | this.serverEndpoint=new ServerSocketKISSFrameEndpoint("0.0.0.0", options.port); 70 | 71 | // If the device happens to look like "host:port" then create a socket endpoint. 72 | var res=/([^\:]+):([0-9]+)/.exec(options.path); 73 | if (res) { 74 | var host=res[1]; 75 | var port=res[2]; 76 | this.targetEndpoint=new SocketKISSFrameEndpoint(); 77 | this.targetEndpoint.host=host; 78 | this.targetEndpoint.port=port; 79 | } else { 80 | this.targetEndpoint=new SerialKISSFrameEndpoint({ 81 | path: options.path, baudRate: options.baud 82 | }); 83 | } 84 | 85 | // Log interesting events... 86 | this.serverEndpoint.on('connect', connection => { 87 | this.emit("clientConnect", "TCP Endpoint received a connection from " + 88 | connection.socket.remoteAddress + ":" + connection.socket.remotePort, 89 | connection.socket.remoteAddress, 90 | connection.socket.remotePort 91 | ); 92 | this.allConnections.add(connection); 93 | connection.on('data', frame => { 94 | this.relayToAllBut(connection,frame); 95 | }); 96 | connection.on('close', () => { 97 | this.allConnections.delete(connection); 98 | this.emit("clientDisconnect", "TCP connection from " + 99 | connection.socket.remoteAddress + ":" + connection.socket.remotePort + 100 | " was closed", 101 | connection.socket.remoteAddress, 102 | connection.socket.remotePort 103 | ); 104 | }); 105 | }); 106 | 107 | this.serverEndpoint.on('listen', () => { 108 | this.emit("listen", "KISS TCP server established - listening for connections on port " + options.host + ":" + options.port, 109 | options.host, 110 | options.port); 111 | }); 112 | 113 | this.serverEndpoint.on('error', err => { 114 | this.emit("error", "Error on server endpoint:" + err); 115 | }); 116 | 117 | this.targetEndpoint.on('error', err => { 118 | this.emit("error", "Error on serial TNC endpoint:" + err); 119 | }); 120 | 121 | this.targetEndpoint.on('connect', connection => { 122 | this.emit("tncConnect", "Connected to TNC on " + options.path + (res?"":" at " + options.baud + " baud.")); 123 | this.allConnections.add(connection); 124 | connection.on('data', frame => { 125 | this.relayToAllBut(connection, frame); 126 | }); 127 | connection.on('disconnect', () => { 128 | this.allConnections.delete(connection); 129 | this.emit("tncDisconnect", 'TNC connection was closed'); 130 | }); 131 | }); 132 | } 133 | util.inherits(SharedEndpoint, EventEmitter); 134 | 135 | SharedEndpoint.prototype.relayToAllBut=function(source, frame) { 136 | //console.log("Got frame from " + source); 137 | this.allConnections.forEach(connection => { 138 | if (connection === source) { 139 | //console.log("Skipping destination " + connection); 140 | } else { 141 | connection.data(frame); 142 | } 143 | }); 144 | } 145 | 146 | module.exports=SharedEndpoint; 147 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "share-tnc", 3 | "version": "1.0.9-alpha", 4 | "description": "Share a Serial TNC to KISS over TCP/IP", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "bin": { 8 | "share-tnc": "./share-tnc.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/trasukg/share-tnc.git" 13 | }, 14 | "keywords": [ 15 | "aprs", 16 | "tnc", 17 | "kiss", 18 | "TCP/IP" 19 | ], 20 | "author": "Greg Trasuk VA3TSK", 21 | "license": "Apache-2.0", 22 | "bugs": { 23 | "url": "https://github.com/trasukg/share-tnc/issues" 24 | }, 25 | "homepage": "https://github.com/trasukg/share-tnc#readme", 26 | "devDependencies": { 27 | "jasmine": "^2.99.0" 28 | }, 29 | "dependencies": { 30 | "serialport": "10.5.0", 31 | "utils-for-aprs": "3.0.0", 32 | "yargs": "17.6.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /setTxDelay: -------------------------------------------------------------------------------- 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 set the TxDelay on a KISS interface 24 | */ 25 | var path=require('path'); 26 | var util=require('util'); 27 | const utilsForAprs=require("utils-for-aprs"); 28 | const SerialKISSFrameEndpoint=utilsForAprs.SerialKISSFrameEndpoint; 29 | const SocketKISSFrameEndpoint=utilsForAprs.SocketKISSFrameEndpoint; 30 | 31 | var opt=require('node-getopt').create([ 32 | ['', 'baud[=BAUD]', 'baud rate'] 33 | ]).bindHelp().parseSystem(); 34 | 35 | if (opt.argv.length != 2) { 36 | console.log("Usage: setTxDelay [--baud=BAUD]"); 37 | return; 38 | } 39 | 40 | var device=opt.argv[0]; 41 | var delay=opt.argv[1]; 42 | var baud= opt.options.baud?parseInt(opt.options.baud):1200 43 | 44 | /* 45 | If the device is formatted as a TCP endpoint, open up a TCPEndpoint, 46 | otherwise make it a serial endpoint. 47 | */ 48 | var targetEndpoint=null; 49 | var res=/([^\:]+):([0-9]+)/.exec(device); 50 | if (res) { 51 | var host=res[1]; 52 | var port=res[2]; 53 | targetEndpoint=new SocketKISSFrameEndpoint(); 54 | targetEndpoint.host=host; 55 | targetEndpoint.port=port; 56 | } else { 57 | targetEndpoint=new SerialKISSFrameEndpoint(options.device, {baudRate: baud}); 58 | } 59 | 60 | targetEndpoint.on("listen", message => console.log(message)); 61 | targetEndpoint.on("connect", conn => { 62 | console.log("Connected to target endpoint."); 63 | var delayValue=Math.round(parseInt(delay)/10.0); 64 | var txDelayFrame=Buffer.from([0x01, delayValue]); 65 | var emptyFrame=Buffer.from([]); 66 | const writeData=function() { 67 | 68 | conn.data(txDelayFrame); 69 | console.log("Wrote the txDelayFrame, delayValue=" + delayValue); 70 | setTimeout(()=>{ 71 | process.exit(0); 72 | }, 1000); 73 | }; 74 | writeData(); 75 | }); 76 | targetEndpoint.on("error", message => console.log(message)); 77 | 78 | targetEndpoint.enable(); 79 | -------------------------------------------------------------------------------- /share-tnc.js: -------------------------------------------------------------------------------- 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 | const yargs = require('yargs/yargs') 32 | const { hideBin } = require('yargs/helpers') 33 | var SharedEndpoint=require("./SharedEndpoint"); 34 | 35 | const startup = function(argv){ 36 | console.log(argv); 37 | var path=argv.path; 38 | var port= argv.port; 39 | var baud= argv.baud; 40 | 41 | var sharedEndpoint=new SharedEndpoint( { 42 | path: path, 43 | port: port, 44 | baud: baud 45 | }); 46 | 47 | 48 | sharedEndpoint.on("listen", message => console.log(message)); 49 | sharedEndpoint.on("clientConnect", message => console.log(message)); 50 | sharedEndpoint.on("tncConnect", message => console.log(message)); 51 | sharedEndpoint.on("tncDisconnect", message => console.log(message)); 52 | sharedEndpoint.on("clientDisconnect", message => console.log(message)); 53 | sharedEndpoint.on("error", message => console.log(message)); 54 | 55 | sharedEndpoint.enable(); 56 | 57 | } 58 | 59 | const argv = yargs(hideBin(process.argv)) 60 | .command( 61 | '$0 ', 62 | 'Setup a shared TNC', 63 | (yargs) => { 64 | yargs.positional('path', { 65 | describe: 'Either the path of the serial device or hostname:port of network KISS device.', 66 | type: 'string' 67 | }).positional('port', { 68 | describe: 'Server port for the shared device', 69 | type: 'number' 70 | }).option('baud', { 71 | describe: 'Baud rate for serial device', 72 | default: 1200 73 | }) 74 | }, 75 | argv => startup(argv)) 76 | .help() 77 | .argv; 78 | 79 | /* 80 | The pipeline is sort of like this: 81 | SerialKISSFrameEndpoint Endpoint -> sharePortToTCP -> ServerSocketKISSFrameEndpoint 82 | */ 83 | 84 | 85 | -------------------------------------------------------------------------------- /share-tnc.service: -------------------------------------------------------------------------------- 1 | 2 | [Unit] 3 | Description=Share serial-connected TNC to KISS-over-TCP 4 | 5 | [Service] 6 | ExecStart=/usr/local/bin/share-tnc /dev/ttyUSB0 8001 --baud=1200 7 | Restart=always 8 | Type=simple 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /spec/ControlCodeSpec.js: -------------------------------------------------------------------------------- 1 | 2 | const net=require('net'); 3 | const Promise=require('bluebird'); 4 | const ServerSocketKISSFrameEndpoint= 5 | require('utils-for-aprs').ServerSocketKISSFrameEndpoint 6 | const SharedEndpoint=require("../SharedEndpoint"); 7 | 8 | /* 9 | The setup here is that we have: 10 | - A ServerSocketKISSFrameEndpoint on port 8001 11 | - A SharedEndpoint on port 8002 that is set to forward to port 8001 12 | */ 13 | describe("Share-tnc's handling of control codes", () => { 14 | var receivingEndpoint=null; 15 | var UUT=null; 16 | var waitForServerSocket=null; 17 | var receivingConnection=null; 18 | 19 | beforeEach(function() { 20 | //Setup a receiving endpoint on port 8001. 21 | waitForServerSocket=new Promise((resolve,reject) => { 22 | receivingEndpoint=new ServerSocketKISSFrameEndpoint("0.0.0.0", 8001); 23 | receivingEndpoint.on('listen', function() { 24 | console.log("KISS TCP server established - listening for connections on port " + "8001"); 25 | }); 26 | receivingEndpoint.on("connect", connection => { 27 | receivingConnection=connection; 28 | resolve(); 29 | }); 30 | receivingEndpoint.on("error", reject); 31 | receivingEndpoint.enable() 32 | }); 33 | 34 | //Setup the sharing endpoint on port 8002 35 | UUT=new SharedEndpoint({ 36 | device: "0.0.0.0:8001", 37 | port: 8002 38 | }); 39 | UUT.on("listen", message => console.log(message)); 40 | UUT.on("clientConnect", message => console.log(message)); 41 | UUT.on("tncConnect", message => console.log(message)); 42 | UUT.on("tncDisconnect", message => console.log(message)); 43 | UUT.on("clientDisconnect", message => console.log(message)); 44 | UUT.on("error", message => console.log(message)); 45 | 46 | UUT.enable(); 47 | }); 48 | 49 | afterEach(function() { 50 | receivingEndpoint.disable(); 51 | UUT.disable(); 52 | }); 53 | 54 | it("Passes a TXDelay setting verbatim.", (done) => { 55 | //Open a socket to the sharing endpoint 56 | var socket; 57 | var receivedData; 58 | openSocket() 59 | //Send a TXDelay setting frame 60 | .then(s => { 61 | socket=s; 62 | /* Have to start up the received data before we send, or it will miss the 63 | event. */ 64 | receivedData=getReceivedData(receivingConnection); 65 | return writeATxDelayFrame(socket); 66 | }) 67 | .then(socket => {return receivedData;}) 68 | .then(receivedData => { 69 | //It should come through verbatim 70 | // 1st byte should be 1 71 | expect(receivedData[0]).toBe(0x01); 72 | expect(receivedData[1]).toBe(0x50); 73 | }) 74 | .then(done) 75 | }); 76 | }); 77 | 78 | const openSocket=function() { 79 | return new Promise(resolve => { 80 | var s=new net.Socket(); 81 | s.connect(8002, 'localhost', evt => { 82 | resolve(s); 83 | }); 84 | }); 85 | }; 86 | 87 | const writeToSocket=function(s, data) { 88 | return new Promise(resolve => { 89 | s.write(data, undefined, () => { 90 | resolve(s); 91 | }); 92 | }); 93 | }; 94 | 95 | const aTxDelayFrame=function() { 96 | return Buffer.from([0xc0, 0x01, 0x50, 0xc0]); 97 | }; 98 | 99 | const writeATxDelayFrame=function(s) { 100 | return writeToSocket(s, aTxDelayFrame()); 101 | } 102 | 103 | const getReceivedData=function(socket) { 104 | return new Promise(resolve => { 105 | console.log("Setting event handler on the receiving socket"); 106 | socket.once("data", data => { 107 | resolve(data); 108 | }); 109 | 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ] 9 | } 10 | --------------------------------------------------------------------------------