├── .gitignore ├── .npmrc ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── AUTHORS ├── LICENSE ├── README.md ├── __tests__ ├── helpers │ └── fake-socket.ts ├── serializer.ts └── utils │ └── async-socket.spec.ts ├── bin ├── busmonitor.js ├── busmonitor.ts ├── groupsread.js ├── groupsread.ts ├── groupswrite.js └── groupswrite.ts ├── dist ├── bus-listener.d.ts ├── bus-listener.js ├── constants.d.ts ├── constants.js ├── deserializer.d.ts ├── deserializer.js ├── index.d.ts ├── index.js ├── interfaces.d.ts ├── interfaces.js ├── query-manager.d.ts ├── query-manager.js ├── serializer.d.ts ├── serializer.js └── utils │ ├── async-socket.d.ts │ ├── async-socket.js │ ├── index.d.ts │ ├── index.js │ ├── smart-cursor.d.ts │ └── smart-cursor.js ├── package.json ├── scripts └── debug.js ├── src ├── bus-listener.ts ├── constants.ts ├── deserializer.ts ├── index.ts ├── interfaces.ts ├── query-manager.ts ├── serializer.ts └── utils │ ├── async-socket.ts │ ├── index.ts │ └── smart-cursor.ts ├── tsconfig.dist.json ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional eslint cache 38 | .eslintcache 39 | 40 | # Optional REPL history 41 | .node_repl_history 42 | 43 | # Output of 'npm pack' 44 | *.tgz 45 | 46 | # Yarn Integrity file 47 | .yarn-integrity 48 | 49 | # Development 50 | src/**/*.js 51 | __tests__/**/*.js 52 | .DS_Store 53 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact[]=true 2 | save[]=true 3 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [{ 4 | "type": "node", 5 | "request": "launch", 6 | "name": "App debug", 7 | "program": "${workspaceRoot}/bin/busmonitor.ts", 8 | "cwd": "${workspaceRoot}", 9 | "preLaunchTask": "tsc", 10 | "internalConsoleOptions": "openOnSessionStart", 11 | "env": { 12 | "NODE_ENV": "development" 13 | }, 14 | "sourceMaps": true 15 | }, { 16 | "type": "node", 17 | "request": "launch", 18 | "name": "Debug file", 19 | "program": "${file}", 20 | "cwd": "${workspaceRoot}", 21 | "preLaunchTask": "tsc", 22 | "internalConsoleOptions": "openOnSessionStart", 23 | "env": { 24 | "NODE_ENV": "development" 25 | }, 26 | "args": [ 27 | "test.knxproj" 28 | ], 29 | "sourceMaps": true 30 | }, { 31 | "name": "Test debug", 32 | "type": "node", 33 | "request": "launch", 34 | "program": "${workspaceRoot}/scripts/debug.js", 35 | "cwd": "${workspaceRoot}", 36 | "preLaunchTask": "tsc", 37 | "args": [ 38 | "${workspaceRoot}", "${file}" 39 | ], 40 | "env": { 41 | "NODE_ENV": "test" 42 | }, 43 | "internalConsoleOptions": "openOnSessionStart", 44 | "runtimeArgs": [ 45 | "--nolazy" 46 | ], 47 | "sourceMaps": true 48 | }] 49 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/.DS_Store": true, 7 | "node_modules": true, 8 | "**/*.js": { 9 | "when": "$(basename).ts" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "tsc", 4 | "isShellCommand": true, 5 | "args": ["-p", "./tsconfig.json"], 6 | "problemMatcher": "$tsc" 7 | } -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Igor Korchagin 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Igor Korchagin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KNX Listener 2 | A thin node client to monitor, write and read groups telegrams through KNX gateway 3 | 4 | [![npm version](https://badge.fury.io/js/knx-listener.svg)](https://badge.fury.io/js/knx-listener) [![npm](https://img.shields.io/npm/l/express.svg)]() 5 | 6 | ## Install 7 | Install the knx-listener globally and use commands from the command line 8 | ``` 9 | sudo npm install -g knx-listener 10 | ``` 11 | ## Remote access to the knx net 12 | ### Monitor telegrams 13 | ``` 14 | Usage bin/busmonitor.js -s 15 | 16 | Options: 17 | -t, --timeout Seconds to retry, 0 - fail on first attemp [default: 0] 18 | -p, --port Remote port number [default: 3671] 19 | -s, --server Remote ip address [required] 20 | -h, --help Show help [boolean] 21 | ``` 22 | ``` 23 | busmonitor -s 192.168.1.100 24 | ``` 25 | ![](http://i.giphy.com/26xBuNRYG1nGUnj3O.gif) 26 | 27 | ### Write value **1** to **0/0/1** through **192.168.1.100** 28 | ``` 29 | Usage bin/groupswrite.js -s -g -d 30 | 31 | Options: 32 | -s, --server Remote ip address [required] 33 | -p, --port Remote port number [default: 3671] 34 | -g, --groupAddress Group address to issue the write telegram to [required] 35 | -d, --data Data to write [required] 36 | -h, --help Show help [boolean] 37 | ``` 38 | ``` 39 | groupswrite -s 192.168.1.100 -g 0/0/1 -d 01 40 | ``` 41 | ![](http://i.giphy.com/26xBvwQEv3gKYdRp6.gif) 42 | 43 | ### Read value from **0/0/1** through **192.168.1.100** 44 | ``` 45 | Usage bin/groupsread.js -s -g 46 | 47 | Options: 48 | -s, --server Remote ip address [required] 49 | -p, --port Remote port number [default: 3671] 50 | -g, --groupAddress Group address to issue the read telegram to [required] 51 | -h, --help Show help [boolean] 52 | ``` 53 | ``` 54 | groupsread -s 192.168.1.100 -g 0/0/1 55 | ``` 56 | ![](http://i.giphy.com/l3q2Yr9ZgyRYYQBva.gif) 57 | 58 | ## Development use cases 59 | ```js 60 | const KnxListener = require("knx-listener"); 61 | 62 | // 1. Initialize bus listener 63 | const client = new KnxListener.BusListener(); 64 | 65 | // helper to terminate tunnel 66 | const die = () => { 67 | return client.disconnect().then( 68 | () => process.exit(), 69 | () => process.exit()); 70 | }; 71 | 72 | // 2. Establish tunneling with recovery time of 1s 73 | client.bind("192.168.1.105", 3671, { 74 | timeout: 1000, 75 | }); 76 | 77 | // 3. Print processed queries to the console 78 | client.on("query", console.log); 79 | 80 | client.ready(() => { 81 | // 4. When connection is established 82 | // 5. Send read telegram and receive response with data 83 | client.read(KnxListener.utils.knxAddr2num("0/0/1")).then((res) => { 84 | console.log("Remote responded with", res); 85 | }, (err) => { 86 | console.error("Request failed", err); 87 | }); 88 | // 6. Send write telegram with data 0xFF to the group address 0/0/2 89 | client.write([0xFF], KnxListener.utils.knxAddr2num("0/0/2")).then(() => { 90 | console.log("Success"); 91 | }, (err) => { 92 | console.error("Request failed", err); 93 | }); 94 | }); 95 | 96 | // ctrl+c to exit 97 | process.on('SIGINT', die); 98 | ``` 99 | 100 | ## TODO 101 | * Generate JSDoc 102 | * Integration testing 103 | * Create another npm module for data types encoding/decoding 104 | 105 | ## What is next? 106 | * You may create stream using websockets to broadcast telegrams to rich web apps 107 | * You may create ETS project parser to get group address and store them in db 108 | * You may create REST API with express 109 | * You may record telegrams to a database and return last values on demand 110 | * You may build visualization with any KNX gateway 111 | * You may delegate endcoding/decoding of data to your rich clients 112 | -------------------------------------------------------------------------------- /__tests__/helpers/fake-socket.ts: -------------------------------------------------------------------------------- 1 | import { createSocket, AddressInfo } from 'dgram'; 2 | import { EventEmitter } from 'events'; 3 | 4 | export class FakeSocket { 5 | info: AddressInfo = { 6 | address: '127.0.0.1', 7 | port: -1, 8 | family: 'IPv4', 9 | }; 10 | events = new EventEmitter(); 11 | bind = jest.fn((port) => { 12 | this.info.port = port; 13 | }); 14 | close = jest.fn(); 15 | send = jest.fn(); 16 | on = jest.fn((...args: any[]) => { 17 | this.events.on.apply(this.events, args); 18 | return this; 19 | }); 20 | once = jest.fn((...args: any[]) => { 21 | this.events.once.apply(this.events, args); 22 | return this; 23 | }); 24 | address = jest.fn(() => { 25 | return this.info; 26 | }); 27 | constructor() { 28 | } 29 | fakeConnected() { 30 | this.events.emit('listening'); 31 | return this; 32 | } 33 | fakeClose() { 34 | this.events.emit('close'); 35 | return this; 36 | } 37 | failNextBind(err: Error) { 38 | this.bind.mockImplementationOnce((...args: any[]) => { 39 | if (this.events.listeners('error').length > 0) { 40 | this.events.emit('error', err); 41 | } else { 42 | throw err; 43 | } 44 | const cb: Function = args[args.length - 1]; 45 | if (typeof cb === 'function') { 46 | cb.call(this); 47 | } 48 | }); 49 | return this; 50 | } 51 | fakeNextSend(err?: any, bytes?: number) { 52 | this.send.mockImplementationOnce((...args: any[]) => { 53 | const cb: Function = args[args.length - 1]; 54 | if (typeof cb === 'function') { 55 | cb.call(this, err, bytes); 56 | } 57 | }); 58 | return this; 59 | } 60 | fakeError(err: any) { 61 | this.events.emit('error', err); 62 | return this; 63 | } 64 | fakeData(data: Buffer, sender?: AddressInfo) { 65 | this.events.emit('message', data, sender); 66 | return this; 67 | } 68 | } 69 | 70 | export const mockSocket = () => { 71 | const socket = new FakeSocket(); 72 | (createSocket as jest.Mock).mockReturnValue(socket); 73 | return socket; 74 | }; 75 | -------------------------------------------------------------------------------- /__tests__/serializer.ts: -------------------------------------------------------------------------------- 1 | import { write, read } from 'src/serializer'; 2 | 3 | describe('Knx Interfaces', () => { 4 | it('can write small data', () => { 5 | const a = Buffer.from([ 6 | 0x06, 0x10, 0x04, 0x20, 0x00, 7 | 0x15, 0x04, 0x3f, 0x00, 0x00, 8 | 0x11, 0x00, 0xbc, 0xe0, 0x00, 9 | 0x00, 0x00, 0x04, 0x01, 0x00, 0x8F]); 10 | expect(write({ 11 | channelId: 63, 12 | data: Buffer.from([0xF]), 13 | dest: 0x04, 14 | source: 0x00, 15 | seqn: 0, 16 | })).toEqual(a); 17 | }); 18 | it('can write larger data', () => { 19 | const a = Buffer.from([ 20 | 0x06, 0x10, 0x04, 0x20, 0x00, 21 | 0x17, 0x04, 0x3f, 0x02, 0x00, 0x11, 0x00, 0xbc, 22 | 0xe0, 0x00, 0x04, 0x00, 0x02, 0x03, 0x00, 0x80, 0x0F, 0xFF]); 23 | expect(write({ 24 | data: Buffer.from([0x0F, 0xFF]), 25 | dest: 0x02, 26 | source: 0x04, 27 | seqn: 2, 28 | channelId: 63, 29 | })).toEqual(a); 30 | }) 31 | it('can read', () => { 32 | const a = Buffer.from([ 33 | 0x06, 0x10, 0x04, 0x20, 0x00, 0x15, 34 | 0x04, 0x3f, 0x01, 0x00, 0x11, 0x00, 35 | 0xbc, 0xe0, 0x00, 0x00, 0x00, 0x01, 36 | 0x01, 0x00, 0x00, 37 | ]); 38 | expect(read({ 39 | channelId: 63, 40 | dest: 0x01, 41 | source: 0x00, 42 | seqn: 1, 43 | })).toEqual(a); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /__tests__/utils/async-socket.spec.ts: -------------------------------------------------------------------------------- 1 | import { AsyncSocket } from 'src/utils/async-socket'; 2 | import { mockSocket, FakeSocket } from '__tests__/helpers/fake-socket'; 3 | 4 | jest.mock('dgram'); 5 | 6 | (jasmine as any).DEFAULT_TIMEOUT_INTERVAL = 0; 7 | 8 | describe('Async socket', () => { 9 | let sock: FakeSocket; 10 | let s: AsyncSocket; 11 | beforeEach(() => { 12 | sock = mockSocket(); 13 | s = new AsyncSocket(); 14 | }); 15 | it('should resolve connect promise', (done) => { 16 | s.connect(8000).then(({port}) => { 17 | expect(port).toBe(8000); 18 | done(); 19 | }); 20 | sock.fakeConnected(); 21 | }); 22 | it('should reject connect promise', (done) => { 23 | const err = new Error('my error'); 24 | sock.failNextBind(err); 25 | s.connect(8000).catch(e => { 26 | expect(e).toBe(err); 27 | done(); 28 | }); 29 | }); 30 | describe('when connected', () => { 31 | const d = Buffer.from([3, 2, 1]); 32 | beforeEach((done) => { 33 | s.connect(8000).then(done); 34 | sock.fakeConnected(); 35 | }); 36 | it('should broadcast raw data', (done) => { 37 | s.on('raw', (raw) => { 38 | expect(raw).toBe(d); 39 | done(); 40 | }); 41 | sock.fakeData(d); 42 | }); 43 | it('should resolve connect promise when already connected and return used port', (done) => { 44 | s.connect(1000).then(({port}) => { 45 | try { 46 | expect(port).toBe(8000); 47 | done(); 48 | } catch (e) { done.fail(e); } 49 | }); 50 | }); 51 | it('should close connection', (done) => { 52 | s.disconnect().then(() => { 53 | try { 54 | expect(sock.close).toBeCalled(); 55 | done(); 56 | } catch (e) { done.fail(e); } 57 | }); 58 | sock.fakeClose(); 59 | }); 60 | it('should complete when closed', (done) => { 61 | s.complete().then(done); 62 | sock.fakeClose(); 63 | }); 64 | it('should send data', (done) => { 65 | sock.fakeNextSend(null, d.byteLength); 66 | s.send('localhost', 1000, d).then(() => { 67 | try { 68 | const call = sock.send.mock.calls[0]; 69 | call.pop(); 70 | expect(call).toEqual([d, 1000, 'localhost']); 71 | done(); 72 | } catch (e) { done.fail(e); } 73 | }); 74 | }); 75 | it('should reject send promise on error', (done) => { 76 | const err = new Error('bad send'); 77 | sock.fakeNextSend(err); 78 | s.send('localhost', 1000, d).catch((e) => { 79 | try { 80 | expect(e).toBe(err); 81 | done(); 82 | } catch (e) { done.fail(e); } 83 | }); 84 | }); 85 | it('should reject send promise if not all data sent', (done) => { 86 | const bytesn = 1; 87 | const err = new Error(`Expected to send ${d.length} bytes, but sent ${bytesn}`); 88 | sock.fakeNextSend(null, bytesn); 89 | s.send('localhost', 1000, d).catch(e => { 90 | try { 91 | expect(e).toEqual(err); 92 | } catch (e) { done.fail(e); } 93 | done(); 94 | }); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /bin/busmonitor.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | const yargs = require("yargs"); 4 | const chalk = require("chalk"); 5 | const bus_listener_1 = require("../dist/bus-listener"); 6 | const index_1 = require("../dist/utils/index"); 7 | const util_1 = require("util"); 8 | const argv = yargs.usage('Usage $0 -s ') 9 | .demand(['server']) 10 | .alias('p', 'port') 11 | .alias('s', 'server') 12 | .alias('t', 'timeout') 13 | .default('port', 3671) 14 | .default('timeout', 0) 15 | .describe('server', 'Remote ip address') 16 | .describe('port', 'Remote port number') 17 | .describe('t', 'Seconds to retry, 0 - fail on first attemp') 18 | .help('help') 19 | .alias('h', 'help') 20 | .coerce('server', (ip) => { 21 | if (!index_1.isIPv4(ip)) { 22 | throw new Error(`Invalid ip address ${ip}`); 23 | } 24 | return ip; 25 | }) 26 | .coerce('port', (port) => { 27 | const portNumber = +port; 28 | if (portNumber < 0 || 65535 < portNumber) { 29 | throw new Error(`Invalid port number ${portNumber}`); 30 | } 31 | return portNumber; 32 | }) 33 | .coerce('timeout', (timeout) => { 34 | return ((+timeout) >>> 0) * 1000; 35 | }) 36 | .example('$0 -s 10.10.10.0', 'Will listen bus through 10.10.10.0 knx gateway') 37 | .epilog(util_1.format('GitHub: %s', chalk.underline('https://github.com/crabicode/knx-listener'))) 38 | .argv; 39 | console.log(`Listening ${argv.server}:${argv.port}`); 40 | const server = new bus_listener_1.BusListener(); 41 | const die = () => { 42 | return server.disconnect().then(() => process.exit(), () => process.exit()); 43 | }; 44 | const fail = (format, ...param) => { 45 | console.error(chalk.red(`[ FAIL ]`) + ` ${util_1.format(format, ...param)}`); 46 | }; 47 | const ok = (format, ...param) => { 48 | console.error(chalk.green(`[ OK ]`) + ` ${util_1.format(format, ...param)}`); 49 | }; 50 | process.on('SIGINT', die); 51 | server.bind(argv.server, argv.port, { 52 | timeout: argv.timeout, 53 | onFailure: (err) => { 54 | fail('Error ocurred while connecting %s', err.code); 55 | }, 56 | }).catch(die); 57 | server.on('query', (query) => { 58 | const action = ((action) => { 59 | switch (action) { 60 | case 0x00: return 'read'; 61 | case 0x40: return 'response'; 62 | case 0x80: return 'write'; 63 | default: return undefined; 64 | } 65 | ; 66 | })(query.action); 67 | if (action) { 68 | const knxaddr = index_1.num2knxAddr(query.dest); 69 | if (action === 'write' || action === 'response') { 70 | const data = Buffer.from(query.data) 71 | .toString('hex').match(/.{1,2}/g).join(':'); 72 | ok('%s data %s to %s', action, data, knxaddr); 73 | } 74 | if (action === 'read') { 75 | ok('read %s', knxaddr); 76 | } 77 | } 78 | }); 79 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVzbW9uaXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImJ1c21vbml0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFDQSwrQkFBK0I7QUFDL0IsK0JBQStCO0FBQy9CLHVEQUFtRDtBQUNuRCwrQ0FBMEQ7QUFDMUQsK0JBQTJDO0FBRTNDLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsMEJBQTBCLENBQUM7S0FDakQsTUFBTSxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUM7S0FDbEIsS0FBSyxDQUFDLEdBQUcsRUFBRSxNQUFNLENBQUM7S0FDbEIsS0FBSyxDQUFDLEdBQUcsRUFBRSxRQUFRLENBQUM7S0FDcEIsS0FBSyxDQUFDLEdBQUcsRUFBRSxTQUFTLENBQUM7S0FDckIsT0FBTyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUM7S0FDckIsT0FBTyxDQUFDLFNBQVMsRUFBRSxDQUFDLENBQUM7S0FDckIsUUFBUSxDQUFDLFFBQVEsRUFBRSxtQkFBbUIsQ0FBQztLQUN2QyxRQUFRLENBQUMsTUFBTSxFQUFFLG9CQUFvQixDQUFDO0tBQ3RDLFFBQVEsQ0FBQyxHQUFHLEVBQUUsNENBQTRDLENBQUM7S0FDM0QsSUFBSSxDQUFDLE1BQU0sQ0FBQztLQUNaLEtBQUssQ0FBQyxHQUFHLEVBQUUsTUFBTSxDQUFDO0tBQ2xCLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQyxFQUFVO0lBQzNCLEVBQUUsQ0FBQyxDQUFDLENBQUMsY0FBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNoQixNQUFNLElBQUksS0FBSyxDQUFDLHNCQUFzQixFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQzlDLENBQUM7SUFDRCxNQUFNLENBQUMsRUFBRSxDQUFDO0FBQ1osQ0FBQyxDQUFDO0tBQ0QsTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDLElBQVk7SUFDM0IsTUFBTSxVQUFVLEdBQUcsQ0FBQyxJQUFJLENBQUM7SUFDekIsRUFBRSxDQUFD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QUksTUFBTSxLQUFLLFVBQVUsQ0FBQyxDQUFDLENBQUM7WUFDaEQsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDO2lCQUNqQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUM5QyxFQUFFLENBQUMsa0JBQWtCLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQztRQUNoRCxDQUFDO1FBQ0QsRUFBRSxDQUFDLENBQUMsTUFBTSxLQUFLLE1BQU0sQ0FBQyxDQUFDLENBQUM7WUFDdEIsRUFBRSxDQUFDLFNBQVMsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUN6QixDQUFDO0lBQ0gsQ0FBQztBQUNILENBQUMsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiIyEvdXNyL2Jpbi9lbnYgbm9kZVxuaW1wb3J0ICogYXMgeWFyZ3MgZnJvbSAneWFyZ3MnO1xuaW1wb3J0ICogYXMgY2hhbGsgZnJvbSAnY2hhbGsnO1xuaW1wb3J0IHsgQnVzTGlzdGVuZXIgfSBmcm9tICcuLi9kaXN0L2J1cy1saXN0ZW5lcic7XG5pbXBvcnQgeyBudW0ya254QWRkciwgaXNJUHY0IH0gZnJvbSAnLi4vZGlzdC91dGlscy9pbmRleCc7XG5pbXBvcnQgeyBmb3JtYXQgYXMgc3RyRm9ybWF0IH0gZnJvbSAndXRpbCc7XG5cbmNvbnN0IGFyZ3YgPSB5YXJncy51c2FnZSgnVXNhZ2UgJDAgLXMgPGlwIGFkZHJlc3M+JylcbiAgLmRlbWFuZChbJ3NlcnZlciddKVxuICAuYWxpYXMoJ3AnLCAncG9ydCcpXG4gIC5hbGlhcygncycsICdzZXJ2ZXInKVxuICAuYWxpYXMoJ3QnLCAndGltZW91dCcpXG4gIC5kZWZhdWx0KCdwb3J0JywgMzY3MSlcbiAgLmRlZmF1bHQoJ3RpbWVvdXQnLCAwKVxuICAuZGVzY3JpYmUoJ3NlcnZlcicsICdSZW1vdGUgaXAgYWRkcmVzcycpXG4gIC5kZXNjcmliZSgncG9ydCcsICdSZW1vdGUgcG9ydCBudW1iZXInKVxuICAuZGVzY3JpYmUoJ3QnLCAnU2Vjb25kcyB0byByZXRyeSwgMCAtIGZhaWwgb24gZmlyc3QgYXR0ZW1wJylcbiAgLmhlbHAoJ2hlbHAnKVxuICAuYWxpYXMoJ2gnLCAnaGVscCcpXG4gIC5jb2VyY2UoJ3NlcnZlcicsIChpcDogc3RyaW5nKSA9PiB7XG4gICAgaWYgKCFpc0lQdjQoaXApKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgaXAgYWRkcmVzcyAke2lwfWApO1xuICAgIH1cbiAgICByZXR1cm4gaXA7XG4gIH0pXG4gIC5jb2VyY2UoJ3BvcnQnLCAocG9ydDogc3RyaW5nKSA9PiB7XG4gICAgY29uc3QgcG9ydE51bWJlciA9ICtwb3J0O1xuICAgIGlmIChwb3J0TnVtYmVyIDwgMCB8fCA2NTUzNSA8IHBvcnROdW1iZXIpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBwb3J0IG51bWJlciAke3BvcnROdW1iZXJ9YCk7XG4gICAgfVxuICAgIHJldHVybiBwb3J0TnVtYmVyO1xuICB9KVxuICAuY29lcmNlKCd0aW1lb3V0JywgKHRpbWVvdXQ6IHN0cmluZykgPT4ge1xuICAgIHJldHVybiAoKCt0aW1lb3V0KSA+Pj4gMCkgKiAxMDAwO1xuICB9KVxuICAuZXhhbXBsZSgnJDAgLXMgMTAuMTAuMTAuMCcsICdXaWxsIGxpc3RlbiBidXMgdGhyb3VnaCAxMC4xMC4xMC4wIGtueCBnYXRld2F5JylcbiAgLmVwaWxvZyhzdHJGb3JtYXQoJ0dpdEh1YjogJXMnLCBjaGFsay51bmRlcmxpbmUoJ2h0dHBzOi8vZ2l0aHViLmNvbS9jcmFiaWNvZGUva254LWxpc3RlbmVyJykpKVxuICAuYXJndjtcblxuLy8gdHNsaW50OmRpc2FibGUtbmV4dC1saW5lOm5vLWNvbnNvbGVcbmNvbnNvbGUubG9nKGBMaXN0ZW5pbmcgJHthcmd2LnNlcnZlcn06JHthcmd2LnBvcnR9YCk7XG5cbmNvbnN0IHNlcnZlciA9IG5ldyBCdXNMaXN0ZW5lcigpO1xuXG5jb25zdCBkaWUgPSAoKSA9PiB7XG4gIHJldHVybiBzZXJ2ZXIuZGlzY29ubmVjdCgpLnRoZW4oXG4gICAgKCkgPT4gcHJvY2Vzcy5leGl0KCksXG4gICAgKCkgPT4gcHJvY2Vzcy5leGl0KCksXG4gICk7XG59O1xuXG5jb25zdCBmYWlsID0gKGZvcm1hdDogYW55LCAuLi5wYXJhbTogYW55W10pID0+IHtcbiAgY29uc29sZS5lcnJvcihcbiAgICBjaGFsay5yZWQoYFsgRkFJTCBdYCkgKyBgICR7c3RyRm9ybWF0KGZvcm1hdCwgLi4ucGFyYW0pfWAsXG4gICk7XG59O1xuXG5jb25zdCBvayA9IChmb3JtYXQ6IGFueSwgLi4ucGFyYW06IGFueVtdKSA9PiB7XG4gIGNvbnNvbGUuZXJyb3IoXG4gICAgY2hhbGsuZ3JlZW4oYFsgT0sgXWApICsgYCAke3N0ckZvcm1hdChmb3JtYXQsIC4uLnBhcmFtKX1gLFxuICApO1xufTtcblxuLy8gQ2xvc2UgdHVubmVsaW5nIG9uIGN0cmwrY1xucHJvY2Vzcy5vbignU0lHSU5UJywgZGllKTtcblxuc2VydmVyLmJpbmQoYXJndi5zZXJ2ZXIsIGFyZ3YucG9ydCwge1xuICB0aW1lb3V0OiBhcmd2LnRpbWVvdXQsXG4gIG9uRmFpbHVyZTogKGVycjogRXJyb3IgJiB7IGNvZGU6IHN0cmluZyB9KSA9PiB7XG4gICAgZmFpbCgnRXJyb3Igb2N1cnJlZCB3aGlsZSBjb25uZWN0aW5nICVzJywgZXJyLmNvZGUpO1xuICB9LFxufSkuY2F0Y2goZGllKTtcblxuc2VydmVyLm9uKCdxdWVyeScsIChxdWVyeTogYW55KSA9PiB7XG4gIGNvbnN0IGFjdGlvbiA9ICgoYWN0aW9uKSA9PiB7XG4gICAgc3dpdGNoIChhY3Rpb24pIHtcbiAgICAgIGNhc2UgMHgwMDogcmV0dXJuICdyZWFkJztcbiAgICAgIGNhc2UgMHg0MDogcmV0dXJuICdyZXNwb25zZSc7XG4gICAgICBjYXNlIDB4ODA6IHJldHVybiAnd3JpdGUnO1xuICAgICAgZGVmYXVsdDogcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9O1xuICB9KShxdWVyeS5hY3Rpb24pO1xuICBpZiAoYWN0aW9uKSB7XG4gICAgY29uc3Qga254YWRkciA9IG51bTJrbnhBZGRyKHF1ZXJ5LmRlc3QpO1xuICAgIGlmIChhY3Rpb24gPT09ICd3cml0ZScgfHwgYWN0aW9uID09PSAncmVzcG9uc2UnKSB7XG4gICAgICBjb25zdCBkYXRhID0gQnVmZmVyLmZyb20ocXVlcnkuZGF0YSlcbiAgICAgICAgLnRvU3RyaW5nKCdoZXgnKS5tYXRjaCgvLnsxLDJ9L2cpLmpvaW4oJzonKTtcbiAgICAgIG9rKCclcyBkYXRhICVzIHRvICVzJywgYWN0aW9uLCBkYXRhLCBrbnhhZGRyKTtcbiAgICB9XG4gICAgaWYgKGFjdGlvbiA9PT0gJ3JlYWQnKSB7XG4gICAgICBvaygncmVhZCAlcycsIGtueGFkZHIpO1xuICAgIH1cbiAgfVxufSk7XG4iXX0= -------------------------------------------------------------------------------- /bin/busmonitor.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as yargs from 'yargs'; 3 | import * as chalk from 'chalk'; 4 | import { BusListener } from '../dist/bus-listener'; 5 | import { num2knxAddr, isIPv4 } from '../dist/utils/index'; 6 | import { format as strFormat } from 'util'; 7 | 8 | const argv = yargs.usage('Usage $0 -s ') 9 | .demand(['server']) 10 | .alias('p', 'port') 11 | .alias('s', 'server') 12 | .alias('t', 'timeout') 13 | .default('port', 3671) 14 | .default('timeout', 0) 15 | .describe('server', 'Remote ip address') 16 | .describe('port', 'Remote port number') 17 | .describe('t', 'Seconds to retry, 0 - fail on first attemp') 18 | .help('help') 19 | .alias('h', 'help') 20 | .coerce('server', (ip: string) => { 21 | if (!isIPv4(ip)) { 22 | throw new Error(`Invalid ip address ${ip}`); 23 | } 24 | return ip; 25 | }) 26 | .coerce('port', (port: string) => { 27 | const portNumber = +port; 28 | if (portNumber < 0 || 65535 < portNumber) { 29 | throw new Error(`Invalid port number ${portNumber}`); 30 | } 31 | return portNumber; 32 | }) 33 | .coerce('timeout', (timeout: string) => { 34 | return ((+timeout) >>> 0) * 1000; 35 | }) 36 | .example('$0 -s 10.10.10.0', 'Will listen bus through 10.10.10.0 knx gateway') 37 | .epilog(strFormat('GitHub: %s', chalk.underline('https://github.com/crabicode/knx-listener'))) 38 | .argv; 39 | 40 | // tslint:disable-next-line:no-console 41 | console.log(`Listening ${argv.server}:${argv.port}`); 42 | 43 | const server = new BusListener(); 44 | 45 | const die = () => { 46 | return server.disconnect().then( 47 | () => process.exit(), 48 | () => process.exit(), 49 | ); 50 | }; 51 | 52 | const fail = (format: any, ...param: any[]) => { 53 | console.error( 54 | chalk.red(`[ FAIL ]`) + ` ${strFormat(format, ...param)}`, 55 | ); 56 | }; 57 | 58 | const ok = (format: any, ...param: any[]) => { 59 | console.error( 60 | chalk.green(`[ OK ]`) + ` ${strFormat(format, ...param)}`, 61 | ); 62 | }; 63 | 64 | // Close tunneling on ctrl+c 65 | process.on('SIGINT', die); 66 | 67 | server.bind(argv.server, argv.port, { 68 | timeout: argv.timeout, 69 | onFailure: (err: Error & { code: string }) => { 70 | fail('Error ocurred while connecting %s', err.code); 71 | }, 72 | }).catch(die); 73 | 74 | server.on('query', (query: any) => { 75 | const action = ((action) => { 76 | switch (action) { 77 | case 0x00: return 'read'; 78 | case 0x40: return 'response'; 79 | case 0x80: return 'write'; 80 | default: return undefined; 81 | }; 82 | })(query.action); 83 | if (action) { 84 | const knxaddr = num2knxAddr(query.dest); 85 | if (action === 'write' || action === 'response') { 86 | const data = Buffer.from(query.data) 87 | .toString('hex').match(/.{1,2}/g).join(':'); 88 | ok('%s data %s to %s', action, data, knxaddr); 89 | } 90 | if (action === 'read') { 91 | ok('read %s', knxaddr); 92 | } 93 | } 94 | }); 95 | -------------------------------------------------------------------------------- /bin/groupsread.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | const chalk = require("chalk"); 4 | const yargs = require("yargs"); 5 | const index_1 = require("../dist/utils/index"); 6 | const bus_listener_1 = require("../dist/bus-listener"); 7 | const util_1 = require("util"); 8 | const argv = yargs.usage('Usage $0 -s -g ') 9 | .demand(['server', 'groupAddress']) 10 | .alias('s', 'server') 11 | .alias('p', 'port') 12 | .alias('g', 'groupAddress') 13 | .alias('h', 'help') 14 | .default('port', 3671) 15 | .describe('server', 'Remote ip address') 16 | .describe('port', 'Remote port number') 17 | .describe('groupAddress', 'Group address to issue the read telegram to') 18 | .coerce('server', (ip) => { 19 | if (!index_1.isIPv4(ip)) { 20 | throw new Error(`Invalid ip address ${ip}`); 21 | } 22 | return ip; 23 | }) 24 | .coerce('port', (port) => { 25 | const portNumber = +port; 26 | if (portNumber < 0 || 65535 < portNumber) { 27 | throw new Error(`Invalid port number ${portNumber}`); 28 | } 29 | return portNumber; 30 | }) 31 | .check((args) => { 32 | if (!index_1.isKnxAddress(args.groupAddress)) { 33 | throw new Error(`Invalid group address ${args.groupAddress}`); 34 | } 35 | return true; 36 | }) 37 | .example('$0 -s 10.10.10.0 -g 0/0/1', 'Will send read telegram to 0/0/1 group address on 10.10.10.0 knx gateway') 38 | .epilog(util_1.format('GitHub: %s', chalk.underline('https://github.com/crabicode/knx-listener'))) 39 | .help('help').argv; 40 | const server = new bus_listener_1.BusListener(); 41 | const die = () => { 42 | return server.disconnect().then(() => process.exit(), () => process.exit()); 43 | }; 44 | const fail = (format, ...param) => { 45 | console.error(chalk.red(`[ FAIL ]`) + ` ${util_1.format(format, ...param)}`); 46 | die(); 47 | }; 48 | const ok = (format, ...param) => { 49 | console.error(chalk.green(`[ OK ]`) + ` ${util_1.format(format, ...param)}`); 50 | die(); 51 | }; 52 | process.on('SIGINT', die); 53 | server.bind(argv.server, argv.port).catch((err) => { 54 | fail('Failed to send request to %s:%d due to %s', argv.server, argv.port, err.code); 55 | }); 56 | server.ready(() => { 57 | server.read(index_1.knxAddr2num(argv.groupAddress)).then((res) => { 58 | const responder = index_1.num2knxAddr(res.source, false); 59 | const ga = index_1.num2knxAddr(res.dest); 60 | const data = Buffer.from([...res.data]).toString('hex').match(/.{1,2}/g).join(' '); 61 | ok('%s responds to %s with %s data', responder, ga, data); 62 | setImmediate(() => { 63 | server.disconnect(); 64 | }); 65 | }).catch((_err) => { 66 | fail(`No response received for read telegram to %s`, chalk.underline(argv.groupAddress)); 67 | }); 68 | }); 69 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ3JvdXBzcmVhZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImdyb3Vwc3JlYWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFDQSwrQkFBK0I7QUFDL0IsK0JBQStCO0FBQy9CLCtDQUFxRjtBQUNyRix1REFBbUQ7QUFDbkQsK0JBQTJDO0FBRTNDLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsNkNBQTZDLENBQUM7S0FDcEUsTUFBTSxDQUFDLENBQUMsUUFBUSxFQUFFLGNBQWMsQ0FBQyxDQUFDO0tBQ2xDLEtBQUssQ0FBQyxHQUFHLEVBQUUsUUFBUSxDQUFDO0tBQ3BCLEtBQUssQ0FBQyxHQUFHLEVBQUUsTUFBTSxDQUFDO0tBQ2xCLEtBQUssQ0FBQyxHQUFHLEVBQUUsY0FBYyxDQUFDO0tBQzFCLEtBQUssQ0FBQyxHQUFHLEVBQUUsTUFBTSxDQUFDO0tBQ2xCLE9BQU8sQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDO0tBQ3JCLFFBQVEsQ0FBQyxRQUFRLEVBQUUsbUJBQW1CLENBQUM7S0FDdkMsUUFBUSxDQUFDLE1BQU0sRUFBRSxvQkFBb0IsQ0FBQztLQUN0QyxRQUFRLENBQUMsY0FBYyxFQUFFLDZDQUE2QyxDQUFDO0tBQ3ZFLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQyxFQUFVO0lBQzNCLEVBQUUsQ0FBQyxDQUFDLENBQUMsY0FBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNoQixNQUFNLElBQUksS0FBSyxDQUFDLHNCQUFzQixFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQzlDLENBQUM7SUFDRCxNQUFNLENBQUMsRUFBRSxDQUFDO0FBQ1osQ0FBQyxDQUFDO0tBQ0QsTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDLElBQVk7SUFDM0IsTUFBTSxVQUFVLEdBQUcsQ0FBQyxJQUFJLENBQUM7SUFDekIsRUFBRSxDQUFDLENBQUMsVUFBVSxHQUFHLENBQUMsSUFBSSxLQUFLLEdBQUcsVUFBVSxDQUFDLENBQUMs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QU0sQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUN0QixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLElBQUk7UUFDWixJQUFJLENBQUMsOENBQThDLEVBQ2pELEtBQUssQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUM7SUFDeEMsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIiMhL3Vzci9iaW4vZW52IG5vZGVcbmltcG9ydCAqIGFzIGNoYWxrIGZyb20gJ2NoYWxrJztcbmltcG9ydCAqIGFzIHlhcmdzIGZyb20gJ3lhcmdzJztcbmltcG9ydCB7IGlzSVB2NCwga254QWRkcjJudW0sIG51bTJrbnhBZGRyLCBpc0tueEFkZHJlc3MgfSBmcm9tICcuLi9kaXN0L3V0aWxzL2luZGV4JztcbmltcG9ydCB7IEJ1c0xpc3RlbmVyIH0gZnJvbSAnLi4vZGlzdC9idXMtbGlzdGVuZXInO1xuaW1wb3J0IHsgZm9ybWF0IGFzIHN0ckZvcm1hdCB9IGZyb20gJ3V0aWwnO1xuXG5jb25zdCBhcmd2ID0geWFyZ3MudXNhZ2UoJ1VzYWdlICQwIC1zIDxpcCBhZGRyZXNzPiAtZyA8Z3JvdXAgYWRkcmVzcz4nKVxuICAuZGVtYW5kKFsnc2VydmVyJywgJ2dyb3VwQWRkcmVzcyddKVxuICAuYWxpYXMoJ3MnLCAnc2VydmVyJylcbiAgLmFsaWFzKCdwJywgJ3BvcnQnKVxuICAuYWxpYXMoJ2cnLCAnZ3JvdXBBZGRyZXNzJylcbiAgLmFsaWFzKCdoJywgJ2hlbHAnKVxuICAuZGVmYXVsdCgncG9ydCcsIDM2NzEpXG4gIC5kZXNjcmliZSgnc2VydmVyJywgJ1JlbW90ZSBpcCBhZGRyZXNzJylcbiAgLmRlc2NyaWJlKCdwb3J0JywgJ1JlbW90ZSBwb3J0IG51bWJlcicpXG4gIC5kZXNjcmliZSgnZ3JvdXBBZGRyZXNzJywgJ0dyb3VwIGFkZHJlc3MgdG8gaXNzdWUgdGhlIHJlYWQgdGVsZWdyYW0gdG8nKVxuICAuY29lcmNlKCdzZXJ2ZXInLCAoaXA6IHN0cmluZykgPT4ge1xuICAgIGlmICghaXNJUHY0KGlwKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIGlwIGFkZHJlc3MgJHtpcH1gKTtcbiAgICB9XG4gICAgcmV0dXJuIGlwO1xuICB9KVxuICAuY29lcmNlKCdwb3J0JywgKHBvcnQ6IHN0cmluZykgPT4ge1xuICAgIGNvbnN0IHBvcnROdW1iZXIgPSArcG9ydDtcbiAgICBpZiAocG9ydE51bWJlciA8IDAgfHwgNjU1MzUgPCBwb3J0TnVtYmVyKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgcG9ydCBudW1iZXIgJHtwb3J0TnVtYmVyfWApO1xuICAgIH1cbiAgICByZXR1cm4gcG9ydE51bWJlcjtcbiAgfSlcbiAgLmNoZWNrKChhcmdzKSA9PiB7XG4gICAgaWYgKCFpc0tueEFkZHJlc3MoYXJncy5ncm91cEFkZHJlc3MpKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgZ3JvdXAgYWRkcmVzcyAke2FyZ3MuZ3JvdXBBZGRyZXNzfWApO1xuICAgIH1cbiAgICByZXR1cm4gdHJ1ZTtcbiAgfSlcbiAgLmV4YW1wbGUoJyQwIC1zIDEwLjEwLjEwLjAgLWcgMC8wLzEnLCAnV2lsbCBzZW5kIHJlYWQgdGVsZWdyYW0gdG8gMC8wLzEgZ3JvdXAgYWRkcmVzcyBvbiAxMC4xMC4xMC4wIGtueCBnYXRld2F5JylcbiAgLmVwaWxvZyhzdHJGb3JtYXQoJ0dpdEh1YjogJXMnLCBjaGFsay51bmRlcmxpbmUoJ2h0dHBzOi8vZ2l0aHViLmNvbS9jcmFiaWNvZGUva254LWxpc3RlbmVyJykpKVxuICAuaGVscCgnaGVscCcpLmFyZ3Y7XG5cbmNvbnN0IHNlcnZlciA9IG5ldyBCdXNMaXN0ZW5lcigpO1xuXG5jb25zdCBkaWUgPSAoKSA9PiB7XG4gIHJldHVybiBzZXJ2ZXIuZGlzY29ubmVjdCgpLnRoZW4oXG4gICAgKCkgPT4gcHJvY2Vzcy5leGl0KCksXG4gICAgKCkgPT4gcHJvY2Vzcy5leGl0KCksXG4gICk7XG59O1xuXG5jb25zdCBmYWlsID0gKGZvcm1hdDogYW55LCAuLi5wYXJhbTogYW55W10pID0+IHtcbiAgY29uc29sZS5lcnJvcihcbiAgICBjaGFsay5yZWQoYFsgRkFJTCBdYCkgKyBgICR7c3RyRm9ybWF0KGZvcm1hdCwgLi4ucGFyYW0pfWAsXG4gICk7XG4gIGRpZSgpO1xufTtcblxuY29uc3Qgb2sgPSAoZm9ybWF0OiBhbnksIC4uLnBhcmFtOiBhbnlbXSkgPT4ge1xuICBjb25zb2xlLmVycm9yKFxuICAgIGNoYWxrLmdyZWVuKGBbIE9LIF1gKSArIGAgJHtzdHJGb3JtYXQoZm9ybWF0LCAuLi5wYXJhbSl9YCxcbiAgKTtcbiAgZGllKCk7XG59O1xuXG5wcm9jZXNzLm9uKCdTSUdJTlQnLCBkaWUpOyAvLyBDbG9zZSB0dW5uZWxpbmcgb24gY3RybCtjXG5cbnNlcnZlci5iaW5kKGFyZ3Yuc2VydmVyLCBhcmd2LnBvcnQpLmNhdGNoKChlcnIpID0+IHtcbiAgZmFpbCgnRmFpbGVkIHRvIHNlbmQgcmVxdWVzdCB0byAlczolZCBkdWUgdG8gJXMnLCBhcmd2LnNlcnZlciwgYXJndi5wb3J0LCBlcnIuY29kZSk7XG59KTtcblxuc2VydmVyLnJlYWR5KCgpID0+IHtcbiAgc2VydmVyLnJlYWQoa254QWRkcjJudW0oYXJndi5ncm91cEFkZHJlc3MpKS50aGVuKChyZXMpID0+IHtcbiAgICBjb25zdCByZXNwb25kZXIgPSBudW0ya254QWRkcihyZXMuc291cmNlLCBmYWxzZSk7XG4gICAgY29uc3QgZ2EgPSBudW0ya254QWRkcihyZXMuZGVzdCk7XG4gICAgY29uc3QgZGF0YSA9IEJ1ZmZlci5mcm9tKFsuLi5yZXMuZGF0YV0pLnRvU3RyaW5nKCdoZXgnKS5tYXRjaCgvLnsxLDJ9L2cpLmpvaW4oJyAnKTtcbiAgICBvaygnJXMgcmVzcG9uZHMgdG8gJXMgd2l0aCAlcyBkYXRhJywgcmVzcG9uZGVyLCBnYSwgZGF0YSk7XG4gICAgc2V0SW1tZWRpYXRlKCgpID0+IHtcbiAgICAgIC8vIHNjaGVkdWxlIGRpc2Nvbm5lY3Qgb24gdGhlIG5leHQgZXZlbnQgY3ljbGVcbiAgICAgIHNlcnZlci5kaXNjb25uZWN0KCk7XG4gICAgfSk7XG4gIH0pLmNhdGNoKChfZXJyKSA9PiB7XG4gICAgZmFpbChgTm8gcmVzcG9uc2UgcmVjZWl2ZWQgZm9yIHJlYWQgdGVsZWdyYW0gdG8gJXNgLFxuICAgICAgY2hhbGsudW5kZXJsaW5lKGFyZ3YuZ3JvdXBBZGRyZXNzKSk7XG4gIH0pO1xufSk7XG4iXX0= -------------------------------------------------------------------------------- /bin/groupsread.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as chalk from 'chalk'; 3 | import * as yargs from 'yargs'; 4 | import { isIPv4, knxAddr2num, num2knxAddr, isKnxAddress } from '../dist/utils/index'; 5 | import { BusListener } from '../dist/bus-listener'; 6 | import { format as strFormat } from 'util'; 7 | 8 | const argv = yargs.usage('Usage $0 -s -g ') 9 | .demand(['server', 'groupAddress']) 10 | .alias('s', 'server') 11 | .alias('p', 'port') 12 | .alias('g', 'groupAddress') 13 | .alias('h', 'help') 14 | .default('port', 3671) 15 | .describe('server', 'Remote ip address') 16 | .describe('port', 'Remote port number') 17 | .describe('groupAddress', 'Group address to issue the read telegram to') 18 | .coerce('server', (ip: string) => { 19 | if (!isIPv4(ip)) { 20 | throw new Error(`Invalid ip address ${ip}`); 21 | } 22 | return ip; 23 | }) 24 | .coerce('port', (port: string) => { 25 | const portNumber = +port; 26 | if (portNumber < 0 || 65535 < portNumber) { 27 | throw new Error(`Invalid port number ${portNumber}`); 28 | } 29 | return portNumber; 30 | }) 31 | .check((args) => { 32 | if (!isKnxAddress(args.groupAddress)) { 33 | throw new Error(`Invalid group address ${args.groupAddress}`); 34 | } 35 | return true; 36 | }) 37 | .example('$0 -s 10.10.10.0 -g 0/0/1', 'Will send read telegram to 0/0/1 group address on 10.10.10.0 knx gateway') 38 | .epilog(strFormat('GitHub: %s', chalk.underline('https://github.com/crabicode/knx-listener'))) 39 | .help('help').argv; 40 | 41 | const server = new BusListener(); 42 | 43 | const die = () => { 44 | return server.disconnect().then( 45 | () => process.exit(), 46 | () => process.exit(), 47 | ); 48 | }; 49 | 50 | const fail = (format: any, ...param: any[]) => { 51 | console.error( 52 | chalk.red(`[ FAIL ]`) + ` ${strFormat(format, ...param)}`, 53 | ); 54 | die(); 55 | }; 56 | 57 | const ok = (format: any, ...param: any[]) => { 58 | console.error( 59 | chalk.green(`[ OK ]`) + ` ${strFormat(format, ...param)}`, 60 | ); 61 | die(); 62 | }; 63 | 64 | process.on('SIGINT', die); // Close tunneling on ctrl+c 65 | 66 | server.bind(argv.server, argv.port).catch((err) => { 67 | fail('Failed to send request to %s:%d due to %s', argv.server, argv.port, err.code); 68 | }); 69 | 70 | server.ready(() => { 71 | server.read(knxAddr2num(argv.groupAddress)).then((res) => { 72 | const responder = num2knxAddr(res.source, false); 73 | const ga = num2knxAddr(res.dest); 74 | const data = Buffer.from([...res.data]).toString('hex').match(/.{1,2}/g).join(' '); 75 | ok('%s responds to %s with %s data', responder, ga, data); 76 | setImmediate(() => { 77 | // schedule disconnect on the next event cycle 78 | server.disconnect(); 79 | }); 80 | }).catch((_err) => { 81 | fail(`No response received for read telegram to %s`, 82 | chalk.underline(argv.groupAddress)); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /bin/groupswrite.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | const chalk = require("chalk"); 4 | const yargs = require("yargs"); 5 | const index_1 = require("../dist/utils/index"); 6 | const bus_listener_1 = require("../dist/bus-listener"); 7 | const util_1 = require("util"); 8 | const argv = yargs.usage('Usage $0 -s -g -d ') 9 | .demand(['server', 'groupAddress', 'data']) 10 | .alias('s', 'server') 11 | .alias('p', 'port') 12 | .alias('g', 'groupAddress') 13 | .alias('d', 'data') 14 | .alias('h', 'help') 15 | .default('port', 3671) 16 | .describe('data', 'Data to write') 17 | .describe('server', 'Remote ip address') 18 | .describe('port', 'Remote port number') 19 | .describe('groupAddress', 'Group address to issue the write telegram to') 20 | .coerce('server', (ip) => { 21 | if (!index_1.isIPv4(ip)) { 22 | throw new Error(`Invalid ip address ${ip}`); 23 | } 24 | return ip; 25 | }) 26 | .coerce('data', (data) => { 27 | if (!/^([0-9A-Fa-f]{2})+([:][0-9A-Fa-f]{2})?$/.test(data)) { 28 | throw new Error(`Invalid data format ${data}`); 29 | } 30 | return data; 31 | }) 32 | .coerce('port', (port) => { 33 | const portNumber = +port; 34 | if (portNumber < 0 || 65535 < portNumber) { 35 | throw new Error(`Invalid port number ${portNumber}`); 36 | } 37 | return portNumber; 38 | }) 39 | .check((args) => { 40 | if (!index_1.isKnxAddress(args.groupAddress)) { 41 | throw new Error(`Invalid group address ${args.groupAddress}`); 42 | } 43 | return true; 44 | }) 45 | .example('$0 -s 10.10.10.0 -g 0/0/1 -d 00:FF', 'Writes 0x00 0xFF to 0/0/1 through 10.10.10.0 knx gateway') 46 | .epilog(util_1.format('GitHub: %s', chalk.underline('https://github.com/crabicode/knx-listener'))) 47 | .help('help').argv; 48 | const server = new bus_listener_1.BusListener(); 49 | const die = () => { 50 | return server.disconnect().then(() => process.exit(), () => process.exit()); 51 | }; 52 | const fail = (format, ...param) => { 53 | console.error(chalk.red(`[ FAIL ]`) + ` ${util_1.format(format, ...param)}`); 54 | die(); 55 | }; 56 | const ok = (format, ...param) => { 57 | console.error(chalk.green(`[ OK ]`) + ` ${util_1.format(format, ...param)}`); 58 | setImmediate(die); 59 | }; 60 | process.on('SIGINT', die); 61 | server.bind(argv.server, argv.port).catch((err) => { 62 | fail('Failed to send request to %s:%d due to %s', argv.server, argv.port, err.code); 63 | }); 64 | server.ready(() => { 65 | const data = Buffer.from(argv.data.split(':').map(i => parseInt(i, 16))); 66 | server.write(data, index_1.knxAddr2num(argv.groupAddress)).then(() => { 67 | ok(`Sent %s to %s`, argv.data, argv.groupAddress); 68 | }).catch((_err) => { 69 | fail(`Failed to write %s to %s`, argv.data, argv.groupAddress); 70 | }); 71 | }); 72 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ3JvdXBzd3JpdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJncm91cHN3cml0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUNBLCtCQUErQjtBQUMvQiwrQkFBK0I7QUFDL0IsK0NBQXdFO0FBQ3hFLHVEQUFtRDtBQUNuRCwrQkFBMkM7QUFFM0MsTUFBTSxJQUFJLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQywyREFBMkQsQ0FBQztLQUNsRixNQUFNLENBQUMsQ0FBQyxRQUFRLEVBQUUsY0FBYyxFQUFFLE1BQU0sQ0FBQyxDQUFDO0tBQzFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsUUFBUSxDQUFDO0tBQ3BCLEtBQUssQ0FBQyxHQUFHLEVBQUUsTUFBTSxDQUFDO0tBQ2xCLEtBQUssQ0FBQyxHQUFHLEVBQUUsY0FBYyxDQUFDO0tBQzFCLEtBQUssQ0FBQyxHQUFHLEVBQUUsTUFBTSxDQUFDO0tBQ2xCLEtBQUssQ0FBQyxHQUFHLEVBQUUsTUFBTSxDQUFDO0tBQ2xCLE9BQU8sQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDO0tBQ3JCLFFBQVEsQ0FBQyxNQUFNLEVBQUUsZUFBZSxDQUFDO0tBQ2pDLFFBQVEsQ0FBQyxRQUFRLEVBQUUsbUJBQW1CLENBQUM7S0FDdkMsUUFBUSxDQUFDLE1BQU0sRUFBRSxvQkFBb0IsQ0FBQztLQUN0QyxRQUFRLENBQUMsY0FBYyxFQUFFLDhDQUE4QyxDQUFDO0tBQ3hFLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQyxFQUFVO0lBQzNCLEVBQUUsQ0FBQyxDQUFDLENBQUMsY0FBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNoQixNQUFNLElBQUksS0FBSyxDQUFDLHNCQUFzQixFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQzlDLENBQUM7SUFDRCxNQUFNLENBQUMsRUFBRSxDQUFDO0FBQ1osQ0FBQyxDQUFDO0tBQ0QsTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDLElBQVk7SUFDM0IsRUFBRSxDQUFDLENB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Q0FBQztJQUN6RSxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxtQkFBVyxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztRQUN0RCxFQUFFLENBQUMsZUFBZSxFQUFFLElBQUksQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQ3BELENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLElBQUk7UUFDWixJQUFJLENBQUMsMEJBQTBCLEVBQzdCLElBQUksQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQ2xDLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIjIS91c3IvYmluL2VudiBub2RlXG5pbXBvcnQgKiBhcyBjaGFsayBmcm9tICdjaGFsayc7XG5pbXBvcnQgKiBhcyB5YXJncyBmcm9tICd5YXJncyc7XG5pbXBvcnQgeyBpc0lQdjQsIGtueEFkZHIybnVtLCBpc0tueEFkZHJlc3MgfSBmcm9tICcuLi9kaXN0L3V0aWxzL2luZGV4JztcbmltcG9ydCB7IEJ1c0xpc3RlbmVyIH0gZnJvbSAnLi4vZGlzdC9idXMtbGlzdGVuZXInO1xuaW1wb3J0IHsgZm9ybWF0IGFzIHN0ckZvcm1hdCB9IGZyb20gJ3V0aWwnO1xuXG5jb25zdCBhcmd2ID0geWFyZ3MudXNhZ2UoJ1VzYWdlICQwIC1zIDxpcCBhZGRyZXNzPiAtZyA8Z3JvdXAgYWRkcmVzcz4gLWQgPFhYOlhYOi4uPicpXG4gIC5kZW1hbmQoWydzZXJ2ZXInLCAnZ3JvdXBBZGRyZXNzJywgJ2RhdGEnXSlcbiAgLmFsaWFzKCdzJywgJ3NlcnZlcicpXG4gIC5hbGlhcygncCcsICdwb3J0JylcbiAgLmFsaWFzKCdnJywgJ2dyb3VwQWRkcmVzcycpXG4gIC5hbGlhcygnZCcsICdkYXRhJylcbiAgLmFsaWFzKCdoJywgJ2hlbHAnKVxuICAuZGVmYXVsdCgncG9ydCcsIDM2NzEpXG4gIC5kZXNjcmliZSgnZGF0YScsICdEYXRhIHRvIHdyaXRlJylcbiAgLmRlc2NyaWJlKCdzZXJ2ZXInLCAnUmVtb3RlIGlwIGFkZHJlc3MnKVxuICAuZGVzY3JpYmUoJ3BvcnQnLCAnUmVtb3RlIHBvcnQgbnVtYmVyJylcbiAgLmRlc2NyaWJlKCdncm91cEFkZHJlc3MnLCAnR3JvdXAgYWRkcmVzcyB0byBpc3N1ZSB0aGUgd3JpdGUgdGVsZWdyYW0gdG8nKVxuICAuY29lcmNlKCdzZXJ2ZXInLCAoaXA6IHN0cmluZykgPT4ge1xuICAgIGlmICghaXNJUHY0KGlwKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIGlwIGFkZHJlc3MgJHtpcH1gKTtcbiAgICB9XG4gICAgcmV0dXJuIGlwO1xuICB9KVxuICAuY29lcmNlKCdkYXRhJywgKGRhdGE6IHN0cmluZykgPT4ge1xuICAgIGlmICghL14oWzAtOUEtRmEtZl17Mn0pKyhbOl1bMC05QS1GYS1mXXsyfSk/JC8udGVzdChkYXRhKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIGRhdGEgZm9ybWF0ICR7ZGF0YX1gKTtcbiAgICB9XG4gICAgcmV0dXJuIGRhdGE7XG4gIH0pXG4gIC5jb2VyY2UoJ3BvcnQnLCAocG9ydDogc3RyaW5nKSA9PiB7XG4gICAgY29uc3QgcG9ydE51bWJlciA9ICtwb3J0O1xuICAgIGlmIChwb3J0TnVtYmVyIDwgMCB8fCA2NTUzNSA8IHBvcnROdW1iZXIpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBwb3J0IG51bWJlciAke3BvcnROdW1iZXJ9YCk7XG4gICAgfVxuICAgIHJldHVybiBwb3J0TnVtYmVyO1xuICB9KVxuICAuY2hlY2soKGFyZ3MpID0+IHtcbiAgICBpZiAoIWlzS254QWRkcmVzcyhhcmdzLmdyb3VwQWRkcmVzcykpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBncm91cCBhZGRyZXNzICR7YXJncy5ncm91cEFkZHJlc3N9YCk7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xuICB9KVxuICAuZXhhbXBsZSgnJDAgLXMgMTAuMTAuMTAuMCAtZyAwLzAvMSAtZCAwMDpGRicsICdXcml0ZXMgMHgwMCAweEZGIHRvIDAvMC8xIHRocm91Z2ggMTAuMTAuMTAuMCBrbnggZ2F0ZXdheScpXG4gIC5lcGlsb2coc3RyRm9ybWF0KCdHaXRIdWI6ICVzJywgY2hhbGsudW5kZXJsaW5lKCdodHRwczovL2dpdGh1Yi5jb20vY3JhYmljb2RlL2tueC1saXN0ZW5lcicpKSlcbiAgLmhlbHAoJ2hlbHAnKS5hcmd2O1xuXG5jb25zdCBzZXJ2ZXIgPSBuZXcgQnVzTGlzdGVuZXIoKTtcblxuY29uc3QgZGllID0gKCkgPT4ge1xuICByZXR1cm4gc2VydmVyLmRpc2Nvbm5lY3QoKS50aGVuKFxuICAgICgpID0+IHByb2Nlc3MuZXhpdCgpLFxuICAgICgpID0+IHByb2Nlc3MuZXhpdCgpLFxuICApO1xufTtcblxuY29uc3QgZmFpbCA9IChmb3JtYXQ6IGFueSwgLi4ucGFyYW06IGFueVtdKSA9PiB7XG4gIGNvbnNvbGUuZXJyb3IoXG4gICAgY2hhbGsucmVkKGBbIEZBSUwgXWApICsgYCAke3N0ckZvcm1hdChmb3JtYXQsIC4uLnBhcmFtKX1gLFxuICApO1xuICBkaWUoKTtcbn07XG5cbmNvbnN0IG9rID0gKGZvcm1hdDogYW55LCAuLi5wYXJhbTogYW55W10pID0+IHtcbiAgY29uc29sZS5lcnJvcihcbiAgICBjaGFsay5ncmVlbihgWyBPSyBdYCkgKyBgICR7c3RyRm9ybWF0KGZvcm1hdCwgLi4ucGFyYW0pfWAsXG4gICk7XG4gIC8vIHNjaGVkdWxlIGRpZSBuZXh0IGN5Y2xlXG4gIHNldEltbWVkaWF0ZShkaWUpO1xufTtcblxucHJvY2Vzcy5vbignU0lHSU5UJywgZGllKTsgLy8gQ2xvc2UgdHVubmVsaW5nIG9uIGN0cmwrY1xuXG5zZXJ2ZXIuYmluZChhcmd2LnNlcnZlciwgYXJndi5wb3J0KS5jYXRjaCgoZXJyKSA9PiB7XG4gIGZhaWwoJ0ZhaWxlZCB0byBzZW5kIHJlcXVlc3QgdG8gJXM6JWQgZHVlIHRvICVzJywgYXJndi5zZXJ2ZXIsIGFyZ3YucG9ydCwgZXJyLmNvZGUpO1xufSk7XG5cbnNlcnZlci5yZWFkeSgoKSA9PiB7XG4gIGNvbnN0IGRhdGEgPSBCdWZmZXIuZnJvbShhcmd2LmRhdGEuc3BsaXQoJzonKS5tYXAoaSA9PiBwYXJzZUludChpLCAxNikpKTtcbiAgc2VydmVyLndyaXRlKGRhdGEsIGtueEFkZHIybnVtKGFyZ3YuZ3JvdXBBZGRyZXNzKSkudGhlbigoKSA9PiB7XG4gICAgb2soYFNlbnQgJXMgdG8gJXNgLCBhcmd2LmRhdGEsIGFyZ3YuZ3JvdXBBZGRyZXNzKTtcbiAgfSkuY2F0Y2goKF9lcnIpID0+IHtcbiAgICBmYWlsKGBGYWlsZWQgdG8gd3JpdGUgJXMgdG8gJXNgLFxuICAgICAgYXJndi5kYXRhLCBhcmd2Lmdyb3VwQWRkcmVzcyk7XG4gIH0pO1xufSk7XG4iXX0= -------------------------------------------------------------------------------- /bin/groupswrite.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as chalk from 'chalk'; 3 | import * as yargs from 'yargs'; 4 | import { isIPv4, knxAddr2num, isKnxAddress } from '../dist/utils/index'; 5 | import { BusListener } from '../dist/bus-listener'; 6 | import { format as strFormat } from 'util'; 7 | 8 | const argv = yargs.usage('Usage $0 -s -g -d ') 9 | .demand(['server', 'groupAddress', 'data']) 10 | .alias('s', 'server') 11 | .alias('p', 'port') 12 | .alias('g', 'groupAddress') 13 | .alias('d', 'data') 14 | .alias('h', 'help') 15 | .default('port', 3671) 16 | .describe('data', 'Data to write') 17 | .describe('server', 'Remote ip address') 18 | .describe('port', 'Remote port number') 19 | .describe('groupAddress', 'Group address to issue the write telegram to') 20 | .coerce('server', (ip: string) => { 21 | if (!isIPv4(ip)) { 22 | throw new Error(`Invalid ip address ${ip}`); 23 | } 24 | return ip; 25 | }) 26 | .coerce('data', (data: string) => { 27 | if (!/^([0-9A-Fa-f]{2})+([:][0-9A-Fa-f]{2})?$/.test(data)) { 28 | throw new Error(`Invalid data format ${data}`); 29 | } 30 | return data; 31 | }) 32 | .coerce('port', (port: string) => { 33 | const portNumber = +port; 34 | if (portNumber < 0 || 65535 < portNumber) { 35 | throw new Error(`Invalid port number ${portNumber}`); 36 | } 37 | return portNumber; 38 | }) 39 | .check((args) => { 40 | if (!isKnxAddress(args.groupAddress)) { 41 | throw new Error(`Invalid group address ${args.groupAddress}`); 42 | } 43 | return true; 44 | }) 45 | .example('$0 -s 10.10.10.0 -g 0/0/1 -d 00:FF', 'Writes 0x00 0xFF to 0/0/1 through 10.10.10.0 knx gateway') 46 | .epilog(strFormat('GitHub: %s', chalk.underline('https://github.com/crabicode/knx-listener'))) 47 | .help('help').argv; 48 | 49 | const server = new BusListener(); 50 | 51 | const die = () => { 52 | return server.disconnect().then( 53 | () => process.exit(), 54 | () => process.exit(), 55 | ); 56 | }; 57 | 58 | const fail = (format: any, ...param: any[]) => { 59 | console.error( 60 | chalk.red(`[ FAIL ]`) + ` ${strFormat(format, ...param)}`, 61 | ); 62 | die(); 63 | }; 64 | 65 | const ok = (format: any, ...param: any[]) => { 66 | console.error( 67 | chalk.green(`[ OK ]`) + ` ${strFormat(format, ...param)}`, 68 | ); 69 | // schedule die next cycle 70 | setImmediate(die); 71 | }; 72 | 73 | process.on('SIGINT', die); // Close tunneling on ctrl+c 74 | 75 | server.bind(argv.server, argv.port).catch((err) => { 76 | fail('Failed to send request to %s:%d due to %s', argv.server, argv.port, err.code); 77 | }); 78 | 79 | server.ready(() => { 80 | const data = Buffer.from(argv.data.split(':').map(i => parseInt(i, 16))); 81 | server.write(data, knxAddr2num(argv.groupAddress)).then(() => { 82 | ok(`Sent %s to %s`, argv.data, argv.groupAddress); 83 | }).catch((_err) => { 84 | fail(`Failed to write %s to %s`, 85 | argv.data, argv.groupAddress); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /dist/bus-listener.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { Channel, ConnectResponseTunnel, TunnelingAck, GroupResponse, Hpai, Subscriber } from './interfaces'; 3 | import { QueryManager } from './query-manager'; 4 | import { AddressInfo } from 'dgram'; 5 | export declare class BusListener { 6 | protected sequenceIds: Set; 7 | protected qmanager: QueryManager; 8 | protected controlPoint: Hpai; 9 | protected heartbeatInterval: NodeJS.Timer; 10 | protected source: number; 11 | protected remoteHost: string; 12 | protected remotePort: number; 13 | protected channelId: number; 14 | constructor(); 15 | /** 16 | * Initializes tunneling. It is `never-resolving` promise 17 | */ 18 | bind(remoteHost: string, remotePort: number, {timeout, onFailure}?: { 19 | timeout?: number; 20 | onFailure?: (err: Error) => void; 21 | }): any; 22 | /** 23 | * returns promise, which indicates socket close 24 | */ 25 | complete(cb?: () => T): Promise; 26 | isConnected(): boolean; 27 | /** 28 | * ready return promises, which only resolves when tunnel is connected 29 | */ 30 | ready(cb?: () => T): Promise; 31 | /** 32 | * Generates next sequence number to number each knx telegram 33 | */ 34 | protected nextSeqn(): number; 35 | /** 36 | * Verifies if the sender the one this tunneling was initially bound to 37 | */ 38 | protected isSameOrigin(res: Channel, sender: AddressInfo): boolean; 39 | /** 40 | * Sends data to the bus 41 | */ 42 | write(data: Buffer | Uint8Array | number[], groupAddress: number): Promise; 43 | /** 44 | * Sends read request, which will only be resolved when response event received 45 | */ 46 | read(groupAddress: number): Promise; 47 | /** 48 | * Terminates tunneling 49 | */ 50 | disconnect(cb?: () => T): Promise; 51 | /** 52 | * Pings remote to verify if the channel is still active 53 | */ 54 | protected startHeartbeat(): Promise; 55 | /** 56 | * Stop heartbeat 57 | */ 58 | protected stopHeartbeat(): void; 59 | /** 60 | * Send ping 61 | */ 62 | protected ping(req: Buffer): Promise; 63 | /** 64 | * Request tunneling 65 | */ 66 | protected openTunnel(host: string, port: number): Promise; 67 | /** 68 | * Supported events 69 | */ 70 | on(event: 'unprocessed', cb: (err: Error, raw?: Buffer, remote?: AddressInfo) => void): Subscriber; 71 | on(event: 'query', cb: (query: T, sender?: AddressInfo) => void): Subscriber; 72 | } 73 | -------------------------------------------------------------------------------- /dist/bus-listener.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const serializer_1 = require("./serializer"); 3 | const query_manager_1 = require("./query-manager"); 4 | const constants_1 = require("./constants"); 5 | class BusListener { 6 | constructor() { 7 | this.sequenceIds = new Set(); 8 | this.qmanager = new query_manager_1.QueryManager(); 9 | } 10 | /** 11 | * Initializes tunneling. It is `never-resolving` promise 12 | */ 13 | bind(remoteHost, remotePort, { timeout, onFailure, } = {}) { 14 | return this.qmanager.connect().then((sock) => { 15 | this.controlPoint = { 16 | ip: constants_1.MyIpNumber, 17 | protocol: 1 /* Udp4 */, 18 | port: sock.port, 19 | }; 20 | return this.openTunnel(remoteHost, remotePort).then((response) => { 21 | // when tunneling is open, store important info 22 | this.source = response.knxAddress; 23 | this.channelId = response.channelId; 24 | this.remoteHost = remoteHost; 25 | this.remotePort = remotePort; 26 | // begin heartbeat to the remote host 27 | return this.startHeartbeat(); 28 | }); 29 | }).catch((err) => { 30 | if (typeof onFailure === 'function') { 31 | onFailure(err); 32 | } 33 | this.stopHeartbeat(); 34 | if (timeout) { 35 | // cast number to uint 36 | timeout = timeout >>> 0; 37 | // schedule retry in `timeout` seconds 38 | return new Promise((resolve) => setTimeout(resolve, timeout).unref()).then(() => { 39 | // call to reconnect 40 | return this.bind(remoteHost, remotePort, { 41 | timeout, onFailure, 42 | }); 43 | }); 44 | } 45 | else { 46 | // if no timeout, then propagate error to the caller 47 | throw err; 48 | } 49 | }); 50 | } 51 | /** 52 | * returns promise, which indicates socket close 53 | */ 54 | complete(cb) { 55 | return this.qmanager.complete(cb); 56 | } 57 | isConnected() { 58 | return this.heartbeatInterval ? true : false; 59 | } 60 | /** 61 | * ready return promises, which only resolves when tunnel is connected 62 | */ 63 | ready(cb) { 64 | return new Promise((resolve) => { 65 | if (this.isConnected()) { 66 | resolve(typeof cb === 'function' ? cb() : undefined); 67 | } 68 | else { 69 | let ref; 70 | const interval = setInterval(() => { 71 | if (this.isConnected()) { 72 | // when connected, clear interval 73 | clearInterval(interval); 74 | ref.unsubscribe(); 75 | resolve(typeof cb === 'function' ? cb() : undefined); 76 | } 77 | }, 0); 78 | interval.unref(); // let node exit 79 | ref = this.qmanager.on('disconnect', () => { 80 | // when disconnect scheduled 81 | clearInterval(interval); 82 | ref.unsubscribe(); 83 | }); 84 | } 85 | }); 86 | } 87 | /** 88 | * Generates next sequence number to number each knx telegram 89 | */ 90 | nextSeqn() { 91 | let id = 0; 92 | while (this.sequenceIds.has(id)) { 93 | if (id++ > 0xFF) { 94 | throw new Error('Maximum sequence number reached'); 95 | } 96 | } 97 | this.sequenceIds.add(id); 98 | return id; 99 | } 100 | /** 101 | * Verifies if the sender the one this tunneling was initially bound to 102 | */ 103 | isSameOrigin(res, sender) { 104 | return res.channelId === this.channelId && 105 | sender.address === this.remoteHost && 106 | sender.port === this.remotePort && 107 | sender.family === 'IPv4'; 108 | } 109 | /** 110 | * Sends data to the bus 111 | */ 112 | write(data, groupAddress) { 113 | const seqn = this.nextSeqn(); 114 | const req = serializer_1.write({ 115 | data, seqn, 116 | channelId: this.channelId, 117 | dest: groupAddress, 118 | source: this.source, 119 | }); 120 | return this.qmanager.request(this.remoteHost, this.remotePort, req, (res, sender) => { 121 | return res.seqn === seqn && this.isSameOrigin(res, sender); 122 | }).then((res) => { 123 | // always free used sequence number 124 | this.sequenceIds.delete(seqn); 125 | return res; 126 | }, (err) => { 127 | // always free used sequence number 128 | this.sequenceIds.delete(seqn); 129 | throw err; 130 | }); 131 | } 132 | /** 133 | * Sends read request, which will only be resolved when response event received 134 | */ 135 | read(groupAddress) { 136 | const seqn = this.nextSeqn(); 137 | const req = serializer_1.read({ 138 | seqn, 139 | channelId: this.channelId, 140 | dest: groupAddress, 141 | source: this.source, 142 | }); 143 | return this.qmanager.request(this.remoteHost, this.remotePort, req, (res, sender) => { 144 | return res.dest === groupAddress && 145 | res.action === 64 /* GroupResponse */ && 146 | this.isSameOrigin(res, sender); 147 | }).then((res) => { 148 | // always free used sequence number 149 | this.sequenceIds.delete(seqn); 150 | return res; 151 | }, (err) => { 152 | // always free used sequence number 153 | this.sequenceIds.delete(seqn); 154 | throw err; 155 | }); 156 | } 157 | /** 158 | * Terminates tunneling 159 | */ 160 | disconnect(cb) { 161 | const req = serializer_1.disconnect(this.channelId, this.controlPoint); 162 | return this.qmanager.request(this.remoteHost, this.remotePort, req, (res, remote) => { 163 | return this.isSameOrigin(res, remote); 164 | }).then(() => { 165 | // when disconnecting, we stop heartbeating 166 | this.stopHeartbeat(); 167 | return this.qmanager.disconnect(cb); 168 | }); 169 | } 170 | /** 171 | * Pings remote to verify if the channel is still active 172 | */ 173 | startHeartbeat() { 174 | const req = serializer_1.ping(this.channelId, this.controlPoint); 175 | return new Promise((_resolve, reject) => { 176 | // check connection with the first ping 177 | return this.ping(req).then(() => { 178 | // indicate that tunnel is ready 179 | // if it is successfull, then begin heartbeat every 60s 180 | this.heartbeatInterval = setInterval(() => { 181 | this.ping(req).catch(reject); 182 | }, 60000); 183 | // let node exit without waiting the interval 184 | this.heartbeatInterval.unref(); 185 | }).catch(reject); 186 | }); 187 | } 188 | /** 189 | * Stop heartbeat 190 | */ 191 | stopHeartbeat() { 192 | if (this.heartbeatInterval) { 193 | // stop heartbeat if started 194 | clearInterval(this.heartbeatInterval); 195 | this.heartbeatInterval = undefined; 196 | } 197 | } 198 | /** 199 | * Send ping 200 | */ 201 | ping(req) { 202 | return this.qmanager.request(this.remoteHost, this.remotePort, req, (res, remote) => { 203 | return this.isSameOrigin(res, remote); 204 | }, 5000); 205 | } 206 | /** 207 | * Request tunneling 208 | */ 209 | openTunnel(host, port) { 210 | const q = serializer_1.openTunnel({ 211 | receiveAt: this.controlPoint, 212 | respondTo: this.controlPoint, 213 | }); 214 | return this.qmanager.request(host, port, q, (res, sender) => { 215 | return sender.address === host && 216 | sender.family === 'IPv4' && 217 | sender.port === port && 218 | res.serviceId === 518 /* ConnectResponse */ && 219 | res.connectionType === 4 /* Tunnel */; 220 | }); 221 | } 222 | on(event, cb) { 223 | return this.qmanager.on(event, cb); 224 | } 225 | } 226 | exports.BusListener = BusListener; 227 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVzLWxpc3RlbmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2J1cy1saXN0ZW5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsNkNBTXNCO0FBVXRCLG1EQUV5QjtBQU96QiwyQ0FFcUI7QUFLckI7SUFTRTtRQUNFLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxHQUFHLEVBQUUsQ0FBQztRQUM3QixJQUFJLENBQUMsUUFBUSxHQUFHLElBQUksNEJBQVksRUFBRSxDQUFDO0lBQ3JDLENBQUM7SUFDRDs7T0FFRztJQUNJLElBQUksQ0FBQyxVQUFrQixFQUFFLFVBQWtCLEVBQUUsRUFDbEQsT0FBTyxFQUFFLFNBQVMsTUFDd0MsRUFBRTtRQUM1RCxNQUFNLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJO1lBQ3ZDLElBQUksQ0FBQyxZQUFZLEdBQUc7Z0JBQ2xCLEVBQUUsRUFBRSxzQkFBVTtnQkFDZCxRQUFRLEVBQUUsWUFBYTtnQkFDdkIsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJO2FBQ2hCLENBQUM7WUFDRixNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxVQUFVLEVBQUUsVUFBVSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsUUFBUTtnQkFDM0QsK0NBQStDO2dCQUMvQyxJQUFJLENBQUMsTUFBTSxHQUFHLFFBQVEsQ0FBQyxVQUFVLENBQUM7Z0JBQ2xDLElBQUksQ0FBQyxTQUFTLEdBQUcsUUFBUSxDQUFDLFNBQVMsQ0FBQztnQkFDcEMsSUFBSSxDQUFDLFVBQVUsR0FBRyxVQUFVLENBQUM7Z0JBQzdCLElBQUksQ0FBQyxVQUFVLEdBQUcsVUFBVSxDQUFDO2dCQUM3QixxQ0FBcUM7Z0JBQ3JDLE1BQU0sQ0FBQyxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7WUFDL0IsQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMs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QztJQU1ELEVBQUUsQ0FBQyxLQUFhLEVBQUUsRUFBNEI7UUFDNUMsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztJQUNyQyxDQUFDO0NBQ0Y7QUE1T0Qsa0NBNE9DIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgZGlzY29ubmVjdCxcbiAgcmVhZCxcbiAgb3BlblR1bm5lbCxcbiAgcGluZyxcbiAgd3JpdGUsXG59IGZyb20gJy4vc2VyaWFsaXplcic7XG5pbXBvcnQge1xuICBEaXNjb25uZWN0UmVwb25zZSxcbiAgQ2hhbm5lbCxcbiAgQ29ubmVjdFJlc3BvbnNlVHVubmVsLFxuICBUdW5uZWxpbmdBY2ssXG4gIEdyb3VwUmVzcG9uc2UsXG4gIEhwYWksXG4gIFN1YnNjcmliZXIsXG59IGZyb20gJy4vaW50ZXJmYWNlcyc7XG5pbXBvcnQge1xuICBRdWVyeU1hbmFnZXIsXG59IGZyb20gJy4vcXVlcnktbWFuYWdlcic7XG5pbXBvcnQge1xuICBTZXJ2aWNlLFxuICBQcm90b2NvbCxcbiAgQ29ubmVjdGlvbixcbiAgQnVzRXZlbnQsXG59IGZyb20gJy4vY29uc3RhbnRzJztcbmltcG9ydCB7XG4gIE15SXBOdW1iZXIsXG59IGZyb20gJy4vY29uc3RhbnRzJztcbmltcG9ydCB7XG4gIEFkZHJlc3NJbmZvLFxufSBmcm9tICdkZ3JhbSc7XG5cbmV4cG9ydCBjbGFzcyBCdXNMaXN0ZW5lciB7XG4gIHByb3RlY3RlZCBzZXF1ZW5jZUlkczogU2V0PG51bWJlcj47XG4gIHByb3RlY3RlZCBxbWFuYWdlcjogUXVlcnlNYW5hZ2VyO1xuICBwcm90ZWN0ZWQgY29udHJvbFBvaW50OiBIcGFpO1xuICBwcm90ZWN0ZWQgaGVhcnRiZWF0SW50ZXJ2YWw6IE5vZGVKUy5UaW1lcjtcbiAgcHJvdGVjdGVkIHNvdXJjZTogbnVtYmVyO1xuICBwcm90ZWN0ZWQgcmVtb3RlSG9zdDogc3RyaW5nO1xuICBwcm90ZWN0ZWQgcmVtb3RlUG9ydDogbnVtYmVyO1xuICBwcm90ZWN0ZWQgY2hhbm5lbElkOiBudW1iZXI7XG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHRoaXMuc2VxdWVuY2VJZHMgPSBuZXcgU2V0KCk7XG4gICAgdGhpcy5xbWFuYWdlciA9IG5ldyBRdWVyeU1hbmFnZXIoKTtcbiAgfVxuICAvKipcbiAgICogSW5pdGlhbGl6ZXMgdHVubmVsaW5nLiBJdCBpcyBgbmV2ZXItcmVzb2x2aW5nYCBwcm9taXNlXG4gICAqL1xuICBwdWJsaWMgYmluZChyZW1vdGVIb3N0OiBzdHJpbmcsIHJlbW90ZVBvcnQ6IG51bWJlciwge1xuICAgIHRpbWVvdXQsIG9uRmFpbHVyZSxcbiAgfTogeyB0aW1lb3V0PzogbnVtYmVyLCBvbkZhaWx1cmU/OiAoZXJyOiBFcnJvcikgPT4gdm9pZCB9ID0ge30pOiBhbnkge1xuICAgIHJldHVybiB0aGlzLnFtYW5hZ2VyLmNvbm5lY3QoKS50aGVuKChzb2NrKSA9PiB7XG4gICAgICB0aGlzLmNvbnRyb2xQb2ludCA9IHtcbiAgICAgICAgaXA6IE15SXBOdW1iZXIsXG4gICAgICAgIHByb3RvY29sOiBQcm90b2NvbC5VZHA0LFxuICAgICAgICBwb3J0OiBzb2NrLnBvcnQsXG4gICAgICB9O1xuICAgICAgcmV0dXJuIHRoaXMub3BlblR1bm5lbChyZW1vdGVIb3N0LCByZW1vdGVQb3J0KS50aGVuKChyZXNwb25zZSkgPT4ge1xuICAgICAgICAvLyB3aGVuIHR1bm5lbGluZyBpcyBvcGVuLCBzdG9yZSBpbXBvcnRhbnQgaW5mb1xuICAgICAgICB0aGlzLnNvdXJjZSA9IHJlc3BvbnNlLmtueEFkZHJlc3M7XG4gICAgICAgIHRoaXMuY2hhbm5lbElkID0gcmVzcG9uc2UuY2hhbm5lbElkO1xuICAgICAgICB0aGlzLnJlbW90ZUhvc3QgPSByZW1vdGVIb3N0O1xuICAgICAgICB0aGlzLnJlbW90ZVBvcnQgPSByZW1vdGVQb3J0O1xuICAgICAgICAvLyBiZWdpbiBoZWFydGJlYXQgdG8gdGhlIHJlbW90ZSBob3N0XG4gICAgICAgIHJldHVybiB0aGlzLnN0YXJ0SGVhcnRiZWF0KCk7XG4gICAgICB9KTtcbiAgICB9KS5jYXRjaCgoZXJyKSA9PiB7XG4gICAgICBpZiAodHlwZW9mIG9uRmFpbHVyZSA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICBvbkZhaWx1cmUoZXJyKTtcbiAgICAgIH1cbiAgICAgIHRoaXMuc3RvcEhlYXJ0YmVhdCgpO1xuICAgICAgaWYgKHRpbWVvdXQpIHtcbiAgICAgICAgLy8gY2FzdCBudW1iZXIgdG8gdWludFxuICAgICAgICB0aW1lb3V0ID0gdGltZW91dCA+Pj4gMDtcbiAgICAgICAgLy8gc2NoZWR1bGUgcmV0cnkgaW4gYHRpbWVvdXRgIHNlY29uZHNcbiAgICAgICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIHRpbWVvdXQpLnVucmVmKCkpLnRoZW4oKCkgPT4ge1xuICAgICAgICAgIC8vIGNhbGwgdG8gcmVjb25uZWN0XG4gICAgICAgICAgcmV0dXJuIHRoaXMuYmluZChyZW1vdGVIb3N0LCByZW1vdGVQb3J0LCB7XG4gICAgICAgICAgICB0aW1lb3V0LCBvbkZhaWx1cmUsXG4gICAgICAgICAgfSk7XG4gICAgICAgIH0pO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gaWYgbm8gdGltZW91dCwgdGhlbiBwcm9wYWdhdGUgZXJyb3IgdG8gdGhlIGNhbGxlclxuICAgICAgICB0aHJvdyBlcnI7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cbiAgLyoqXG4gICAqIHJldHVybnMgcHJvbWlzZSwgd2hpY2ggaW5kaWNhdGVzIHNvY2tldCBjbG9zZVxuICAgKi9cbiAgcHVibGljIGNvbXBsZXRlPFQ+KGNiPzogKCkgPT4gVCkge1xuICAgIHJldHVybiB0aGlzLnFtYW5hZ2VyLmNvbXBsZXRlKGNiKTtcbiAgfVxuICBwdWJsaWMgaXNDb25uZWN0ZWQoKSB7XG4gICAgcmV0dXJuIHRoaXMuaGVhcnRiZWF0SW50ZXJ2YWwgPyB0cnVlIDogZmFsc2U7XG4gIH1cbiAgLyoqXG4gICAqIHJlYWR5IHJldHVybiBwcm9taXNlcywgd2hpY2ggb25seSByZXNvbHZlcyB3aGVuIHR1bm5lbCBpcyBjb25uZWN0ZWRcbiAgICovXG4gIHB1YmxpYyByZWFkeTxUPihjYj86ICgpID0+IFQpIHtcbiAgICByZXR1cm4gbmV3IFByb21pc2U8VD4oKHJlc29sdmUpID0+IHtcbiAgICAgIGlmICh0aGlzLmlzQ29ubmVjdGVkKCkpIHtcbiAgICAgICAgcmVzb2x2ZSh0eXBlb2YgY2IgPT09ICdmdW5jdGlvbicgPyBjYigpIDogdW5kZWZpbmVkKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGxldCByZWY6IFN1YnNjcmliZXI7XG4gICAgICAgIGNvbnN0IGludGVydmFsID0gc2V0SW50ZXJ2YWwoKCkgPT4ge1xuICAgICAgICAgIGlmICh0aGlzLmlzQ29ubmVjdGVkKCkpIHtcbiAgICAgICAgICAgIC8vIHdoZW4gY29ubmVjdGVkLCBjbGVhciBpbnRlcnZhbFxuICAgICAgICAgICAgY2xlYXJJbnRlcnZhbChpbnRlcnZhbCk7XG4gICAgICAgICAgICByZWYudW5zdWJzY3JpYmUoKTtcbiAgICAgICAgICAgIHJlc29sdmUodHlwZW9mIGNiID09PSAnZnVuY3Rpb24nID8gY2IoKSA6IHVuZGVmaW5lZCk7XG4gICAgICAgICAgfVxuICAgICAgICB9LCAwKTtcbiAgICAgICAgaW50ZXJ2YWwudW5yZWYoKTsgLy8gbGV0IG5vZGUgZXhpdFxuICAgICAgICByZWYgPSB0aGlzLnFtYW5hZ2VyLm9uKCdkaXNjb25uZWN0JywgKCkgPT4ge1xuICAgICAgICAgIC8vIHdoZW4gZGlzY29ubmVjdCBzY2hlZHVsZWRcbiAgICAgICAgICBjbGVhckludGVydmFsKGludGVydmFsKTtcbiAgICAgICAgICByZWYudW5zdWJzY3JpYmUoKTtcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cbiAgLyoqXG4gICAqIEdlbmVyYXRlcyBuZXh0IHNlcXVlbmNlIG51bWJlciB0byBudW1iZXIgZWFjaCBrbnggdGVsZWdyYW1cbiAgICovXG4gIHByb3RlY3RlZCBuZXh0U2VxbigpIHtcbiAgICBsZXQgaWQgPSAwO1xuICAgIHdoaWxlICh0aGlzLnNlcXVlbmNlSWRzLmhhcyhpZCkpIHtcbiAgICAgIGlmIChpZCsrID4gMHhGRikge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01heGltdW0gc2VxdWVuY2UgbnVtYmVyIHJlYWNoZWQnKTtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy5zZXF1ZW5jZUlkcy5hZGQoaWQpO1xuICAgIHJldHVybiBpZDtcbiAgfVxuICAvKipcbiAgICogVmVyaWZpZXMgaWYgdGhlIHNlbmRlciB0aGUgb25lIHRoaXMgdHVubmVsaW5nIHdhcyBpbml0aWFsbHkgYm91bmQgdG9cbiAgICovXG4gIHByb3RlY3RlZCBpc1NhbWVPcmlnaW4ocmVzOiBDaGFubmVsLCBzZW5kZXI6IEFkZHJlc3NJbmZvKSB7XG4gICAgcmV0dXJuIHJlcy5jaGFubmVsSWQgPT09IHRoaXMuY2hhbm5lbElkICYmXG4gICAgICBzZW5kZXIuYWRkcmVzcyA9PT0gdGhpcy5yZW1vdGVIb3N0ICYmXG4gICAgICBzZW5kZXIucG9ydCA9PT0gdGhpcy5yZW1vdGVQb3J0ICYmXG4gICAgICBzZW5kZXIuZmFtaWx5ID09PSAnSVB2NCc7XG4gIH1cbiAgLyoqXG4gICAqIFNlbmRzIGRhdGEgdG8gdGhlIGJ1c1xuICAgKi9cbiAgcHVibGljIHdyaXRlKGRhdGE6IEJ1ZmZlciB8IFVpbnQ4QXJyYXkgfCBudW1iZXJbXSwgZ3JvdXBBZGRyZXNzOiBudW1iZXIpIHtcbiAgICBjb25zdCBzZXFuID0gdGhpcy5uZXh0U2VxbigpO1xuICAgIGNvbnN0IHJlcSA9IHdyaXRlKHtcbiAgICAgIGRhdGEsIHNlcW4sXG4gICAgICBjaGFubmVsSWQ6IHRoaXMuY2hhbm5lbElkLFxuICAgICAgZGVzdDogZ3JvdXBBZGRyZXNzLFxuICAgICAgc291cmNlOiB0aGlzLnNvdXJjZSxcbiAgICB9KTtcbiAgICByZXR1cm4gdGhpcy5xbWFuYWdlci5yZXF1ZXN0PFR1bm5lbGluZ0Fjaz4odGhpcy5yZW1vdGVIb3N0LCB0aGlzLnJlbW90ZVBvcnQsIHJlcSwgKHJlcywgc2VuZGVyKSA9PiB7XG4gICAgICByZXR1cm4gcmVzLnNlcW4gPT09IHNlcW4gJiYgdGhpcy5pc1NhbWVPcmlnaW4ocmVzLCBzZW5kZXIpO1xuICAgIH0pLnRoZW4oKHJlcykgPT4ge1xuICAgICAgLy8gYWx3YXlzIGZyZWUgdXNlZCBzZXF1ZW5jZSBudW1iZXJcbiAgICAgIHRoaXMuc2VxdWVuY2VJZHMuZGVsZXRlKHNlcW4pO1xuICAgICAgcmV0dXJuIHJlcztcbiAgICB9LCAoZXJyKSA9PiB7XG4gICAgICAvLyBhbHdheXMgZnJlZSB1c2VkIHNlcXVlbmNlIG51bWJlclxuICAgICAgdGhpcy5zZXF1ZW5jZUlkcy5kZWxldGUoc2Vxbik7XG4gICAgICB0aHJvdyBlcnI7XG4gICAgfSk7XG4gIH1cbiAgLyoqXG4gICAqIFNlbmRzIHJlYWQgcmVxdWVzdCwgd2hpY2ggd2lsbCBvbmx5IGJlIHJlc29sdmVkIHdoZW4gcmVzcG9uc2UgZXZlbnQgcmVjZWl2ZWRcbiAgICovXG4gIHB1YmxpYyByZWFkKGdyb3VwQWRkcmVzczogbnVtYmVyKSB7XG4gICAgY29uc3Qgc2VxbiA9IHRoaXMubmV4dFNlcW4oKTtcbiAgICBjb25zdCByZXEgPSByZWFkKHtcbiAgICAgIHNlcW4sXG4gICAgICBjaGFubmVsSWQ6IHRoaXMuY2hhbm5lbElkLFxuICAgICAgZGVzdDogZ3JvdXBBZGRyZXNzLFxuICAgICAgc291cmNlOiB0aGlzLnNvdXJjZSxcbiAgICB9KTtcbiAgICByZXR1cm4gdGhpcy5xbWFuYWdlci5yZXF1ZXN0PEdyb3VwUmVzcG9uc2U+KHRoaXMucmVtb3RlSG9zdCwgdGhpcy5yZW1vdGVQb3J0LCByZXEsIChyZXMsIHNlbmRlcikgPT4ge1xuICAgICAgcmV0dXJuIHJlcy5kZXN0ID09PSBncm91cEFkZHJlc3MgJiZcbiAgICAgICAgcmVzLmFjdGlvbiA9PT0gQnVzRXZlbnQuR3JvdXBSZXNwb25zZSAmJlxuICAgICAgICB0aGlzLmlzU2FtZU9yaWdpbihyZXMsIHNlbmRlcik7XG4gICAgfSkudGhlbigocmVzKSA9PiB7XG4gICAgICAvLyBhbHdheXMgZnJlZSB1c2VkIHNlcXVlbmNlIG51bWJlclxuICAgICAgdGhpcy5zZXF1ZW5jZUlkcy5kZWxldGUoc2Vxbik7XG4gICAgICByZXR1cm4gcmVzO1xuICAgIH0sIChlcnIpID0+IHtcbiAgICAgIC8vIGFsd2F5cyBmcmVlIHVzZWQgc2VxdWVuY2UgbnVtYmVyXG4gICAgICB0aGlzLnNlcXVlbmNlSWRzLmRlbGV0ZShzZXFuKTtcbiAgICAgIHRocm93IGVycjtcbiAgICB9KTtcbiAgfVxuICAvKipcbiAgICogVGVybWluYXRlcyB0dW5uZWxpbmdcbiAgICovXG4gIHB1YmxpYyBkaXNjb25uZWN0PFQ+KGNiPzogKCkgPT4gVCkge1xuICAgIGNvbnN0IHJlcSA9IGRpc2Nvbm5lY3QodGhpcy5jaGFubmVsSWQsIHRoaXMuY29udHJvbFBvaW50KTtcbiAgICByZXR1cm4gdGhpcy5xbWFuYWdlci5yZXF1ZXN0PERpc2Nvbm5lY3RSZXBvbnNlPihcbiAgICAgIHRoaXMucmVtb3RlSG9zdCwgdGhpcy5yZW1vdGVQb3J0LCByZXEsIChyZXMsIHJlbW90ZSkgPT4ge1xuICAgICAgICByZXR1cm4gdGhpcy5pc1NhbWVPcmlnaW4ocmVzLCByZW1vdGUpO1xuICAgICAgfSkudGhlbigoKSA9PiB7XG4gICAgICAgIC8vIHdoZW4gZGlzY29ubmVjdGluZywgd2Ugc3RvcCBoZWFydGJlYXRpbmdcbiAgICAgICAgdGhpcy5zdG9wSGVhcnRiZWF0KCk7XG4gICAgICAgIHJldHVybiB0aGlzLnFtYW5hZ2VyLmRpc2Nvbm5lY3QoY2IpO1xuICAgICAgfSk7XG4gIH1cbiAgLyoqXG4gICAqIFBpbmdzIHJlbW90ZSB0byB2ZXJpZnkgaWYgdGhlIGNoYW5uZWwgaXMgc3RpbGwgYWN0aXZlXG4gICAqL1xuICBwcm90ZWN0ZWQgc3RhcnRIZWFydGJlYXQoKSB7XG4gICAgY29uc3QgcmVxID0gcGluZyh0aGlzLmNoYW5uZWxJZCwgdGhpcy5jb250cm9sUG9pbnQpO1xuICAgIHJldHVybiBuZXcgUHJvbWlzZTx2b2lkPigoX3Jlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgLy8gY2hlY2sgY29ubmVjdGlvbiB3aXRoIHRoZSBmaXJzdCBwaW5nXG4gICAgICByZXR1cm4gdGhpcy5waW5nKHJlcSkudGhlbigoKSA9PiB7XG4gICAgICAgIC8vIGluZGljYXRlIHRoYXQgdHVubmVsIGlzIHJlYWR5XG4gICAgICAgIC8vIGlmIGl0IGlzIHN1Y2Nlc3NmdWxsLCB0aGVuIGJlZ2luIGhlYXJ0YmVhdCBldmVyeSA2MHNcbiAgICAgICAgdGhpcy5oZWFydGJlYXRJbnRlcnZhbCA9IHNldEludGVydmFsKCgpID0+IHtcbiAgICAgICAgICB0aGlzLnBpbmcocmVxKS5jYXRjaChyZWplY3QpO1xuICAgICAgICB9LCA2MDAwMCk7XG4gICAgICAgIC8vIGxldCBub2RlIGV4aXQgd2l0aG91dCB3YWl0aW5nIHRoZSBpbnRlcnZhbFxuICAgICAgICB0aGlzLmhlYXJ0YmVhdEludGVydmFsLnVucmVmKCk7XG4gICAgICB9KS5jYXRjaChyZWplY3QpO1xuICAgIH0pO1xuICB9XG4gIC8qKlxuICAgKiBTdG9wIGhlYXJ0YmVhdFxuICAgKi9cbiAgcHJvdGVjdGVkIHN0b3BIZWFydGJlYXQoKSB7XG4gICAgaWYgKHRoaXMuaGVhcnRiZWF0SW50ZXJ2YWwpIHtcbiAgICAgIC8vIHN0b3AgaGVhcnRiZWF0IGlmIHN0YXJ0ZWRcbiAgICAgIGNsZWFySW50ZXJ2YWwodGhpcy5oZWFydGJlYXRJbnRlcnZhbCk7XG4gICAgICB0aGlzLmhlYXJ0YmVhdEludGVydmFsID0gdW5kZWZpbmVkO1xuICAgIH1cbiAgfVxuICAvKipcbiAgICogU2VuZCBwaW5nXG4gICAqL1xuICBwcm90ZWN0ZWQgcGluZyhyZXE6IEJ1ZmZlcikge1xuICAgIHJldHVybiB0aGlzLnFtYW5hZ2VyLnJlcXVlc3Q8Q2hhbm5lbD4oXG4gICAgICB0aGlzLnJlbW90ZUhvc3QsIHRoaXMucmVtb3RlUG9ydCwgcmVxLCAocmVzLCByZW1vdGUpID0+IHtcbiAgICAgICAgcmV0dXJuIHRoaXMuaXNTYW1lT3JpZ2luKHJlcywgcmVtb3RlKTtcbiAgICAgIH0sIDUwMDApO1xuICB9XG4gIC8qKlxuICAgKiBSZXF1ZXN0IHR1bm5lbGluZ1xuICAgKi9cbiAgcHJvdGVjdGVkIG9wZW5UdW5uZWwoaG9zdDogc3RyaW5nLCBwb3J0OiBudW1iZXIpIHtcbiAgICBjb25zdCBxID0gb3BlblR1bm5lbCh7XG4gICAgICByZWNlaXZlQXQ6IHRoaXMuY29udHJvbFBvaW50LFxuICAgICAgcmVzcG9uZFRvOiB0aGlzLmNvbnRyb2xQb2ludCxcbiAgICB9KTtcbiAgICByZXR1cm4gdGhpcy5xbWFuYWdlci5yZXF1ZXN0PENvbm5lY3RSZXNwb25zZVR1bm5lbD4oXG4gICAgICBob3N0LCBwb3J0LCBxLCAocmVzLCBzZW5kZXIpID0+IHtcbiAgICAgICAgcmV0dXJuIHNlbmRlci5hZGRyZXNzID09PSBob3N0ICYmXG4gICAgICAgICAgc2VuZGVyLmZhbWlseSA9PT0gJ0lQdjQnICYmXG4gICAgICAgICAgc2VuZGVyLnBvcnQgPT09IHBvcnQgJiZcbiAgICAgICAgICByZXMuc2VydmljZUlkID09PSBTZXJ2aWNlLkNvbm5lY3RSZXNwb25zZSAmJlxuICAgICAgICAgIHJlcy5jb25uZWN0aW9uVHlwZSA9PT0gQ29ubmVjdGlvbi5UdW5uZWw7XG4gICAgICB9KTtcbiAgfVxuICAvKipcbiAgICogU3VwcG9ydGVkIGV2ZW50c1xuICAgKi9cbiAgb24oZXZlbnQ6ICd1bnByb2Nlc3NlZCcsIGNiOiAoZXJyOiBFcnJvciwgcmF3PzogQnVmZmVyLCByZW1vdGU/OiBBZGRyZXNzSW5mbykgPT4gdm9pZCk6IFN1YnNjcmliZXI7XG4gIG9uPFQ+KGV2ZW50OiAncXVlcnknLCBjYjogKHF1ZXJ5OiBULCBzZW5kZXI/OiBBZGRyZXNzSW5mbykgPT4gdm9pZCk6IFN1YnNjcmliZXI7XG4gIG9uKGV2ZW50OiBzdHJpbmcsIGNiOiAoLi4uYXJnczogYW55W10pID0+IHZvaWQpOiBTdWJzY3JpYmVyIHtcbiAgICByZXR1cm4gdGhpcy5xbWFuYWdlci5vbihldmVudCwgY2IpO1xuICB9XG59XG4iXX0= -------------------------------------------------------------------------------- /dist/constants.d.ts: -------------------------------------------------------------------------------- 1 | export declare const enum Service { 2 | ConnectRequest = 517, 3 | ConnectResponse = 518, 4 | ConnectionStateRequest = 519, 5 | ConnectStateResponse = 520, 6 | DisconnectRequest = 521, 7 | DisconnectResponse = 522, 8 | TunnelingAck = 1057, 9 | TunnelingRequest = 1056, 10 | } 11 | export declare const enum Protocol { 12 | Udp4 = 1, 13 | Tcp4 = 2, 14 | } 15 | export declare const enum Connection { 16 | Tunnel = 4, 17 | } 18 | export declare const enum Status { 19 | ConnectionId = 33, 20 | ConnectionOption = 35, 21 | ConnectionType = 34, 22 | DataConnection = 38, 23 | HostProtocolType = 1, 24 | KnxConnection = 39, 25 | NoError = 0, 26 | NoMoreConnections = 36, 27 | SequenceNumber = 4, 28 | TunnelingLayer = 41, 29 | VersionNotSupported = 2, 30 | } 31 | export declare const enum BusEvent { 32 | GroupRead = 0, 33 | GroupResponse = 64, 34 | GroupWrite = 128, 35 | } 36 | export declare const MyIp: string; 37 | export declare const MyIpNumber: number; 38 | -------------------------------------------------------------------------------- /dist/constants.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const index_1 = require("./utils/index"); 3 | ; 4 | ; 5 | exports.MyIp = index_1.getCurrentIp(); 6 | exports.MyIpNumber = index_1.ip2num(exports.MyIp); 7 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uc3RhbnRzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2NvbnN0YW50cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEseUNBR3VCO0FBZ0J0QixDQUFDO0FBSUQsQ0FBQztBQXNCVyxRQUFBLElBQUksR0FBRyxvQkFBWSxFQUFFLENBQUM7QUFFdEIsUUFBQSxVQUFVLEdBQUcsY0FBTSxDQUFDLFlBQUksQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgZ2V0Q3VycmVudElwLFxuICBpcDJudW0sXG59IGZyb20gJy4vdXRpbHMvaW5kZXgnO1xuXG5leHBvcnQgY29uc3QgZW51bSBTZXJ2aWNlIHtcbiAgQ29ubmVjdFJlcXVlc3QgPSAweDIwNSxcbiAgQ29ubmVjdFJlc3BvbnNlID0gMHgyMDYsXG4gIENvbm5lY3Rpb25TdGF0ZVJlcXVlc3QgPSAweDIwNyxcbiAgQ29ubmVjdFN0YXRlUmVzcG9uc2UgPSAweDIwOCxcbiAgRGlzY29ubmVjdFJlcXVlc3QgPSAweDIwOSxcbiAgRGlzY29ubmVjdFJlc3BvbnNlID0gMHgyMGEsXG4gIFR1bm5lbGluZ0FjayA9IDB4NDIxLFxuICBUdW5uZWxpbmdSZXF1ZXN0ID0gMHg0MjAsXG59XG5cbmV4cG9ydCBjb25zdCBlbnVtIFByb3RvY29sIHtcbiAgVWRwNCA9IDB4MDEsXG4gIFRjcDQgPSAweDAyLFxufTtcblxuZXhwb3J0IGNvbnN0IGVudW0gQ29ubmVjdGlvbiB7XG4gIFR1bm5lbCA9IDB4NCxcbn07XG5cbmV4cG9ydCBjb25zdCBlbnVtIFN0YXR1cyB7XG4gIENvbm5lY3Rpb25JZCA9IDB4MjEsXG4gIENvbm5lY3Rpb25PcHRpb24gPSAweDIzLFxuICBDb25uZWN0aW9uVHlwZSA9IDB4MjIsXG4gIERhdGFDb25uZWN0aW9uID0gMHgyNixcbiAgSG9zdFByb3RvY29sVHlwZSA9IDB4MSxcbiAgS254Q29ubmVjdGlvbiA9IDB4MjcsXG4gIE5vRXJyb3IgPSAweDAsXG4gIE5vTW9yZUNvbm5lY3Rpb25zID0gMHgyNCxcbiAgU2VxdWVuY2VOdW1iZXIgPSAweDQsXG4gIFR1bm5lbGluZ0xheWVyID0gMHgyOSxcbiAgVmVyc2lvbk5vdFN1cHBvcnRlZCA9IDB4Mixcbn1cblxuZXhwb3J0IGNvbnN0IGVudW0gQnVzRXZlbnQge1xuICBHcm91cFJlYWQgPSAweDAsXG4gIEdyb3VwUmVzcG9uc2UgPSAweDQwLFxuICBHcm91cFdyaXRlID0gMHg4MCxcbn1cblxuZXhwb3J0IGNvbnN0IE15SXAgPSBnZXRDdXJyZW50SXAoKTtcblxuZXhwb3J0IGNvbnN0IE15SXBOdW1iZXIgPSBpcDJudW0oTXlJcCk7XG4iXX0= -------------------------------------------------------------------------------- /dist/deserializer.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { SmartCursor } from './utils/smart-cursor'; 3 | import { Channel, Header, Hpai } from './interfaces'; 4 | export declare function header(raw: Buffer, pos: SmartCursor): Header; 5 | export declare function channel(raw: Buffer, pos: SmartCursor): Channel; 6 | export declare function hpai(raw: Buffer, pos: SmartCursor): Hpai; 7 | export declare function connectResponse(raw: Buffer, pos: SmartCursor): { 8 | connectionType: number; 9 | knxAddress: number; 10 | }; 11 | export declare function seqnum(raw: Buffer, pos: SmartCursor): { 12 | channelId: number; 13 | seqn: number; 14 | status: number; 15 | }; 16 | export declare function tunnelCemi(raw: Buffer, pos: SmartCursor): { 17 | data: Uint8Array; 18 | action: number; 19 | dest: number; 20 | source: number; 21 | } | { 22 | action: number; 23 | dest: number; 24 | source: number; 25 | }; 26 | -------------------------------------------------------------------------------- /dist/deserializer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function header(raw, pos) { 3 | const headerLength = raw.readUInt8(pos.next()); 4 | const protocolVersion = raw.readUInt8(pos.next()); 5 | const serviceId = raw.readUInt16BE(pos.next(2)); 6 | const totalLength = raw.readUInt16BE(pos.next(2)); 7 | if (headerLength !== 0x06) { 8 | throw new Error(`Invalid header length ${headerLength}`); 9 | } 10 | if (protocolVersion !== 0x10) { 11 | throw new Error(`Invalid protocol version ${protocolVersion}`); 12 | } 13 | if (raw.length !== totalLength) { 14 | throw new Error(`Invalid total length, expected ${raw.length}, but got ${totalLength}`); 15 | } 16 | return { 17 | serviceId, 18 | }; 19 | } 20 | exports.header = header; 21 | ; 22 | function channel(raw, pos) { 23 | const channelId = raw.readUInt8(pos.next()); 24 | const status = raw.readUInt8(pos.next()); 25 | if (channelId === 0) { 26 | throw new Error(`Invalid channel id ${channelId}`); 27 | } 28 | return { 29 | channelId, status, 30 | }; 31 | } 32 | exports.channel = channel; 33 | ; 34 | function hpai(raw, pos) { 35 | const size = raw.readUInt8(pos.next()); 36 | if (size !== 0x8) { 37 | throw new Error(`Failed to read hpai at ${pos.cur}`); 38 | } 39 | const protocol = raw.readUInt8(pos.next()); 40 | const ip = raw.readUIntBE(pos.next(4), 4); 41 | const port = raw.readInt16BE(pos.next(2)); 42 | return { 43 | ip, port, protocol, 44 | }; 45 | } 46 | exports.hpai = hpai; 47 | ; 48 | function connectResponse(raw, pos) { 49 | const size = raw.readInt8(pos.next()); 50 | const contype = raw.readInt8(pos.next()); 51 | switch (contype) { 52 | case 4 /* Tunnel */: { 53 | if (size !== 0x4) { 54 | throw new Error(`Failed to read connect response for tunneling at ${pos.cur}`); 55 | } 56 | const knxAddress = raw.readUInt16BE(pos.next(2)); 57 | return { 58 | connectionType: contype, 59 | knxAddress, 60 | }; 61 | } 62 | default: throw new Error(`Unknown connection type ${contype}`); 63 | } 64 | } 65 | exports.connectResponse = connectResponse; 66 | ; 67 | function seqnum(raw, pos) { 68 | const size = raw.readUInt8(pos.next()); 69 | if (size !== 0x4) { 70 | throw new Error(`Failed to read structure at ${pos.cur}`); 71 | } 72 | const channelId = raw.readUInt8(pos.next()); 73 | const seqn = raw.readInt8(pos.next()); 74 | const status = raw.readUInt8(pos.next()); 75 | return { 76 | channelId, seqn, status, 77 | }; 78 | } 79 | exports.seqnum = seqnum; 80 | ; 81 | function tunnelCemi(raw, pos) { 82 | pos.skip('messageCode'); 83 | const additionalInfoLength = raw.readUInt8(pos.next()); 84 | if (additionalInfoLength) { 85 | pos.skip('additionalInfo', additionalInfoLength); 86 | } 87 | pos.skip('controlField1'); 88 | pos.skip('controlField2'); 89 | const source = raw.readUInt16BE(pos.next(2)); 90 | const dest = raw.readUInt16BE(pos.next(2)); 91 | const npduLength = raw.readUInt8(pos.next()); 92 | const apdu = raw.readUInt16BE(pos.next(2)); 93 | const action = apdu & (128 /* GroupWrite */ | 94 | 64 /* GroupResponse */ | 0 /* GroupRead */); 95 | if (action & (128 /* GroupWrite */ | 64 /* GroupResponse */)) { 96 | let data; 97 | if (npduLength > 1) { 98 | // data appended 99 | data = raw.subarray(pos.next(npduLength), pos.cur); 100 | } 101 | else { 102 | // data merged into 6 bits 103 | data = new Uint8Array([apdu & 0x3f]); 104 | } 105 | return { 106 | data, action, dest, source, 107 | }; 108 | } 109 | else { 110 | // read 111 | return { 112 | action, dest, source, 113 | }; 114 | } 115 | } 116 | exports.tunnelCemi = tunnelCemi; 117 | ; 118 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVzZXJpYWxpemVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2Rlc2VyaWFsaXplci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBYUEsZ0JBQXVCLEdBQVcsRUFBRSxHQUFnQjtJQUNsRCxNQUFNLFlBQVksR0FBRyxHQUFHLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQy9DLE1BQU0sZUFBZSxHQUFHLEdBQUcsQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7SUFDbEQsTUFBTSxTQUFTLEdBQUcsR0FBRyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDaEQsTUFBTSxXQUFXLEdBQUcsR0FBRyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDbEQsRUFBRSxDQUFDLENBQUMsWUFBWSxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUM7UUFDMUIsTUFBTSxJQUFJLEtBQUssQ0FBQyx5QkFBeUIsWUFBWSxFQUFFLENBQUMsQ0FBQztJQUMzRCxDQUFDO0lBQ0QsRUFBRSxDQUFDLENBQUMsZUFBZSxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUM7UUFDN0IsTUFBTSxJQUFJLEtBQUssQ0FBQyw0QkFBNEIsZUFBZSxFQUFFLENBQUMsQ0FBQztJQUNqRSxDQUFDO0lBQ0QsRUFBRSxDQUFDLENBQUMsR0FBRyxDQUFDLE1BQU0sS0FBSyxXQUFXLENBQUMsQ0FBQyxDQUFDO1FBQy9CLE1BQU0sSUFBSSxLQUFLLENBQUMsa0NBQWtDLEdBQUcsQ0FBQyxNQUFNLGFBQWEsV0FBVyxFQUFFLENBQUMsQ0FBQztJQUMxRixDQUFDO0lBQ0QsTUFBTSxDQUFDO1FBQ0wsU0FBUztLQUNWLENBQUM7QUFDSixDQUFDO0FBakJELHdCQWlCQztBQUFBLENBQUM7QUFFRixpQkFBd0Is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QU0sR0FBRyxJQUFJLEdBQUcsQ0FBQyxvQkFBbUI7UUFDeEMsc0JBQXNCLEdBQUcsaUJBQWtCLENBQUMsQ0FBQztJQUMvQyxFQUFFLENBQUMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxvQkFBbUIsR0FBRyxzQkFBc0IsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUM1RCxJQUFJLElBQWdCLENBQUM7UUFDckIsRUFBRSxDQUFDLENBQUMsVUFBVSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDbkIsZ0JBQWdCO1lBQ2hCLElBQUksR0FBRyxHQUFHLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLEVBQUUsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3JELENBQUM7UUFBQyxJQUFJLENBQUMsQ0FBQztZQUNOLDBCQUEwQjtZQUMxQixJQUFJLEdBQUcsSUFBSSxVQUFVLENBQUMsQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUN2QyxDQUFDO1FBQ0QsTUFBTSxDQUFDO1lBQ0wsSUFBSSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsTUFBTTtTQUMzQixDQUFDO0lBQ0osQ0FBQztJQUFDLElBQUksQ0FBQyxDQUFDO1FBQ04sT0FBTztRQUNQLE1BQU0sQ0FBQztZQUNMLE1BQU0sRUFBRSxJQUFJLEVBQUUsTUFBTTtTQUNyQixDQUFDO0lBQ0osQ0FBQztBQUNILENBQUM7QUFoQ0QsZ0NBZ0NDO0FBQUEsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIENvbm5lY3Rpb24sXG4gIEJ1c0V2ZW50LFxufSBmcm9tICcuL2NvbnN0YW50cyc7XG5pbXBvcnQge1xuICBTbWFydEN1cnNvcixcbn0gZnJvbSAnLi91dGlscy9zbWFydC1jdXJzb3InO1xuaW1wb3J0IHtcbiAgQ2hhbm5lbCxcbiAgSGVhZGVyLFxuICBIcGFpLFxufSBmcm9tICcuL2ludGVyZmFjZXMnO1xuXG5leHBvcnQgZnVuY3Rpb24gaGVhZGVyKHJhdzogQnVmZmVyLCBwb3M6IFNtYXJ0Q3Vyc29yKTogSGVhZGVyIHtcbiAgY29uc3QgaGVhZGVyTGVuZ3RoID0gcmF3LnJlYWRVSW50OChwb3MubmV4dCgpKTtcbiAgY29uc3QgcHJvdG9jb2xWZXJzaW9uID0gcmF3LnJlYWRVSW50OChwb3MubmV4dCgpKTtcbiAgY29uc3Qgc2VydmljZUlkID0gcmF3LnJlYWRVSW50MTZCRShwb3MubmV4dCgyKSk7XG4gIGNvbnN0IHRvdGFsTGVuZ3RoID0gcmF3LnJlYWRVSW50MTZCRShwb3MubmV4dCgyKSk7XG4gIGlmIChoZWFkZXJMZW5ndGggIT09IDB4MDYpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgaGVhZGVyIGxlbmd0aCAke2hlYWRlckxlbmd0aH1gKTtcbiAgfVxuICBpZiAocHJvdG9jb2xWZXJzaW9uICE9PSAweDEwKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIHByb3RvY29sIHZlcnNpb24gJHtwcm90b2NvbFZlcnNpb259YCk7XG4gIH1cbiAgaWYgKHJhdy5sZW5ndGggIT09IHRvdGFsTGVuZ3RoKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIHRvdGFsIGxlbmd0aCwgZXhwZWN0ZWQgJHtyYXcubGVuZ3RofSwgYnV0IGdvdCAke3RvdGFsTGVuZ3RofWApO1xuICB9XG4gIHJldHVybiB7XG4gICAgc2VydmljZUlkLFxuICB9O1xufTtcblxuZXhwb3J0IGZ1bmN0aW9uIGNoYW5uZWwocmF3OiBCdWZmZXIsIHBvczogU21hcnRDdXJzb3IpOiBDaGFubmVsIHtcbiAgY29uc3QgY2hhbm5lbElkID0gcmF3LnJlYWRVSW50OChwb3MubmV4dCgpKTtcbiAgY29uc3Qgc3RhdHVzID0gcmF3LnJlYWRVSW50OChwb3MubmV4dCgpKTtcbiAgaWYgKGNoYW5uZWxJZCA9PT0gMCkge1xuICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBjaGFubmVsIGlkICR7Y2hhbm5lbElkfWApO1xuICB9XG4gIHJldHVybiB7XG4gICAgY2hhbm5lbElkLCBzdGF0dXMsXG4gIH07XG59O1xuXG5leHBvcnQgZnVuY3Rpb24gaHBhaShyYXc6IEJ1ZmZlciwgcG9zOiBTbWFydEN1cnNvcik6IEhwYWkge1xuICBjb25zdCBzaXplID0gcmF3LnJlYWRVSW50OChwb3MubmV4dCgpKTtcbiAgaWYgKHNpemUgIT09IDB4OCkge1xuICAgIHRocm93IG5ldyBFcnJvcihgRmFpbGVkIHRvIHJlYWQgaHBhaSBhdCAke3Bvcy5jdXJ9YCk7XG4gIH1cbiAgY29uc3QgcHJvdG9jb2wgPSByYXcucmVhZFVJbnQ4KHBvcy5uZXh0KCkpO1xuICBjb25zdCBpcCA9IHJhdy5yZWFkVUludEJFKHBvcy5uZXh0KDQpLCA0KTtcbiAgY29uc3QgcG9ydCA9IHJhdy5yZWFkSW50MTZCRShwb3MubmV4dCgyKSk7XG4gIHJldHVybiB7XG4gICAgaXAsIHBvcnQsIHByb3RvY29sLFxuICB9O1xufTtcblxuZXhwb3J0IGZ1bmN0aW9uIGNvbm5lY3RSZXNwb25zZShyYXc6IEJ1ZmZlciwgcG9zOiBTbWFydEN1cnNvcikge1xuICBjb25zdCBzaXplID0gcmF3LnJlYWRJbnQ4KHBvcy5uZXh0KCkpO1xuICBjb25zdCBjb250eXBlID0gcmF3LnJlYWRJbnQ4KHBvcy5uZXh0KCkpO1xuICBzd2l0Y2ggKGNvbnR5cGUpIHtcbiAgICBjYXNlIENvbm5lY3Rpb24uVHVubmVsOiB7XG4gICAgICBpZiAoc2l6ZSAhPT0gMHg0KSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRmFpbGVkIHRvIHJlYWQgY29ubmVjdCByZXNwb25zZSBmb3IgdHVubmVsaW5nIGF0ICR7cG9zLmN1cn1gKTtcbiAgICAgIH1cbiAgICAgIGNvbnN0IGtueEFkZHJlc3MgPSByYXcucmVhZFVJbnQxNkJFKHBvcy5uZXh0KDIpKTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIGNvbm5lY3Rpb25UeXBlOiBjb250eXBlLFxuICAgICAgICBrbnhBZGRyZXNzLFxuICAgICAgfTtcbiAgICB9XG4gICAgZGVmYXVsdDogdGhyb3cgbmV3IEVycm9yKGBVbmtub3duIGNvbm5lY3Rpb24gdHlwZSAke2NvbnR5cGV9YCk7XG4gIH1cbn07XG5cbmV4cG9ydCBmdW5jdGlvbiBzZXFudW0ocmF3OiBCdWZmZXIsIHBvczogU21hcnRDdXJzb3IpIHtcbiAgY29uc3Qgc2l6ZSA9IHJhdy5yZWFkVUludDgocG9zLm5leHQoKSk7XG4gIGlmIChzaXplICE9PSAweDQpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYEZhaWxlZCB0byByZWFkIHN0cnVjdHVyZSBhdCAke3Bvcy5jdXJ9YCk7XG4gIH1cbiAgY29uc3QgY2hhbm5lbElkID0gcmF3LnJlYWRVSW50OChwb3MubmV4dCgpKTtcbiAgY29uc3Qgc2VxbiA9IHJhdy5yZWFkSW50OChwb3MubmV4dCgpKTtcbiAgY29uc3Qgc3RhdHVzID0gcmF3LnJlYWRVSW50OChwb3MubmV4dCgpKTtcbiAgcmV0dXJuIHtcbiAgICBjaGFubmVsSWQsIHNlcW4sIHN0YXR1cyxcbiAgfTtcbn07XG5cbmV4cG9ydCBmdW5jdGlvbiB0dW5uZWxDZW1pKHJhdzogQnVmZmVyLCBwb3M6IFNtYXJ0Q3Vyc29yKSB7XG4gIHBvcy5za2lwKCdtZXNzYWdlQ29kZScpO1xuICBjb25zdCBhZGRpdGlvbmFsSW5mb0xlbmd0aCA9IHJhdy5yZWFkVUludDgocG9zLm5leHQoKSk7XG4gIGlmIChhZGRpdGlvbmFsSW5mb0xlbmd0aCkge1xuICAgIHBvcy5za2lwKCdhZGRpdGlvbmFsSW5mbycsIGFkZGl0aW9uYWxJbmZvTGVuZ3RoKTtcbiAgfVxuICBwb3Muc2tpcCgnY29udHJvbEZpZWxkMScpO1xuICBwb3Muc2tpcCgnY29udHJvbEZpZWxkMicpO1xuICBjb25zdCBzb3VyY2UgPSByYXcucmVhZFVJbnQxNkJFKHBvcy5uZXh0KDIpKTtcbiAgY29uc3QgZGVzdCA9IHJhdy5yZWFkVUludDE2QkUocG9zLm5leHQoMikpO1xuICBjb25zdCBucGR1TGVuZ3RoID0gcmF3LnJlYWRVSW50OChwb3MubmV4dCgpKTtcbiAgY29uc3QgYXBkdSA9IHJhdy5yZWFkVUludDE2QkUocG9zLm5leHQoMikpO1xuICBjb25zdCBhY3Rpb24gPSBhcGR1ICYgKEJ1c0V2ZW50Lkdyb3VwV3JpdGUgfFxuICAgIEJ1c0V2ZW50Lkdyb3VwUmVzcG9uc2UgfCBCdXNFdmVudC5Hcm91cFJlYWQpO1xuICBpZiAoYWN0aW9uICYgKEJ1c0V2ZW50Lkdyb3VwV3JpdGUgfCBCdXNFdmVudC5Hcm91cFJlc3BvbnNlKSkge1xuICAgIGxldCBkYXRhOiBVaW50OEFycmF5O1xuICAgIGlmIChucGR1TGVuZ3RoID4gMSkge1xuICAgICAgLy8gZGF0YSBhcHBlbmRlZFxuICAgICAgZGF0YSA9IHJhdy5zdWJhcnJheShwb3MubmV4dChucGR1TGVuZ3RoKSwgcG9zLmN1cik7XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIGRhdGEgbWVyZ2VkIGludG8gNiBiaXRzXG4gICAgICBkYXRhID0gbmV3IFVpbnQ4QXJyYXkoW2FwZHUgJiAweDNmXSk7XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICBkYXRhLCBhY3Rpb24sIGRlc3QsIHNvdXJjZSxcbiAgICB9O1xuICB9IGVsc2Uge1xuICAgIC8vIHJlYWRcbiAgICByZXR1cm4ge1xuICAgICAgYWN0aW9uLCBkZXN0LCBzb3VyY2UsXG4gICAgfTtcbiAgfVxufTtcbiJdfQ== -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './utils/async-socket'; 2 | export * from './utils/smart-cursor'; 3 | export * from './utils/index'; 4 | export * from './interfaces'; 5 | export * from './serializer'; 6 | export * from './deserializer'; 7 | export * from './constants'; 8 | export * from './query-manager'; 9 | export * from './bus-listener'; 10 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function __export(m) { 3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 4 | } 5 | __export(require("./utils/async-socket")); 6 | __export(require("./utils/smart-cursor")); 7 | __export(require("./utils/index")); 8 | __export(require("./serializer")); 9 | __export(require("./deserializer")); 10 | __export(require("./constants")); 11 | __export(require("./query-manager")); 12 | __export(require("./bus-listener")); 13 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7OztBQUFBLDBDQUFxQztBQUNyQywwQ0FBcUM7QUFDckMsbUNBQThCO0FBRTlCLGtDQUE2QjtBQUM3QixvQ0FBK0I7QUFDL0IsaUNBQTRCO0FBQzVCLHFDQUFnQztBQUNoQyxvQ0FBK0IiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgKiBmcm9tICcuL3V0aWxzL2FzeW5jLXNvY2tldCc7XG5leHBvcnQgKiBmcm9tICcuL3V0aWxzL3NtYXJ0LWN1cnNvcic7XG5leHBvcnQgKiBmcm9tICcuL3V0aWxzL2luZGV4JztcbmV4cG9ydCAqIGZyb20gJy4vaW50ZXJmYWNlcyc7XG5leHBvcnQgKiBmcm9tICcuL3NlcmlhbGl6ZXInO1xuZXhwb3J0ICogZnJvbSAnLi9kZXNlcmlhbGl6ZXInO1xuZXhwb3J0ICogZnJvbSAnLi9jb25zdGFudHMnO1xuZXhwb3J0ICogZnJvbSAnLi9xdWVyeS1tYW5hZ2VyJztcbmV4cG9ydCAqIGZyb20gJy4vYnVzLWxpc3RlbmVyJztcbiJdfQ== -------------------------------------------------------------------------------- /dist/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { Protocol } from './constants'; 3 | export interface Subscriber { 4 | unsubscribe: () => void; 5 | } 6 | export interface Hpai { 7 | protocol: Protocol.Tcp4 | Protocol.Udp4; 8 | ip: number; 9 | port: number; 10 | } 11 | export interface Channel { 12 | channelId: number; 13 | status: number; 14 | } 15 | export interface ConnectResponseTunnel { 16 | connectionType: number; 17 | knxAddress: number; 18 | protocol: number; 19 | ip: number; 20 | port: number; 21 | channelId: number; 22 | status: number; 23 | serviceId: number; 24 | } 25 | export interface Header { 26 | serviceId: number; 27 | } 28 | export interface DisconnectReponse { 29 | channelId: number; 30 | status: number; 31 | } 32 | export interface TunnelingAck { 33 | channelId: number; 34 | status: number; 35 | seqn: number; 36 | } 37 | export interface GroupResponse { 38 | source: number; 39 | data: Uint8Array | Buffer | number[]; 40 | channelId: number; 41 | status: number; 42 | seqn: number; 43 | action: number; 44 | dest: number; 45 | } 46 | -------------------------------------------------------------------------------- /dist/interfaces.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | ; 3 | ; 4 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9pbnRlcmZhY2VzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFFd0QsQ0FBQztBQXNCeEQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IFByb3RvY29sIH0gZnJvbSAnLi9jb25zdGFudHMnO1xuXG5leHBvcnQgaW50ZXJmYWNlIFN1YnNjcmliZXIgeyB1bnN1YnNjcmliZTogKCkgPT4gdm9pZDsgfTtcblxuZXhwb3J0IGludGVyZmFjZSBIcGFpIHtcbiAgcHJvdG9jb2w6IFByb3RvY29sLlRjcDQgfCBQcm90b2NvbC5VZHA0O1xuICBpcDogbnVtYmVyO1xuICBwb3J0OiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ2hhbm5lbCB7XG4gIGNoYW5uZWxJZDogbnVtYmVyO1xuICBzdGF0dXM6IG51bWJlcjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBDb25uZWN0UmVzcG9uc2VUdW5uZWwge1xuICBjb25uZWN0aW9uVHlwZTogbnVtYmVyO1xuICBrbnhBZGRyZXNzOiBudW1iZXI7XG4gIHByb3RvY29sOiBudW1iZXI7XG4gIGlwOiBudW1iZXI7XG4gIHBvcnQ6IG51bWJlcjtcbiAgY2hhbm5lbElkOiBudW1iZXI7XG4gIHN0YXR1czogbnVtYmVyO1xuICBzZXJ2aWNlSWQ6IG51bWJlcjtcbn07XG5cbmV4cG9ydCBpbnRlcmZhY2UgSGVhZGVyIHtcbiAgc2VydmljZUlkOiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgRGlzY29ubmVjdFJlcG9uc2Uge1xuICBjaGFubmVsSWQ6IG51bWJlcjtcbiAgc3RhdHVzOiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgVHVubmVsaW5nQWNrIHtcbiAgY2hhbm5lbElkOiBudW1iZXI7XG4gIHN0YXR1czogbnVtYmVyO1xuICBzZXFuOiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgR3JvdXBSZXNwb25zZSB7XG4gIHNvdXJjZTogbnVtYmVyO1xuICBkYXRhOiBVaW50OEFycmF5IHwgQnVmZmVyIHwgbnVtYmVyW107XG4gIGNoYW5uZWxJZDogbnVtYmVyO1xuICBzdGF0dXM6IG51bWJlcjtcbiAgc2VxbjogbnVtYmVyO1xuICBhY3Rpb246IG51bWJlcjtcbiAgZGVzdDogbnVtYmVyO1xufVxuIl19 -------------------------------------------------------------------------------- /dist/query-manager.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { RemoteInfo } from 'dgram'; 3 | import { AsyncSocket } from './utils/async-socket'; 4 | /** 5 | * Manages io server queries and tracks resolution of mappable requests 6 | */ 7 | export declare class QueryManager extends AsyncSocket { 8 | connect(port?: number): Promise; 9 | /** 10 | * Creates a mapable request to track responses with timeout 11 | */ 12 | request(host: string, port: number, data: Buffer, select: (res: T, sender?: RemoteInfo) => boolean, timeout?: number): Promise; 13 | /** 14 | * Processes raw messages from socket stream 15 | */ 16 | private process(raw, remote); 17 | } 18 | -------------------------------------------------------------------------------- /dist/query-manager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const tslib_1 = require("tslib"); 3 | const async_socket_1 = require("./utils/async-socket"); 4 | const smart_cursor_1 = require("./utils/smart-cursor"); 5 | const deserializer_1 = require("./deserializer"); 6 | const serializer_1 = require("./serializer"); 7 | /** 8 | * Manages io server queries and tracks resolution of mappable requests 9 | */ 10 | class QueryManager extends async_socket_1.AsyncSocket { 11 | connect(port = 0 /* OS assigned port */) { 12 | // forward raw data for processing 13 | const ref = super.on('raw', this.process.bind(this)); 14 | return super.connect(port).catch((err) => { 15 | ref.unsubscribe(); 16 | // propagate error to the caller 17 | throw err; 18 | }); 19 | } 20 | /** 21 | * Creates a mapable request to track responses with timeout 22 | */ 23 | request(host, port, data, select, timeout) { 24 | return new Promise((resolve, reject) => { 25 | // keep ref to unsub to avoid a memory leak 26 | const ref = this.on('query', (query, remote) => { 27 | // map response to the request 28 | if (select(query, remote)) { 29 | if (query.status === 0 /* NoError */) { 30 | resolve(query); 31 | } 32 | else { 33 | reject(new Error(`Request error ${query.status}`)); 34 | } 35 | } 36 | }); 37 | // set timeout if no response within given time 38 | setTimeout(() => { 39 | ref.unsubscribe(); // avoid memory leak 40 | const err = new Error(`Request timeout`); 41 | err.code = 'ETIMEOUT'; 42 | reject(err); 43 | }, timeout > 300 ? timeout : 300).unref(); // unref timeout to let node exit 44 | // make request and propagate errors 45 | return super.send(host, port, data).catch((err) => { 46 | ref.unsubscribe(); // avoid memory leak 47 | reject(err); 48 | }); 49 | }); 50 | } 51 | /** 52 | * Processes raw messages from socket stream 53 | */ 54 | process(raw, remote) { 55 | try { 56 | const pos = new smart_cursor_1.SmartCursor(); 57 | const header = deserializer_1.header(raw, pos); 58 | switch (header.serviceId) { 59 | case 518 /* ConnectResponse */: { 60 | const channel = deserializer_1.channel(raw, pos); 61 | const sender = deserializer_1.hpai(raw, pos); 62 | const response = deserializer_1.connectResponse(raw, pos); 63 | return this.events.emit('query', tslib_1.__assign({}, header, channel, sender, response), remote); 64 | } 65 | case 520 /* ConnectStateResponse */: { 66 | const channel = deserializer_1.channel(raw, pos); 67 | return this.events.emit('query', tslib_1.__assign({}, channel), remote); 68 | } 69 | case 1057 /* TunnelingAck */: { 70 | const seqn = deserializer_1.seqnum(raw, pos); 71 | return this.events.emit('query', tslib_1.__assign({}, seqn), remote); 72 | } 73 | case 1056 /* TunnelingRequest */: { 74 | const seqn = deserializer_1.seqnum(raw, pos); 75 | const cemi = deserializer_1.tunnelCemi(raw, pos); 76 | // reply ack to indicate successful reception of the message 77 | this.send(remote.address, remote.port, serializer_1.ack(seqn.seqn, seqn.channelId, 0 /* NoError */)); 78 | return this.events.emit('query', tslib_1.__assign({}, cemi, seqn), remote); 79 | } 80 | case 522 /* DisconnectResponse */: { 81 | const channel = deserializer_1.channel(raw, pos); 82 | return this.events.emit('query', tslib_1.__assign({}, channel), remote); 83 | } 84 | default: throw new Error(`Failed to process ${header.serviceId}`); 85 | } 86 | } 87 | catch (err) { 88 | return this.events.emit('unprocessed', err, raw, remote); 89 | } 90 | } 91 | } 92 | exports.QueryManager = QueryManager; 93 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVlcnktbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9xdWVyeS1tYW5hZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBR0EsdURBRThCO0FBQzlCLHVEQUU4QjtBQUM5QixpREFPd0I7QUFDeEIsNkNBRXNCO0FBTXRCOztHQUVHO0FBQ0gsa0JBQTBCLFNBQVEsMEJBQVc7SUFDM0MsT0FBTyxDQUFDLE9BQWUsQ0FBQyxDQUFDLHNCQUFzQjtRQUM3QyxrQ0FBa0M7UUFDbEMsTUFBTSxHQUFHLEdBQUcsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUNyRCxNQUFNLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHO1lBQ25DLEdBQUcsQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUNsQixnQ0FBZ0M7WUFDaEMsTUFBTSxHQUFHLENBQUM7UUFDWixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFDRDs7T0FFRztJQUNILE9BQU8sQ0FDTCxJQUFZLEVBQUUsSUFBWSxFQUFFLElBQVksRUFDeEMsTUFBZ0QsRUFBRSxPQUFnQjtRQUVsRSxNQUFNLENBQUMsSUFBSSxPQUFPLENBQUksQ0FBQyxPQUFPLEVBQUUsTUFBTTtZQUNwQywyQ0FBMkM7WUFDM0MsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEVBQUUsQ0FBeUIsT0FBTyxFQUFFLENBQUMsS0FBSyxFQUFFLE1BQU07Z0JBQ2pFLDhCQUE4QjtnQkFDOUIsRUFBRSxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7b0JBQzFCLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxNQUFNLEtBQUssZUFBYyxDQUFDLENBQUMsQ0FBQzt3QkFDcEMsT0FBTyxDQUFDLEtBQUssQ0FB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eEMsSUFBSSxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxFQUFFLGVBQWMsQ0FDMUMsQ0FBQyxDQUFDO29CQUNILE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLHVCQUFPLElBQUksRUFBSyxJQUFJLEdBQUksTUFBTSxDQUFDLENBQUM7Z0JBQ2pFLENBQUM7Z0JBQ0QsS0FBSyw0QkFBMEIsRUFBRSxDQUFDO29CQUNoQyxNQUFNLE9BQU8sR0FBRyxzQkFBVyxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQztvQkFDdEMsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sdUJBQU8sT0FBTyxHQUFJLE1BQU0sQ0FBQyxDQUFDO2dCQUMzRCxDQUFDO2dCQUNELFNBQVMsTUFBTSxJQUFJLEtBQUssQ0FBQyxxQkFBcUIsTUFBTSxDQUFDLFNBQVMsRUFBRSxDQUFDLENBQUM7WUFDcEUsQ0FBQztRQUNILENBQUU7UUFBQSxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1lBQ2IsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLGFBQWEsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQzNELENBQUM7SUFDSCxDQUFDO0NBQ0Y7QUF0RkQsb0NBc0ZDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgUmVtb3RlSW5mbyxcbn0gZnJvbSAnZGdyYW0nO1xuaW1wb3J0IHtcbiAgQXN5bmNTb2NrZXQsXG59IGZyb20gJy4vdXRpbHMvYXN5bmMtc29ja2V0JztcbmltcG9ydCB7XG4gIFNtYXJ0Q3Vyc29yLFxufSBmcm9tICcuL3V0aWxzL3NtYXJ0LWN1cnNvcic7XG5pbXBvcnQge1xuICBjaGFubmVsIGFzIHJlYWRDaGFubmVsLFxuICBjb25uZWN0UmVzcG9uc2UsXG4gIGhlYWRlciBhcyByZWFkSGVhZGVyLFxuICBocGFpLFxuICBzZXFudW0sXG4gIHR1bm5lbENlbWksXG59IGZyb20gJy4vZGVzZXJpYWxpemVyJztcbmltcG9ydCB7XG4gIGFjayxcbn0gZnJvbSAnLi9zZXJpYWxpemVyJztcbmltcG9ydCB7XG4gIFNlcnZpY2UsXG4gIFN0YXR1cyxcbn0gZnJvbSAnLi9jb25zdGFudHMnO1xuXG4vKipcbiAqIE1hbmFnZXMgaW8gc2VydmVyIHF1ZXJpZXMgYW5kIHRyYWNrcyByZXNvbHV0aW9uIG9mIG1hcHBhYmxlIHJlcXVlc3RzXG4gKi9cbmV4cG9ydCBjbGFzcyBRdWVyeU1hbmFnZXIgZXh0ZW5kcyBBc3luY1NvY2tldCB7XG4gIGNvbm5lY3QocG9ydDogbnVtYmVyID0gMCAvKiBPUyBhc3NpZ25lZCBwb3J0ICovKTogUHJvbWlzZTxSZW1vdGVJbmZvPiB7XG4gICAgLy8gZm9yd2FyZCByYXcgZGF0YSBmb3IgcHJvY2Vzc2luZ1xuICAgIGNvbnN0IHJlZiA9IHN1cGVyLm9uKCdyYXcnLCB0aGlzLnByb2Nlc3MuYmluZCh0aGlzKSk7XG4gICAgcmV0dXJuIHN1cGVyLmNvbm5lY3QocG9ydCkuY2F0Y2goKGVycikgPT4ge1xuICAgICAgcmVmLnVuc3Vic2NyaWJlKCk7XG4gICAgICAvLyBwcm9wYWdhdGUgZXJyb3IgdG8gdGhlIGNhbGxlclxuICAgICAgdGhyb3cgZXJyO1xuICAgIH0pO1xuICB9XG4gIC8qKlxuICAgKiBDcmVhdGVzIGEgbWFwYWJsZSByZXF1ZXN0IHRvIHRyYWNrIHJlc3BvbnNlcyB3aXRoIHRpbWVvdXRcbiAgICovXG4gIHJlcXVlc3Q8VD4oXG4gICAgaG9zdDogc3RyaW5nLCBwb3J0OiBudW1iZXIsIGRhdGE6IEJ1ZmZlcixcbiAgICBzZWxlY3Q6IChyZXM6IFQsIHNlbmRlcj86IFJlbW90ZUluZm8pID0+IGJvb2xlYW4sIHRpbWVvdXQ/OiBudW1iZXIsXG4gICkge1xuICAgIHJldHVybiBuZXcgUHJvbWlzZTxUPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAvLyBrZWVwIHJlZiB0byB1bnN1YiB0byBhdm9pZCBhIG1lbW9yeSBsZWFrXG4gICAgICBjb25zdCByZWYgPSB0aGlzLm9uPFQgJiB7IHN0YXR1czogbnVtYmVyIH0+KCdxdWVyeScsIChxdWVyeSwgcmVtb3RlKSA9PiB7XG4gICAgICAgIC8vIG1hcCByZXNwb25zZSB0byB0aGUgcmVxdWVzdFxuICAgICAgICBpZiAoc2VsZWN0KHF1ZXJ5LCByZW1vdGUpKSB7XG4gICAgICAgICAgaWYgKHF1ZXJ5LnN0YXR1cyA9PT0gU3RhdHVzLk5vRXJyb3IpIHtcbiAgICAgICAgICAgIHJlc29sdmUocXVlcnkpO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBSZXF1ZXN0IGVycm9yICR7cXVlcnkuc3RhdHVzfWApKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgICAgLy8gc2V0IHRpbWVvdXQgaWYgbm8gcmVzcG9uc2Ugd2l0aGluIGdpdmVuIHRpbWVcbiAgICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICByZWYudW5zdWJzY3JpYmUoKTsgLy8gYXZvaWQgbWVtb3J5IGxlYWtcbiAgICAgICAgY29uc3QgZXJyOiBOb2RlSlMuRXJybm9FeGNlcHRpb24gPSBuZXcgRXJyb3IoYFJlcXVlc3QgdGltZW91dGApO1xuICAgICAgICBlcnIuY29kZSA9ICdFVElNRU9VVCc7XG4gICAgICAgIHJlamVjdChlcnIpO1xuICAgICAgfSwgdGltZW91dCA+IDMwMCA/IHRpbWVvdXQgOiAzMDApLnVucmVmKCk7IC8vIHVucmVmIHRpbWVvdXQgdG8gbGV0IG5vZGUgZXhpdFxuICAgICAgLy8gbWFrZSByZXF1ZXN0IGFuZCBwcm9wYWdhdGUgZXJyb3JzXG4gICAgICByZXR1cm4gc3VwZXIuc2VuZChob3N0LCBwb3J0LCBkYXRhKS5jYXRjaCgoZXJyKSA9PiB7XG4gICAgICAgIHJlZi51bnN1YnNjcmliZSgpOyAvLyBhdm9pZCBtZW1vcnkgbGVha1xuICAgICAgICByZWplY3QoZXJyKTtcbiAgICAgIH0pO1xuICAgIH0pO1xuICB9XG4gIC8qKlxuICAgKiBQcm9jZXNzZXMgcmF3IG1lc3NhZ2VzIGZyb20gc29ja2V0IHN0cmVhbVxuICAgKi9cbiAgcHJpdmF0ZSBwcm9jZXNzKHJhdzogQnVmZmVyLCByZW1vdGU6IFJlbW90ZUluZm8pIHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcG9zID0gbmV3IFNtYXJ0Q3Vyc29yKCk7XG4gICAgICBjb25zdCBoZWFkZXIgPSByZWFkSGVhZGVyKHJhdywgcG9zKTtcbiAgICAgIHN3aXRjaCAoaGVhZGVyLnNlcnZpY2VJZCkge1xuICAgICAgICBjYXNlIFNlcnZpY2UuQ29ubmVjdFJlc3BvbnNlOiB7XG4gICAgICAgICAgY29uc3QgY2hhbm5lbCA9IHJlYWRDaGFubmVsKHJhdywgcG9zKTtcbiAgICAgICAgICBjb25zdCBzZW5kZXIgPSBocGFpKHJhdywgcG9zKTtcbiAgICAgICAgICBjb25zdCByZXNwb25zZSA9IGNvbm5lY3RSZXNwb25zZShyYXcsIHBvcyk7XG4gICAgICAgICAgcmV0dXJuIHRoaXMuZXZlbnRzLmVtaXQoJ3F1ZXJ5Jywge1xuICAgICAgICAgICAgLi4uaGVhZGVyLCAuLi5jaGFubmVsLCAuLi5zZW5kZXIsIC4uLnJlc3BvbnNlLFxuICAgICAgICAgIH0sIHJlbW90ZSk7XG4gICAgICAgIH1cbiAgICAgICAgY2FzZSBTZXJ2aWNlLkNvbm5lY3RTdGF0ZVJlc3BvbnNlOiB7XG4gICAgICAgICAgY29uc3QgY2hhbm5lbCA9IHJlYWRDaGFubmVsKHJhdywgcG9zKTtcbiAgICAgICAgICByZXR1cm4gdGhpcy5ldmVudHMuZW1pdCgncXVlcnknLCB7IC4uLmNoYW5uZWwgfSwgcmVtb3RlKTtcbiAgICAgICAgfVxuICAgICAgICBjYXNlIFNlcnZpY2UuVHVubmVsaW5nQWNrOiB7XG4gICAgICAgICAgY29uc3Qgc2VxbiA9IHNlcW51bShyYXcsIHBvcyk7XG4gICAgICAgICAgcmV0dXJuIHRoaXMuZXZlbnRzLmVtaXQoJ3F1ZXJ5JywgeyAuLi5zZXFuIH0sIHJlbW90ZSk7XG4gICAgICAgIH1cbiAgICAgICAgY2FzZSBTZXJ2aWNlLlR1bm5lbGluZ1JlcXVlc3Q6IHtcbiAgICAgICAgICBjb25zdCBzZXFuID0gc2VxbnVtKHJhdywgcG9zKTtcbiAgICAgICAgICBjb25zdCBjZW1pID0gdHVubmVsQ2VtaShyYXcsIHBvcyk7XG4gICAgICAgICAgLy8gcmVwbHkgYWNrIHRvIGluZGljYXRlIHN1Y2Nlc3NmdWwgcmVjZXB0aW9uIG9mIHRoZSBtZXNzYWdlXG4gICAgICAgICAgdGhpcy5zZW5kKHJlbW90ZS5hZGRyZXNzLCByZW1vdGUucG9ydCwgYWNrKFxuICAgICAgICAgICAgc2Vxbi5zZXFuLCBzZXFuLmNoYW5uZWxJZCwgU3RhdHVzLk5vRXJyb3IsXG4gICAgICAgICAgKSk7XG4gICAgICAgICAgcmV0dXJuIHRoaXMuZXZlbnRzLmVtaXQoJ3F1ZXJ5JywgeyAuLi5jZW1pLCAuLi5zZXFuIH0sIHJlbW90ZSk7XG4gICAgICAgIH1cbiAgICAgICAgY2FzZSBTZXJ2aWNlLkRpc2Nvbm5lY3RSZXNwb25zZToge1xuICAgICAgICAgIGNvbnN0IGNoYW5uZWwgPSByZWFkQ2hhbm5lbChyYXcsIHBvcyk7XG4gICAgICAgICAgcmV0dXJuIHRoaXMuZXZlbnRzLmVtaXQoJ3F1ZXJ5JywgeyAuLi5jaGFubmVsIH0sIHJlbW90ZSk7XG4gICAgICAgIH1cbiAgICAgICAgZGVmYXVsdDogdGhyb3cgbmV3IEVycm9yKGBGYWlsZWQgdG8gcHJvY2VzcyAke2hlYWRlci5zZXJ2aWNlSWR9YCk7XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICByZXR1cm4gdGhpcy5ldmVudHMuZW1pdCgndW5wcm9jZXNzZWQnLCBlcnIsIHJhdywgcmVtb3RlKTtcbiAgICB9XG4gIH1cbn1cbiJdfQ== -------------------------------------------------------------------------------- /dist/serializer.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { Hpai } from './interfaces'; 3 | export declare const enum DataType { 4 | Uint8 = 1, 5 | Uint16 = 2, 6 | Uint32 = 4, 7 | Uint64 = 8, 8 | Uint128 = 16, 9 | } 10 | export declare function ack(seqn: number, channelId: number, status: number): Buffer; 11 | export declare function disconnect(channelId: number, respondTo: Hpai): Buffer; 12 | export declare function ping(channelId: number, respondTo: Hpai): Buffer; 13 | export declare function openTunnel({receiveAt, respondTo}: { 14 | respondTo: Hpai; 15 | receiveAt: Hpai; 16 | }): Buffer; 17 | export declare function write({data, seqn, channelId, source, dest}: { 18 | data: Buffer | Uint8Array | number[]; 19 | seqn: number; 20 | channelId: number; 21 | source: number; 22 | dest: number; 23 | }): Buffer; 24 | export declare function read(params: { 25 | seqn: number; 26 | channelId: number; 27 | source: number; 28 | dest: number; 29 | }): Buffer; 30 | -------------------------------------------------------------------------------- /dist/serializer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const smart_cursor_1 = require("./utils/smart-cursor"); 3 | function header(service, bodyLength) { 4 | const size = 0x06; 5 | const pos = new smart_cursor_1.SmartCursor(); 6 | const raw = Buffer.allocUnsafe(size); 7 | raw.writeUInt8(size, pos.next()); // header length 8 | raw.writeUInt8(0x10, pos.next()); // version 9 | raw.writeUInt16BE(service, pos.next(2)); // service type 10 | raw.writeUInt16BE(size + bodyLength, pos.next(2)); // total length 11 | return raw; 12 | } 13 | ; 14 | function message(service, includes) { 15 | const size = includes.reduce((acc, item) => acc += item.length, 0); 16 | const head = header(service, size); 17 | const ret = Buffer.concat([head, ...includes]); 18 | return ret; 19 | } 20 | ; 21 | function hpai(protocol, ip, port) { 22 | const size = 0x08; 23 | const pos = new smart_cursor_1.SmartCursor(); 24 | const raw = Buffer.allocUnsafe(size); 25 | raw.writeUInt8(size, pos.next()); // structure length 26 | raw.writeUInt8(protocol, pos.next()); // protocol 27 | raw.writeUInt32BE(ip, pos.next(4)); // ip 28 | raw.writeUInt16BE(port, pos.next(2)); // port 29 | return raw; 30 | } 31 | ; 32 | function tunneling() { 33 | const size = 0x04; 34 | const pos = new smart_cursor_1.SmartCursor(); 35 | const raw = Buffer.allocUnsafe(size); 36 | raw.writeUInt8(size, pos.next()); // structure length 37 | raw.writeUInt8(0x04, pos.next()); // TUNNEL_CONNECTION 38 | raw.writeUInt8(0x02, pos.next()); // TUNNEL_LINKLAYER 39 | raw.writeUInt8(0x00, pos.next()); // reserved 40 | return raw; 41 | } 42 | ; 43 | function channel(channelId) { 44 | const pos = new smart_cursor_1.SmartCursor(); 45 | const raw = Buffer.allocUnsafe(2); 46 | raw.writeUInt8(channelId, pos.next()); 47 | raw.writeUInt8(0x00, pos.next()); // reserved 48 | return raw; 49 | } 50 | ; 51 | /** 52 | * Creates buffer of sequence counter, channel id and status code 53 | */ 54 | function seqnum(seqn, channelId, status = 0x00) { 55 | const size = 0x04; 56 | const pos = new smart_cursor_1.SmartCursor(); 57 | const raw = Buffer.allocUnsafe(size); 58 | raw.writeUInt8(size, pos.next()); // structure length 59 | raw.writeUInt8(channelId, pos.next()); // channelId 60 | raw.writeUInt8(seqn, pos.next()); // sequenceCounter 61 | raw.writeUInt8(status, pos.next()); // reserved or status 62 | return raw; 63 | } 64 | ; 65 | // ready to use messages 66 | function ack(seqn, channelId, status) { 67 | return message(1057 /* TunnelingAck */, [ 68 | seqnum(seqn, channelId, status), 69 | ]); 70 | } 71 | exports.ack = ack; 72 | ; 73 | function disconnect(channelId, respondTo) { 74 | return message(521 /* DisconnectRequest */, [ 75 | channel(channelId), 76 | hpai(respondTo.protocol, respondTo.ip, respondTo.port), 77 | ]); 78 | } 79 | exports.disconnect = disconnect; 80 | ; 81 | function ping(channelId, respondTo) { 82 | return message(519 /* ConnectionStateRequest */, [ 83 | channel(channelId), 84 | hpai(respondTo.protocol, respondTo.ip, respondTo.port), 85 | ]); 86 | } 87 | exports.ping = ping; 88 | ; 89 | function openTunnel({ receiveAt, respondTo }) { 90 | return message(517 /* ConnectRequest */, [ 91 | hpai(respondTo.protocol, respondTo.ip, respondTo.port), 92 | hpai(receiveAt.protocol, receiveAt.ip, receiveAt.port), 93 | tunneling(), 94 | ]); 95 | } 96 | exports.openTunnel = openTunnel; 97 | ; 98 | function write({ data, seqn, channelId, source, dest }) { 99 | if (data.length > 16 /* Uint128 */) { 100 | // if data is longer than 16 bytes 101 | throw new Error(`Data is too long, expected maximum ${16 /* Uint128 */} bytes, got ${data.length}`); 102 | } 103 | // cemi 104 | const isUint6 = data.length === 1 /* Uint8 */ && data[0] <= 0x3f; 105 | const size = isUint6 ? 1 /* Uint8 */ : data.length + 1; 106 | const pos = new smart_cursor_1.SmartCursor(); 107 | const cemi = Buffer.alloc(0x0A + size); 108 | cemi.writeUInt8(0x11, pos.next()); // L_Data_req 109 | cemi.writeUInt8(0x00, pos.next()); // additional info length 110 | cemi.writeUInt8(0xbc, pos.next()); // control field 1 111 | cemi.writeUInt8(0xe0, pos.next()); // control field 2 112 | cemi.writeUInt16BE(source, pos.next(2)); // source address 0.0.0 113 | cemi.writeUInt16BE(dest, pos.next(2)); // destination address 114 | if (isUint6) { 115 | // data can be merged 116 | cemi.writeUInt8(size, pos.next()); // payload length 117 | cemi.writeUInt16BE(data[0] | 0x80, pos.next(2)); // 0x80 GROUPVALUE_WRITE 118 | } 119 | else { 120 | // data must be appended at the end 121 | cemi.writeUInt8(size, pos.next()); // payload length 122 | cemi.writeUInt16BE(0x80, pos.next(2)); // apci 0x80 GROUPVALUE_WRITE 123 | cemi.set(data, pos.next(size)); 124 | } 125 | return message(1056 /* TunnelingRequest */, [ 126 | seqnum(seqn, channelId), 127 | cemi, 128 | ]); 129 | } 130 | exports.write = write; 131 | ; 132 | function read(params) { 133 | // cemi 134 | const pos = new smart_cursor_1.SmartCursor(); 135 | const cemi = Buffer.alloc(0x0B); 136 | cemi.writeUInt8(0x11, pos.next()); // L_Data_req 137 | cemi.writeUInt8(0x00, pos.next()); // additional info length 138 | cemi.writeUInt8(0xbc, pos.next()); // control field 1 139 | cemi.writeUInt8(0xe0, pos.next()); // control field 2 140 | cemi.writeUInt16BE(params.source, pos.next(2)); // source address 0.0.0 141 | cemi.writeUInt16BE(params.dest, pos.next(2)); // destination address 142 | cemi.writeUInt8(0x01, pos.next()); // payload length 143 | cemi.writeUInt16BE(0x00, pos.next(2)); // 0x00 GROUPVALUE_READ 144 | return message(1056 /* TunnelingRequest */, [ 145 | seqnum(params.seqn, params.channelId), 146 | cemi, 147 | ]); 148 | } 149 | exports.read = read; 150 | ; 151 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VyaWFsaXplci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9zZXJpYWxpemVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSx1REFFOEI7QUFROUIsZ0JBQWdCLE9BQWdCLEVBQUUsVUFBa0I7SUFDbEQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDO0lBQ2xCLE1BQU0sR0FBRyxHQUFHLElBQUksMEJBQVcsRUFBRSxDQUFDO0lBQzlCLE1BQU0sR0FBRyxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDckMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxnQkFBZ0I7SUFDbEQsR0FBRyxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxVQUFVO0lBQzVDLEdBQUcsQ0FBQyxhQUFhLENBQUMsT0FBTyxFQUFFLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLGVBQWU7SUFDeEQsR0FBRyxDQUFDLGFBQWEsQ0FBQyxJQUFJLEdBQUcsVUFBVSxFQUFFLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLGVBQWU7SUFDbEUsTUFBTSxDQUFDLEdBQUcsQ0FBQztBQUNiLENBQUM7QUFBQSxDQUFDO0FBRUYsaUJBQWlCLE9BQWdCLEVBQUUsUUFBa0I7SUFDbkQsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEdBQUcsRUFBRSxJQUFJLEtBQUssR0FBRyxJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDbkUsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsQ0FBQztJQUNuQyxNQUFNLEdBQUcsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFD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U0FBUyxDQUFDO1FBQ3JDLElBQUk7S0FDTCxDQUFDLENBQUM7QUFDTCxDQUFDO0FBckJELG9CQXFCQztBQUFBLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge1xuICBTbWFydEN1cnNvcixcbn0gZnJvbSAnLi91dGlscy9zbWFydC1jdXJzb3InO1xuaW1wb3J0IHtcbiAgU2VydmljZSxcbn0gZnJvbSAnLi9jb25zdGFudHMnO1xuaW1wb3J0IHtcbiAgSHBhaSxcbn0gZnJvbSAnLi9pbnRlcmZhY2VzJztcblxuZnVuY3Rpb24gaGVhZGVyKHNlcnZpY2U6IFNlcnZpY2UsIGJvZHlMZW5ndGg6IG51bWJlcikge1xuICBjb25zdCBzaXplID0gMHgwNjtcbiAgY29uc3QgcG9zID0gbmV3IFNtYXJ0Q3Vyc29yKCk7XG4gIGNvbnN0IHJhdyA9IEJ1ZmZlci5hbGxvY1Vuc2FmZShzaXplKTtcbiAgcmF3LndyaXRlVUludDgoc2l6ZSwgcG9zLm5leHQoKSk7IC8vIGhlYWRlciBsZW5ndGhcbiAgcmF3LndyaXRlVUludDgoMHgxMCwgcG9zLm5leHQoKSk7IC8vIHZlcnNpb25cbiAgcmF3LndyaXRlVUludDE2QkUoc2VydmljZSwgcG9zLm5leHQoMikpOyAvLyBzZXJ2aWNlIHR5cGVcbiAgcmF3LndyaXRlVUludDE2QkUoc2l6ZSArIGJvZHlMZW5ndGgsIHBvcy5uZXh0KDIpKTsgLy8gdG90YWwgbGVuZ3RoXG4gIHJldHVybiByYXc7XG59O1xuXG5mdW5jdGlvbiBtZXNzYWdlKHNlcnZpY2U6IFNlcnZpY2UsIGluY2x1ZGVzOiBCdWZmZXJbXSkge1xuICBjb25zdCBzaXplID0gaW5jbHVkZXMucmVkdWNlKChhY2MsIGl0ZW0pID0+IGFjYyArPSBpdGVtLmxlbmd0aCwgMCk7XG4gIGNvbnN0IGhlYWQgPSBoZWFkZXIoc2VydmljZSwgc2l6ZSk7XG4gIGNvbnN0IHJldCA9IEJ1ZmZlci5jb25jYXQoW2hlYWQsIC4uLmluY2x1ZGVzXSk7XG4gIHJldHVybiByZXQ7XG59O1xuXG5mdW5jdGlvbiBocGFpKHByb3RvY29sOiBudW1iZXIsIGlwOiBudW1iZXIsIHBvcnQ6IG51bWJlcikge1xuICBjb25zdCBzaXplID0gMHgwODtcbiAgY29uc3QgcG9zID0gbmV3IFNtYXJ0Q3Vyc29yKCk7XG4gIGNvbnN0IHJhdyA9IEJ1ZmZlci5hbGxvY1Vuc2FmZShzaXplKTtcbiAgcmF3LndyaXRlVUludDgoc2l6ZSwgcG9zLm5leHQoKSk7IC8vIHN0cnVjdHVyZSBsZW5ndGhcbiAgcmF3LndyaXRlVUludDgocHJvdG9jb2wsIHBvcy5uZXh0KCkpOyAvLyBwcm90b2NvbFxuICByYXcud3JpdGVVSW50MzJCRShpcCwgcG9zLm5leHQoNCkpOyAvLyBpcFxuICByYXcud3JpdGVVSW50MTZCRShwb3J0LCBwb3MubmV4dCgyKSk7IC8vIHBvcnRcbiAgcmV0dXJuIHJhdztcbn07XG5cbmZ1bmN0aW9uIHR1bm5lbGluZygpIHtcbiAgY29uc3Qgc2l6ZSA9IDB4MDQ7XG4gIGNvbnN0IHBvcyA9IG5ldyBTbWFydEN1cnNvcigpO1xuICBjb25zdCByYXcgPSBCdWZmZXIuYWxsb2NVbnNhZmUoc2l6ZSk7XG4gIHJhdy53cml0ZVVJbnQ4KHNpemUsIHBvcy5uZXh0KCkpOyAvLyBzdHJ1Y3R1cmUgbGVuZ3RoXG4gIHJhdy53cml0ZVVJbnQ4KDB4MDQsIHBvcy5uZXh0KCkpOyAvLyBUVU5ORUxfQ09OTkVDVElPTlxuICByYXcud3JpdGVVSW50OCgweDAyLCBwb3MubmV4dCgpKTsgLy8gVFVOTkVMX0xJTktMQVlFUlxuICByYXcud3JpdGVVSW50OCgweDAwLCBwb3MubmV4dCgpKTsgLy8gcmVzZXJ2ZWRcbiAgcmV0dXJuIHJhdztcbn07XG5cbmZ1bmN0aW9uIGNoYW5uZWwoY2hhbm5lbElkOiBudW1iZXIpIHtcbiAgY29uc3QgcG9zID0gbmV3IFNtYXJ0Q3Vyc29yKCk7XG4gIGNvbnN0IHJhdyA9IEJ1ZmZlci5hbGxvY1Vuc2FmZSgyKTtcbiAgcmF3LndyaXRlVUludDgoY2hhbm5lbElkLCBwb3MubmV4dCgpKTtcbiAgcmF3LndyaXRlVUludDgoMHgwMCwgcG9zLm5leHQoKSk7IC8vIHJlc2VydmVkXG4gIHJldHVybiByYXc7XG59O1xuXG4vKipcbiAqIENyZWF0ZXMgYnVmZmVyIG9mIHNlcXVlbmNlIGNvdW50ZXIsIGNoYW5uZWwgaWQgYW5kIHN0YXR1cyBjb2RlXG4gKi9cbmZ1bmN0aW9uIHNlcW51bShzZXFuOiBudW1iZXIsIGNoYW5uZWxJZDogbnVtYmVyLCBzdGF0dXMgPSAweDAwKSB7XG4gIGNvbnN0IHNpemUgPSAweDA0O1xuICBjb25zdCBwb3MgPSBuZXcgU21hcnRDdXJzb3IoKTtcbiAgY29uc3QgcmF3ID0gQnVmZmVyLmFsbG9jVW5zYWZlKHNpemUpO1xuICByYXcud3JpdGVVSW50OChzaXplLCBwb3MubmV4dCgpKTsgLy8gc3RydWN0dXJlIGxlbmd0aFxuICByYXcud3JpdGVVSW50OChjaGFubmVsSWQsIHBvcy5uZXh0KCkpOyAvLyBjaGFubmVsSWRcbiAgcmF3LndyaXRlVUludDgoc2VxbiwgcG9zLm5leHQoKSk7IC8vIHNlcXVlbmNlQ291bnRlclxuICByYXcud3JpdGVVSW50OChzdGF0dXMsIHBvcy5uZXh0KCkpOyAvLyByZXNlcnZlZCBvciBzdGF0dXNcbiAgcmV0dXJuIHJhdztcbn07XG5cbmV4cG9ydCBjb25zdCBlbnVtIERhdGFUeXBlIHtcbiAgVWludDggPSAxLFxuICBVaW50MTYgPSAyLFxuICBVaW50MzIgPSA0LFxuICBVaW50NjQgPSA4LFxuICBVaW50MTI4ID0gMTYsXG59XG5cbi8vIHJlYWR5IHRvIHVzZSBtZXNzYWdlc1xuXG5leHBvcnQgZnVuY3Rpb24gYWNrKHNlcW46IG51bWJlciwgY2hhbm5lbElkOiBudW1iZXIsIHN0YXR1czogbnVtYmVyKSB7XG4gIHJldHVybiBtZXNzYWdlKFNlcnZpY2UuVHVubmVsaW5nQWNrLCBbXG4gICAgc2VxbnVtKHNlcW4sIGNoYW5uZWxJZCwgc3RhdHVzKSxcbiAgXSk7XG59O1xuXG5leHBvcnQgZnVuY3Rpb24gZGlzY29ubmVjdChjaGFubmVsSWQ6IG51bWJlciwgcmVzcG9uZFRvOiBIcGFpKSB7XG4gIHJldHVybiBtZXNzYWdlKFNlcnZpY2UuRGlzY29ubmVjdFJlcXVlc3QsIFtcbiAgICBjaGFubmVsKGNoYW5uZWxJZCksXG4gICAgaHBhaShyZXNwb25kVG8ucHJvdG9jb2wsIHJlc3BvbmRUby5pcCwgcmVzcG9uZFRvLnBvcnQpLFxuICBdKTtcbn07XG5cbmV4cG9ydCBmdW5jdGlvbiBwaW5nKGNoYW5uZWxJZDogbnVtYmVyLCByZXNwb25kVG86IEhwYWkpIHtcbiAgcmV0dXJuIG1lc3NhZ2UoU2VydmljZS5Db25uZWN0aW9uU3RhdGVSZXF1ZXN0LCBbXG4gICAgY2hhbm5lbChjaGFubmVsSWQpLFxuICAgIGhwYWkocmVzcG9uZFRvLnByb3RvY29sLCByZXNwb25kVG8uaXAsIHJlc3BvbmRUby5wb3J0KSxcbiAgXSk7XG59O1xuXG5leHBvcnQgZnVuY3Rpb24gb3BlblR1bm5lbCh7IHJlY2VpdmVBdCwgcmVzcG9uZFRvIH06IHtcbiAgcmVzcG9uZFRvOiBIcGFpO1xuICByZWNlaXZlQXQ6IEhwYWk7XG59KSB7XG4gIHJldHVybiBtZXNzYWdlKFNlcnZpY2UuQ29ubmVjdFJlcXVlc3QsIFtcbiAgICBocGFpKHJlc3BvbmRUby5wcm90b2NvbCwgcmVzcG9uZFRvLmlwLCByZXNwb25kVG8ucG9ydCksXG4gICAgaHBhaShyZWNlaXZlQXQucHJvdG9jb2wsIHJlY2VpdmVBdC5pcCwgcmVjZWl2ZUF0LnBvcnQpLFxuICAgIHR1bm5lbGluZygpLFxuICBdKTtcbn07XG5cbmV4cG9ydCBmdW5jdGlvbiB3cml0ZSh7IGRhdGEsIHNlcW4sIGNoYW5uZWxJZCwgc291cmNlLCBkZXN0IH06IHtcbiAgZGF0YTogQnVmZmVyIHwgVWludDhBcnJheSB8IG51bWJlcltdO1xuICBzZXFuOiBudW1iZXI7XG4gIGNoYW5uZWxJZDogbnVtYmVyO1xuICBzb3VyY2U6IG51bWJlcjtcbiAgZGVzdDogbnVtYmVyO1xufSkge1xuICBpZiAoZGF0YS5sZW5ndGggPiBEYXRhVHlwZS5VaW50MTI4KSB7XG4gICAgLy8gaWYgZGF0YSBpcyBsb25nZXIgdGhhbiAxNiBieXRlc1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgIGBEYXRhIGlzIHRvbyBsb25nLCBleHBlY3RlZCBtYXhpbXVtICR7RGF0YVR5cGUuVWludDEyOH0gYnl0ZXMsIGdvdCAke2RhdGEubGVuZ3RofWApO1xuICB9XG4gIC8vIGNlbWlcbiAgY29uc3QgaXNVaW50NiA9IGRhdGEubGVuZ3RoID09PSBEYXRhVHlwZS5VaW50OCAmJiBkYXRhWzBdIDw9IDB4M2Y7XG4gIGNvbnN0IHNpemUgPSBpc1VpbnQ2ID8gRGF0YVR5cGUuVWludDggOiBkYXRhLmxlbmd0aCArIDE7XG4gIGNvbnN0IHBvcyA9IG5ldyBTbWFydEN1cnNvcigpO1xuICBjb25zdCBjZW1pID0gQnVmZmVyLmFsbG9jKDB4MEEgKyBzaXplKTtcbiAgY2VtaS53cml0ZVVJbnQ4KDB4MTEsIHBvcy5uZXh0KCkpOyAvLyBMX0RhdGFfcmVxXG4gIGNlbWkud3JpdGVVSW50OCgweDAwLCBwb3MubmV4dCgpKTsgLy8gYWRkaXRpb25hbCBpbmZvIGxlbmd0aFxuICBjZW1pLndyaXRlVUludDgoMHhiYywgcG9zLm5leHQoKSk7IC8vIGNvbnRyb2wgZmllbGQgMVxuICBjZW1pLndyaXRlVUludDgoMHhlMCwgcG9zLm5leHQoKSk7IC8vIGNvbnRyb2wgZmllbGQgMlxuICBjZW1pLndyaXRlVUludDE2QkUoc291cmNlLCBwb3MubmV4dCgyKSk7IC8vIHNvdXJjZSBhZGRyZXNzIDAuMC4wXG4gIGNlbWkud3JpdGVVSW50MTZCRShkZXN0LCBwb3MubmV4dCgyKSk7IC8vIGRlc3RpbmF0aW9uIGFkZHJlc3NcbiAgaWYgKGlzVWludDYpIHtcbiAgICAvLyBkYXRhIGNhbiBiZSBtZXJnZWRcbiAgICBjZW1pLndyaXRlVUludDgoc2l6ZSwgcG9zLm5leHQoKSk7IC8vIHBheWxvYWQgbGVuZ3RoXG4gICAgY2VtaS53cml0ZVVJbnQxNkJFKGRhdGFbMF0gfCAweDgwLCBwb3MubmV4dCgyKSk7IC8vIDB4ODAgR1JPVVBWQUxVRV9XUklURVxuICB9IGVsc2Uge1xuICAgIC8vIGRhdGEgbXVzdCBiZSBhcHBlbmRlZCBhdCB0aGUgZW5kXG4gICAgY2VtaS53cml0ZVVJbnQ4KHNpemUsIHBvcy5uZXh0KCkpOyAvLyBwYXlsb2FkIGxlbmd0aFxuICAgIGNlbWkud3JpdGVVSW50MTZCRSgweDgwLCBwb3MubmV4dCgyKSk7IC8vIGFwY2kgMHg4MCBHUk9VUFZBTFVFX1dSSVRFXG4gICAgY2VtaS5zZXQoZGF0YSwgcG9zLm5leHQoc2l6ZSkpO1xuICB9XG4gIHJldHVybiBtZXNzYWdlKFNlcnZpY2UuVHVubmVsaW5nUmVxdWVzdCwgW1xuICAgIHNlcW51bShzZXFuLCBjaGFubmVsSWQpLFxuICAgIGNlbWksXG4gIF0pO1xufTtcblxuZXhwb3J0IGZ1bmN0aW9uIHJlYWQocGFyYW1zOiB7XG4gIHNlcW46IG51bWJlcjtcbiAgY2hhbm5lbElkOiBudW1iZXI7XG4gIHNvdXJjZTogbnVtYmVyO1xuICBkZXN0OiBudW1iZXI7XG59KSB7XG4gIC8vIGNlbWlcbiAgY29uc3QgcG9zID0gbmV3IFNtYXJ0Q3Vyc29yKCk7XG4gIGNvbnN0IGNlbWkgPSBCdWZmZXIuYWxsb2MoMHgwQik7XG4gIGNlbWkud3JpdGVVSW50OCgweDExLCBwb3MubmV4dCgpKTsgLy8gTF9EYXRhX3JlcVxuICBjZW1pLndyaXRlVUludDgoMHgwMCwgcG9zLm5leHQoKSk7IC8vIGFkZGl0aW9uYWwgaW5mbyBsZW5ndGhcbiAgY2VtaS53cml0ZVVJbnQ4KDB4YmMsIHBvcy5uZXh0KCkpOyAvLyBjb250cm9sIGZpZWxkIDFcbiAgY2VtaS53cml0ZVVJbnQ4KDB4ZTAsIHBvcy5uZXh0KCkpOyAvLyBjb250cm9sIGZpZWxkIDJcbiAgY2VtaS53cml0ZVVJbnQxNkJFKHBhcmFtcy5zb3VyY2UsIHBvcy5uZXh0KDIpKTsgLy8gc291cmNlIGFkZHJlc3MgMC4wLjBcbiAgY2VtaS53cml0ZVVJbnQxNkJFKHBhcmFtcy5kZXN0LCBwb3MubmV4dCgyKSk7IC8vIGRlc3RpbmF0aW9uIGFkZHJlc3NcbiAgY2VtaS53cml0ZVVJbnQ4KDB4MDEsIHBvcy5uZXh0KCkpOyAvLyBwYXlsb2FkIGxlbmd0aFxuICBjZW1pLndyaXRlVUludDE2QkUoMHgwMCwgcG9zLm5leHQoMikpOyAvLyAweDAwIEdST1VQVkFMVUVfUkVBRFxuICByZXR1cm4gbWVzc2FnZShTZXJ2aWNlLlR1bm5lbGluZ1JlcXVlc3QsIFtcbiAgICBzZXFudW0ocGFyYW1zLnNlcW4sIHBhcmFtcy5jaGFubmVsSWQpLFxuICAgIGNlbWksXG4gIF0pO1xufTtcbiJdfQ== -------------------------------------------------------------------------------- /dist/utils/async-socket.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { RemoteInfo } from 'dgram'; 3 | import { EventEmitter } from 'events'; 4 | import { Subscriber } from '../interfaces'; 5 | /** 6 | * Simple promisable udp socket 7 | */ 8 | export declare class AsyncSocket { 9 | private socket; 10 | protected events: EventEmitter; 11 | isConnected(): boolean; 12 | connect(port?: number): Promise; 13 | complete(cb?: () => T): Promise; 14 | disconnect(cb?: () => T): Promise; 15 | send(host: string, port: number, data: any): Promise; 16 | on(event: 'disconnect', cb: () => void): Subscriber; 17 | on(event: 'raw', cb: (raw: Buffer, sender: RemoteInfo) => void): Subscriber; 18 | on(event: string, cb: (query: T, sender: RemoteInfo) => void): Subscriber; 19 | } 20 | -------------------------------------------------------------------------------- /dist/utils/async-socket.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const dgram_1 = require("dgram"); 3 | const events_1 = require("events"); 4 | /** 5 | * Simple promisable udp socket 6 | */ 7 | class AsyncSocket { 8 | constructor() { 9 | this.events = new events_1.EventEmitter(); 10 | } 11 | isConnected() { 12 | return this.socket ? true : false; 13 | } 14 | connect(port = 0 /* OS assigned port */) { 15 | return new Promise((resolve, reject) => { 16 | if (this.isConnected()) { 17 | resolve(this.socket.address()); 18 | } 19 | else { 20 | this.socket = dgram_1.createSocket('udp4') 21 | .on('message', (raw, remote) => { 22 | this.events.emit('raw', raw, remote); 23 | }) 24 | .once('close', () => { 25 | this.socket = undefined; 26 | // emit disconnect event 27 | this.events.emit('disconnect'); 28 | // and remove all listeners to prevent any memory leak 29 | this.events.removeAllListeners(); 30 | }) 31 | .once('error', (err) => { 32 | reject(err); 33 | }) 34 | .once('listening', () => { 35 | resolve(this.socket.address()); 36 | }); 37 | this.socket.bind(port); 38 | } 39 | }); 40 | } 41 | complete(cb) { 42 | return new Promise((resolve) => { 43 | if (this.isConnected()) { 44 | this.socket.once('close', () => { 45 | resolve(typeof cb === 'function' ? cb() : undefined); 46 | }); 47 | } 48 | else { 49 | resolve(typeof cb === 'function' ? cb() : undefined); 50 | } 51 | }); 52 | } 53 | disconnect(cb) { 54 | return new Promise((resolve) => { 55 | if (this.isConnected()) { 56 | this.socket.once('close', () => { 57 | resolve(typeof cb === 'function' ? cb() : undefined); 58 | }); 59 | this.socket.close(); 60 | } 61 | else { 62 | resolve(typeof cb === 'function' ? cb() : undefined); 63 | } 64 | }); 65 | } 66 | send(host, port, data) { 67 | return new Promise((resolve, reject) => { 68 | if (this.isConnected()) { 69 | this.socket.send(data, port, host, (err, bytes) => { 70 | if (err) { 71 | reject(err); 72 | } 73 | if (bytes !== data.length) { 74 | reject(new Error(`Expected to send ${data.length} bytes, but sent ${bytes}`)); 75 | } 76 | resolve(); 77 | }); 78 | } 79 | else { 80 | throw new Error(`No connection`); 81 | } 82 | }); 83 | } 84 | on(event, cb) { 85 | if (this.events.on(event, cb)) { 86 | return { 87 | unsubscribe: () => this.events.removeListener(event, cb), 88 | }; 89 | } 90 | else { 91 | throw new Error(`Failed to subscribe`); 92 | } 93 | } 94 | } 95 | exports.AsyncSocket = AsyncSocket; 96 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXN5bmMtc29ja2V0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL2FzeW5jLXNvY2tldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsaUNBSWU7QUFDZixtQ0FFZ0I7QUFLaEI7O0dBRUc7QUFDSDtJQUFBO1FBRVksV0FBTSxHQUFpQixJQUFJLHFCQUFZLEVBQUUsQ0FBQztJQWtGdEQsQ0FBQztJQWpGQyxXQUFXO1FBQ1QsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxHQUFHLEtBQUssQ0FBQztJQUNwQyxDQUFDO0lBQ0QsT0FBTyxDQUFDLE9BQWUsQ0FBQyxDQUFDLHNCQUFzQjtRQUM3QyxNQUFNLENBQUMsSUFBSSxPQUFPLENBQWEsQ0FBQyxPQUFPLEVBQUUsTUFBTTtZQUM3QyxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUMsQ0FBQyxDQUFDO2dCQUN2QixPQUFPLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1lBQ2pDLENBQUM7WUFBQyxJQUFJLENBQUMsQ0FBQztnQkFDTixJQUFJLENBQUMsTUFBTSxHQUFHLG9CQUFZLENBQUMsTUFBTSxDQUFDO3FCQUMvQixFQUFFLENBQUMsU0FBUyxFQUFFLENBQUMsR0FBVyxFQUFFLE1BQU07b0JBQ2pDLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssRUFBRSxHQUFHLEVBQUUsTUFBTSxDQUFDLENBQUM7Z0JBQ3ZDLENBQUMsQ0FBQztxQkFDRCxJQUFJLENBQUMsT0FBTyxFQUFFO29CQUNiLElBQUksQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDO29CQUN4Qix3QkFBd0I7b0JBQ3hCLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDO29CQUMvQixzREFBc0Q7b0JBQ3RELElBQUksQ0FBQyxNQUFNLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztnQkFD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QzVDLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDOUIsTUFBTSxDQUFDO2dCQUNMLFdBQVcsRUFBRSxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsY0FBYyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUM7YUFDekQsQ0FBQztRQUNKLENBQUM7UUFBQyxJQUFJLENBQUMsQ0FBQztZQUNOLE1BQU0sSUFBSSxLQUFLLENBQUMscUJBQXFCLENBQUMsQ0FBQztRQUN6QyxDQUFDO0lBQ0gsQ0FBQztDQUNGO0FBcEZELGtDQW9GQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIGNyZWF0ZVNvY2tldCxcbiAgU29ja2V0LFxuICBSZW1vdGVJbmZvLFxufSBmcm9tICdkZ3JhbSc7XG5pbXBvcnQge1xuICBFdmVudEVtaXR0ZXIsXG59IGZyb20gJ2V2ZW50cyc7XG5pbXBvcnQge1xuICBTdWJzY3JpYmVyLFxufSBmcm9tICcuLi9pbnRlcmZhY2VzJztcblxuLyoqXG4gKiBTaW1wbGUgcHJvbWlzYWJsZSB1ZHAgc29ja2V0XG4gKi9cbmV4cG9ydCBjbGFzcyBBc3luY1NvY2tldCB7XG4gIHByaXZhdGUgc29ja2V0OiBTb2NrZXQ7XG4gIHByb3RlY3RlZCBldmVudHM6IEV2ZW50RW1pdHRlciA9IG5ldyBFdmVudEVtaXR0ZXIoKTtcbiAgaXNDb25uZWN0ZWQoKSB7XG4gICAgcmV0dXJuIHRoaXMuc29ja2V0ID8gdHJ1ZSA6IGZhbHNlO1xuICB9XG4gIGNvbm5lY3QocG9ydDogbnVtYmVyID0gMCAvKiBPUyBhc3NpZ25lZCBwb3J0ICovKSB7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlPFJlbW90ZUluZm8+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgIGlmICh0aGlzLmlzQ29ubmVjdGVkKCkpIHtcbiAgICAgICAgcmVzb2x2ZSh0aGlzLnNvY2tldC5hZGRyZXNzKCkpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5zb2NrZXQgPSBjcmVhdGVTb2NrZXQoJ3VkcDQnKVxuICAgICAgICAgIC5vbignbWVzc2FnZScsIChyYXc6IEJ1ZmZlciwgcmVtb3RlKSA9PiB7XG4gICAgICAgICAgICB0aGlzLmV2ZW50cy5lbWl0KCdyYXcnLCByYXcsIHJlbW90ZSk7XG4gICAgICAgICAgfSlcbiAgICAgICAgICAub25jZSgnY2xvc2UnLCAoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLnNvY2tldCA9IHVuZGVmaW5lZDtcbiAgICAgICAgICAgIC8vIGVtaXQgZGlzY29ubmVjdCBldmVudFxuICAgICAgICAgICAgdGhpcy5ldmVudHMuZW1pdCgnZGlzY29ubmVjdCcpO1xuICAgICAgICAgICAgLy8gYW5kIHJlbW92ZSBhbGwgbGlzdGVuZXJzIHRvIHByZXZlbnQgYW55IG1lbW9yeSBsZWFrXG4gICAgICAgICAgICB0aGlzLmV2ZW50cy5yZW1vdmVBbGxMaXN0ZW5lcnMoKTtcbiAgICAgICAgICB9KVxuICAgICAgICAgIC5vbmNlKCdlcnJvcicsIChlcnIpID0+IHtcbiAgICAgICAgICAgIHJlamVjdChlcnIpO1xuICAgICAgICAgIH0pXG4gICAgICAgICAgLm9uY2UoJ2xpc3RlbmluZycsICgpID0+IHtcbiAgICAgICAgICAgIHJlc29sdmUodGhpcy5zb2NrZXQuYWRkcmVzcygpKTtcbiAgICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5zb2NrZXQuYmluZChwb3J0KTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfVxuICBjb21wbGV0ZTxUPihjYj86ICgpID0+IFQpIHtcbiAgICByZXR1cm4gbmV3IFByb21pc2U8VD4oKHJlc29sdmUpID0+IHtcbiAgICAgIGlmICh0aGlzLmlzQ29ubmVjdGVkKCkpIHtcbiAgICAgICAgdGhpcy5zb2NrZXQub25jZSgnY2xvc2UnLCAoKSA9PiB7XG4gICAgICAgICAgcmVzb2x2ZSh0eXBlb2YgY2IgPT09ICdmdW5jdGlvbicgPyBjYigpIDogdW5kZWZpbmVkKTtcbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXNvbHZlKHR5cGVvZiBjYiA9PT0gJ2Z1bmN0aW9uJyA/IGNiKCkgOiB1bmRlZmluZWQpO1xuICAgICAgfVxuICAgIH0pO1xuICB9XG4gIGRpc2Nvbm5lY3Q8VD4oY2I/OiAoKSA9PiBUKSB7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlPFQ+KChyZXNvbHZlKSA9PiB7XG4gICAgICBpZiAodGhpcy5pc0Nvbm5lY3RlZCgpKSB7XG4gICAgICAgIHRoaXMuc29ja2V0Lm9uY2UoJ2Nsb3NlJywgKCkgPT4ge1xuICAgICAgICAgIHJlc29sdmUodHlwZW9mIGNiID09PSAnZnVuY3Rpb24nID8gY2IoKSA6IHVuZGVmaW5lZCk7XG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLnNvY2tldC5jbG9zZSgpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgcmVzb2x2ZSh0eXBlb2YgY2IgPT09ICdmdW5jdGlvbicgPyBjYigpIDogdW5kZWZpbmVkKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfVxuICBzZW5kKGhvc3Q6IHN0cmluZywgcG9ydDogbnVtYmVyLCBkYXRhKSB7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlPHZvaWQ+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgIGlmICh0aGlzLmlzQ29ubmVjdGVkKCkpIHtcbiAgICAgICAgdGhpcy5zb2NrZXQuc2VuZChkYXRhLCBwb3J0LCBob3N0LCAoZXJyLCBieXRlcykgPT4ge1xuICAgICAgICAgIGlmIChlcnIpIHtcbiAgICAgICAgICAgIHJlamVjdChlcnIpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBpZiAoYnl0ZXMgIT09IGRhdGEubGVuZ3RoKSB7XG4gICAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBFeHBlY3RlZCB0byBzZW5kICR7ZGF0YS5sZW5ndGh9IGJ5dGVzLCBidXQgc2VudCAke2J5dGVzfWApKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICB9KTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgTm8gY29ubmVjdGlvbmApO1xuICAgICAgfVxuICAgIH0pO1xuICB9XG4gIG9uKGV2ZW50OiAnZGlzY29ubmVjdCcsIGNiOiAoKSA9PiB2b2lkKTogU3Vic2NyaWJlcjtcbiAgb24oZXZlbnQ6ICdyYXcnLCBjYjogKHJhdzogQnVmZmVyLCBzZW5kZXI6IFJlbW90ZUluZm8pID0+IHZvaWQpOiBTdWJzY3JpYmVyO1xuICBvbjxUPihldmVudDogc3RyaW5nLCBjYjogKHF1ZXJ5OiBULCBzZW5kZXI6IFJlbW90ZUluZm8pID0+IHZvaWQpOiBTdWJzY3JpYmVyO1xuICBvbihldmVudDogc3RyaW5nLCBjYjogKC4uLmFyZ3M6IGFueVtdKSA9PiB2b2lkKTogU3Vic2NyaWJlciB7XG4gICAgaWYgKHRoaXMuZXZlbnRzLm9uKGV2ZW50LCBjYikpIHtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHVuc3Vic2NyaWJlOiAoKSA9PiB0aGlzLmV2ZW50cy5yZW1vdmVMaXN0ZW5lcihldmVudCwgY2IpLFxuICAgICAgfTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBGYWlsZWQgdG8gc3Vic2NyaWJlYCk7XG4gICAgfVxuICB9XG59XG4iXX0= -------------------------------------------------------------------------------- /dist/utils/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare function ip2num(ipString: string): number; 2 | export declare function num2ip(ipNumber: number): string; 3 | export declare function num2mac(macNumber: number): string; 4 | export declare function mac2num(macString: string): number; 5 | export declare function getCurrentIp(): string; 6 | export declare function sizeOf(value: number): number; 7 | export declare function knxAddr2num(addrStr: string): number; 8 | export declare function num2knxAddr(addrNum: number, isGroupAddr?: boolean): string; 9 | export declare function removeNonPrintable(str: string): string; 10 | export declare function noop(..._: any[]): any; 11 | export declare function isIPv4(ipStr: string): boolean; 12 | export declare function isKnxAddress(knxStrAddr: string, isGroupAddress?: boolean): boolean; 13 | -------------------------------------------------------------------------------- /dist/utils/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const os_1 = require("os"); 3 | function ip2num(ipString) { 4 | const ipNumber = ipString.split('.'); 5 | return ((((((+ipNumber[0]) * 256) + (+ipNumber[1])) * 256) + (+ipNumber[2])) * 256) + (+ipNumber[3]); 6 | } 7 | exports.ip2num = ip2num; 8 | function num2ip(ipNumber) { 9 | let ipString = (ipNumber % 256).toString(); 10 | for (let i = 3; i > 0; i--) { 11 | ipNumber = Math.floor(ipNumber / 256); 12 | ipString = ipNumber % 256 + '.' + ipString; 13 | } 14 | return ipString; 15 | } 16 | exports.num2ip = num2ip; 17 | function num2mac(macNumber) { 18 | return String(1e12 + (macNumber) 19 | .toString(16)).slice(-12).match(/.{1,2}/g).join(':'); 20 | } 21 | exports.num2mac = num2mac; 22 | function mac2num(macString) { 23 | return parseInt(macString.split(':').join(''), 16); 24 | } 25 | exports.mac2num = mac2num; 26 | function getCurrentIp() { 27 | const ifaces = os_1.networkInterfaces(); 28 | for (const dev in ifaces) { 29 | for (const details of ifaces[dev]) { 30 | if (details.family === 'IPv4' && details.internal === false) { 31 | return details.address; 32 | } 33 | } 34 | } 35 | throw new Error('Failed to get current ip'); 36 | } 37 | exports.getCurrentIp = getCurrentIp; 38 | function sizeOf(value) { 39 | return Math.ceil(Math.log2(value + 1) / 4) || 1; 40 | } 41 | exports.sizeOf = sizeOf; 42 | function knxAddr2num(addrStr) { 43 | const m = addrStr.split(/[\.\/]/); 44 | if (m && m.length > 0) { 45 | return (((+m[0]) & 0x01f) << 11) + (((+m[1]) & 0x07) << 8) + ((+m[2]) & 0xff); 46 | } 47 | throw Error(`Could not encode ${addrStr} address`); 48 | } 49 | exports.knxAddr2num = knxAddr2num; 50 | function num2knxAddr(addrNum, isGroupAddr = true) { 51 | return [ 52 | (addrNum >> 11) & 0xf, 53 | isGroupAddr ? (addrNum >> 8) & 0x7 : (addrNum >> 8) & 0xf, 54 | (addrNum & 0xff) 55 | ].join(isGroupAddr ? '/' : '.'); 56 | } 57 | exports.num2knxAddr = num2knxAddr; 58 | function removeNonPrintable(str) { 59 | return str.replace(/[^\x20-\x7E]+/g, ''); 60 | } 61 | exports.removeNonPrintable = removeNonPrintable; 62 | function noop(..._) { } 63 | exports.noop = noop; 64 | function isIPv4(ipStr) { 65 | return /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/ 66 | .test(ipStr); 67 | } 68 | exports.isIPv4 = isIPv4; 69 | function isKnxAddress(knxStrAddr, isGroupAddress = true) { 70 | if (isGroupAddress) { 71 | // group address 1/1/1 72 | return /^(3[01]|([0-2]?[0-9]))\/(([0-7]\/(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(([01]?\d{1,3})|(20[0-4][0-7])))$/ 73 | .test(knxStrAddr); 74 | } 75 | else { 76 | // individual address 1.1.15 77 | return /^([01]?\d)\.([01]?\d)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])?$/.test(knxStrAddr); 78 | } 79 | } 80 | exports.isKnxAddress = isKnxAddress; 81 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLDJCQUF1QztBQUV2QyxnQkFBdUIsUUFBZ0I7SUFDckMsTUFBTSxRQUFRLEdBQUcsUUFBUSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNyQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDdkcsQ0FBQztBQUhELHdCQUdDO0FBRUQsZ0JBQXVCLFFBQWdCO0lBQ3JDLElBQUksUUFBUSxHQUFHLENBQUMsUUFBUSxHQUFHLEdBQUcsQ0FBQyxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQzNDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUM7UUFDM0IsUUFBUSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsUUFBUSxHQUFHLEdBQUcsQ0FBQyxDQUFDO1FBQ3RDLFFBQVEsR0FBRyxRQUFRLEdBQUcsR0FBRyxHQUFHLEdBQUcsR0FBRyxRQUFRLENBQUM7SUFDN0MsQ0FBQztJQUNELE1BQU0sQ0FBQyxRQUFRLENBQUM7QUFDbEIsQ0FBQztBQVBELHdCQU9DO0FBRUQsaUJBQXdCLFNBQWlCO0lBQ3ZDLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxHQUFHLENBQUMsU0FBUyxDQUFDO1NBQzdCLFFBQVEs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RCxzQkFBNkIsVUFBa0IsRUFBRSxjQUFjLEdBQUcsSUFBSTtJQUNwRSxFQUFFLENBQUMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDO1FBQ25CLHNCQUFzQjtRQUN0QixNQUFNLENBQUMsNEdBQTRHO2FBQ2hILElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUN0QixDQUFDO0lBQUMsSUFBSSxDQUFDLENBQUM7UUFDTiw0QkFBNEI7UUFDNUIsTUFBTSxDQUFDLGlFQUFpRSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUM1RixDQUFDO0FBQ0gsQ0FBQztBQVRELG9DQVNDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgbmV0d29ya0ludGVyZmFjZXMgfSBmcm9tICdvcyc7XG5cbmV4cG9ydCBmdW5jdGlvbiBpcDJudW0oaXBTdHJpbmc6IHN0cmluZykge1xuICBjb25zdCBpcE51bWJlciA9IGlwU3RyaW5nLnNwbGl0KCcuJyk7XG4gIHJldHVybiAoKCgoKCgraXBOdW1iZXJbMF0pICogMjU2KSArICgraXBOdW1iZXJbMV0pKSAqIDI1NikgKyAoK2lwTnVtYmVyWzJdKSkgKiAyNTYpICsgKCtpcE51bWJlclszXSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBudW0yaXAoaXBOdW1iZXI6IG51bWJlcikge1xuICBsZXQgaXBTdHJpbmcgPSAoaXBOdW1iZXIgJSAyNTYpLnRvU3RyaW5nKCk7XG4gIGZvciAobGV0IGkgPSAzOyBpID4gMDsgaS0tKSB7XG4gICAgaXBOdW1iZXIgPSBNYXRoLmZsb29yKGlwTnVtYmVyIC8gMjU2KTtcbiAgICBpcFN0cmluZyA9IGlwTnVtYmVyICUgMjU2ICsgJy4nICsgaXBTdHJpbmc7XG4gIH1cbiAgcmV0dXJuIGlwU3RyaW5nO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gbnVtMm1hYyhtYWNOdW1iZXI6IG51bWJlcikge1xuICByZXR1cm4gU3RyaW5nKDFlMTIgKyAobWFjTnVtYmVyKVxuICAgIC50b1N0cmluZygxNikpLnNsaWNlKC0xMikubWF0Y2goLy57MSwyfS9nKS5qb2luKCc6Jyk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBtYWMybnVtKG1hY1N0cmluZzogc3RyaW5nKSB7XG4gIHJldHVybiBwYXJzZUludChtYWNTdHJpbmcuc3BsaXQoJzonKS5qb2luKCcnKSwgMTYpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0Q3VycmVudElwKCkge1xuICBjb25zdCBpZmFjZXMgPSBuZXR3b3JrSW50ZXJmYWNlcygpO1xuICBmb3IgKGNvbnN0IGRldiBpbiBpZmFjZXMpIHtcbiAgICBmb3IgKGNvbnN0IGRldGFpbHMgb2YgaWZhY2VzW2Rldl0pIHtcbiAgICAgIGlmIChkZXRhaWxzLmZhbWlseSA9PT0gJ0lQdjQnICYmIGRldGFpbHMuaW50ZXJuYWwgPT09IGZhbHNlKSB7XG4gICAgICAgIHJldHVybiBkZXRhaWxzLmFkZHJlc3M7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHRocm93IG5ldyBFcnJvcignRmFpbGVkIHRvIGdldCBjdXJyZW50IGlwJyk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBzaXplT2YodmFsdWU6IG51bWJlcikge1xuICByZXR1cm4gTWF0aC5jZWlsKE1hdGgubG9nMih2YWx1ZSArIDEpIC8gNCkgfHwgMTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGtueEFkZHIybnVtKGFkZHJTdHI6IHN0cmluZykge1xuICBjb25zdCBtID0gYWRkclN0ci5zcGxpdCgvW1xcLlxcL10vKTtcbiAgaWYgKG0gJiYgbS5sZW5ndGggPiAwKSB7XG4gICAgcmV0dXJuICgoKCttWzBdKSAmIDB4MDFmKSA8PCAxMSkgKyAoKCgrbVsxXSkgJiAweDA3KSA8PCA4KSArICgoK21bMl0pICYgMHhmZik7XG4gIH1cbiAgdGhyb3cgRXJyb3IoYENvdWxkIG5vdCBlbmNvZGUgJHthZGRyU3RyfSBhZGRyZXNzYCk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBudW0ya254QWRkcihhZGRyTnVtOiBudW1iZXIsIGlzR3JvdXBBZGRyID0gdHJ1ZSkge1xuICByZXR1cm4gW1xuICAgIChhZGRyTnVtID4+IDExKSAmIDB4ZixcbiAgICBpc0dyb3VwQWRkciA/IChhZGRyTnVtID4+IDgpICYgMHg3IDogKGFkZHJOdW0gPj4gOCkgJiAweGYsXG4gICAgKGFkZHJOdW0gJiAweGZmKV0uam9pbihpc0dyb3VwQWRkciA/ICcvJyA6ICcuJyk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiByZW1vdmVOb25QcmludGFibGUoc3RyOiBzdHJpbmcpIHtcbiAgcmV0dXJuIHN0ci5yZXBsYWNlKC9bXlxceDIwLVxceDdFXSsvZywgJycpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gbm9vcCguLi5fOiBhbnlbXSk6IGFueSB7IH1cblxuZXhwb3J0IGZ1bmN0aW9uIGlzSVB2NChpcFN0cjogc3RyaW5nKSB7XG4gIHJldHVybiAvKDI1WzAtNV18MlswLTRdWzAtOV18WzAxXT9bMC05XVswLTldPylcXC4oMjVbMC01XXwyWzAtNF1bMC05XXxbMDFdP1swLTldWzAtOV0/KVxcLigyNVswLTVdfDJbMC00XVswLTldfFswMV0/WzAtOV1bMC05XT8pXFwuKDI1WzAtNV18MlswLTRdWzAtOV18WzAxXT9bMC05XVswLTldPykvXG4gICAgLnRlc3QoaXBTdHIpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gaXNLbnhBZGRyZXNzKGtueFN0ckFkZHI6IHN0cmluZywgaXNHcm91cEFkZHJlc3MgPSB0cnVlKSB7XG4gIGlmIChpc0dyb3VwQWRkcmVzcykge1xuICAgIC8vIGdyb3VwIGFkZHJlc3MgMS8xLzFcbiAgICByZXR1cm4gL14oM1swMV18KFswLTJdP1swLTldKSlcXC8oKFswLTddXFwvKDI1WzAtNV18MlswLTRdWzAtOV18WzAxXT9bMC05XVswLTldPykpfCgoWzAxXT9cXGR7MSwzfSl8KDIwWzAtNF1bMC03XSkpKSQvXG4gICAgICAudGVzdChrbnhTdHJBZGRyKTtcbiAgfSBlbHNlIHtcbiAgICAvLyBpbmRpdmlkdWFsIGFkZHJlc3MgMS4xLjE1XG4gICAgcmV0dXJuIC9eKFswMV0/XFxkKVxcLihbMDFdP1xcZClcXC4oMjVbMC01XXwyWzAtNF1bMC05XXxbMDFdP1swLTldP1swLTldKT8kLy50ZXN0KGtueFN0ckFkZHIpO1xuICB9XG59XG4iXX0= -------------------------------------------------------------------------------- /dist/utils/smart-cursor.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Class helper acts as iterrator to access buffer 3 | */ 4 | export declare class SmartCursor { 5 | private cursor; 6 | private memorizedCursor; 7 | constructor(i?: number); 8 | jump(i: number): this; 9 | skip(_field?: string, nextPos?: number): this; 10 | diff(sync?: boolean): number; 11 | memorize(): this; 12 | next(nextPos?: number): number; 13 | readonly memorized: number; 14 | readonly cur: number; 15 | } 16 | -------------------------------------------------------------------------------- /dist/utils/smart-cursor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function check(i) { 3 | if (i < 0) { 4 | throw Error('Index should not be zero or negative number'); 5 | } 6 | } 7 | /** 8 | * Class helper acts as iterrator to access buffer 9 | */ 10 | class SmartCursor { 11 | constructor(i = 0) { 12 | this.cursor = i; 13 | this.memorizedCursor = i; 14 | } 15 | jump(i) { 16 | check(i); 17 | this.cursor = i; 18 | this.memorizedCursor = i; 19 | return this; 20 | } 21 | skip(_field, nextPos = 1) { 22 | this.next(nextPos); 23 | return this; 24 | } 25 | diff(sync = true) { 26 | const memorized = this.cursor - this.memorizedCursor; 27 | if (sync) { 28 | this.jump(this.cursor); 29 | } 30 | return memorized; 31 | } 32 | memorize() { 33 | this.memorizedCursor = this.cursor; 34 | return this; 35 | } 36 | next(nextPos = 1) { 37 | check(nextPos); 38 | const clone = this.cursor; 39 | this.cursor += nextPos; 40 | return clone; 41 | } 42 | get memorized() { 43 | return this.memorizedCursor; 44 | } 45 | get cur() { 46 | return this.cursor; 47 | } 48 | } 49 | exports.SmartCursor = SmartCursor; 50 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnQtY3Vyc29yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL3NtYXJ0LWN1cnNvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsZUFBZSxDQUFTO0lBQ3RCLEVBQUUsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ1YsTUFBTSxLQUFLLENBQUMsNkNBQTZDLENBQUMsQ0FBQztJQUM3RCxDQUFDO0FBQ0gsQ0FBQztBQUVEOztHQUVHO0FBQ0g7SUFHRSxZQUFZLElBQVksQ0FBQztRQUN2QixJQUFJLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQztRQUNoQixJQUFJLENBQUMsZUFBZSxHQUFHLENBQUMsQ0FBQztJQUMzQixDQUFDO0lBQ00sSUFBSSxDQUFDLENBQVM7UUFDbkIsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ1QsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUM7UUFDaEIsSUFBSSxDQUFDLGVBQWUsR0FBRyxDQUFDLENBQUM7UUFDekIsTUFBTSxDQUFDLElBQUksQ0FBQztJQUNkLENBQUM7SUFDTSxJQUFJLENBQUMsTUFBZSxFQUFFLE9BQU8sR0FBRyxDQUFDO1FBQ3RDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDbkIsTUFBTSxDQUFDLElBQUksQ0FBQztJQUNkLENBQUM7SUFDTSxJQUFJLENBQUMsSUFBSSxHQUFHLElBQUk7UUFDckIsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsZUFBZSxDQUFDO1FBQ3JELEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7WUFDVCxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUN6QixDQUFDO1FBQ0QsTUFBTSxDQUFDLFNBQVMsQ0FBQztJQUNuQixDQUFDO0lBQ00sUUFBUTtRQUNiLElBQUksQ0FBQyxlQUFlLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQztRQUNuQyxNQUFNLENBQUMsSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUNNLElBQUksQ0FBQyxPQUFPLEdBQUcsQ0FBQztRQUNyQixLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDZixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQzFCLElBQUksQ0FBQyxNQUFNLElBQUksT0FBTyxDQUFDO1FBQ3ZCLE1BQU0sQ0FBQyxLQUFLLENBQUM7SUFDZixDQUFDO0lBQ0QsSUFBSSxTQUFTO1FBQ1gsTUFBTSxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUM7SUFDOUIsQ0FBQztJQUNELElBQUksR0FBRztRQUNMLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDO0lBQ3JCLENBQUM7Q0FDRjtBQXhDRCxrQ0F3Q0MiLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBjaGVjayhpOiBudW1iZXIpIHtcbiAgaWYgKGkgPCAwKSB7XG4gICAgdGhyb3cgRXJyb3IoJ0luZGV4IHNob3VsZCBub3QgYmUgemVybyBvciBuZWdhdGl2ZSBudW1iZXInKTtcbiAgfVxufVxuXG4vKipcbiAqIENsYXNzIGhlbHBlciBhY3RzIGFzIGl0ZXJyYXRvciB0byBhY2Nlc3MgYnVmZmVyXG4gKi9cbmV4cG9ydCBjbGFzcyBTbWFydEN1cnNvciB7XG4gIHByaXZhdGUgY3Vyc29yOiBudW1iZXI7XG4gIHByaXZhdGUgbWVtb3JpemVkQ3Vyc29yOiBudW1iZXI7XG4gIGNvbnN0cnVjdG9yKGk6IG51bWJlciA9IDApIHtcbiAgICB0aGlzLmN1cnNvciA9IGk7XG4gICAgdGhpcy5tZW1vcml6ZWRDdXJzb3IgPSBpO1xuICB9XG4gIHB1YmxpYyBqdW1wKGk6IG51bWJlcikge1xuICAgIGNoZWNrKGkpO1xuICAgIHRoaXMuY3Vyc29yID0gaTtcbiAgICB0aGlzLm1lbW9yaXplZEN1cnNvciA9IGk7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH1cbiAgcHVibGljIHNraXAoX2ZpZWxkPzogc3RyaW5nLCBuZXh0UG9zID0gMSkge1xuICAgIHRoaXMubmV4dChuZXh0UG9zKTtcbiAgICByZXR1cm4gdGhpcztcbiAgfVxuICBwdWJsaWMgZGlmZihzeW5jID0gdHJ1ZSkge1xuICAgIGNvbnN0IG1lbW9yaXplZCA9IHRoaXMuY3Vyc29yIC0gdGhpcy5tZW1vcml6ZWRDdXJzb3I7XG4gICAgaWYgKHN5bmMpIHtcbiAgICAgIHRoaXMuanVtcCh0aGlzLmN1cnNvcik7XG4gICAgfVxuICAgIHJldHVybiBtZW1vcml6ZWQ7XG4gIH1cbiAgcHVibGljIG1lbW9yaXplKCkge1xuICAgIHRoaXMubWVtb3JpemVkQ3Vyc29yID0gdGhpcy5jdXJzb3I7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH1cbiAgcHVibGljIG5leHQobmV4dFBvcyA9IDEpIHtcbiAgICBjaGVjayhuZXh0UG9zKTtcbiAgICBjb25zdCBjbG9uZSA9IHRoaXMuY3Vyc29yO1xuICAgIHRoaXMuY3Vyc29yICs9IG5leHRQb3M7XG4gICAgcmV0dXJuIGNsb25lO1xuICB9XG4gIGdldCBtZW1vcml6ZWQoKSB7XG4gICAgcmV0dXJuIHRoaXMubWVtb3JpemVkQ3Vyc29yO1xuICB9XG4gIGdldCBjdXIoKSB7XG4gICAgcmV0dXJuIHRoaXMuY3Vyc29yO1xuICB9XG59XG4iXX0= -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "knx-listener", 3 | "license": "MIT", 4 | "version": "0.0.7", 5 | "main": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "description": "A thin client that creates a tunnel to knx gateway to listen to telegrams within knx net", 8 | "keywords": [ 9 | "knx", "eibd", "house automation", "smart home" 10 | ], 11 | "bugs": { 12 | "url": "http://github.com/crabicode/knx-listener/issues" 13 | }, 14 | "engines": { 15 | "node": ">=6.9.2" 16 | }, 17 | "author": { 18 | "name": "Igor Korchagin", 19 | "email": "se.meridian@gmail.com", 20 | "url": "https://github.com/crabicode" 21 | }, 22 | "homepage": "https://github.com/crabicode/knx-listener", 23 | "readme": "README.md", 24 | "scripts": { 25 | "compile:dist": "tsc -p tsconfig.dist.json", 26 | "compile:dev": "tsc -p tsconfig.json", 27 | "start": "node bin/busmonitor.js", 28 | "busmonitor": "node bin/busmonitor.js", 29 | "groupswrite": "node bin/groupswrite.js", 30 | "groupsread": "node bin/groupsread.js", 31 | "test": "concurrently --raw \"tsc -w > /dev/null\" \"jest --watchAll\"" 32 | }, 33 | "devDependencies": { 34 | "@types/chalk": "0.4.31", 35 | "@types/jest": "^16.0.3", 36 | "@types/node": "^6.0.55", 37 | "@types/yargs": "6.5.0", 38 | "concurrently": "^3.1.0", 39 | "jest": "^18.1.0", 40 | "ts-helpers": "^1.1.2", 41 | "tslint": "^4.2.0", 42 | "typescript": "^2.1.4" 43 | }, 44 | "dependencies": { 45 | "tslib": "^1.4.0", 46 | "chalk": "1.1.3", 47 | "yargs": "6.6.0" 48 | }, 49 | "jest": { 50 | "bail": true, 51 | "testPathIgnorePatterns": [ 52 | "/__tests__/helpers", 53 | "node_modules" 54 | ], 55 | "testEnvironment": "node", 56 | "testPathDirs": [ 57 | "__tests__" 58 | ], 59 | "modulePaths": [ 60 | "" 61 | ] 62 | }, 63 | "bin": { 64 | "busmonitor": "./bin/busmonitor.js", 65 | "groupsread": "./bin/groupsread.js", 66 | "groupswrite": "./bin/groupswrite.js" 67 | } 68 | } -------------------------------------------------------------------------------- /scripts/debug.js: -------------------------------------------------------------------------------- 1 | // tslint:disable:quotemark: 2 | // tslint:disable:eofline 3 | 4 | const root = process.argv[2]; 5 | const file = process.argv[3]; 6 | 7 | const jest = require('jest'); 8 | const path = require('path'); 9 | 10 | let targetFile; 11 | 12 | if (/spec.ts$/.test(file)) { 13 | // simply run the test file 14 | targetFile = path.relative(path.join(root, '__tests__'), file) 15 | .replace(/ts$/, 'js'); 16 | } else { 17 | // find test file 18 | targetFile = path.relative(path.join(root, 'src'), file) 19 | .replace(/ts$/, 'spec.js'); 20 | } 21 | 22 | jest.run(['--runInBand', targetFile]); -------------------------------------------------------------------------------- /src/bus-listener.ts: -------------------------------------------------------------------------------- 1 | import { 2 | disconnect, 3 | read, 4 | openTunnel, 5 | ping, 6 | write, 7 | } from './serializer'; 8 | import { 9 | DisconnectReponse, 10 | Channel, 11 | ConnectResponseTunnel, 12 | TunnelingAck, 13 | GroupResponse, 14 | Hpai, 15 | Subscriber, 16 | } from './interfaces'; 17 | import { 18 | QueryManager, 19 | } from './query-manager'; 20 | import { 21 | Service, 22 | Protocol, 23 | Connection, 24 | BusEvent, 25 | } from './constants'; 26 | import { 27 | MyIpNumber, 28 | } from './constants'; 29 | import { 30 | AddressInfo, 31 | } from 'dgram'; 32 | 33 | export class BusListener { 34 | protected sequenceIds: Set; 35 | protected qmanager: QueryManager; 36 | protected controlPoint: Hpai; 37 | protected heartbeatInterval: NodeJS.Timer; 38 | protected source: number; 39 | protected remoteHost: string; 40 | protected remotePort: number; 41 | protected channelId: number; 42 | constructor() { 43 | this.sequenceIds = new Set(); 44 | this.qmanager = new QueryManager(); 45 | } 46 | /** 47 | * Initializes tunneling. It is `never-resolving` promise 48 | */ 49 | public bind(remoteHost: string, remotePort: number, { 50 | timeout, onFailure, 51 | }: { timeout?: number, onFailure?: (err: Error) => void } = {}): any { 52 | return this.qmanager.connect().then((sock) => { 53 | this.controlPoint = { 54 | ip: MyIpNumber, 55 | protocol: Protocol.Udp4, 56 | port: sock.port, 57 | }; 58 | return this.openTunnel(remoteHost, remotePort).then((response) => { 59 | // when tunneling is open, store important info 60 | this.source = response.knxAddress; 61 | this.channelId = response.channelId; 62 | this.remoteHost = remoteHost; 63 | this.remotePort = remotePort; 64 | // begin heartbeat to the remote host 65 | return this.startHeartbeat(); 66 | }); 67 | }).catch((err) => { 68 | if (typeof onFailure === 'function') { 69 | onFailure(err); 70 | } 71 | this.stopHeartbeat(); 72 | if (timeout) { 73 | // cast number to uint 74 | timeout = timeout >>> 0; 75 | // schedule retry in `timeout` seconds 76 | return new Promise((resolve) => setTimeout(resolve, timeout).unref()).then(() => { 77 | // call to reconnect 78 | return this.bind(remoteHost, remotePort, { 79 | timeout, onFailure, 80 | }); 81 | }); 82 | } else { 83 | // if no timeout, then propagate error to the caller 84 | throw err; 85 | } 86 | }); 87 | } 88 | /** 89 | * returns promise, which indicates socket close 90 | */ 91 | public complete(cb?: () => T) { 92 | return this.qmanager.complete(cb); 93 | } 94 | public isConnected() { 95 | return this.heartbeatInterval ? true : false; 96 | } 97 | /** 98 | * ready return promises, which only resolves when tunnel is connected 99 | */ 100 | public ready(cb?: () => T) { 101 | return new Promise((resolve) => { 102 | if (this.isConnected()) { 103 | resolve(typeof cb === 'function' ? cb() : undefined); 104 | } else { 105 | let ref: Subscriber; 106 | const interval = setInterval(() => { 107 | if (this.isConnected()) { 108 | // when connected, clear interval 109 | clearInterval(interval); 110 | ref.unsubscribe(); 111 | resolve(typeof cb === 'function' ? cb() : undefined); 112 | } 113 | }, 0); 114 | interval.unref(); // let node exit 115 | ref = this.qmanager.on('disconnect', () => { 116 | // when disconnect scheduled 117 | clearInterval(interval); 118 | ref.unsubscribe(); 119 | }); 120 | } 121 | }); 122 | } 123 | /** 124 | * Generates next sequence number to number each knx telegram 125 | */ 126 | protected nextSeqn() { 127 | let id = 0; 128 | while (this.sequenceIds.has(id)) { 129 | if (id++ > 0xFF) { 130 | throw new Error('Maximum sequence number reached'); 131 | } 132 | } 133 | this.sequenceIds.add(id); 134 | return id; 135 | } 136 | /** 137 | * Verifies if the sender the one this tunneling was initially bound to 138 | */ 139 | protected isSameOrigin(res: Channel, sender: AddressInfo) { 140 | return res.channelId === this.channelId && 141 | sender.address === this.remoteHost && 142 | sender.port === this.remotePort && 143 | sender.family === 'IPv4'; 144 | } 145 | /** 146 | * Sends cemi to the bus 147 | */ 148 | public sendCemi(data: Buffer) { 149 | const seqn = this.nextSeqn(); 150 | const req = cemi({ 151 | cemi: data, 152 | seqn, 153 | channelId: this.channelId, 154 | }); 155 | return this.qmanager.request(this.remoteHost, this.remotePort, req, (res, sender) => { 156 | return res.seqn === seqn && this.isSameOrigin(res, sender); 157 | }).then((res) => { 158 | // always free used sequence number 159 | this.sequenceIds.delete(seqn); 160 | return res; 161 | }, (err) => { 162 | // always free used sequence number 163 | this.sequenceIds.delete(seqn); 164 | throw err; 165 | }); 166 | } 167 | /** 168 | * Sends data to the bus 169 | */ 170 | public write(data: Buffer | Uint8Array | number[], groupAddress: number) { 171 | const seqn = this.nextSeqn(); 172 | const req = write({ 173 | data, seqn, 174 | channelId: this.channelId, 175 | dest: groupAddress, 176 | source: this.source, 177 | }); 178 | return this.qmanager.request(this.remoteHost, this.remotePort, req, (res, sender) => { 179 | return res.seqn === seqn && this.isSameOrigin(res, sender); 180 | }).then((res) => { 181 | // always free used sequence number 182 | this.sequenceIds.delete(seqn); 183 | return res; 184 | }, (err) => { 185 | // always free used sequence number 186 | this.sequenceIds.delete(seqn); 187 | throw err; 188 | }); 189 | } 190 | /** 191 | * Sends read request, which will only be resolved when response event received 192 | */ 193 | public read(groupAddress: number) { 194 | const seqn = this.nextSeqn(); 195 | const req = read({ 196 | seqn, 197 | channelId: this.channelId, 198 | dest: groupAddress, 199 | source: this.source, 200 | }); 201 | return this.qmanager.request(this.remoteHost, this.remotePort, req, (res, sender) => { 202 | return res.dest === groupAddress && 203 | res.action === BusEvent.GroupResponse && 204 | this.isSameOrigin(res, sender); 205 | }).then((res) => { 206 | // always free used sequence number 207 | this.sequenceIds.delete(seqn); 208 | return res; 209 | }, (err) => { 210 | // always free used sequence number 211 | this.sequenceIds.delete(seqn); 212 | throw err; 213 | }); 214 | } 215 | /** 216 | * Terminates tunneling 217 | */ 218 | public disconnect(cb?: () => T) { 219 | const req = disconnect(this.channelId, this.controlPoint); 220 | return this.qmanager.request( 221 | this.remoteHost, this.remotePort, req, (res, remote) => { 222 | return this.isSameOrigin(res, remote); 223 | }).then(() => { 224 | // when disconnecting, we stop heartbeating 225 | this.stopHeartbeat(); 226 | return this.qmanager.disconnect(cb); 227 | }); 228 | } 229 | /** 230 | * Pings remote to verify if the channel is still active 231 | */ 232 | protected startHeartbeat() { 233 | const req = ping(this.channelId, this.controlPoint); 234 | return new Promise((_resolve, reject) => { 235 | // check connection with the first ping 236 | return this.ping(req).then(() => { 237 | // indicate that tunnel is ready 238 | // if it is successfull, then begin heartbeat every 60s 239 | this.heartbeatInterval = setInterval(() => { 240 | this.ping(req).catch(reject); 241 | }, 60000); 242 | // let node exit without waiting the interval 243 | this.heartbeatInterval.unref(); 244 | }).catch(reject); 245 | }); 246 | } 247 | /** 248 | * Stop heartbeat 249 | */ 250 | protected stopHeartbeat() { 251 | if (this.heartbeatInterval) { 252 | // stop heartbeat if started 253 | clearInterval(this.heartbeatInterval); 254 | this.heartbeatInterval = undefined; 255 | } 256 | } 257 | /** 258 | * Send ping 259 | */ 260 | protected ping(req: Buffer) { 261 | return this.qmanager.request( 262 | this.remoteHost, this.remotePort, req, (res, remote) => { 263 | return this.isSameOrigin(res, remote); 264 | }, 5000); 265 | } 266 | /** 267 | * Request tunneling 268 | */ 269 | protected openTunnel(host: string, port: number) { 270 | const q = openTunnel({ 271 | receiveAt: this.controlPoint, 272 | respondTo: this.controlPoint, 273 | }); 274 | return this.qmanager.request( 275 | host, port, q, (res, sender) => { 276 | return sender.address === host && 277 | sender.family === 'IPv4' && 278 | sender.port === port && 279 | res.serviceId === Service.ConnectResponse && 280 | res.connectionType === Connection.Tunnel; 281 | }); 282 | } 283 | /** 284 | * Supported events 285 | */ 286 | on(event: 'unprocessed', cb: (err: Error, raw?: Buffer, remote?: AddressInfo) => void): Subscriber; 287 | on(event: 'query', cb: (query: T, sender?: AddressInfo) => void): Subscriber; 288 | on(event: string, cb: (...args: any[]) => void): Subscriber { 289 | return this.qmanager.on(event, cb); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getCurrentIp, 3 | ip2num, 4 | } from './utils/index'; 5 | 6 | export const enum Service { 7 | ConnectRequest = 0x205, 8 | ConnectResponse = 0x206, 9 | ConnectionStateRequest = 0x207, 10 | ConnectStateResponse = 0x208, 11 | DisconnectRequest = 0x209, 12 | DisconnectResponse = 0x20a, 13 | TunnelingAck = 0x421, 14 | TunnelingRequest = 0x420, 15 | } 16 | 17 | export const enum Protocol { 18 | Udp4 = 0x01, 19 | Tcp4 = 0x02, 20 | }; 21 | 22 | export const enum Connection { 23 | Tunnel = 0x4, 24 | }; 25 | 26 | export const enum Status { 27 | ConnectionId = 0x21, 28 | ConnectionOption = 0x23, 29 | ConnectionType = 0x22, 30 | DataConnection = 0x26, 31 | HostProtocolType = 0x1, 32 | KnxConnection = 0x27, 33 | NoError = 0x0, 34 | NoMoreConnections = 0x24, 35 | SequenceNumber = 0x4, 36 | TunnelingLayer = 0x29, 37 | VersionNotSupported = 0x2, 38 | } 39 | 40 | export const enum BusEvent { 41 | GroupRead = 0x0, 42 | GroupResponse = 0x40, 43 | GroupWrite = 0x80, 44 | } 45 | 46 | export const MyIp = getCurrentIp(); 47 | 48 | export const MyIpNumber = ip2num(MyIp); 49 | -------------------------------------------------------------------------------- /src/deserializer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | BusEvent, 4 | } from './constants'; 5 | import { 6 | SmartCursor, 7 | } from './utils/smart-cursor'; 8 | import { 9 | Channel, 10 | Header, 11 | Hpai, 12 | } from './interfaces'; 13 | 14 | export function header(raw: Buffer, pos: SmartCursor): Header { 15 | const headerLength = raw.readUInt8(pos.next()); 16 | const protocolVersion = raw.readUInt8(pos.next()); 17 | const serviceId = raw.readUInt16BE(pos.next(2)); 18 | const totalLength = raw.readUInt16BE(pos.next(2)); 19 | if (headerLength !== 0x06) { 20 | throw new Error(`Invalid header length ${headerLength}`); 21 | } 22 | if (protocolVersion !== 0x10) { 23 | throw new Error(`Invalid protocol version ${protocolVersion}`); 24 | } 25 | if (raw.length !== totalLength) { 26 | throw new Error(`Invalid total length, expected ${raw.length}, but got ${totalLength}`); 27 | } 28 | return { 29 | serviceId, 30 | }; 31 | }; 32 | 33 | export function channel(raw: Buffer, pos: SmartCursor): Channel { 34 | const channelId = raw.readUInt8(pos.next()); 35 | const status = raw.readUInt8(pos.next()); 36 | if (channelId === 0) { 37 | throw new Error(`Invalid channel id ${channelId}`); 38 | } 39 | return { 40 | channelId, status, 41 | }; 42 | }; 43 | 44 | export function hpai(raw: Buffer, pos: SmartCursor): Hpai { 45 | const size = raw.readUInt8(pos.next()); 46 | if (size !== 0x8) { 47 | throw new Error(`Failed to read hpai at ${pos.cur}`); 48 | } 49 | const protocol = raw.readUInt8(pos.next()); 50 | const ip = raw.readUIntBE(pos.next(4), 4); 51 | const port = raw.readInt16BE(pos.next(2)); 52 | return { 53 | ip, port, protocol, 54 | }; 55 | }; 56 | 57 | export function connectResponse(raw: Buffer, pos: SmartCursor) { 58 | const size = raw.readInt8(pos.next()); 59 | const contype = raw.readInt8(pos.next()); 60 | switch (contype) { 61 | case Connection.Tunnel: { 62 | if (size !== 0x4) { 63 | throw new Error(`Failed to read connect response for tunneling at ${pos.cur}`); 64 | } 65 | const knxAddress = raw.readUInt16BE(pos.next(2)); 66 | return { 67 | connectionType: contype, 68 | knxAddress, 69 | }; 70 | } 71 | default: throw new Error(`Unknown connection type ${contype}`); 72 | } 73 | }; 74 | 75 | export function seqnum(raw: Buffer, pos: SmartCursor) { 76 | const size = raw.readUInt8(pos.next()); 77 | if (size !== 0x4) { 78 | throw new Error(`Failed to read structure at ${pos.cur}`); 79 | } 80 | const channelId = raw.readUInt8(pos.next()); 81 | const seqn = raw.readInt8(pos.next()); 82 | const status = raw.readUInt8(pos.next()); 83 | return { 84 | channelId, seqn, status, 85 | }; 86 | }; 87 | 88 | export function tunnelCemi(raw: Buffer, pos: SmartCursor) { 89 | pos.skip('messageCode'); 90 | const additionalInfoLength = raw.readUInt8(pos.next()); 91 | if (additionalInfoLength) { 92 | pos.skip('additionalInfo', additionalInfoLength); 93 | } 94 | pos.skip('controlField1'); 95 | pos.skip('controlField2'); 96 | const source = raw.readUInt16BE(pos.next(2)); 97 | const dest = raw.readUInt16BE(pos.next(2)); 98 | const npduLength = raw.readUInt8(pos.next()); 99 | const apdu = raw.readUInt16BE(pos.next(2)); 100 | const action = apdu & (BusEvent.GroupWrite | 101 | BusEvent.GroupResponse | BusEvent.GroupRead); 102 | if (action & (BusEvent.GroupWrite | BusEvent.GroupResponse)) { 103 | let data: Uint8Array; 104 | if (npduLength > 1) { 105 | // data appended 106 | data = raw.subarray(pos.next(npduLength), pos.cur); 107 | } else { 108 | // data merged into 6 bits 109 | data = new Uint8Array([apdu & 0x3f]); 110 | } 111 | return { 112 | data, action, dest, source, 113 | }; 114 | } else { 115 | // read 116 | return { 117 | action, dest, source, 118 | }; 119 | } 120 | }; 121 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils/async-socket'; 2 | export * from './utils/smart-cursor'; 3 | export * from './utils/index'; 4 | export * from './interfaces'; 5 | export * from './serializer'; 6 | export * from './deserializer'; 7 | export * from './constants'; 8 | export * from './query-manager'; 9 | export * from './bus-listener'; 10 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { Protocol } from './constants'; 2 | 3 | export interface Subscriber { unsubscribe: () => void; }; 4 | 5 | export interface Hpai { 6 | protocol: Protocol.Tcp4 | Protocol.Udp4; 7 | ip: number; 8 | port: number; 9 | } 10 | 11 | export interface Channel { 12 | channelId: number; 13 | status: number; 14 | } 15 | 16 | export interface ConnectResponseTunnel { 17 | connectionType: number; 18 | knxAddress: number; 19 | protocol: number; 20 | ip: number; 21 | port: number; 22 | channelId: number; 23 | status: number; 24 | serviceId: number; 25 | }; 26 | 27 | export interface Header { 28 | serviceId: number; 29 | } 30 | 31 | export interface DisconnectReponse { 32 | channelId: number; 33 | status: number; 34 | } 35 | 36 | export interface TunnelingAck { 37 | channelId: number; 38 | status: number; 39 | seqn: number; 40 | } 41 | 42 | export interface GroupResponse { 43 | source: number; 44 | data: Uint8Array | Buffer | number[]; 45 | channelId: number; 46 | status: number; 47 | seqn: number; 48 | action: number; 49 | dest: number; 50 | } 51 | -------------------------------------------------------------------------------- /src/query-manager.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RemoteInfo, 3 | } from 'dgram'; 4 | import { 5 | AsyncSocket, 6 | } from './utils/async-socket'; 7 | import { 8 | SmartCursor, 9 | } from './utils/smart-cursor'; 10 | import { 11 | channel as readChannel, 12 | connectResponse, 13 | header as readHeader, 14 | hpai, 15 | seqnum, 16 | tunnelCemi, 17 | } from './deserializer'; 18 | import { 19 | ack, 20 | } from './serializer'; 21 | import { 22 | Service, 23 | Status, 24 | } from './constants'; 25 | 26 | /** 27 | * Manages io server queries and tracks resolution of mappable requests 28 | */ 29 | export class QueryManager extends AsyncSocket { 30 | connect(port: number = 0 /* OS assigned port */): Promise { 31 | // forward raw data for processing 32 | const ref = super.on('raw', this.process.bind(this)); 33 | return super.connect(port).catch((err) => { 34 | ref.unsubscribe(); 35 | // propagate error to the caller 36 | throw err; 37 | }); 38 | } 39 | /** 40 | * Creates a mapable request to track responses with timeout 41 | */ 42 | request( 43 | host: string, port: number, data: Buffer, 44 | select: (res: T, sender?: RemoteInfo) => boolean, timeout?: number, 45 | ) { 46 | return new Promise((resolve, reject) => { 47 | // keep ref to unsub to avoid a memory leak 48 | const ref = this.on('query', (query, remote) => { 49 | // map response to the request 50 | if (select(query, remote)) { 51 | if (query.status === Status.NoError) { 52 | resolve(query); 53 | } else { 54 | reject(new Error(`Request error ${query.status}`)); 55 | } 56 | } 57 | }); 58 | // set timeout if no response within given time 59 | setTimeout(() => { 60 | ref.unsubscribe(); // avoid memory leak 61 | const err: NodeJS.ErrnoException = new Error(`Request timeout`); 62 | err.code = 'ETIMEOUT'; 63 | reject(err); 64 | }, timeout > 300 ? timeout : 300).unref(); // unref timeout to let node exit 65 | // make request and propagate errors 66 | return super.send(host, port, data).catch((err) => { 67 | ref.unsubscribe(); // avoid memory leak 68 | reject(err); 69 | }); 70 | }); 71 | } 72 | /** 73 | * Processes raw messages from socket stream 74 | */ 75 | private process(raw: Buffer, remote: RemoteInfo) { 76 | try { 77 | const pos = new SmartCursor(); 78 | const header = readHeader(raw, pos); 79 | switch (header.serviceId) { 80 | case Service.ConnectResponse: { 81 | const channel = readChannel(raw, pos); 82 | const sender = hpai(raw, pos); 83 | const response = connectResponse(raw, pos); 84 | return this.events.emit('query', { 85 | ...header, ...channel, ...sender, ...response, 86 | }, remote); 87 | } 88 | case Service.ConnectStateResponse: { 89 | const channel = readChannel(raw, pos); 90 | return this.events.emit('query', { ...channel }, remote); 91 | } 92 | case Service.TunnelingAck: { 93 | const seqn = seqnum(raw, pos); 94 | return this.events.emit('query', { ...seqn }, remote); 95 | } 96 | case Service.TunnelingRequest: { 97 | const seqn = seqnum(raw, pos); 98 | const cemi = tunnelCemi(raw, pos); 99 | // reply ack to indicate successful reception of the message 100 | this.send(remote.address, remote.port, ack( 101 | seqn.seqn, seqn.channelId, Status.NoError, 102 | )); 103 | this.events.emit('cemi', raw.slice(10), remote); 104 | return this.events.emit('query', { ...cemi, ...seqn }, remote); 105 | } 106 | case Service.DisconnectResponse: { 107 | const channel = readChannel(raw, pos); 108 | return this.events.emit('query', { ...channel }, remote); 109 | } 110 | default: throw new Error(`Failed to process ${header.serviceId}`); 111 | } 112 | } catch (err) { 113 | return this.events.emit('unprocessed', err, raw, remote); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/serializer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SmartCursor, 3 | } from './utils/smart-cursor'; 4 | import { 5 | Service, 6 | } from './constants'; 7 | import { 8 | Hpai, 9 | } from './interfaces'; 10 | 11 | function header(service: Service, bodyLength: number) { 12 | const size = 0x06; 13 | const pos = new SmartCursor(); 14 | const raw = Buffer.allocUnsafe(size); 15 | raw.writeUInt8(size, pos.next()); // header length 16 | raw.writeUInt8(0x10, pos.next()); // version 17 | raw.writeUInt16BE(service, pos.next(2)); // service type 18 | raw.writeUInt16BE(size + bodyLength, pos.next(2)); // total length 19 | return raw; 20 | }; 21 | 22 | function message(service: Service, includes: Buffer[]) { 23 | const size = includes.reduce((acc, item) => acc += item.length, 0); 24 | const head = header(service, size); 25 | const ret = Buffer.concat([head, ...includes]); 26 | return ret; 27 | }; 28 | 29 | function hpai(protocol: number, ip: number, port: number) { 30 | const size = 0x08; 31 | const pos = new SmartCursor(); 32 | const raw = Buffer.allocUnsafe(size); 33 | raw.writeUInt8(size, pos.next()); // structure length 34 | raw.writeUInt8(protocol, pos.next()); // protocol 35 | raw.writeUInt32BE(ip, pos.next(4)); // ip 36 | raw.writeUInt16BE(port, pos.next(2)); // port 37 | return raw; 38 | }; 39 | 40 | function tunneling() { 41 | const size = 0x04; 42 | const pos = new SmartCursor(); 43 | const raw = Buffer.allocUnsafe(size); 44 | raw.writeUInt8(size, pos.next()); // structure length 45 | raw.writeUInt8(0x04, pos.next()); // TUNNEL_CONNECTION 46 | raw.writeUInt8(0x02, pos.next()); // TUNNEL_LINKLAYER 47 | raw.writeUInt8(0x00, pos.next()); // reserved 48 | return raw; 49 | }; 50 | 51 | function channel(channelId: number) { 52 | const pos = new SmartCursor(); 53 | const raw = Buffer.allocUnsafe(2); 54 | raw.writeUInt8(channelId, pos.next()); 55 | raw.writeUInt8(0x00, pos.next()); // reserved 56 | return raw; 57 | }; 58 | 59 | /** 60 | * Creates buffer of sequence counter, channel id and status code 61 | */ 62 | function seqnum(seqn: number, channelId: number, status = 0x00) { 63 | const size = 0x04; 64 | const pos = new SmartCursor(); 65 | const raw = Buffer.allocUnsafe(size); 66 | raw.writeUInt8(size, pos.next()); // structure length 67 | raw.writeUInt8(channelId, pos.next()); // channelId 68 | raw.writeUInt8(seqn, pos.next()); // sequenceCounter 69 | raw.writeUInt8(status, pos.next()); // reserved or status 70 | return raw; 71 | }; 72 | 73 | export const enum DataType { 74 | Uint8 = 1, 75 | Uint16 = 2, 76 | Uint32 = 4, 77 | Uint64 = 8, 78 | Uint128 = 16, 79 | } 80 | 81 | // ready to use messages 82 | 83 | export function ack(seqn: number, channelId: number, status: number) { 84 | return message(Service.TunnelingAck, [ 85 | seqnum(seqn, channelId, status), 86 | ]); 87 | }; 88 | 89 | export function disconnect(channelId: number, respondTo: Hpai) { 90 | return message(Service.DisconnectRequest, [ 91 | channel(channelId), 92 | hpai(respondTo.protocol, respondTo.ip, respondTo.port), 93 | ]); 94 | }; 95 | 96 | export function ping(channelId: number, respondTo: Hpai) { 97 | return message(Service.ConnectionStateRequest, [ 98 | channel(channelId), 99 | hpai(respondTo.protocol, respondTo.ip, respondTo.port), 100 | ]); 101 | }; 102 | 103 | export function openTunnel({ receiveAt, respondTo }: { 104 | respondTo: Hpai; 105 | receiveAt: Hpai; 106 | }) { 107 | return message(Service.ConnectRequest, [ 108 | hpai(respondTo.protocol, respondTo.ip, respondTo.port), 109 | hpai(receiveAt.protocol, receiveAt.ip, receiveAt.port), 110 | tunneling(), 111 | ]); 112 | }; 113 | 114 | export function cemi({ cemi, seqn, channelId }: { 115 | cemi: Buffer; 116 | seqn: number; 117 | channelId: number; 118 | }) { 119 | return message(Service.TunnelingRequest, [ 120 | seqnum(seqn, channelId), 121 | cemi, 122 | ]); 123 | }; 124 | 125 | export function write({ data, seqn, channelId, source, dest }: { 126 | data: Buffer | Uint8Array | number[]; 127 | seqn: number; 128 | channelId: number; 129 | source: number; 130 | dest: number; 131 | }) { 132 | if (data.length > DataType.Uint128) { 133 | // if data is longer than 16 bytes 134 | throw new Error( 135 | `Data is too long, expected maximum ${DataType.Uint128} bytes, got ${data.length}`); 136 | } 137 | // cemi 138 | const isUint6 = data.length === DataType.Uint8 && data[0] <= 0x3f; 139 | const size = isUint6 ? DataType.Uint8 : data.length + 1; 140 | const pos = new SmartCursor(); 141 | const cemi = Buffer.alloc(0x0A + size); 142 | cemi.writeUInt8(0x11, pos.next()); // L_Data_req 143 | cemi.writeUInt8(0x00, pos.next()); // additional info length 144 | cemi.writeUInt8(0xbc, pos.next()); // control field 1 145 | cemi.writeUInt8(0xe0, pos.next()); // control field 2 146 | cemi.writeUInt16BE(source, pos.next(2)); // source address 0.0.0 147 | cemi.writeUInt16BE(dest, pos.next(2)); // destination address 148 | if (isUint6) { 149 | // data can be merged 150 | cemi.writeUInt8(size, pos.next()); // payload length 151 | cemi.writeUInt16BE(data[0] | 0x80, pos.next(2)); // 0x80 GROUPVALUE_WRITE 152 | } else { 153 | // data must be appended at the end 154 | cemi.writeUInt8(size, pos.next()); // payload length 155 | cemi.writeUInt16BE(0x80, pos.next(2)); // apci 0x80 GROUPVALUE_WRITE 156 | cemi.set(data, pos.next(size)); 157 | } 158 | return message(Service.TunnelingRequest, [ 159 | seqnum(seqn, channelId), 160 | cemi, 161 | ]); 162 | }; 163 | 164 | export function read(params: { 165 | seqn: number; 166 | channelId: number; 167 | source: number; 168 | dest: number; 169 | }) { 170 | // cemi 171 | const pos = new SmartCursor(); 172 | const cemi = Buffer.alloc(0x0B); 173 | cemi.writeUInt8(0x11, pos.next()); // L_Data_req 174 | cemi.writeUInt8(0x00, pos.next()); // additional info length 175 | cemi.writeUInt8(0xbc, pos.next()); // control field 1 176 | cemi.writeUInt8(0xe0, pos.next()); // control field 2 177 | cemi.writeUInt16BE(params.source, pos.next(2)); // source address 0.0.0 178 | cemi.writeUInt16BE(params.dest, pos.next(2)); // destination address 179 | cemi.writeUInt8(0x01, pos.next()); // payload length 180 | cemi.writeUInt16BE(0x00, pos.next(2)); // 0x00 GROUPVALUE_READ 181 | return message(Service.TunnelingRequest, [ 182 | seqnum(params.seqn, params.channelId), 183 | cemi, 184 | ]); 185 | }; 186 | -------------------------------------------------------------------------------- /src/utils/async-socket.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createSocket, 3 | Socket, 4 | RemoteInfo, 5 | } from 'dgram'; 6 | import { 7 | EventEmitter, 8 | } from 'events'; 9 | import { 10 | Subscriber, 11 | } from '../interfaces'; 12 | 13 | /** 14 | * Simple promisable udp socket 15 | */ 16 | export class AsyncSocket { 17 | private socket: Socket; 18 | protected events: EventEmitter = new EventEmitter(); 19 | isConnected() { 20 | return this.socket ? true : false; 21 | } 22 | connect(port: number = 0 /* OS assigned port */) { 23 | return new Promise((resolve, reject) => { 24 | if (this.isConnected()) { 25 | resolve(this.socket.address()); 26 | } else { 27 | this.socket = createSocket('udp4') 28 | .on('message', (raw: Buffer, remote) => { 29 | this.events.emit('raw', raw, remote); 30 | }) 31 | .once('close', () => { 32 | this.socket = undefined; 33 | // emit disconnect event 34 | this.events.emit('disconnect'); 35 | // and remove all listeners to prevent any memory leak 36 | this.events.removeAllListeners(); 37 | }) 38 | .once('error', (err) => { 39 | reject(err); 40 | }) 41 | .once('listening', () => { 42 | resolve(this.socket.address()); 43 | }); 44 | this.socket.bind(port); 45 | } 46 | }); 47 | } 48 | complete(cb?: () => T) { 49 | return new Promise((resolve) => { 50 | if (this.isConnected()) { 51 | this.socket.once('close', () => { 52 | resolve(typeof cb === 'function' ? cb() : undefined); 53 | }); 54 | } else { 55 | resolve(typeof cb === 'function' ? cb() : undefined); 56 | } 57 | }); 58 | } 59 | disconnect(cb?: () => T) { 60 | return new Promise((resolve) => { 61 | if (this.isConnected()) { 62 | this.socket.once('close', () => { 63 | resolve(typeof cb === 'function' ? cb() : undefined); 64 | }); 65 | this.socket.close(); 66 | } else { 67 | resolve(typeof cb === 'function' ? cb() : undefined); 68 | } 69 | }); 70 | } 71 | send(host: string, port: number, data) { 72 | return new Promise((resolve, reject) => { 73 | if (this.isConnected()) { 74 | this.socket.send(data, port, host, (err, bytes) => { 75 | if (err) { 76 | reject(err); 77 | } 78 | if (bytes !== data.length) { 79 | reject(new Error(`Expected to send ${data.length} bytes, but sent ${bytes}`)); 80 | } 81 | resolve(); 82 | }); 83 | } else { 84 | throw new Error(`No connection`); 85 | } 86 | }); 87 | } 88 | on(event: 'disconnect', cb: () => void): Subscriber; 89 | on(event: 'raw', cb: (raw: Buffer, sender: RemoteInfo) => void): Subscriber; 90 | on(event: string, cb: (query: T, sender: RemoteInfo) => void): Subscriber; 91 | on(event: string, cb: (...args: any[]) => void): Subscriber { 92 | if (this.events.on(event, cb)) { 93 | return { 94 | unsubscribe: () => this.events.removeListener(event, cb), 95 | }; 96 | } else { 97 | throw new Error(`Failed to subscribe`); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { networkInterfaces } from 'os'; 2 | 3 | export function ip2num(ipString: string) { 4 | const ipNumber = ipString.split('.'); 5 | return ((((((+ipNumber[0]) * 256) + (+ipNumber[1])) * 256) + (+ipNumber[2])) * 256) + (+ipNumber[3]); 6 | } 7 | 8 | export function num2ip(ipNumber: number) { 9 | let ipString = (ipNumber % 256).toString(); 10 | for (let i = 3; i > 0; i--) { 11 | ipNumber = Math.floor(ipNumber / 256); 12 | ipString = ipNumber % 256 + '.' + ipString; 13 | } 14 | return ipString; 15 | } 16 | 17 | export function num2mac(macNumber: number) { 18 | return String(1e12 + (macNumber) 19 | .toString(16)).slice(-12).match(/.{1,2}/g).join(':'); 20 | } 21 | 22 | export function mac2num(macString: string) { 23 | return parseInt(macString.split(':').join(''), 16); 24 | } 25 | 26 | export function getCurrentIp() { 27 | const ifaces = networkInterfaces(); 28 | for (const dev in ifaces) { 29 | for (const details of ifaces[dev]) { 30 | if (details.family === 'IPv4' && details.internal === false) { 31 | return details.address; 32 | } 33 | } 34 | } 35 | throw new Error('Failed to get current ip'); 36 | } 37 | 38 | export function sizeOf(value: number) { 39 | return Math.ceil(Math.log2(value + 1) / 4) || 1; 40 | } 41 | 42 | export function knxAddr2num(addrStr: string) { 43 | const m = addrStr.split(/[\.\/]/); 44 | if (m && m.length > 0) { 45 | return (((+m[0]) & 0x01f) << 11) + (((+m[1]) & 0x07) << 8) + ((+m[2]) & 0xff); 46 | } 47 | throw Error(`Could not encode ${addrStr} address`); 48 | } 49 | 50 | export function num2knxAddr(addrNum: number, isGroupAddr = true) { 51 | return [ 52 | (addrNum >> 11) & 0xf, 53 | isGroupAddr ? (addrNum >> 8) & 0x7 : (addrNum >> 8) & 0xf, 54 | (addrNum & 0xff)].join(isGroupAddr ? '/' : '.'); 55 | } 56 | 57 | export function removeNonPrintable(str: string) { 58 | return str.replace(/[^\x20-\x7E]+/g, ''); 59 | } 60 | 61 | export function noop(..._: any[]): any { } 62 | 63 | export function isIPv4(ipStr: string) { 64 | return /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/ 65 | .test(ipStr); 66 | } 67 | 68 | export function isKnxAddress(knxStrAddr: string, isGroupAddress = true) { 69 | if (isGroupAddress) { 70 | // group address 1/1/1 71 | return /^(3[01]|([0-2]?[0-9]))\/(([0-7]\/(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(([01]?\d{1,3})|(20[0-4][0-7])))$/ 72 | .test(knxStrAddr); 73 | } else { 74 | // individual address 1.1.15 75 | return /^([01]?\d)\.([01]?\d)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])?$/.test(knxStrAddr); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/utils/smart-cursor.ts: -------------------------------------------------------------------------------- 1 | function check(i: number) { 2 | if (i < 0) { 3 | throw Error('Index should not be zero or negative number'); 4 | } 5 | } 6 | 7 | /** 8 | * Class helper acts as iterrator to access buffer 9 | */ 10 | export class SmartCursor { 11 | private cursor: number; 12 | private memorizedCursor: number; 13 | constructor(i: number = 0) { 14 | this.cursor = i; 15 | this.memorizedCursor = i; 16 | } 17 | public jump(i: number) { 18 | check(i); 19 | this.cursor = i; 20 | this.memorizedCursor = i; 21 | return this; 22 | } 23 | public skip(_field?: string, nextPos = 1) { 24 | this.next(nextPos); 25 | return this; 26 | } 27 | public diff(sync = true) { 28 | const memorized = this.cursor - this.memorizedCursor; 29 | if (sync) { 30 | this.jump(this.cursor); 31 | } 32 | return memorized; 33 | } 34 | public memorize() { 35 | this.memorizedCursor = this.cursor; 36 | return this; 37 | } 38 | public next(nextPos = 1) { 39 | check(nextPos); 40 | const clone = this.cursor; 41 | this.cursor += nextPos; 42 | return clone; 43 | } 44 | get memorized() { 45 | return this.memorizedCursor; 46 | } 47 | get cur() { 48 | return this.cursor; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "target": "es2016", 5 | "inlineSourceMap": true, 6 | "inlineSources": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "outDir": "dist", 9 | "module": "commonjs", 10 | "preserveConstEnums": false, 11 | "importHelpers": true, 12 | "alwaysStrict": true, 13 | "declarationDir": "dist", 14 | "allowJs": false, 15 | "newLine": "LF" 16 | }, 17 | "include": [ 18 | "src/*" 19 | ] 20 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "alwaysStrict": true, 5 | "experimentalDecorators": true, 6 | "removeComments": true, 7 | "inlineSourceMap": true, 8 | "inlineSources": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "pretty": true, 11 | "preserveConstEnums": false, 12 | "noEmitHelpers": false, 13 | "newLine": "LF", 14 | "noUnusedParameters": true, 15 | "noUnusedLocals": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "noImplicitReturns": true, 18 | "module": "commonjs", 19 | "moduleResolution": "node", 20 | "target": "es2016", 21 | "noImplicitAny": false, 22 | "importHelpers": true 23 | } 24 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:latest", 3 | "jsRules": { 4 | "class-name": true, 5 | "object-literal-sort-keys": false, 6 | "comment-format": [ 7 | true, 8 | "check-space" 9 | ], 10 | "indent": [ 11 | true, 12 | "spaces" 13 | ], 14 | "no-duplicate-variable": true, 15 | "no-eval": true, 16 | "no-trailing-whitespace": true, 17 | "no-unsafe-finally": true, 18 | "one-line": [ 19 | true, 20 | "check-open-brace", 21 | "check-whitespace" 22 | ], 23 | "quotemark": [ 24 | true, 25 | "double" 26 | ], 27 | "semicolon": [ 28 | true, 29 | "always" 30 | ], 31 | "triple-equals": [ 32 | true, 33 | "allow-null-check" 34 | ], 35 | "variable-name": [ 36 | true, 37 | "ban-keywords" 38 | ], 39 | "whitespace": [ 40 | true, 41 | "check-branch", 42 | "check-decl", 43 | "check-operator", 44 | "check-separator", 45 | "check-type" 46 | ], 47 | "no-shadowed-variable": false 48 | }, 49 | "rules": { 50 | "no-empty": false, 51 | "max-line-length": [ 52 | false 53 | ], 54 | "arrow-parens": false, 55 | "max-classes-per-file": [ 56 | false 57 | ], 58 | "member-ordering": [ 59 | false 60 | ], 61 | "interface-name": [ 62 | false 63 | ], 64 | "forin": false, 65 | "ordered-imports": [ 66 | false 67 | ], 68 | "object-literal-sort-keys": false, 69 | "align": [ 70 | false 71 | ], 72 | "no-bitwise": false, 73 | "class-name": true, 74 | "comment-format": [ 75 | true, 76 | "check-space" 77 | ], 78 | "indent": [ 79 | true, 80 | "spaces" 81 | ], 82 | "no-eval": true, 83 | "no-internal-module": true, 84 | "no-trailing-whitespace": true, 85 | "no-unsafe-finally": true, 86 | "no-var-keyword": true, 87 | "one-line": [ 88 | true, 89 | "check-open-brace", 90 | "check-whitespace" 91 | ], 92 | "quotemark": [ 93 | true, 94 | "single" 95 | ], 96 | "semicolon": [ 97 | true, 98 | "always" 99 | ], 100 | "triple-equals": [ 101 | true, 102 | "allow-null-check" 103 | ], 104 | "member-access": [ 105 | false 106 | ], 107 | "no-shadowed-variable": false, 108 | "typedef-whitespace": [ 109 | true, { 110 | "call-signature": "nospace", 111 | "index-signature": "nospace", 112 | "parameter": "nospace", 113 | "property-declaration": "nospace", 114 | "variable-declaration": "nospace" 115 | } 116 | ], 117 | "variable-name": [ 118 | true, 119 | "ban-keywords" 120 | ], 121 | "whitespace": [ 122 | true, 123 | "check-branch", 124 | "check-decl", 125 | "check-operator", 126 | "check-separator", 127 | "check-type" 128 | ] 129 | } 130 | } --------------------------------------------------------------------------------