├── .gitignore ├── .gitmodules ├── LEEME.md ├── LICENSE ├── README.md ├── aquila-gateway.js ├── dist ├── CrcUtils.js ├── CrcUtils.js.map ├── Forwarder.js ├── Forwarder.js.map ├── Gateway.js ├── Gateway.js.map ├── GatewayDB.js ├── GatewayDB.js.map ├── GwMonitor.js ├── GwMonitor.js.map ├── Logger.js ├── Logger.js.map ├── MQTTTransport.js ├── MQTTTransport.js.map ├── SerialTransport.js ├── SerialTransport.js.map ├── TCPTransport.js ├── TCPTransport.js.map ├── interfaces.js ├── interfaces.js.map ├── main.js └── main.js.map ├── doc ├── index.md └── messagesFormat.md ├── gulpfile.js ├── index.js ├── package.json ├── src ├── CrcUtils.ts ├── Forwarder.ts ├── Gateway.ts ├── GatewayDB.ts ├── GwMonitor.ts ├── Logger.ts ├── MQTTTransport.ts ├── SerialTransport.ts ├── TCPTransport.ts ├── declarations.d.ts ├── interfaces.ts └── main.ts ├── tests ├── TestForwarder.js ├── VirtualDevice.js ├── VirtualNetwork.js └── testMain.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | ###Node### 2 | 3 | lib-cov 4 | *.seed 5 | *.log 6 | *.csv 7 | *.dat 8 | *.out 9 | *.pid 10 | *.gz 11 | 12 | pids 13 | logs 14 | results 15 | 16 | npm-debug.log 17 | node_modules 18 | 19 | ###OSX### 20 | 21 | .DS_Store 22 | .AppleDouble 23 | .LSOverride 24 | Icon 25 | 26 | 27 | # Thumbnails 28 | ._* 29 | 30 | # Files that might appear on external disk 31 | .Spotlight-V100 32 | .Trashes 33 | 34 | data.json -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rodmg/aquila-mqtt-sn-gateway/97e4ed53e579b13a75652ea9f4777c5593fa1206/.gitmodules -------------------------------------------------------------------------------- /LEEME.md: -------------------------------------------------------------------------------- 1 | # Aquila MQTT-SN Gateway 2 | 3 | Puerta de enlace MQTT-SN para la plataforma Aquila 2.0. 4 | 5 | Este software actúa como un enlace transparente entre una red de sensores de bajo consumo (como [Altair](http://www.aquila.io/en) u otros dispositivos 802.15.4 o RF) y un Broker MQTT (como mosca o mosquitto). Esto nos permite integrar fácilmente estos dispositivos con bibliotecas y aplicaciones MQTT existentes. 6 | 7 | Puedes encontrar más información técnica en la [documentación](doc/). 8 | 9 | ``` 10 | (Device)_____ ____________________ _______________ 11 | \___________ | | | | 12 | MQTT-SN \__ ________| | MQTT | | 13 | (Device)_______________| Bridge | Gateway |__________| MQTT Broker | 14 | __ --------| (aquila-gateway) | | | 15 | __________________/ | | | | 16 | (Device) -------------------- --------------- 17 | | 18 | MQTT | 19 | _______|________ 20 | | | 21 | | Other MQTT | 22 | | Devices | 23 | --------------- 24 | ``` 25 | 26 | ## Implementaciones del Bridge y clientes 27 | 28 | Este Gateway busca ser lo más independiente posible de las capas de transporte de red, esto nos permite utilizar casi cualquier red de sensores con sólo desarrollar un puente o "Bridge" de hardware que implemente el protocolo serial "forwarder" definido en la [documentación](doc/). 29 | 30 | Actualmente existen implementaciones para la placa de desarrollo 802.15.4 [Altair](http://www.aquila.io/en) y para dispositivos basados en el módulo rfm69 de radiofrecuencia con microcontroladores Atmel AVR. 31 | 32 | - Altair: [Bridge](https://github.com/Rodmg/altair-mqtt-sn-bridge) y [ejemplo/plantilla de cliente](https://github.com/Rodmg/altair-mqtt-sn-client-example) 33 | - rfm69: [Bridge](https://github.com/Rodmg/rfm-mqtt-sn-bridge) y [ejemplo/plantilla de cliente](https://github.com/Rodmg/rfm-mqtt-sn-client-example) 34 | 35 | ## Requisitos 36 | 37 | - [Node.js](https://nodejs.org/en/) v4.X.X o superior. (Probado con v4.5.0 y v6.3.1) 38 | 39 | ## Uso 40 | 41 | 1. Clona este repositorio y haz ``cd`` al directorio del proyecto 42 | 43 | 2. Instala dependencias: 44 | 45 | ``` 46 | npm install 47 | npm install -g bunyan 48 | ``` 49 | 3. Corre un broker MQTT en tu PC, por ejemplo: [Mosca](https://github.com/mcollina/mosca) 50 | 51 | 4. Conecta el Bridge a la PC e identifica a que puerto serial está conectado 52 | 53 | 5. Corre: 54 | 55 | ``` 56 | ./aquila-gateway.js -p | bunyan 57 | ``` 58 | 59 | ## Uso avanzado 60 | 61 | Obtén ayuda: 62 | 63 | ``` 64 | ./aquila-gateway.js -h 65 | ``` 66 | 67 | ``` 68 | Usage: aquila-gateway [options] 69 | 70 | Options: 71 | 72 | -h, --help output usage information 73 | -V, --version output the version number 74 | -v, --verbose [level] Verbosity level for logging (fatal, error, warn, info, debug, trace) [info] 75 | -p, --port [serial port] Serial Port path [/dev/tty.SLAB_USBtoUART] 76 | -b, --broker [url] MQTT broker URL [http://localhost:1883] 77 | -u, --allow-unknown-devices [true/false] Allow connection of previously unknown (not paired) devices [true] 78 | ``` 79 | 80 | Conectarse a un Broker remoto (ejemplo): 81 | 82 | ``` 83 | ./aquila-gateway.js -p /dev/tty.SLAB_USBtoUART -b http://test.mosquitto.org:1883 | bunyan 84 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Rodrigo Méndez Gamboa, http://rodrigomendez.me 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aquila MQTT-SN Gateway 2 | 3 | MQTT-SN gateway for the Aquila 2.0 platform. 4 | 5 | This software acts as a transparent link between a sensor network of low power devices (like [Altair](http://www.aquila.io/en) or other 802.15.4 or RF devices) and a MQTT broker (like mosca or mosquitto). This allows us to seamlessly and easily integrate those devices with existing MQTT applications and libraries. 6 | 7 | You can find more information in the [documentation](doc/) 8 | 9 | ``` 10 | (Device)_____ ____________________ _______________ 11 | \___________ | | | | 12 | MQTT-SN \__ ________| | MQTT | | 13 | (Device)_______________| Bridge | Gateway |__________| MQTT Broker | 14 | __ --------| (aquila-gateway) | | | 15 | __________________/ | | | | 16 | (Device) -------------------- --------------- 17 | | 18 | MQTT | 19 | _______|________ 20 | | | 21 | | Other MQTT | 22 | | Devices | 23 | --------------- 24 | ``` 25 | 26 | ## Bridge and client implementations 27 | 28 | This gateway is meant to be as transport agnostic as possible, this allows us to use almost any sensor network just by developing a hardware bridge that implements the serial forwarder protocol defined in the [documentation](doc/). 29 | 30 | Currently there are implementations for the [Altair](http://www.aquila.io/en) 802.15.4 development board and for rfm69 915Mhz RF devices with Atmel AVR processors. 31 | 32 | * Altair: [Bridge](https://github.com/Rodmg/altair-mqtt-sn-bridge) and [example client template](https://github.com/Rodmg/altair-mqtt-sn-client-example) 33 | * rfm69: [Bridge](https://github.com/Rodmg/rfm-mqtt-sn-bridge) and [example client template](https://github.com/Rodmg/rfm-mqtt-sn-client-example) 34 | 35 | ## Requirements 36 | 37 | * [Node.js](https://nodejs.org/en/) v4.X.X or newer. (Tested with v4.5.0+ and v6.3.1+) 38 | 39 | ## Usage 40 | 41 | 1. Install: 42 | 43 | ``` 44 | npm install -g aquila-gateway bunyan 45 | ``` 46 | 47 | _If you have problems installing, try with:_ `sudo npm install -g aquila-gateway bunyan --unsafe-perm` 48 | 49 | 2. Run a MQTT broker on your PC, for example [Mosca](https://github.com/mcollina/mosca) 50 | 51 | 3. Connect the Bridge to the PC and identify which serial port it's connected to 52 | 53 | 4. Run: 54 | 55 | ``` 56 | aquila-gateway -p | bunyan 57 | ``` 58 | 59 | ## Advanced usage 60 | 61 | Get help: 62 | 63 | ``` 64 | aquila-gateway -h 65 | ``` 66 | 67 | ``` 68 | Usage: aquila-gateway [options] 69 | 70 | Options: 71 | 72 | -h, --help output usage information 73 | -V, --version output the version number 74 | -v, --verbose [level] Verbosity level for logging (fatal, error, warn, info, debug, trace) [info] 75 | -t, --transport [transport] Forwarder transport type (serial, tcp) [serial] 76 | -p, --port [serial port] Serial Port path if using serial transport, TCP port number if using TCP transport [/dev/tty.SLAB_USBtoUART | 6969] 77 | -b, --broker [url] MQTT broker URL [http://localhost:1883] 78 | -u, --allow-unknown-devices [true/false] Allow connection of previously unknown (not paired) devices [true] 79 | -s, --subnet [pan id] PAN subnet number (1 to 254) [1] 80 | -k, --key [16 byte array] 16 byte encryption key [null] 81 | -d, --data-path [path] Path to data persist file [/Users/rod/.aquila-gateway/data.json] 82 | -m, --monitor-prefix [prefix] Gateway monitor topics prefix [gw] 83 | ``` 84 | 85 | Connect to a remote broker (example): 86 | 87 | ``` 88 | aquila-gateway -p /dev/tty.SLAB_USBtoUART -b http://test.mosquitto.org:1883 | bunyan 89 | ``` 90 | 91 | ## Developement 92 | 93 | 1. Clone this repository and `cd` to the project directory 94 | 95 | 2. Install dependencies: 96 | 97 | ``` 98 | npm install 99 | npm install -g bunyan 100 | ``` 101 | 102 | 3. Run a MQTT broker on your PC, for example [Mosca](https://github.com/mcollina/mosca) 103 | 104 | 4. Connect the Bridge to the PC and identify which serial port it's connected to 105 | 106 | 5. Build: 107 | 108 | ``` 109 | npm run build 110 | ``` 111 | 112 | 6. Run: 113 | 114 | ``` 115 | ./aquila-gateway.js -p | bunyan 116 | ``` 117 | 118 | ## Supported MQTT-SN features 119 | 120 | * QoS: supports QoS0, QoS1 and QoS2 (QoS2 implementation between device and gateway is mostly dummy, equivalent to QoS1) 121 | * Commands: 122 | * ADVERTISE 123 | * CONNECT 124 | * CONNACK 125 | * DISCONNECT 126 | * WILLTOPICREQ 127 | * WILLTOPIC 128 | * WILLMSGREQ 129 | * WILLMSG 130 | * REGISTER 131 | * REGACK 132 | * PUBLISH 133 | * PUBACK 134 | * SUBSCRIBE 135 | * SUBACK 136 | * UNSUBSCRIBE 137 | * UNSUBACK 138 | * PINGREQ 139 | * PINGRESP 140 | * WILLTOPICUPD 141 | * WILLMSGUPD 142 | * WILLTOPICRESP 143 | * WILLMSGRESP 144 | * PUBREC, PUBREL, PBCOMP (QoS2) 145 | * SEARCHGW (basic, not spec checked) 146 | * GWINFO (basic, not spec checked) 147 | * Implements Forwarder Encapsulation spec with a little modification: adds lqi and rssi data at the start of the frame. 148 | * Supports topic register 149 | * Supports last will 150 | * Supports and manages disconnect timeout 151 | * Will update 152 | * Subscribe, Unsubscribe 153 | * Retained messages support (issue: when a device subscribes to a topic with a retained message, it will be resent to all devices susbscribed to it, check if it's a problem) 154 | * Sleeping nodes -> message buffering 155 | 156 | ## TODO 157 | 158 | * QoS -1 159 | * WILDCARDS support as mqtt-sn spec 160 | * Short and predefined MQTT-SN topics 161 | * QoS 1 and 2 retries 162 | 163 | ## In progress 164 | 165 | * Advanced device management: implement predefined Gateway topics for getting info of connected devices, events etc. (non MQTT-SN standard, implement as module) (Preliminar API implemented) 166 | * ~~Device pairing management (Transport dependent)~~ (DONE for Altair and rfm69) 167 | * ~~Support other Forwarder software transports: TCP socket (for using an [ESPino](http://www.espino.io/en) or similar as forwarder)~~ (TCP transport Done, experimental) 168 | 169 | ## Not supported 170 | 171 | * 3 byte MQTT-SN header 172 | 173 | ## TOTEST 174 | 175 | * Check if parser on willtopicupd accept empty flags and topic (for removing will) 176 | * Will update 177 | * Make sure that buffered messages are sent in order (database dependent, check what lokijs does now) 178 | -------------------------------------------------------------------------------- /aquila-gateway.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const ascii = "" + 6 | " O o-o o o o-O-o o O \n" + 7 | " / \\ o o | | | | / \\ \n" + 8 | " o---o| | | | | | o---o \n" + 9 | " | |o O | | | | | | \n" + 10 | " o o o-O\\ o-o o-O-o O---oo o \n" + 11 | " \n" + 12 | " o \n" + 13 | " | \n" + 14 | " o--o oo -o- o-o o o o oo o o \n" + 15 | " | | | | | |-' \\ / \\ / | | | | \n" + 16 | " o--O o-o- o o-o o o o-o-o--O \n" + 17 | " | | \n" + 18 | " o--o o--o \n"; 19 | 20 | const ascii2 = "" + 21 | " O o-o o o o-O-o o O \n" + 22 | " / \\ o o | | | | / \\ \n" + 23 | " o---o| | | | | | o---o \n" + 24 | " | |o O | | | | | | \n" + 25 | " o o o-O\\ o-o o-O-o O---oo o \n" + 26 | " _ mqtt-sn \n" + 27 | " __ _ __ _| |_ _____ __ ____ _ _ _ \n" + 28 | " / _` / _` | _/ -_) V V / _` | || | \n" + 29 | " \\__, \\__,_|\\__\\___|\\_/\\_/\\__,_|\\_, |\n" + 30 | " |___/ |__/ \n"; 31 | 32 | console.log(ascii2); 33 | 34 | const main = require('./dist/main'); -------------------------------------------------------------------------------- /dist/CrcUtils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | function calcCrc(data) { 4 | let crc = 0; 5 | let size = data.length; 6 | let i; 7 | let index = 0; 8 | while (--size >= 0) { 9 | crc = (crc ^ data[index++] << 8) & 0xFFFF; 10 | i = 8; 11 | do { 12 | if (crc & 0x8000) { 13 | crc = (crc << 1 ^ 0x1021) & 0xFFFF; 14 | } 15 | else { 16 | crc = (crc << 1) & 0xFFFF; 17 | } 18 | } while (--i); 19 | } 20 | return crc & 0xFFFF; 21 | } 22 | exports.calcCrc = calcCrc; 23 | ; 24 | function checkCrc(data) { 25 | let dataCrc, calcdCrc; 26 | dataCrc = (data[data.length - 1]) << 8; 27 | dataCrc |= (data[data.length - 2]) & 0x00FF; 28 | calcdCrc = calcCrc(data.slice(0, data.length - 2)); 29 | return calcdCrc === dataCrc; 30 | } 31 | exports.checkCrc = checkCrc; 32 | ; 33 | 34 | //# sourceMappingURL=CrcUtils.js.map 35 | -------------------------------------------------------------------------------- /dist/CrcUtils.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/CrcUtils.ts"],"names":[],"mappings":";;AAEA,iBAAwB,IAAY;IAClC,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC;IACvB,IAAI,CAAC,CAAC;IACN,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,OAAM,EAAE,IAAI,IAAI,CAAC,EACjB;QACE,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC;QAC1C,CAAC,GAAG,CAAC,CAAC;QACN,GACA;YACE,IAAG,GAAG,GAAG,MAAM,EACf;gBACE,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC;aACpC;iBAED;gBACE,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC;aAC3B;SACF,QAAO,EAAE,CAAC,EAAE;KACd;IAED,OAAO,GAAG,GAAG,MAAM,CAAC;AACtB,CAAC;AAxBD,0BAwBC;AAAA,CAAC;AAEF,kBAAyB,IAAY;IACnC,IAAI,OAAe,EAAE,QAAgB,CAAC;IAEtC,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;IAE5C,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IAEnD,OAAO,QAAQ,KAAK,OAAO,CAAC;AAC9B,CAAC;AATD,4BASC;AAAA,CAAC","file":"CrcUtils.js","sourcesContent":["\n// CRC algorithm based on Xmodem AVR code\nexport function calcCrc(data: Buffer): number {\n let crc = 0;\n let size = data.length;\n let i;\n let index = 0;\n\n while(--size >= 0)\n {\n crc = (crc ^ data[index++] << 8) & 0xFFFF;\n i = 8;\n do\n {\n if(crc & 0x8000)\n {\n crc = (crc << 1 ^ 0x1021) & 0xFFFF;\n }\n else\n {\n crc = (crc << 1) & 0xFFFF;\n }\n } while(--i);\n }\n\n return crc & 0xFFFF;\n};\n\nexport function checkCrc(data: Buffer): boolean {\n let dataCrc: number, calcdCrc: number;\n // Getting crc from packet\n dataCrc = (data[data.length - 1]) << 8;\n dataCrc |= (data[data.length - 2]) & 0x00FF;\n // Calculating crc\n calcdCrc = calcCrc(data.slice(0, data.length - 2));\n // Comparing\n return calcdCrc === dataCrc;\n};\n"],"sourceRoot":"/Users/rod/workshop/aquila-gateway/src"} -------------------------------------------------------------------------------- /dist/Forwarder.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const events_1 = require("events"); 12 | const Logger_1 = require("./Logger"); 13 | const ACKTIMEOUT = 5000; 14 | const MAX_BUFFER_ALLOWED = 10; 15 | const NACK_CMD = 0x00; 16 | const ACK_CMD = 0x01; 17 | const CONFIG_CMD = 0x02; 18 | const PAIR_CMD = 0x03; 19 | const NO_KEY = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; 20 | class Forwarder extends events_1.EventEmitter { 21 | constructor(db, transport, pan, encryptionKey) { 22 | super(); 23 | this.readyToSend = true; 24 | this.frameBuffer = []; 25 | this.ackTimeout = null; 26 | this.pan = 0x01; 27 | this.key = NO_KEY; 28 | this.pairMode = false; 29 | this.db = db; 30 | this.transport = transport; 31 | if (pan != null) 32 | this.pan = pan; 33 | if (encryptionKey != null) { 34 | if (encryptionKey.length !== 16) 35 | Logger_1.log.warn("Invalid encryption key received, starting without encryption"); 36 | else 37 | this.key = encryptionKey; 38 | } 39 | } 40 | connect() { 41 | this.transport.on('error', (err) => { 42 | Logger_1.log.error("There was an error connecting to the Bridge, make sure it's connected to the computer."); 43 | throw err; 44 | }); 45 | this.transport.on('disconnect', (err) => { 46 | Logger_1.log.error("The Bridge was disconnected from the computer."); 47 | throw err; 48 | }); 49 | this.transport.on('data', (data) => { 50 | if (this.pairMode) 51 | return this.handlePairMode(data); 52 | if (data.length < 4) 53 | return Logger_1.log.error('Forwarder: got message with not enough data'); 54 | let lqi = data[0]; 55 | let rssi = data[1]; 56 | let len = data[2]; 57 | let msgType = data[3]; 58 | if (msgType !== 0xFE) { 59 | if (msgType === NACK_CMD) { 60 | this.readyToSend = true; 61 | clearTimeout(this.ackTimeout); 62 | this.sendNow(); 63 | } 64 | else if (msgType === ACK_CMD) { 65 | this.readyToSend = true; 66 | clearTimeout(this.ackTimeout); 67 | this.sendNow(); 68 | } 69 | else if (msgType === CONFIG_CMD) { 70 | Logger_1.log.trace("GOT CONFIG"); 71 | this.sendConfig(); 72 | this.sendNow(); 73 | } 74 | else 75 | return Logger_1.log.error('Forwarder: bad forwarder msg type'); 76 | return; 77 | } 78 | if (data.length < 7) 79 | return Logger_1.log.error('Forwarder: got message with not enough data'); 80 | let ctrl = data[4]; 81 | let addr = data.readUInt16LE(5); 82 | let mqttsnFrame = data.slice(7); 83 | if (addr === 0 && !this.pairMode) 84 | return; 85 | let message = { 86 | lqi: lqi, 87 | rssi: rssi, 88 | len: len, 89 | msgType: msgType, 90 | ctrl: ctrl, 91 | addr: addr, 92 | mqttsnFrame: mqttsnFrame 93 | }; 94 | this.emit('data', message); 95 | }); 96 | this.transport.on('crcError', (data) => Logger_1.log.error('crcError', data)); 97 | this.transport.on('framingError', (data) => Logger_1.log.error('framingError', data)); 98 | this.transport.on('escapeError', (data) => Logger_1.log.error('escapeError', data)); 99 | return this.transport.connect() 100 | .then(() => { 101 | setTimeout(() => { 102 | setTimeout(() => this.sendConfig(), 2100); 103 | }, 100); 104 | return null; 105 | }); 106 | } 107 | disconnect() { 108 | this.transport.removeAllListeners('data'); 109 | this.transport.removeAllListeners('crcError'); 110 | this.transport.removeAllListeners('framingError'); 111 | this.transport.removeAllListeners('escapeError'); 112 | this.transport.close(); 113 | delete this.transport; 114 | delete this.db; 115 | } 116 | enterPairMode() { 117 | this.pairMode = true; 118 | let frame = new Buffer([3, 0x03, 0x01]); 119 | this.frameBuffer.push(frame); 120 | this.sendNow(); 121 | } 122 | exitPairMode() { 123 | this.pairMode = false; 124 | let frame = new Buffer([3, 0x03, 0x00]); 125 | this.frameBuffer.push(frame); 126 | this.sendNow(); 127 | } 128 | getMode() { 129 | return this.pairMode ? 'pair' : 'normal'; 130 | } 131 | handlePairMode(data) { 132 | return __awaiter(this, void 0, void 0, function* () { 133 | if (data.length < 4) 134 | return Logger_1.log.error('Forwarder: got message with not enough data'); 135 | let lqi = data[0]; 136 | let rssi = data[1]; 137 | let len = data[2]; 138 | let msgType = data[3]; 139 | if (msgType !== 0x03) { 140 | if (msgType === 0x00) { 141 | this.readyToSend = true; 142 | clearTimeout(this.ackTimeout); 143 | this.sendNow(); 144 | } 145 | else if (msgType === 0x01) { 146 | this.readyToSend = true; 147 | clearTimeout(this.ackTimeout); 148 | this.sendNow(); 149 | } 150 | else 151 | return Logger_1.log.error('Forwarder: bad forwarder msg type'); 152 | return; 153 | } 154 | if (data.length < 10) 155 | return Logger_1.log.error('Forwarder: got message with not enough data'); 156 | let ctrl = data[4]; 157 | if (ctrl !== 0x02) 158 | return Logger_1.log.error('Forwarder: bad message'); 159 | let addr = data.readUInt16LE(5); 160 | if (addr !== 0) 161 | return Logger_1.log.error('Forwarder: bad address for pair mode'); 162 | let paircmd = data[8]; 163 | if (paircmd !== PAIR_CMD) 164 | return Logger_1.log.warn("Bad cmd on pair message"); 165 | let randomId = data[9]; 166 | let newAddr; 167 | try { 168 | newAddr = yield this.db.getNextDeviceAddress(); 169 | } 170 | catch (err) { 171 | return Logger_1.log.error("Error getting next device address from DB.", err); 172 | } 173 | if (newAddr == null || isNaN(newAddr)) 174 | return Logger_1.log.warn("WARNING: Max registered devices reached..."); 175 | let device = { 176 | address: newAddr, 177 | connected: false, 178 | state: 'disconnected', 179 | waitingPingres: false, 180 | lqi: 0, 181 | rssi: 0, 182 | duration: 10, 183 | lastSeen: new Date(), 184 | willTopic: null, 185 | willMessage: null, 186 | willQoS: null, 187 | willRetain: null 188 | }; 189 | try { 190 | yield this.db.setDevice(device); 191 | } 192 | catch (err) { 193 | return Logger_1.log.error("Error saving Device to DB.", err); 194 | } 195 | let frame = Buffer.from([7, 0x03, 0x03, 0x00, 0x00, 21, 0x03, randomId, newAddr, this.pan]); 196 | let key = Buffer.from(this.key); 197 | frame = Buffer.concat([frame, key]); 198 | this.frameBuffer.push(frame); 199 | this.sendNow(); 200 | this.exitPairMode(); 201 | this.emit("devicePaired", device); 202 | }); 203 | } 204 | send(addr, packet) { 205 | if (this.pairMode) 206 | return false; 207 | if (this.frameBuffer.length >= MAX_BUFFER_ALLOWED) { 208 | Logger_1.log.trace('Forwarder buffer full, packet dropped'); 209 | this.sendNow(); 210 | return false; 211 | } 212 | let addrL = (addr) & 0xFF; 213 | let addrH = (addr >> 8) & 0xFF; 214 | let frame = new Buffer([5, 0xFE, 1, addrL, addrH]); 215 | frame = Buffer.concat([frame, packet]); 216 | this.frameBuffer.push(frame); 217 | this.sendNow(); 218 | return true; 219 | } 220 | sendNow() { 221 | if (!this.readyToSend) 222 | return; 223 | let frame = this.frameBuffer.shift(); 224 | if (typeof (frame) === 'undefined') 225 | return; 226 | this.readyToSend = false; 227 | this.transport.write(frame); 228 | this.ackTimeout = setTimeout(() => { 229 | this.readyToSend = true; 230 | this.sendNow(); 231 | }, ACKTIMEOUT); 232 | } 233 | sendConfig() { 234 | let frame = Buffer.from([19, CONFIG_CMD, this.pan]); 235 | let key = Buffer.from(this.key); 236 | frame = Buffer.concat([frame, key]); 237 | Logger_1.log.trace("Sending config:", frame); 238 | this.frameBuffer.push(frame); 239 | this.sendNow(); 240 | } 241 | } 242 | exports.Forwarder = Forwarder; 243 | 244 | //# sourceMappingURL=Forwarder.js.map 245 | -------------------------------------------------------------------------------- /dist/Forwarder.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/Forwarder.ts"],"names":[],"mappings":"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","file":"Forwarder.js","sourcesContent":["\nimport { EventEmitter } from 'events';\nimport { log } from './Logger';\nimport { TransportInterface, DBInterface } from './interfaces';\n\n/*\n Manages connections with bridge and initial parsing\n\n Events:\n data ({lqi, rssi, addr, mqttsnFrame})\n\n Serial frame formats:\n\n MQTT-SN forwarder: msgType = 0xFE\n len, msgType, ctrl, addrL, addrH, mqttsnpacket\n NACK\n len, 0x00\n ACK:\n len, 0x01\n CONFIG:\n len, 0x02, [PAN], [encryption key x 16]\n ENTER PAIR:\n len, 0x03, 0x01\n EXIT PAIR\n len, 0x03, 0x00\n PAIR REQ\n len, 0x03, 0x02, addrL, addrH, length (3), pair cmd (0x03), randomId\n PAIR RES\n len, 0x03, 0x03, addrL, addrH, length (4), pair cmd (0x03), randomId, newAddr, newPan (, [encryption key x 16] )\n\n TODO: add not connected state management\n */\n\nconst ACKTIMEOUT = 5000;\nconst MAX_BUFFER_ALLOWED = 10;\n\nconst NACK_CMD = 0x00;\nconst ACK_CMD = 0x01;\nconst CONFIG_CMD = 0x02;\nconst PAIR_CMD = 0x03;\n\nconst NO_KEY = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF];\n\nexport interface ForwarderMessage {\n lqi: number;\n rssi: number;\n len: number;\n msgType: number,\n ctrl: number,\n addr: number,\n mqttsnFrame: Buffer\n}\n\nexport class Forwarder extends EventEmitter {\n\n db: DBInterface;\n transport: TransportInterface;\n readyToSend: boolean = true;\n frameBuffer: Array = [];\n ackTimeout: NodeJS.Timer = null;\n pan: number = 0x01;\n key: Array = NO_KEY;\n pairMode: boolean = false;\n\n constructor(db: DBInterface, transport: TransportInterface, pan?: number, encryptionKey?: Array) {\n super();\n\n // For pair address management\n this.db = db;\n this.transport = transport;\n\n if(pan != null) this.pan = pan;\n if(encryptionKey != null) {\n if(encryptionKey.length !== 16) log.warn(\"Invalid encryption key received, starting without encryption\");\n else this.key = encryptionKey;\n }\n\n }\n\n connect(): Promise {\n \n this.transport.on('error', (err: any) => {\n log.error(\"There was an error connecting to the Bridge, make sure it's connected to the computer.\");\n throw err;\n });\n\n this.transport.on('disconnect', (err: any) => {\n log.error(\"The Bridge was disconnected from the computer.\");\n throw err;\n });\n\n this.transport.on('data', (data: Buffer) => {\n //log.trace('Data: ', data);\n \n if(this.pairMode) return this.handlePairMode(data);\n\n // 5 of mqtt-sn forwarder, 2 of lqi and rssi\n if(data.length < 4) return log.error('Forwarder: got message with not enough data');\n let lqi = data[0];\n let rssi = data[1];\n let len = data[2];\n let msgType = data[3];\n if(msgType !== 0xFE) {\n if(msgType === NACK_CMD) {\n // NACK\n //console.log(\"NACK\");\n this.readyToSend = true;\n clearTimeout(this.ackTimeout);\n this.sendNow(); // Send any remaining messages\n }\n else if(msgType === ACK_CMD) {\n // ACK\n //console.log(\"ACK\");\n this.readyToSend = true;\n clearTimeout(this.ackTimeout);\n this.sendNow(); // Send any remaining messages\n }\n else if(msgType === CONFIG_CMD) {\n log.trace(\"GOT CONFIG\");\n // CONFIG req, respond with CONFIG\n this.sendConfig();\n this.sendNow(); // Send any remaining messages\n }\n else return log.error('Forwarder: bad forwarder msg type');\n return;\n } \n if(data.length < 7) return log.error('Forwarder: got message with not enough data');\n let ctrl = data[4];\n let addr = data.readUInt16LE(5);\n let mqttsnFrame = data.slice(7);\n\n // If not in pair mode, ignore any message from address 0 (pair mode address)\n if(addr === 0 && !this.pairMode) return;\n\n let message: ForwarderMessage = {\n lqi: lqi,\n rssi: rssi,\n len: len,\n msgType: msgType,\n ctrl: ctrl,\n addr: addr,\n mqttsnFrame: mqttsnFrame\n }\n\n this.emit('data', message);\n \n });\n this.transport.on('crcError', (data: Buffer) => log.error('crcError', data) );\n this.transport.on('framingError', (data: Buffer) => log.error('framingError', data) );\n this.transport.on('escapeError', (data: Buffer) => log.error('escapeError', data) );\n\n \n\n return this.transport.connect()\n .then(() => {\n // Assure that config is sent on start, in addition to when the bridge requests it\n // Some USB-Serial chips have problems sending the config request on startup, this is a workaround for that\n // We wait 2.1 seconds for accounting to most Arduino bootloader's startup time (2s)\n setTimeout(() => {\n setTimeout(() => this.sendConfig(), 2100);\n }, 100);\n return null;\n });\n \n }\n\n disconnect() {\n this.transport.removeAllListeners('data');\n this.transport.removeAllListeners('crcError');\n this.transport.removeAllListeners('framingError');\n this.transport.removeAllListeners('escapeError');\n this.transport.close();\n delete this.transport;\n delete this.db;\n }\n\n enterPairMode() {\n this.pairMode = true;\n let frame = new Buffer([3, 0x03, 0x01]);\n this.frameBuffer.push(frame);\n this.sendNow();\n }\n\n exitPairMode() {\n this.pairMode = false;\n let frame = new Buffer([3, 0x03, 0x00]);\n this.frameBuffer.push(frame);\n this.sendNow();\n }\n\n getMode() {\n return this.pairMode ? 'pair' : 'normal';\n }\n\n async handlePairMode(data: Buffer) {\n if(data.length < 4) return log.error('Forwarder: got message with not enough data');\n let lqi = data[0];\n let rssi = data[1];\n let len = data[2];\n let msgType = data[3];\n if(msgType !== 0x03) {\n if(msgType === 0x00) {\n // NACK\n //console.log(\"NACK\");\n this.readyToSend = true;\n clearTimeout(this.ackTimeout);\n this.sendNow(); // Send any remaining messages\n }\n else if(msgType === 0x01) {\n // ACK\n //console.log(\"ACK\");\n this.readyToSend = true;\n clearTimeout(this.ackTimeout);\n this.sendNow(); // Send any remaining messages\n }\n else return log.error('Forwarder: bad forwarder msg type');\n return;\n } \n // Parse PAIR REQ\n if(data.length < 10) return log.error('Forwarder: got message with not enough data');\n let ctrl = data[4];\n if(ctrl !== 0x02) return log.error('Forwarder: bad message');\n let addr = data.readUInt16LE(5);\n if(addr !== 0) return log.error('Forwarder: bad address for pair mode');\n //let len = data[7];\n let paircmd = data [8];\n if(paircmd !== PAIR_CMD) return log.warn(\"Bad cmd on pair message\");\n\n let randomId = data[9]; // For managin when multiple devices try to pair, temporal \"addressing\"\n\n // Assing address and send\n let newAddr\n try {\n newAddr = await this.db.getNextDeviceAddress();\n }\n catch(err) {\n return log.error(\"Error getting next device address from DB.\", err);\n }\n if(newAddr == null || isNaN(newAddr)) return log.warn(\"WARNING: Max registered devices reached...\");\n // Create empty device for occupying the new address\n let device = {\n address: newAddr,\n connected: false,\n state: 'disconnected',\n waitingPingres: false,\n lqi: 0,\n rssi: 0,\n duration: 10,\n lastSeen: new Date(),\n willTopic: null,\n willMessage: null,\n willQoS: null,\n willRetain: null\n };\n\n try {\n await this.db.setDevice(device);\n }\n catch(err) {\n return log.error(\"Error saving Device to DB.\", err);\n }\n\n // PAIR RES\n let frame = Buffer.from([7, 0x03, 0x03, 0x00, 0x00, 21, 0x03, randomId, newAddr, this.pan]);\n let key = Buffer.from(this.key);\n frame = Buffer.concat([frame, key]);\n //console.log(\"Pair RES:\", frame);\n this.frameBuffer.push(frame);\n this.sendNow();\n\n this.exitPairMode();\n\n this.emit(\"devicePaired\", device);\n }\n\n send(addr: number, packet: Buffer) {\n // Dont allow sending any message out of pair messages in pair mode\n if(this.pairMode) return false;\n\n // Check for max buffer allowed\n if(this.frameBuffer.length >= MAX_BUFFER_ALLOWED) {\n log.trace('Forwarder buffer full, packet dropped');\n this.sendNow();\n return false;\n }\n\n // len, msgType, ctrl, addrL, addrH, mqttsnpacket\n let addrL = (addr) & 0xFF;\n let addrH = (addr>>8) & 0xFF;\n let frame = new Buffer([5, 0xFE, 1, addrL, addrH]);\n frame = Buffer.concat([frame, packet]);\n this.frameBuffer.push(frame);\n this.sendNow();\n\n return true;\n }\n\n sendNow() {\n if(!this.readyToSend) return;\n let frame = this.frameBuffer.shift();\n if(typeof(frame) === 'undefined') return;\n this.readyToSend = false;\n this.transport.write(frame);\n this.ackTimeout = setTimeout( () => {\n this.readyToSend = true;\n this.sendNow(); // Make sure any pending messages are sent\n }, ACKTIMEOUT);\n }\n\n sendConfig() {\n let frame = Buffer.from([19, CONFIG_CMD, this.pan]);\n let key = Buffer.from(this.key);\n frame = Buffer.concat([frame, key])\n log.trace(\"Sending config:\", frame);\n this.frameBuffer.push(frame);\n this.sendNow();\n }\n\n}\n"],"sourceRoot":"/Users/rod/workshop/aquila-gateway/src"} -------------------------------------------------------------------------------- /dist/GatewayDB.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const loki = require("lokijs"); 12 | const path = require("path"); 13 | const fs = require("fs"); 14 | class GatewayDB { 15 | constructor(dataPath) { 16 | this.deviceIndex = 1; 17 | this.topicIndex = 1; 18 | let dataDir = path.dirname(dataPath); 19 | if (!fs.existsSync(dataDir)) { 20 | fs.mkdirSync(dataDir); 21 | } 22 | this._onSigint = () => { 23 | this.db.close(() => { 24 | console.log('closed'); 25 | process.exit(); 26 | }); 27 | }; 28 | this.dataPath = dataPath; 29 | } 30 | destructor() { 31 | this.db.close(() => { }); 32 | process.removeListener('SIGINT', this._onSigint); 33 | } 34 | connect() { 35 | return new Promise((resolve, reject) => { 36 | this.db = new loki(this.dataPath, { 37 | autosave: true, 38 | autosaveInterval: 60000, 39 | autoload: true, 40 | autoloadCallback: () => this.loadHandler(resolve) 41 | }); 42 | }); 43 | } 44 | loadHandler(resolve) { 45 | this.devices = this.db.getCollection('devices'); 46 | if (this.devices === null) 47 | this.devices = this.db.addCollection('devices'); 48 | this.devices.findAndUpdate(() => { 49 | return true; 50 | }, device => { 51 | device.connected = false; 52 | device.waitingPingres = false; 53 | device.state = 'disconnected'; 54 | return device; 55 | }); 56 | this.topics = this.db.getCollection('topics'); 57 | if (this.topics === null) 58 | this.topics = this.db.addCollection('topics'); 59 | this.subscriptions = this.db.getCollection('subscriptions'); 60 | if (this.subscriptions === null) 61 | this.subscriptions = this.db.addCollection('subscriptions'); 62 | this.messages = this.db.getCollection('messages'); 63 | if (this.messages === null) 64 | this.messages = this.db.addCollection('messages'); 65 | process.on('SIGINT', this._onSigint); 66 | this.deviceIndex = this.devices.maxId + 1; 67 | this.topicIndex = this.topics.maxId + 1; 68 | return resolve(null); 69 | } 70 | setDevice(device) { 71 | let found = null; 72 | if (device.address !== undefined) 73 | found = this.devices.findOne({ address: device.address }); 74 | else if (device.id !== undefined) 75 | found = this.devices.findOne({ id: device.id }); 76 | if (!found) { 77 | if (!device.id) { 78 | device.id = this.deviceIndex; 79 | this.deviceIndex++; 80 | } 81 | this.devices.insert(device); 82 | } 83 | else { 84 | if (device.address !== undefined) 85 | found.address = device.address; 86 | if (device.id !== undefined) 87 | found.id = device.id; 88 | if (device.connected !== undefined) 89 | found.connected = device.connected; 90 | if (device.waitingPingres !== undefined) 91 | found.waitingPingres = device.waitingPingres; 92 | if (device.lqi !== undefined) 93 | found.lqi = device.lqi; 94 | if (device.rssi !== undefined) 95 | found.rssi = device.rssi; 96 | if (device.duration !== undefined) 97 | found.duration = device.duration; 98 | if (device.willTopic !== undefined) 99 | found.willTopic = device.willTopic; 100 | if (device.willMessage !== undefined) 101 | found.willMessage = device.willMessage; 102 | this.devices.update(found); 103 | } 104 | return Promise.resolve(found); 105 | } 106 | removeDevice(device) { 107 | let found = null; 108 | if (device.address !== undefined) 109 | found = this.devices.findOne({ address: device.address }); 110 | else if (device.id !== undefined) 111 | found = this.devices.findOne({ id: device.id }); 112 | if (found == null) 113 | return Promise.resolve(false); 114 | this.topics.removeWhere({ device: found.id }); 115 | this.subscriptions.removeWhere({ device: found.id }); 116 | this.messages.removeWhere({ device: found.id }); 117 | this.devices.removeWhere({ id: found.id }); 118 | return Promise.resolve(true); 119 | } 120 | getDeviceByAddr(addr) { 121 | let found = this.devices.findOne({ address: addr }); 122 | return Promise.resolve(found); 123 | } 124 | getDeviceById(id) { 125 | let found = this.devices.findOne({ id: id }); 126 | return Promise.resolve(found); 127 | } 128 | getAllDevices() { 129 | let found = this.devices.find(); 130 | return Promise.resolve(found); 131 | } 132 | getNextDeviceAddress() { 133 | let found = this.devices 134 | .chain() 135 | .find() 136 | .simplesort('address') 137 | .data() 138 | .map(item => { 139 | return item.address; 140 | }); 141 | let nextIndex = null; 142 | if (found.length === 0) 143 | return Promise.resolve(1); 144 | for (let i = 0; i < found.length; i++) { 145 | let current = found[i]; 146 | let prev = 0; 147 | if (i != 0) 148 | prev = found[i - 1]; 149 | if (current > prev + 1) { 150 | nextIndex = prev + 1; 151 | return Promise.resolve(nextIndex); 152 | } 153 | } 154 | nextIndex = found[found.length - 1] + 1; 155 | if (nextIndex > 0xfe || nextIndex === 0xf0) 156 | return Promise.resolve(null); 157 | return Promise.resolve(nextIndex); 158 | } 159 | getAllTopics() { 160 | let found = this.topics.find(); 161 | return Promise.resolve(found); 162 | } 163 | setTopic(deviceIdOrAddress, topic, topicId, type) { 164 | return __awaiter(this, void 0, void 0, function* () { 165 | if (typeof type === 'undefined' || type === null) 166 | type = 'normal'; 167 | if (deviceIdOrAddress.id === undefined) { 168 | if (deviceIdOrAddress.address === undefined) 169 | return Promise.resolve(false); 170 | let dev = yield this.getDeviceByAddr(deviceIdOrAddress.address); 171 | if (dev) 172 | deviceIdOrAddress.id = dev.id; 173 | if (deviceIdOrAddress.id == null) 174 | return Promise.resolve(false); 175 | } 176 | let found = this.topics.findOne({ $and: [{ device: deviceIdOrAddress.id }, { id: topicId }] }); 177 | if (!found) { 178 | if (!topicId) { 179 | topicId = this.topicIndex; 180 | this.topicIndex++; 181 | } 182 | found = { 183 | device: deviceIdOrAddress.id, 184 | name: topic, 185 | id: topicId, 186 | type: type 187 | }; 188 | this.topics.insert(found); 189 | } 190 | else { 191 | found.device = deviceIdOrAddress.id; 192 | found.name = topic; 193 | found.id = topicId; 194 | found.type = type; 195 | this.topics.update(found); 196 | } 197 | return Promise.resolve(found); 198 | }); 199 | } 200 | getTopic(deviceIdOrAddress, idOrName) { 201 | return __awaiter(this, void 0, void 0, function* () { 202 | if (deviceIdOrAddress.id === undefined) { 203 | if (deviceIdOrAddress.address === undefined) 204 | return Promise.resolve(false); 205 | let dev = yield this.getDeviceByAddr(deviceIdOrAddress.address); 206 | if (dev) 207 | deviceIdOrAddress.id = dev.id; 208 | if (deviceIdOrAddress.id == null) 209 | return Promise.resolve(false); 210 | } 211 | let query = { $and: [{ device: deviceIdOrAddress.id }] }; 212 | if (idOrName.id !== undefined) 213 | query.$and.push({ id: idOrName.id }); 214 | if (idOrName.name !== undefined) 215 | query.$and.push({ name: idOrName.name }); 216 | let found = this.topics.findOne(query); 217 | return Promise.resolve(found); 218 | }); 219 | } 220 | getTopicsFromDevice(deviceIdOrAddress) { 221 | return __awaiter(this, void 0, void 0, function* () { 222 | if (deviceIdOrAddress.id === undefined) { 223 | if (deviceIdOrAddress.address === undefined) 224 | return Promise.resolve(false); 225 | let dev = yield this.getDeviceByAddr(deviceIdOrAddress.address); 226 | if (dev) 227 | deviceIdOrAddress.id = dev.id; 228 | if (deviceIdOrAddress.id == null) 229 | return Promise.resolve(false); 230 | } 231 | let query = { device: deviceIdOrAddress.id }; 232 | let found = this.topics.find(query); 233 | return Promise.resolve(found); 234 | }); 235 | } 236 | getAllSubscriptions() { 237 | let found = this.subscriptions.find(); 238 | return Promise.resolve(found); 239 | } 240 | setSubscription(deviceIdOrAddress, topicIdOrName, qos) { 241 | return __awaiter(this, void 0, void 0, function* () { 242 | if (typeof qos === 'undefined' || qos === null) 243 | qos = 0; 244 | if (deviceIdOrAddress.id === undefined) { 245 | if (deviceIdOrAddress.address === undefined) 246 | return Promise.resolve(false); 247 | let dev = yield this.getDeviceByAddr(deviceIdOrAddress.address); 248 | if (dev) 249 | deviceIdOrAddress.id = dev.id; 250 | if (deviceIdOrAddress.id == null) 251 | return Promise.resolve(false); 252 | } 253 | if (topicIdOrName.name === undefined) { 254 | if (topicIdOrName.id === undefined) 255 | return Promise.resolve(false); 256 | let topic = yield this.getTopic({ id: deviceIdOrAddress.id }, { id: topicIdOrName.id }); 257 | topicIdOrName.name = topic.name; 258 | } 259 | let found = this.subscriptions.findOne({ 260 | $and: [{ device: deviceIdOrAddress.id }, { topic: topicIdOrName.name }] 261 | }); 262 | if (!found) { 263 | found = { 264 | device: deviceIdOrAddress.id, 265 | topic: topicIdOrName.name, 266 | qos: qos 267 | }; 268 | this.subscriptions.insert(found); 269 | } 270 | else { 271 | found.device = deviceIdOrAddress.id; 272 | found.topic = topicIdOrName.name; 273 | found.qos = qos; 274 | this.subscriptions.update(found); 275 | } 276 | return Promise.resolve(found); 277 | }); 278 | } 279 | getSubscriptionsFromTopic(topicName) { 280 | let found = this.subscriptions.find({ topic: topicName }); 281 | return Promise.resolve(found); 282 | } 283 | getSubscriptionsFromDevice(deviceIdOrAddress) { 284 | return __awaiter(this, void 0, void 0, function* () { 285 | if (deviceIdOrAddress.id === undefined) { 286 | if (deviceIdOrAddress.address === undefined) 287 | return Promise.resolve(false); 288 | let dev = yield this.getDeviceByAddr(deviceIdOrAddress.address); 289 | if (dev) 290 | deviceIdOrAddress.id = dev.id; 291 | if (deviceIdOrAddress.id == null) 292 | return Promise.resolve(false); 293 | } 294 | let found = this.subscriptions.find({ device: deviceIdOrAddress.id }); 295 | return Promise.resolve(found); 296 | }); 297 | } 298 | removeSubscriptionsFromDevice(deviceIdOrAddress) { 299 | return __awaiter(this, void 0, void 0, function* () { 300 | if (deviceIdOrAddress.id === undefined) { 301 | if (deviceIdOrAddress.address === undefined) 302 | return Promise.resolve(false); 303 | let dev = yield this.getDeviceByAddr(deviceIdOrAddress.address); 304 | if (dev) 305 | deviceIdOrAddress.id = dev.id; 306 | if (deviceIdOrAddress.id == null) 307 | return Promise.resolve(false); 308 | } 309 | this.subscriptions.removeWhere({ device: deviceIdOrAddress.id }); 310 | return Promise.resolve(true); 311 | }); 312 | } 313 | removeSubscription(deviceIdOrAddress, topicName, topicType) { 314 | return __awaiter(this, void 0, void 0, function* () { 315 | if (deviceIdOrAddress.id === undefined) { 316 | if (deviceIdOrAddress.address === undefined) 317 | return Promise.resolve(false); 318 | let dev = yield this.getDeviceByAddr(deviceIdOrAddress.address); 319 | if (dev) 320 | deviceIdOrAddress.id = dev.id; 321 | if (deviceIdOrAddress.id == null) 322 | return Promise.resolve(false); 323 | } 324 | this.subscriptions.removeWhere({ 325 | $and: [{ device: deviceIdOrAddress.id }, { topic: topicName }] 326 | }); 327 | return Promise.resolve(true); 328 | }); 329 | } 330 | pushMessage(message) { 331 | this.messages.insert(message); 332 | return Promise.resolve(true); 333 | } 334 | popMessagesFromDevice(deviceId) { 335 | if (typeof deviceId === 'undefined' || deviceId === null) 336 | return Promise.resolve(false); 337 | let messages = this.messages.find({ device: deviceId }); 338 | this.messages.removeWhere({ device: deviceId }); 339 | return Promise.resolve(messages); 340 | } 341 | } 342 | exports.GatewayDB = GatewayDB; 343 | 344 | //# sourceMappingURL=GatewayDB.js.map 345 | -------------------------------------------------------------------------------- /dist/GwMonitor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const events_1 = require("events"); 12 | const Logger_1 = require("./Logger"); 13 | class GwMonitor extends events_1.EventEmitter { 14 | constructor(gateway, prefix) { 15 | super(); 16 | this.prefix = prefix; 17 | if (this.prefix == null) 18 | this.prefix = 'gw'; 19 | this.gateway = gateway; 20 | this.gateway.client.subscribe(this.prefix + '/devices/get'); 21 | this.gateway.client.subscribe(this.prefix + '/subscriptions/get'); 22 | this.gateway.client.subscribe(this.prefix + '/topics/get'); 23 | this.gateway.client.subscribe(this.prefix + '/forwarder/mode/get'); 24 | this.gateway.client.subscribe(this.prefix + '/devices/req'); 25 | this.gateway.client.subscribe(this.prefix + '/devices/remove/req'); 26 | this.gateway.client.subscribe(this.prefix + '/subscriptions/req'); 27 | this.gateway.client.subscribe(this.prefix + '/topics/req'); 28 | this.gateway.client.subscribe(this.prefix + '/forwarder/mode/req'); 29 | this.gateway.client.subscribe(this.prefix + '/forwarder/enterpair'); 30 | this.gateway.client.subscribe(this.prefix + '/forwarder/exitpair'); 31 | this.gateway.client.subscribe(this.prefix + '/forwarder/mode/get'); 32 | this._onMessage = (topic, message, packet) => this.onMessage(topic, message, packet); 33 | this._onDeviceConnected = (device) => this.onDeviceConnected(device); 34 | this._onDeviceDisconnected = (device) => this.onDeviceDisconnected(device); 35 | this._onDevicePaired = (device) => this.onDevicePaired(device); 36 | this.gateway.client.on('message', this._onMessage); 37 | this.gateway.on('deviceConnected', this._onDeviceConnected); 38 | this.gateway.on('deviceDisconnected', this._onDeviceDisconnected); 39 | this.gateway.forwarder.on('devicePaired', this._onDevicePaired); 40 | } 41 | destructor() { 42 | this.gateway.client.unsubscribe(this.prefix + '/devices/get'); 43 | this.gateway.client.unsubscribe(this.prefix + '/subscriptions/get'); 44 | this.gateway.client.unsubscribe(this.prefix + '/topics/get'); 45 | this.gateway.client.unsubscribe(this.prefix + '/forwarder/mode/get'); 46 | this.gateway.client.unsubscribe(this.prefix + '/devices/req'); 47 | this.gateway.client.unsubscribe(this.prefix + '/devices/remove/req'); 48 | this.gateway.client.unsubscribe(this.prefix + '/subscriptions/req'); 49 | this.gateway.client.unsubscribe(this.prefix + '/topics/req'); 50 | this.gateway.client.unsubscribe(this.prefix + '/forwarder/mode/req'); 51 | this.gateway.client.unsubscribe(this.prefix + '/forwarder/enterpair'); 52 | this.gateway.client.unsubscribe(this.prefix + '/forwarder/exitpair'); 53 | this.gateway.client.unsubscribe(this.prefix + '/forwarder/mode/get'); 54 | this.gateway.client.removeListener('message', this._onMessage); 55 | this.gateway.removeListener('deviceConnected', this._onDeviceConnected); 56 | this.gateway.removeListener('deviceDisconnected', this._onDeviceDisconnected); 57 | this.gateway.forwarder.removeListener('devicePaired', this._onDevicePaired); 58 | delete this.gateway; 59 | } 60 | onMessage(topic, message, packet) { 61 | return __awaiter(this, void 0, void 0, function* () { 62 | if (topic === this.prefix + '/devices/req' || topic === this.prefix + '/devices/get') { 63 | let temp; 64 | try { 65 | temp = yield this.gateway.db.getAllDevices(); 66 | } 67 | catch (err) { 68 | return Logger_1.log.error(err); 69 | } 70 | let devices = JSON.parse(JSON.stringify(temp)); 71 | for (let i in devices) { 72 | delete devices[i].meta; 73 | delete devices[i].$loki; 74 | } 75 | this.gateway.client.publish(this.prefix + '/devices/res', JSON.stringify(devices)); 76 | } 77 | if (topic === this.prefix + '/devices/remove/req') { 78 | let result = false; 79 | let device = null; 80 | try { 81 | device = JSON.parse(message.toString()); 82 | result = yield this.gateway.db.removeDevice(device); 83 | } 84 | catch (err) { 85 | Logger_1.log.warn(err); 86 | result = false; 87 | } 88 | let response = { 89 | success: result 90 | }; 91 | this.gateway.client.publish(this.prefix + '/devices/remove/res', JSON.stringify(response)); 92 | } 93 | if (topic === this.prefix + '/subscriptions/req' || 94 | topic === this.prefix + '/subscriptions/get') { 95 | let temp; 96 | try { 97 | temp = yield this.gateway.db.getAllSubscriptions(); 98 | } 99 | catch (err) { 100 | return Logger_1.log.error(err); 101 | } 102 | let subscriptions = JSON.parse(JSON.stringify(temp)); 103 | for (let i in subscriptions) { 104 | delete subscriptions[i].meta; 105 | delete subscriptions[i].$loki; 106 | } 107 | this.gateway.client.publish(this.prefix + '/subscriptions/res', JSON.stringify(subscriptions)); 108 | } 109 | if (topic === this.prefix + '/topics/req' || topic === this.prefix + '/topics/get') { 110 | let temp; 111 | try { 112 | temp = yield this.gateway.db.getAllTopics(); 113 | } 114 | catch (err) { 115 | return Logger_1.log.error(err); 116 | } 117 | let topics = JSON.parse(JSON.stringify(temp)); 118 | for (let i in topics) { 119 | delete topics[i].meta; 120 | delete topics[i].$loki; 121 | } 122 | this.gateway.client.publish(this.prefix + '/topics/res', JSON.stringify(topics)); 123 | } 124 | if (topic === this.prefix + '/forwarder/enterpair') { 125 | this.gateway.forwarder.enterPairMode(); 126 | } 127 | if (topic === this.prefix + '/forwarder/exitpair') { 128 | this.gateway.forwarder.exitPairMode(); 129 | } 130 | if (topic === this.prefix + '/forwarder/mode/req' || 131 | topic === this.prefix + '/forwarder/mode/get') { 132 | let mode = this.gateway.forwarder.getMode(); 133 | this.gateway.client.publish(this.prefix + '/forwarder/mode/res', JSON.stringify({ mode: mode })); 134 | } 135 | }); 136 | } 137 | onDeviceConnected(device) { 138 | let dev = JSON.parse(JSON.stringify(device)); 139 | delete dev.meta; 140 | delete dev.$loki; 141 | this.gateway.client.publish(this.prefix + '/devices/connected', JSON.stringify(dev)); 142 | } 143 | onDeviceDisconnected(device) { 144 | let dev = JSON.parse(JSON.stringify(device)); 145 | delete dev.meta; 146 | delete dev.$loki; 147 | this.gateway.client.publish(this.prefix + '/devices/disconnected', JSON.stringify(dev)); 148 | } 149 | onDevicePaired(device) { 150 | let dev = JSON.parse(JSON.stringify(device)); 151 | delete dev.meta; 152 | delete dev.$loki; 153 | this.gateway.client.publish(this.prefix + '/devices/paired', JSON.stringify(dev)); 154 | } 155 | } 156 | exports.GwMonitor = GwMonitor; 157 | 158 | //# sourceMappingURL=GwMonitor.js.map 159 | -------------------------------------------------------------------------------- /dist/GwMonitor.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/GwMonitor.ts"],"names":[],"mappings":"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","file":"GwMonitor.js","sourcesContent":["import { EventEmitter } from 'events';\nimport { Gateway } from './Gateway';\nimport { log } from './Logger';\n\nexport class GwMonitor extends EventEmitter {\n gateway: Gateway;\n prefix: string;\n\n _onMessage: any;\n _onDeviceConnected: any;\n _onDeviceDisconnected: any;\n _onDevicePaired: any;\n\n constructor(gateway: Gateway, prefix?: string) {\n super();\n\n // Monitor topics prefix\n this.prefix = prefix;\n if (this.prefix == null) this.prefix = 'gw';\n\n this.gateway = gateway;\n\n // \"*/get\" requests deprecated, please use \"*/req\" for consistency with \"*/res\" responses.\n this.gateway.client.subscribe(this.prefix + '/devices/get');\n this.gateway.client.subscribe(this.prefix + '/subscriptions/get');\n this.gateway.client.subscribe(this.prefix + '/topics/get');\n this.gateway.client.subscribe(this.prefix + '/forwarder/mode/get');\n\n this.gateway.client.subscribe(this.prefix + '/devices/req');\n this.gateway.client.subscribe(this.prefix + '/devices/remove/req');\n this.gateway.client.subscribe(this.prefix + '/subscriptions/req');\n this.gateway.client.subscribe(this.prefix + '/topics/req');\n this.gateway.client.subscribe(this.prefix + '/forwarder/mode/req');\n this.gateway.client.subscribe(this.prefix + '/forwarder/enterpair');\n this.gateway.client.subscribe(this.prefix + '/forwarder/exitpair');\n this.gateway.client.subscribe(this.prefix + '/forwarder/mode/get');\n\n this._onMessage = (topic: string, message: Buffer, packet: any) =>\n this.onMessage(topic, message, packet);\n this._onDeviceConnected = (device: any) => this.onDeviceConnected(device);\n this._onDeviceDisconnected = (device: any) => this.onDeviceDisconnected(device);\n this._onDevicePaired = (device: any) => this.onDevicePaired(device);\n\n this.gateway.client.on('message', this._onMessage);\n this.gateway.on('deviceConnected', this._onDeviceConnected);\n this.gateway.on('deviceDisconnected', this._onDeviceDisconnected);\n this.gateway.forwarder.on('devicePaired', this._onDevicePaired);\n }\n\n destructor() {\n this.gateway.client.unsubscribe(this.prefix + '/devices/get');\n this.gateway.client.unsubscribe(this.prefix + '/subscriptions/get');\n this.gateway.client.unsubscribe(this.prefix + '/topics/get');\n this.gateway.client.unsubscribe(this.prefix + '/forwarder/mode/get');\n\n this.gateway.client.unsubscribe(this.prefix + '/devices/req');\n this.gateway.client.unsubscribe(this.prefix + '/devices/remove/req');\n this.gateway.client.unsubscribe(this.prefix + '/subscriptions/req');\n this.gateway.client.unsubscribe(this.prefix + '/topics/req');\n this.gateway.client.unsubscribe(this.prefix + '/forwarder/mode/req');\n this.gateway.client.unsubscribe(this.prefix + '/forwarder/enterpair');\n this.gateway.client.unsubscribe(this.prefix + '/forwarder/exitpair');\n this.gateway.client.unsubscribe(this.prefix + '/forwarder/mode/get');\n\n this.gateway.client.removeListener('message', this._onMessage);\n this.gateway.removeListener('deviceConnected', this._onDeviceConnected);\n this.gateway.removeListener('deviceDisconnected', this._onDeviceDisconnected);\n this.gateway.forwarder.removeListener('devicePaired', this._onDevicePaired);\n\n delete this.gateway;\n }\n\n async onMessage(topic: string, message: Buffer, packet: any) {\n if (topic === this.prefix + '/devices/req' || topic === this.prefix + '/devices/get') {\n let temp;\n try {\n temp = await this.gateway.db.getAllDevices();\n } catch (err) {\n return log.error(err);\n }\n let devices = JSON.parse(JSON.stringify(temp)); // make copy, fixes crash with lokijs\n // Cleanup\n for (let i in devices) {\n delete devices[i].meta;\n delete devices[i].$loki;\n }\n this.gateway.client.publish(this.prefix + '/devices/res', JSON.stringify(devices));\n }\n\n if (topic === this.prefix + '/devices/remove/req') {\n let result = false;\n let device = null;\n try {\n device = JSON.parse(message.toString());\n result = await this.gateway.db.removeDevice(device);\n } catch (err) {\n log.warn(err);\n result = false;\n }\n let response = {\n success: result\n };\n this.gateway.client.publish(this.prefix + '/devices/remove/res', JSON.stringify(response));\n }\n\n if (\n topic === this.prefix + '/subscriptions/req' ||\n topic === this.prefix + '/subscriptions/get'\n ) {\n let temp;\n try {\n temp = await this.gateway.db.getAllSubscriptions();\n } catch (err) {\n return log.error(err);\n }\n let subscriptions = JSON.parse(JSON.stringify(temp)); // make copy, fixes crash with lokijs\n // Cleanup\n for (let i in subscriptions) {\n delete subscriptions[i].meta;\n delete subscriptions[i].$loki;\n }\n this.gateway.client.publish(\n this.prefix + '/subscriptions/res',\n JSON.stringify(subscriptions)\n );\n }\n\n if (topic === this.prefix + '/topics/req' || topic === this.prefix + '/topics/get') {\n let temp;\n try {\n temp = await this.gateway.db.getAllTopics();\n } catch (err) {\n return log.error(err);\n }\n let topics = JSON.parse(JSON.stringify(temp)); // make copy, fixes crash with lokijs\n // Cleanup\n for (let i in topics) {\n delete topics[i].meta;\n delete topics[i].$loki;\n }\n this.gateway.client.publish(this.prefix + '/topics/res', JSON.stringify(topics));\n }\n\n if (topic === this.prefix + '/forwarder/enterpair') {\n this.gateway.forwarder.enterPairMode();\n }\n\n if (topic === this.prefix + '/forwarder/exitpair') {\n this.gateway.forwarder.exitPairMode();\n }\n\n if (\n topic === this.prefix + '/forwarder/mode/req' ||\n topic === this.prefix + '/forwarder/mode/get'\n ) {\n let mode = this.gateway.forwarder.getMode();\n this.gateway.client.publish(\n this.prefix + '/forwarder/mode/res',\n JSON.stringify({ mode: mode })\n );\n }\n }\n\n onDeviceConnected(device: any) {\n let dev = JSON.parse(JSON.stringify(device));\n delete dev.meta;\n delete dev.$loki;\n this.gateway.client.publish(this.prefix + '/devices/connected', JSON.stringify(dev));\n }\n\n onDeviceDisconnected(device: any) {\n let dev = JSON.parse(JSON.stringify(device));\n delete dev.meta;\n delete dev.$loki;\n this.gateway.client.publish(this.prefix + '/devices/disconnected', JSON.stringify(dev));\n }\n\n onDevicePaired(device: any) {\n let dev = JSON.parse(JSON.stringify(device));\n delete dev.meta;\n delete dev.$loki;\n this.gateway.client.publish(this.prefix + '/devices/paired', JSON.stringify(dev));\n }\n}\n"],"sourceRoot":"/Users/rod/workshop/aquila-gateway/src"} -------------------------------------------------------------------------------- /dist/Logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const bunyan = require("bunyan"); 4 | exports.log = bunyan.createLogger({ 5 | name: 'aquila-gateway', 6 | level: 'trace' 7 | }); 8 | 9 | //# sourceMappingURL=Logger.js.map 10 | -------------------------------------------------------------------------------- /dist/Logger.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/Logger.ts"],"names":[],"mappings":";;AACA,iCAAiC;AAEpB,QAAA,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC;IACrC,IAAI,EAAE,gBAAgB;IACtB,KAAK,EAAE,OAAO;CACf,CAAC,CAAC","file":"Logger.js","sourcesContent":["\nimport * as bunyan from 'bunyan';\n\nexport const log = bunyan.createLogger({ \n name: 'aquila-gateway',\n level: 'trace'\n});\n"],"sourceRoot":"/Users/rod/workshop/aquila-gateway/src"} -------------------------------------------------------------------------------- /dist/MQTTTransport.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const Slip = require("node-slip"); 4 | const events_1 = require("events"); 5 | const mqtt = require("mqtt"); 6 | const Logger_1 = require("./Logger"); 7 | const CrcUtils_1 = require("./CrcUtils"); 8 | class MQTTTransport extends events_1.EventEmitter { 9 | constructor(url, inTopic, outTopic, client) { 10 | super(); 11 | this.fake = false; 12 | this.writing = false; 13 | this.writeBuffer = []; 14 | this.externalClient = false; 15 | this.client = null; 16 | this.url = url; 17 | this.inTopic = inTopic; 18 | this.outTopic = outTopic; 19 | if (client != null) { 20 | this.externalClient = true; 21 | this.client = client; 22 | } 23 | this._onClientConnect = () => this.onClientConnect(); 24 | this._onClientOffline = () => this.onClientOffline(); 25 | this._onClientReconnect = () => this.onClientReconnect(); 26 | this._onClientMessage = (topic, message, packet) => this.onClientMessage(topic, message, packet); 27 | let receiver = { 28 | data: (input) => { 29 | let crcOk = CrcUtils_1.checkCrc(input); 30 | let data = input.slice(0, input.length - 2); 31 | if (crcOk) { 32 | this.emit("data", data); 33 | } 34 | else { 35 | this.emit("crcError", data); 36 | } 37 | }, 38 | framing: (input) => { 39 | this.emit("framingError", input); 40 | }, 41 | escape: (input) => { 42 | this.emit("escapeError", input); 43 | } 44 | }; 45 | this.parser = new Slip.parser(receiver); 46 | } 47 | onClientConnect() { 48 | Logger_1.log.debug('Connected to MQTT broker (MQTTTransport)'); 49 | this.client.subscribe(this.outTopic, { qos: 2 }); 50 | } 51 | onClientOffline() { 52 | Logger_1.log.warn('MQTT broker offline (MQTTTransport)'); 53 | } 54 | onClientReconnect() { 55 | Logger_1.log.warn('Trying to reconnect with MQTT broker (MQTTTransport)'); 56 | } 57 | onClientMessage(topic, message, packet) { 58 | if (topic !== this.outTopic) 59 | return; 60 | message = Buffer.from(message.toString(), 'base64'); 61 | this.parser.write(message); 62 | } 63 | connect() { 64 | if (this.client == null) 65 | this.client = mqtt.connect(this.url); 66 | this.client.on('connect', this._onClientConnect); 67 | this.client.on('offline', this._onClientOffline); 68 | this.client.on('reconnect', this._onClientReconnect); 69 | this.client.on('message', this._onClientMessage); 70 | if (this.externalClient || this.client.connected) { 71 | this.client.subscribe(this.outTopic, { qos: 2 }); 72 | return Promise.resolve(null); 73 | } 74 | else { 75 | return new Promise((resolve, reject) => { 76 | this.client.once('connect', () => { 77 | this.client.subscribe(this.outTopic, { qos: 2 }); 78 | resolve(null); 79 | }); 80 | }); 81 | } 82 | } 83 | close(callback) { 84 | if (!callback) 85 | callback = function () { }; 86 | this.client.removeListener('connect', this._onClientConnect); 87 | this.client.removeListener('offline', this._onClientOffline); 88 | this.client.removeListener('reconnect', this._onClientReconnect); 89 | this.client.removeListener('message', this._onClientMessage); 90 | if (this.externalClient) 91 | delete this.client; 92 | if (this.client == null || this.externalClient) 93 | return; 94 | this.client.end(false, () => { 95 | delete this.client; 96 | callback(); 97 | }); 98 | } 99 | write(data) { 100 | data = new Buffer(data); 101 | let crc = CrcUtils_1.calcCrc(data); 102 | let crcBuf = new Buffer(2); 103 | crcBuf.writeUInt16LE(crc, 0); 104 | let buffer = Buffer.concat([data, crcBuf]); 105 | let slipData = Slip.generator(buffer); 106 | slipData = new Buffer(slipData.toString('base64')); 107 | this.writeBuffer.push(slipData); 108 | this.writeNow(); 109 | } 110 | writeNow() { 111 | if (this.client == null) 112 | return; 113 | if (this.writeBuffer.length <= 0) 114 | return; 115 | if (this.writing) 116 | return; 117 | this.writing = true; 118 | if (this.fake) { 119 | this.writing = false; 120 | return; 121 | } 122 | var data = this.writeBuffer.shift(); 123 | this.client.publish(this.inTopic, data, { 124 | qos: 2, 125 | retain: false 126 | }); 127 | this.writing = false; 128 | if (this.writeBuffer.length > 0) 129 | this.writeNow(); 130 | } 131 | } 132 | exports.MQTTTransport = MQTTTransport; 133 | 134 | //# sourceMappingURL=MQTTTransport.js.map 135 | -------------------------------------------------------------------------------- /dist/MQTTTransport.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/MQTTTransport.ts"],"names":[],"mappings":"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","file":"MQTTTransport.js","sourcesContent":["\nimport * as Slip from 'node-slip';\nimport { EventEmitter } from 'events';\nimport * as mqtt from 'mqtt';\nimport { log } from './Logger';\nimport { calcCrc, checkCrc } from './CrcUtils';\nimport { TransportInterface } from './interfaces';\n\nexport class MQTTTransport extends EventEmitter implements TransportInterface {\n\n fake: boolean = false;\n url: string;\n inTopic: string;\n outTopic: string;\n\n // Serial port write buffer control\n writing: boolean = false;\n writeBuffer: Array = [];\n\n externalClient: boolean = false;\n client: mqtt.Client = null;\n parser: any;\n\n _onClientConnect: any;\n _onClientOffline: any;\n _onClientReconnect: any;\n _onClientMessage: any;\n\n constructor(url: string, inTopic: string, outTopic: string, client?: mqtt.Client) {\n super();\n\n this.url = url;\n this.inTopic = inTopic;\n this.outTopic = outTopic;\n\n if(client != null) {\n this.externalClient = true;\n this.client = client;\n }\n\n this._onClientConnect = () => this.onClientConnect();\n this._onClientOffline = () => this.onClientOffline();\n this._onClientReconnect = () => this.onClientReconnect();\n this._onClientMessage = (topic: string, message: Buffer, packet: any) => this.onClientMessage(topic, message, packet);\n\n let receiver = {\n data: (input: Buffer) => {\n // Check CRC\n let crcOk = checkCrc(input);\n // Strip CRC data\n let data = input.slice(0, input.length - 2);\n\n if(crcOk) {\n this.emit(\"data\", data);\n }\n else {\n this.emit(\"crcError\", data);\n }\n \n },\n framing: (input: Buffer) => {\n this.emit(\"framingError\", input);\n },\n escape: (input: Buffer) => {\n this.emit(\"escapeError\", input);\n }\n };\n\n this.parser = new Slip.parser(receiver);\n }\n\n onClientConnect() {\n log.debug('Connected to MQTT broker (MQTTTransport)');\n // Subscribe to bridge out topic\n this.client.subscribe(this.outTopic, { qos: 2 });\n }\n\n onClientOffline() {\n log.warn('MQTT broker offline (MQTTTransport)');\n }\n\n onClientReconnect() {\n log.warn('Trying to reconnect with MQTT broker (MQTTTransport)');\n }\n\n onClientMessage(topic: string, message: Buffer, packet: any) {\n //if(message.length > MAXLEN) return log.warn(\"message too long\");\n if(topic !== this.outTopic) return; //log.error(\"bad topic\");\n // Convert from base64\n message = Buffer.from(message.toString(), 'base64');\n //console.log(message, message.toString('utf-8'));\n this.parser.write(message);\n }\n\n connect(): Promise {\n\n if(this.client == null) this.client = mqtt.connect(this.url);\n\n this.client.on('connect', this._onClientConnect);\n\n this.client.on('offline', this._onClientOffline);\n\n this.client.on('reconnect', this._onClientReconnect);\n\n this.client.on('message', this._onClientMessage);\n\n if(this.externalClient || this.client.connected) {\n // Do connect event for the first time\n // Make subscriptions for the first time\n this.client.subscribe(this.outTopic, { qos: 2 });\n return Promise.resolve(null);\n }\n else {\n return new Promise((resolve, reject) => {\n this.client.once('connect', () => {\n // Make subscriptions for the first time\n this.client.subscribe(this.outTopic, { qos: 2 });\n resolve(null);\n });\n });\n }\n\n }\n\n close(callback: Function) {\n if(!callback) callback = function(){};\n\n this.client.removeListener('connect', this._onClientConnect);\n this.client.removeListener('offline', this._onClientOffline);\n this.client.removeListener('reconnect', this._onClientReconnect);\n this.client.removeListener('message', this._onClientMessage);\n\n if(this.externalClient) delete this.client;\n if(this.client == null || this.externalClient) return;\n this.client.end(false, () => {\n delete this.client;\n callback(); \n });\n }\n\n write(data: any) {\n data = new Buffer(data);\n // Append CRC\n let crc = calcCrc(data);\n let crcBuf = new Buffer(2);\n\n crcBuf.writeUInt16LE(crc, 0);\n\n let buffer = Buffer.concat([data, crcBuf]);\n\n // Convert to Slip\n let slipData = Slip.generator(buffer);\n\n // Convert to Base64\n slipData = new Buffer(slipData.toString('base64'));\n\n this.writeBuffer.push(slipData);\n this.writeNow();\n }\n\n writeNow() {\n if(this.client == null) return;\n\n // Nothing to do here\n if(this.writeBuffer.length <= 0) return;\n // We are busy, do nothing\n if(this.writing) return;\n this.writing = true;\n\n // do nothing if we are in fake mode\n if(this.fake) { this.writing = false; return; }\n\n\n var data = this.writeBuffer.shift();\n this.client.publish(this.inTopic, data, {\n qos: 2,\n retain: false\n });\n\n //if(config.debug) console.log(\"Sending:\", data);\n\n this.writing = false;\n if(this.writeBuffer.length > 0) this.writeNow();\n }\n\n}\n"],"sourceRoot":"/Users/rod/workshop/aquila-gateway/src"} -------------------------------------------------------------------------------- /dist/SerialTransport.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const SerialPort = require("serialport"); 4 | const Slip = require("node-slip"); 5 | const events_1 = require("events"); 6 | const CrcUtils_1 = require("./CrcUtils"); 7 | class SerialTransport extends events_1.EventEmitter { 8 | constructor(baudrate, port) { 9 | super(); 10 | this.fake = false; 11 | this.writing = false; 12 | this.writeBuffer = []; 13 | const receiver = { 14 | data: (input) => { 15 | let crcOk = CrcUtils_1.checkCrc(input); 16 | let data = input.slice(0, input.length - 2); 17 | if (crcOk) { 18 | this.emit("data", data); 19 | } 20 | else { 21 | this.emit("crcError", data); 22 | } 23 | }, 24 | framing: (input) => { 25 | this.emit("framingError", input); 26 | }, 27 | escape: (input) => { 28 | this.emit("escapeError", input); 29 | } 30 | }; 31 | this.parser = new Slip.parser(receiver); 32 | this.serialPort = new SerialPort(port, { 33 | baudRate: baudrate, 34 | autoOpen: false 35 | }); 36 | this.serialPort.on("data", (data) => { 37 | this.parser.write(data); 38 | }); 39 | this.serialPort.on("open", () => { 40 | this.emit("ready"); 41 | }); 42 | this.serialPort.on("error", (err) => { 43 | this.emit("error", err); 44 | }); 45 | this.serialPort.on("disconnect", (err) => { 46 | this.emit("disconnect", err); 47 | }); 48 | this.serialPort.on("close", () => { 49 | this.emit("close"); 50 | }); 51 | } 52 | connect() { 53 | return new Promise((resolve, reject) => { 54 | this.serialPort.open((err) => { 55 | if (err) { 56 | this.emit("error", err); 57 | return reject(err); 58 | } 59 | return resolve(null); 60 | }); 61 | }); 62 | } 63 | close(callback) { 64 | if (!callback) 65 | callback = function () { }; 66 | this.serialPort.flush((err) => { 67 | if (err) 68 | return callback(err); 69 | this.serialPort.drain((err) => { 70 | if (err) 71 | return callback(err); 72 | this.serialPort.close(() => callback()); 73 | }); 74 | }); 75 | } 76 | write(data) { 77 | data = new Buffer(data); 78 | let crc = CrcUtils_1.calcCrc(data); 79 | let crcBuf = new Buffer(2); 80 | crcBuf.writeUInt16LE(crc, 0); 81 | let buffer = Buffer.concat([data, crcBuf]); 82 | let slipData = Slip.generator(buffer); 83 | this.writeBuffer.push(slipData); 84 | this.writeNow(); 85 | } 86 | writeNow() { 87 | if (this.writeBuffer.length <= 0) 88 | return; 89 | if (this.writing) 90 | return; 91 | this.writing = true; 92 | if (this.fake) { 93 | this.writing = false; 94 | return; 95 | } 96 | this.serialPort.drain(() => { 97 | let data = this.writeBuffer.shift(); 98 | this.serialPort.write(data); 99 | this.writing = false; 100 | if (this.writeBuffer.length > 0) 101 | this.writeNow(); 102 | }); 103 | } 104 | } 105 | exports.SerialTransport = SerialTransport; 106 | 107 | //# sourceMappingURL=SerialTransport.js.map 108 | -------------------------------------------------------------------------------- /dist/SerialTransport.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/SerialTransport.ts"],"names":[],"mappings":"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","file":"SerialTransport.js","sourcesContent":["// SerialTransport.js\n\nimport * as SerialPort from 'serialport';\nimport * as Slip from 'node-slip';\nimport { EventEmitter } from 'events';\nimport { calcCrc, checkCrc } from './CrcUtils';\nimport { TransportInterface } from './interfaces';\n\nexport class SerialTransport extends EventEmitter implements TransportInterface {\n\n fake: boolean = false;\n // Serial port write buffer control\n writing: boolean = false;\n writeBuffer: Array = [];\n\n parser: any;\n serialPort: SerialPort;\n\n constructor(baudrate: number, port: string) {\n super();\n\n const receiver = {\n data: (input: Buffer) => {\n // Check CRC\n let crcOk = checkCrc(input);\n // Strip CRC data\n let data = input.slice(0, input.length - 2);\n\n if(crcOk) {\n this.emit(\"data\", data);\n }\n else {\n this.emit(\"crcError\", data);\n }\n },\n framing: (input: Buffer) => {\n this.emit(\"framingError\", input);\n },\n escape: (input: Buffer) => {\n this.emit(\"escapeError\", input);\n }\n };\n\n this.parser = new Slip.parser(receiver);\n\n this.serialPort = new SerialPort(port, {\n baudRate: baudrate,\n autoOpen: false\n });\n\n this.serialPort.on(\"data\", (data) => {\n this.parser.write(data);\n });\n\n this.serialPort.on(\"open\", () => {\n this.emit(\"ready\");\n });\n\n this.serialPort.on(\"error\", (err) => {\n this.emit(\"error\", err);\n });\n\n this.serialPort.on(\"disconnect\", (err) => {\n this.emit(\"disconnect\", err);\n });\n\n this.serialPort.on(\"close\", () => {\n this.emit(\"close\");\n });\n }\n\n connect(): Promise {\n return new Promise((resolve, reject) => {\n this.serialPort.open((err: any) => {\n if(err) {\n this.emit(\"error\", err);\n return reject(err);\n }\n return resolve(null);\n });\n });\n }\n\n close(callback: Function) {\n if(!callback) callback = function(){};\n this.serialPort.flush((err) => {\n if(err) return callback(err);\n this.serialPort.drain((err) => {\n if(err) return callback(err);\n this.serialPort.close( () => callback() );\n });\n });\n }\n\n write(data: any) {\n data = new Buffer(data);\n // Append CRC\n let crc = calcCrc(data);\n let crcBuf = new Buffer(2);\n\n crcBuf.writeUInt16LE(crc, 0);\n\n let buffer = Buffer.concat([data, crcBuf]);\n\n // Convert to Slip\n let slipData = Slip.generator(buffer);\n\n this.writeBuffer.push(slipData);\n this.writeNow();\n }\n\n writeNow() {\n // Nothing to do here\n if(this.writeBuffer.length <= 0) return;\n // We are busy, do nothing\n if(this.writing) return;\n this.writing = true;\n\n // do nothing if we are in fake mode\n if(this.fake) { this.writing = false; return; }\n\n this.serialPort.drain(() => {\n let data = this.writeBuffer.shift();\n this.serialPort.write(data);\n\n //if(config.debug) console.log(\"Sending:\", data);\n\n this.writing = false;\n if(this.writeBuffer.length > 0) this.writeNow();\n });\n }\n\n}\n"],"sourceRoot":"/Users/rod/workshop/aquila-gateway/src"} -------------------------------------------------------------------------------- /dist/TCPTransport.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const Slip = require("node-slip"); 4 | const events_1 = require("events"); 5 | const net = require("net"); 6 | const Logger_1 = require("./Logger"); 7 | const CrcUtils_1 = require("./CrcUtils"); 8 | class TCPTransport extends events_1.EventEmitter { 9 | constructor(port) { 10 | super(); 11 | this.fake = false; 12 | this.writing = false; 13 | this.writeBuffer = []; 14 | this.server = null; 15 | this.sock = null; 16 | this.port = port; 17 | const receiver = { 18 | data: (input) => { 19 | let crcOk = CrcUtils_1.checkCrc(input); 20 | let data = input.slice(0, input.length - 2); 21 | if (crcOk) { 22 | this.emit("data", data); 23 | } 24 | else { 25 | this.emit("crcError", data); 26 | } 27 | }, 28 | framing: (input) => { 29 | this.emit("framingError", input); 30 | }, 31 | escape: (input) => { 32 | this.emit("escapeError", input); 33 | } 34 | }; 35 | this.parser = new Slip.parser(receiver); 36 | } 37 | connect() { 38 | if (this.server != null) 39 | return Promise.reject(new Error('Already connected')); 40 | Logger_1.log.info("TCP Transport server listening on port", this.port); 41 | return new Promise((resolve, reject) => { 42 | this.server = net.createServer((sock) => { 43 | Logger_1.log.info('TCP client connected: ' + sock.remoteAddress + ':' + sock.remotePort); 44 | if (this.sock != null) { 45 | Logger_1.log.warn('There is a bridge already connected, ignoring new connection'); 46 | return; 47 | } 48 | this.sock = sock; 49 | this.sock.setKeepAlive(true, 0); 50 | this.sock.on("data", (data) => { 51 | this.parser.write(data); 52 | }); 53 | this.sock.on("connect", () => { 54 | this.emit("ready"); 55 | }); 56 | this.sock.on("error", (err) => { 57 | Logger_1.log.debug("Socket error"); 58 | this.emit("error", err); 59 | }); 60 | this.sock.on("end", (err) => { 61 | Logger_1.log.debug("Socket end"); 62 | this.emit("disconnect", err); 63 | this.sock = null; 64 | }); 65 | this.sock.on("close", () => { 66 | Logger_1.log.debug("Socket close"); 67 | this.emit("close"); 68 | this.sock = null; 69 | }); 70 | this.sock.on("timeout", () => { 71 | Logger_1.log.debug("Socket timeout"); 72 | this.sock.end(); 73 | }); 74 | resolve(null); 75 | }).listen(this.port); 76 | }); 77 | } 78 | close(callback) { 79 | if (!callback) 80 | callback = function () { }; 81 | if (this.sock == null) 82 | return; 83 | this.sock.close((err) => { 84 | if (err) 85 | return callback(err); 86 | }); 87 | } 88 | write(data) { 89 | data = new Buffer(data); 90 | let crc = CrcUtils_1.calcCrc(data); 91 | let crcBuf = new Buffer(2); 92 | crcBuf.writeUInt16LE(crc, 0); 93 | let buffer = Buffer.concat([data, crcBuf]); 94 | let slipData = Slip.generator(buffer); 95 | this.writeBuffer.push(slipData); 96 | this.writeNow(); 97 | } 98 | writeNow() { 99 | if (this.sock == null) 100 | return; 101 | if (this.writeBuffer.length <= 0) 102 | return; 103 | if (this.writing) 104 | return; 105 | this.writing = true; 106 | if (this.fake) { 107 | this.writing = false; 108 | return; 109 | } 110 | let data = this.writeBuffer.shift(); 111 | this.sock.write(data); 112 | this.writing = false; 113 | if (this.writeBuffer.length > 0) 114 | this.writeNow(); 115 | } 116 | } 117 | exports.TCPTransport = TCPTransport; 118 | 119 | //# sourceMappingURL=TCPTransport.js.map 120 | -------------------------------------------------------------------------------- /dist/TCPTransport.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/TCPTransport.ts"],"names":[],"mappings":"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","file":"TCPTransport.js","sourcesContent":["// TCPTransport.js\n\nimport * as Slip from 'node-slip';\nimport { EventEmitter } from 'events';\nimport * as net from 'net';\nimport { log } from './Logger';\nimport { calcCrc, checkCrc } from './CrcUtils';\nimport { TransportInterface } from './interfaces';\n\nexport class TCPTransport extends EventEmitter implements TransportInterface {\n\n fake: boolean = false;\n port: number;\n // Serial port write buffer control\n writing: boolean = false;\n writeBuffer: Array = [];\n\n parser: any;\n server: any = null;\n sock: any = null;\n\n constructor(port: number) {\n super();\n\n this.port = port;\n\n const receiver = {\n data: (input: Buffer) => {\n // Check CRC\n let crcOk = checkCrc(input);\n // Strip CRC data\n let data = input.slice(0, input.length - 2);\n\n if(crcOk) {\n this.emit(\"data\", data);\n }\n else {\n this.emit(\"crcError\", data);\n }\n },\n framing: (input: Buffer) => {\n this.emit(\"framingError\", input);\n },\n escape: (input: Buffer) => {\n this.emit(\"escapeError\", input);\n }\n };\n\n this.parser = new Slip.parser(receiver);\n }\n\n connect(): Promise {\n if(this.server != null) return Promise.reject(new Error('Already connected')); // Already connected\n \n log.info(\"TCP Transport server listening on port\", this.port);\n\n return new Promise((resolve, reject) => {\n this.server = net.createServer((sock) => {\n log.info('TCP client connected: ' + sock.remoteAddress +':'+ sock.remotePort);\n if(this.sock != null) {\n log.warn('There is a bridge already connected, ignoring new connection');\n return;\n }\n\n this.sock = sock;\n\n // TODO: Keep alive not working, try: https://www.npmjs.com/package/net-keepalive\n //this.sock.setTimeout(10000);\n this.sock.setKeepAlive(true, 0);\n\n this.sock.on(\"data\", (data: any) => {\n this.parser.write(data);\n });\n\n this.sock.on(\"connect\", () => {\n this.emit(\"ready\");\n });\n\n this.sock.on(\"error\", (err: any) => {\n log.debug(\"Socket error\");\n this.emit(\"error\", err);\n });\n\n this.sock.on(\"end\", (err: any) => {\n log.debug(\"Socket end\");\n this.emit(\"disconnect\", err);\n this.sock = null;\n });\n\n this.sock.on(\"close\", () => {\n log.debug(\"Socket close\");\n this.emit(\"close\");\n this.sock = null;\n });\n\n this.sock.on(\"timeout\", () => {\n log.debug(\"Socket timeout\");\n this.sock.end();\n });\n\n resolve(null);\n\n }).listen(this.port);\n });\n \n }\n\n close(callback: Function) {\n if(!callback) callback = function(){};\n if(this.sock == null) return;\n this.sock.close((err: any) => {\n if(err) return callback(err); \n });\n }\n\n write(data: any) {\n data = new Buffer(data);\n // Append CRC\n let crc = calcCrc(data);\n let crcBuf = new Buffer(2);\n\n crcBuf.writeUInt16LE(crc, 0);\n\n let buffer = Buffer.concat([data, crcBuf]);\n\n // Convert to Slip\n let slipData = Slip.generator(buffer);\n\n this.writeBuffer.push(slipData);\n this.writeNow();\n }\n\n writeNow() {\n if(this.sock == null) return;\n\n // Nothing to do here\n if(this.writeBuffer.length <= 0) return;\n // We are busy, do nothing\n if(this.writing) return;\n this.writing = true;\n\n // do nothing if we are in fake mode\n if(this.fake) { this.writing = false; return; }\n\n\n let data = this.writeBuffer.shift();\n this.sock.write(data);\n\n //if(config.debug) console.log(\"Sending:\", data);\n\n this.writing = false;\n if(this.writeBuffer.length > 0) this.writeNow();\n }\n\n}\n"],"sourceRoot":"/Users/rod/workshop/aquila-gateway/src"} -------------------------------------------------------------------------------- /dist/interfaces.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | 4 | //# sourceMappingURL=interfaces.js.map 5 | -------------------------------------------------------------------------------- /dist/interfaces.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"interfaces.js","sourcesContent":[],"sourceRoot":"/Users/rod/workshop/aquila-gateway/src"} -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const TCPTransport_1 = require("./TCPTransport"); 4 | const SerialTransport_1 = require("./SerialTransport"); 5 | const MQTTTransport_1 = require("./MQTTTransport"); 6 | const GatewayDB_1 = require("./GatewayDB"); 7 | const Forwarder_1 = require("./Forwarder"); 8 | const Gateway_1 = require("./Gateway"); 9 | const GwMonitor_1 = require("./GwMonitor"); 10 | const Logger_1 = require("./Logger"); 11 | const program = require("commander"); 12 | const path = require("path"); 13 | const pjson = require('./../package.json'); 14 | function parseBool(s) { 15 | if (s === 'false') 16 | return false; 17 | return true; 18 | } 19 | function parseKey(key) { 20 | let keyArr = key.split(','); 21 | if (keyArr.length !== 16) { 22 | Logger_1.log.warn("Invalid encryption key received, starting without encryption"); 23 | return null; 24 | } 25 | keyArr = keyArr.map((item) => { 26 | return parseInt(item); 27 | }); 28 | return keyArr; 29 | } 30 | function parseSubnet(s) { 31 | return parseInt(s); 32 | } 33 | const DEFAULT_DATA_DIR = path.join(process.env[(process.platform === 'win32') ? 'ALLUSERSPROFILE' : 'HOME'], '.aquila-gateway'); 34 | const DEFAULT_DATA_PATH = path.join(DEFAULT_DATA_DIR, 'data.json'); 35 | program 36 | .version(pjson.version) 37 | .option('-v, --verbose [level]', 'Verbosity level for logging (fatal, error, warn, info, debug, trace) [info]', 'info') 38 | .option('-t, --transport [transport]', 'Forwarder transport type (serial, tcp) [serial]', 'serial') 39 | .option('-p, --port [serial port]', 'Serial Port path if using serial transport, TCP port number if using TCP transport [/dev/tty.SLAB_USBtoUART | 6969]', '/dev/tty.SLAB_USBtoUART') 40 | .option('-b, --broker [url]', 'MQTT broker URL [http://localhost:1883]', 'http://localhost:1883') 41 | .option('-u, --allow-unknown-devices [true/false]', 'Allow connection of previously unknown (not paired) devices [true]', parseBool, true) 42 | .option('-s, --subnet [pan id]', 'PAN subnet number (1 to 254) [1]', parseSubnet, 1) 43 | .option('-k, --key [16 byte array]', '16 byte encryption key [null]', parseKey, null) 44 | .option('-d, --data-path [path]', 'Path to data persist file [' + DEFAULT_DATA_PATH + ']', DEFAULT_DATA_PATH) 45 | .option('-m, --monitor-prefix [prefix]', 'Gateway monitor topics prefix [gw]', 'gw') 46 | .parse(process.argv); 47 | Logger_1.log.level(program.verbose); 48 | let transport; 49 | if (program.transport === 'tcp') { 50 | let tcpPort = parseInt(program.port); 51 | if (isNaN(tcpPort)) 52 | tcpPort = 6969; 53 | transport = new TCPTransport_1.TCPTransport(tcpPort); 54 | } 55 | else if (program.transport === 'mqtt') { 56 | transport = new MQTTTransport_1.MQTTTransport(program.broker, "91bbef0fa64c130d0b274c7299c424/bridge/in", "91bbef0fa64c130d0b274c7299c424/bridge/out"); 57 | } 58 | else { 59 | transport = new SerialTransport_1.SerialTransport(115200, program.port); 60 | } 61 | let db = new GatewayDB_1.GatewayDB(program.dataPath); 62 | let gw; 63 | db.connect() 64 | .then(() => { 65 | let forwarder = new Forwarder_1.Forwarder(db, transport, program.subnet, program.key); 66 | gw = new Gateway_1.Gateway(db, forwarder); 67 | return gw.init(program.broker, program.allowUnknownDevices); 68 | }) 69 | .then(() => { 70 | let gwMon = new GwMonitor_1.GwMonitor(gw, program.monitorPrefix); 71 | Logger_1.log.info("Gateway Started"); 72 | }); 73 | 74 | //# sourceMappingURL=main.js.map 75 | -------------------------------------------------------------------------------- /dist/main.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/main.ts"],"names":[],"mappings":"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","file":"main.js","sourcesContent":["\nimport { TCPTransport } from './TCPTransport';\nimport { SerialTransport } from './SerialTransport';\nimport { MQTTTransport } from './MQTTTransport';\nimport { GatewayDB } from './GatewayDB';\nimport { Forwarder } from './Forwarder';\nimport { Gateway } from './Gateway';\nimport { GwMonitor } from './GwMonitor';\nimport { log } from './Logger';\nimport * as program from 'commander';\nimport * as path from 'path';\nconst pjson = require('./../package.json');\n\nfunction parseBool(s: string)\n{\n if(s === 'false') return false;\n return true;\n}\n\nfunction parseKey(key: string)\n{\n let keyArr: Array = key.split(',');\n if(keyArr.length !== 16)\n {\n log.warn(\"Invalid encryption key received, starting without encryption\");\n return null;\n }\n keyArr = keyArr.map((item) => {\n return parseInt(item);\n });\n return keyArr;\n}\n\nfunction parseSubnet(s: string)\n{\n return parseInt(s);\n}\n\nconst DEFAULT_DATA_DIR = path.join(process.env[(process.platform === 'win32') ? 'ALLUSERSPROFILE' : 'HOME'], '.aquila-gateway');\nconst DEFAULT_DATA_PATH = path.join(DEFAULT_DATA_DIR, 'data.json');\n\nprogram\n .version(pjson.version)\n .option('-v, --verbose [level]', 'Verbosity level for logging (fatal, error, warn, info, debug, trace) [info]', 'info')\n .option('-t, --transport [transport]', 'Forwarder transport type (serial, tcp) [serial]', 'serial')\n .option('-p, --port [serial port]', 'Serial Port path if using serial transport, TCP port number if using TCP transport [/dev/tty.SLAB_USBtoUART | 6969]', '/dev/tty.SLAB_USBtoUART')\n .option('-b, --broker [url]', 'MQTT broker URL [http://localhost:1883]', 'http://localhost:1883')\n .option('-u, --allow-unknown-devices [true/false]', 'Allow connection of previously unknown (not paired) devices [true]', parseBool, true)\n .option('-s, --subnet [pan id]', 'PAN subnet number (1 to 254) [1]', parseSubnet, 1)\n .option('-k, --key [16 byte array]', '16 byte encryption key [null]', parseKey, null)\n .option('-d, --data-path [path]', 'Path to data persist file [' + DEFAULT_DATA_PATH + ']', DEFAULT_DATA_PATH)\n .option('-m, --monitor-prefix [prefix]', 'Gateway monitor topics prefix [gw]', 'gw')\n .parse(process.argv);\n\nlog.level(program.verbose);\n\n// Select Forwarder transport\nlet transport;\nif(program.transport === 'tcp')\n{\n let tcpPort = parseInt(program.port);\n if(isNaN(tcpPort)) tcpPort = 6969;\n transport = new TCPTransport(tcpPort);\n}\nelse if(program.transport === 'mqtt')\n{\n transport = new MQTTTransport(program.broker, \n \"91bbef0fa64c130d0b274c7299c424/bridge/in\", \n \"91bbef0fa64c130d0b274c7299c424/bridge/out\");\n}\nelse\n{\n transport = new SerialTransport(115200, program.port);\n}\n\nlet db = new GatewayDB(program.dataPath);\nlet gw: Gateway;\n\ndb.connect()\n.then(() => {\n let forwarder = new Forwarder(db, transport, program.subnet, program.key);\n gw = new Gateway(db, forwarder);\n return gw.init(program.broker, program.allowUnknownDevices);\n})\n.then(() => {\n let gwMon = new GwMonitor(gw, program.monitorPrefix);\n log.info(\"Gateway Started\");\n});\n\n\n"],"sourceRoot":"/Users/rod/workshop/aquila-gateway/src"} -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | # Aquila Gateway Documentation 2 | 3 | Aquila Gateway is an implementation of a MQTT-SN Gateway for the Aquila 2.0 platform. 4 | 5 | This software acts as a transparent link between a sensor network of low power devices (like Altair or other 802.15.4 or RF devices) and a MQTT broker (like mosca or mosquitto). This allows us to seamlessly and easily integrate those devices with existing MQTT applications and libraries. 6 | 7 | The low power devices would run a "light" version of the MQTT protocol, called MQTT-SN, and the gateway is tasked with managing and translating those connections to a standard MQTT broker. 8 | 9 | In the current implementation, we support communication with sensor networks via a "Bridge" device, connected via serial port. An example Bridge firmware implementation exists for the Altair development board, but should be easily portable to other RF boards. 10 | 11 | The communication between the "Bridge" and the "Gateway" is done via a "Forwarder" protocol, the protocol in this implementation is mostly the protocol described in the MQTT-SN spec 1.2, section 5.5 "Forwarder Encapsulation", with a two main modifications: added lqi and rssi data at the start of the frame, and added ACK an NACK support. 12 | 13 | The gateway implementation is of an "Aggregating Gateway", as described in the MQTT-SN spec 1.2, section 4.2 14 | 15 | For more information on MQTT-SN and its workings, please read: [MQTT-SN spec 1.2](http://mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf) 16 | 17 | ## Topology 18 | 19 | ``` 20 | (Device)_____ ____________________ _______________ 21 | \___________ | | | | 22 | MQTT-SN \__ ________| | MQTT | | 23 | (Device)_______________| Bridge | Gateway |__________| MQTT Broker | 24 | __ --------| (aquila-gateway) | | | 25 | __________________/ | | | | 26 | (Device) -------------------- --------------- 27 | | 28 | MQTT | 29 | _______|________ 30 | | | 31 | | Other MQTT | 32 | | Devices | 33 | --------------- 34 | ``` 35 | 36 | ## Practical limits 37 | 38 | By default, the bridge and devices are configured with the following limits: 39 | 40 | - Max Payload length: 54 bytes 41 | - Max Topic length: 21 bytes 42 | - Max subscriptions in a device: 6 43 | 44 | These limits are set due to memory constraints in the embedded devices, or by low level network constraints. 45 | 46 | The real theoric network limits of the current network implementations are: 47 | 48 | - Altair: Max payload size: 128 bytes, real limits after network layers headers about 96 bytes (TODO: Confirm) 49 | - rfm69: Max payload size: 60 bytes (TODO: Confirm) 50 | 51 | Also be aware that the MQTT-SN headers ocuppy some bytes. (TODO: say exactly how many) 52 | 53 | Currently, the limits are set for the rfm69 implementation to work correctly. 54 | 55 | You could vary those constraints depending on the bridge and device implementation as follows: 56 | 57 | 1. Change the MAXLEN global variable in Gateway.js of aquila-gateway 58 | 2. Change SERIAL_BUFF_SIZE definition in SerialEndpoint.h of the bridge firmware 59 | 3. Change recBuffer size of WSNetwork.cpp of the device firmware 60 | 4. Change MAX_PACKET_SIZE, MAX_MESSAGE_HANDLERS, MAX_REGISTRATION_TOPIC_NAME_LENGTH, MAX_WILL_TOPIC_LENGTH from MQTTSNClient.h of the device firmware 61 | 62 | ## Monitoring gateway status 63 | 64 | You can make requests to the gateway for getting the list of registered devices, topics and subscriptions. 65 | 66 | By default, all the monitor topics start whith the ``gw`` prefix. You can change the prefix by passing a different one with the ``-m`` option when launching aquila-gateway. 67 | 68 | #### Getting devices: 69 | 70 | 1. subscribe to gw/devices/res 71 | 2. publish to gw/devices/req 72 | 3. You should get a JSON response via the subscription made to gw/devices/res 73 | 74 | #### Removing a device: 75 | 76 | 1. subscribe to gw/devices/remove/res 77 | 2. publish to gw/devices/remove/req a json with the address or id of the device to delete. Example: ``{ "address": 23 }`` or ``{ "id": 23 }`` 78 | 3. You should get a JSON response via the subscription to gw/devices/remove/res: ``{ "success": true }`` (or false). 79 | 80 | 81 | #### Getting topics: 82 | 83 | 1. subscribe to gw/topics/res 84 | 2. publish to gw/topics/req 85 | 3. You should get a JSON response via the subscription made to gw/topics/response 86 | 87 | #### Getting subscriptions: 88 | 89 | 1. subscribe to gw/subscriptions/res 90 | 2. publish to gw/subscriptions/req 91 | 3. You should get a JSON response via the subscription made to gw/subscriptions/res 92 | 93 | #### Enter forwarder pair mode: 94 | 95 | publish gw/forwarder/enterpair 96 | 97 | #### Exit forwarder pair mode: 98 | 99 | publish gw/forwarder/exitpair 100 | 101 | #### Get forwarder current mode 102 | 103 | 1. subscribe to gw/forwarder/mode/res 104 | 2. publish to gw/forwarder/mode/req 105 | 3. You should get a JSON response via the subscription made to gw/forwarder/mode/res 106 | 107 | ### Events: 108 | 109 | gw/devices/connected: published when a device its connected, has the device details as JSON in the payload 110 | 111 | gw/devices/disconnected: published when a device gets disconnected, has the device details as JSON in the payload 112 | 113 | gw/devices/paired: published when a device gets paired, has some device details as JSON in the payload, may be incomplete as device has yet to register and subscribe to topics. 114 | -------------------------------------------------------------------------------- /doc/messagesFormat.md: -------------------------------------------------------------------------------- 1 | # Messages format 2 | 3 | Aquila Gateway for the most part implements the MQTT-SN spec v 1.2, the communication between the software and the bridge is done via serial port with a variation of the "Forwarder Encapsulation" format of page 19 of the spec. There is also a special pairing mode that doesn't comply with the spec but doesn't interfere with it. 4 | 5 | ## Forwarder Encapsulation 6 | 7 | Let [MQTT-SN FE] be the format specified in the MQTT-SN spec v1.2: 8 | 9 | [Length][MsgType][Ctrl][Wireless Node Id (16 bit)][MQTT-SN Message (n bytes)] 10 | 11 | The format used in Aquila Gateway is as follows: 12 | 13 | **Note:** All the data is embedded in a [SLIP packet](https://en.wikipedia.org/wiki/Serial_Line_Internet_Protocol), this means that it has an SLIP start and stop byte (END), and could have SLIP escape sequences if the start and stop bytes are found in the data. 14 | 15 | ### Forwarder packet: 16 | 17 | [SLIP END][LQI][RSSI][MQTT-SN FE][16 bit CRC][SLIP END] 18 | 19 | ## Bridge Control messages 20 | 21 | The Aquila Gateway Forwarder also implements extra control messages for controlling the bridge: 22 | 23 | - NACK and ACK: For flow control 24 | - Enter and exit pair mode 25 | - CONFIG: For setting encryption key **(NOT YET IMPLEMENTED)** 26 | 27 | ### NACK 28 | 29 | [SLIP END][0][0] [Length (2)][0x00] [16 bit CRC][SLIP END] 30 | 31 | ### ACK 32 | 33 | [SLIP END][0][0] [Length (2)][0x01] [16 bit CRC][SLIP END] 34 | 35 | ### CONFIG 36 | 37 | Sent by the Gateway to the bridge on initial connection for configuring PAN an Encryption. 38 | If sent by Bridge, it's a request for the bridge to send its settings. 39 | 40 | - Sent From the Bridge: 41 | 42 | [SLIP END][0][0] [Length (2)][0x02] [16 bit CRC][SLIP END] 43 | 44 | - Sent From the Gateway: 45 | 46 | [SLIP END][0][0] [Length (19)][0x02] [PAN][KEY1]...[KEY16] [16 bit CRC][SLIP END] 47 | 48 | ### EXIT PAIR 49 | 50 | [SLIP END][0][0] [Length (3)][0x03][0x00] [16 bit CRC][SLIP END] 51 | 52 | ### ENTER PAIR 53 | 54 | [SLIP END][0][0] [Length (3)][0x03][0x01] [16 bit CRC][SLIP END] 55 | 56 | ### PAIR REQ 57 | 58 | Sent by the device when searching trying to pair. This has the same structure as the MQTT-SN Forwarder, but uses a non-standard command. 59 | 60 | [SLIP END][0][0] [Length (3)][0x03][0x02][addrL (0x00)][addrH (0x00)] [Length (3)][Pair cmd (0x03, non standard MQTT-SN)][randomId] [16 bit CRC][SLIP END] 61 | 62 | ### PAIR RES 63 | 64 | Sent by the Forwarder as a response to PAIR REQ. This has the same structure as the MQTT-SN Forwarder, but uses a non-standard command. 65 | 66 | - Without encryption: 67 | 68 | [SLIP END][0][0] [Length (3)][0x03][0x02][addrL (0x00)][addrH (0x00)] [Length (5)][Pair cmd (0x03, non standard MQTT-SN)][randomId][newAddr][newPan] [16 bit CRC][SLIP END] 69 | 70 | - With encryption: 71 | 72 | [SLIP END][0][0] [Length (3)][0x03][0x02][addrL (0x00)][addrH (0x00)] [Length (21)][Pair cmd (0x03, non standard MQTT-SN)][randomId][newAddr][newPan] [encryption key (16 BYTES)] [16 bit CRC][SLIP END] 73 | 74 | 75 | ## Pair Mode 76 | 77 | This is not a part of MQTT-SN, it's a custom implementation for allowing easy device pairing. 78 | 79 | Devices in pair mode are always configured with the address 0x0000 and PAN (subnet) 0x00, and transmit unencrypted messages. 80 | 81 | For pairing devices, the Gateway needs to enter a special mode, this mode ignores any packet not coming or going to the device address 0. this also sets the Bridge in pair mode (if it had encryption, it gets disabled and treats messages differently, changes pan to 0x00). 82 | 83 | In normal mode, messages from address 0 are ignored. 84 | 85 | When the Gateway is in pair mode and receives a PAIR REQ message, it assigns an unassigned address to the device and sends it via a PAIR RES message. Then, it puts itself back into normal mode. It also sends the PAN id and optionally, an encryption key. 86 | 87 | For avoiding collisions, each device sends a random Id with the PAIR REQ, and expects to receive the same id in the response. 88 | 89 | ### Network parameters 90 | 91 | - Addresses: Valid addresses: 1 to 253. 255 broadcast. 0 for config mode. 92 | - PAN (subnet): Valid values: 1 to 254. 0 for config mode. 93 | - Encryption key: 16 byte value, if every byte is set to 0xFF encryption is disabled. 94 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var gulp = require('gulp'); 3 | var tsc = require("gulp-typescript"); 4 | var del = require('del'); 5 | var sourcemaps = require('gulp-sourcemaps'); 6 | var path = require('path'); 7 | 8 | var tsProject = tsc.createProject("tsconfig.json"); 9 | 10 | gulp.task('clean', function(cb){ 11 | return del('dist', cb) 12 | }); 13 | 14 | gulp.task('build', ['clean'], function() { 15 | var tsResult = gulp.src(["src/**/*.ts"]) 16 | .pipe(sourcemaps.init()) 17 | .pipe(tsProject()); 18 | return tsResult.js 19 | .pipe(sourcemaps.write('.', { 20 | sourceRoot: function(file){ return file.cwd + '/src'; } 21 | })) 22 | .pipe(gulp.dest("dist")); 23 | }); 24 | 25 | gulp.task('default', ['build']); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports.TCPTransport = require('./dist/TCPTransport').TCPTransport; 3 | module.exports.SerialTransport = require('./dist/SerialTransport').SerialTransport; 4 | module.exports.MQTTTransport = require('./dist/MQTTTransport').MQTTTransport; 5 | module.exports.GatewayDB = require('./dist/GatewayDB').GatewayDB; 6 | module.exports.Forwarder = require('./dist/Forwarder').Forwarder; 7 | module.exports.Gateway = require('./dist/Gateway').Gateway; 8 | module.exports.GwMonitor = require('./dist/GwMonitor').GwMonitor; 9 | module.exports.Logger = require('./dist/Logger').log; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aquila-gateway", 3 | "version": "0.7.0", 4 | "description": "MQTT-SN Gateway (Serial MQTT-SN to MQTT bridge)", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "gulp", 8 | "start": "node aquila-gateway.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "Rodrigo Méndez ", 12 | "license": "MIT", 13 | "dependencies": { 14 | "async": "^2.3.0", 15 | "bunyan": "^1.8.10", 16 | "commander": "^2.9.0", 17 | "lokijs": "^1.4.3", 18 | "mqtt": "^2.6.2", 19 | "mqttsn-packet": "git+https://github.com/Rodmg/mqttsn-packet.git", 20 | "node-slip": "git+https://github.com/Rodmg/node-slip.git", 21 | "serialport": "^4.0.7" 22 | }, 23 | "devDependencies": { 24 | "@types/async": "^2.0.40", 25 | "@types/bunyan": "0.0.36", 26 | "@types/commander": "^2.9.0", 27 | "@types/lokijs": "^1.2.30", 28 | "@types/mqtt": "0.0.34", 29 | "@types/node": "^7.0.13", 30 | "@types/serialport": "^4.0.9", 31 | "del": "^2.2.2", 32 | "gulp": "^3.9.1", 33 | "gulp-sourcemaps": "^2.6.0", 34 | "gulp-typescript": "^3.1.6", 35 | "suspend": "^0.7.0", 36 | "typescript": "^2.2.2" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/Rodmg/aquila-mqtt-sn-gateway" 41 | }, 42 | "engines": { 43 | "node": ">=6.5.0" 44 | }, 45 | "preferGlobal": "true", 46 | "bin": { 47 | "aquila-gateway": "aquila-gateway.js" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/CrcUtils.ts: -------------------------------------------------------------------------------- 1 | 2 | // CRC algorithm based on Xmodem AVR code 3 | export function calcCrc(data: Buffer): number { 4 | let crc = 0; 5 | let size = data.length; 6 | let i; 7 | let index = 0; 8 | 9 | while(--size >= 0) 10 | { 11 | crc = (crc ^ data[index++] << 8) & 0xFFFF; 12 | i = 8; 13 | do 14 | { 15 | if(crc & 0x8000) 16 | { 17 | crc = (crc << 1 ^ 0x1021) & 0xFFFF; 18 | } 19 | else 20 | { 21 | crc = (crc << 1) & 0xFFFF; 22 | } 23 | } while(--i); 24 | } 25 | 26 | return crc & 0xFFFF; 27 | }; 28 | 29 | export function checkCrc(data: Buffer): boolean { 30 | let dataCrc: number, calcdCrc: number; 31 | // Getting crc from packet 32 | dataCrc = (data[data.length - 1]) << 8; 33 | dataCrc |= (data[data.length - 2]) & 0x00FF; 34 | // Calculating crc 35 | calcdCrc = calcCrc(data.slice(0, data.length - 2)); 36 | // Comparing 37 | return calcdCrc === dataCrc; 38 | }; 39 | -------------------------------------------------------------------------------- /src/Forwarder.ts: -------------------------------------------------------------------------------- 1 | 2 | import { EventEmitter } from 'events'; 3 | import { log } from './Logger'; 4 | import { TransportInterface, DBInterface } from './interfaces'; 5 | 6 | /* 7 | Manages connections with bridge and initial parsing 8 | 9 | Events: 10 | data ({lqi, rssi, addr, mqttsnFrame}) 11 | 12 | Serial frame formats: 13 | 14 | MQTT-SN forwarder: msgType = 0xFE 15 | len, msgType, ctrl, addrL, addrH, mqttsnpacket 16 | NACK 17 | len, 0x00 18 | ACK: 19 | len, 0x01 20 | CONFIG: 21 | len, 0x02, [PAN], [encryption key x 16] 22 | ENTER PAIR: 23 | len, 0x03, 0x01 24 | EXIT PAIR 25 | len, 0x03, 0x00 26 | PAIR REQ 27 | len, 0x03, 0x02, addrL, addrH, length (3), pair cmd (0x03), randomId 28 | PAIR RES 29 | len, 0x03, 0x03, addrL, addrH, length (4), pair cmd (0x03), randomId, newAddr, newPan (, [encryption key x 16] ) 30 | 31 | TODO: add not connected state management 32 | */ 33 | 34 | const ACKTIMEOUT = 5000; 35 | const MAX_BUFFER_ALLOWED = 10; 36 | 37 | const NACK_CMD = 0x00; 38 | const ACK_CMD = 0x01; 39 | const CONFIG_CMD = 0x02; 40 | const PAIR_CMD = 0x03; 41 | 42 | const NO_KEY = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF]; 43 | 44 | export interface ForwarderMessage { 45 | lqi: number; 46 | rssi: number; 47 | len: number; 48 | msgType: number, 49 | ctrl: number, 50 | addr: number, 51 | mqttsnFrame: Buffer 52 | } 53 | 54 | export class Forwarder extends EventEmitter { 55 | 56 | db: DBInterface; 57 | transport: TransportInterface; 58 | readyToSend: boolean = true; 59 | frameBuffer: Array = []; 60 | ackTimeout: NodeJS.Timer = null; 61 | pan: number = 0x01; 62 | key: Array = NO_KEY; 63 | pairMode: boolean = false; 64 | 65 | constructor(db: DBInterface, transport: TransportInterface, pan?: number, encryptionKey?: Array) { 66 | super(); 67 | 68 | // For pair address management 69 | this.db = db; 70 | this.transport = transport; 71 | 72 | if(pan != null) this.pan = pan; 73 | if(encryptionKey != null) { 74 | if(encryptionKey.length !== 16) log.warn("Invalid encryption key received, starting without encryption"); 75 | else this.key = encryptionKey; 76 | } 77 | 78 | } 79 | 80 | connect(): Promise { 81 | 82 | this.transport.on('error', (err: any) => { 83 | log.error("There was an error connecting to the Bridge, make sure it's connected to the computer."); 84 | throw err; 85 | }); 86 | 87 | this.transport.on('disconnect', (err: any) => { 88 | log.error("The Bridge was disconnected from the computer."); 89 | throw err; 90 | }); 91 | 92 | this.transport.on('data', (data: Buffer) => { 93 | //log.trace('Data: ', data); 94 | 95 | if(this.pairMode) return this.handlePairMode(data); 96 | 97 | // 5 of mqtt-sn forwarder, 2 of lqi and rssi 98 | if(data.length < 4) return log.error('Forwarder: got message with not enough data'); 99 | let lqi = data[0]; 100 | let rssi = data[1]; 101 | let len = data[2]; 102 | let msgType = data[3]; 103 | if(msgType !== 0xFE) { 104 | if(msgType === NACK_CMD) { 105 | // NACK 106 | //console.log("NACK"); 107 | this.readyToSend = true; 108 | clearTimeout(this.ackTimeout); 109 | this.sendNow(); // Send any remaining messages 110 | } 111 | else if(msgType === ACK_CMD) { 112 | // ACK 113 | //console.log("ACK"); 114 | this.readyToSend = true; 115 | clearTimeout(this.ackTimeout); 116 | this.sendNow(); // Send any remaining messages 117 | } 118 | else if(msgType === CONFIG_CMD) { 119 | log.trace("GOT CONFIG"); 120 | // CONFIG req, respond with CONFIG 121 | this.sendConfig(); 122 | this.sendNow(); // Send any remaining messages 123 | } 124 | else return log.error('Forwarder: bad forwarder msg type'); 125 | return; 126 | } 127 | if(data.length < 7) return log.error('Forwarder: got message with not enough data'); 128 | let ctrl = data[4]; 129 | let addr = data.readUInt16LE(5); 130 | let mqttsnFrame = data.slice(7); 131 | 132 | // If not in pair mode, ignore any message from address 0 (pair mode address) 133 | if(addr === 0 && !this.pairMode) return; 134 | 135 | let message: ForwarderMessage = { 136 | lqi: lqi, 137 | rssi: rssi, 138 | len: len, 139 | msgType: msgType, 140 | ctrl: ctrl, 141 | addr: addr, 142 | mqttsnFrame: mqttsnFrame 143 | } 144 | 145 | this.emit('data', message); 146 | 147 | }); 148 | this.transport.on('crcError', (data: Buffer) => log.error('crcError', data) ); 149 | this.transport.on('framingError', (data: Buffer) => log.error('framingError', data) ); 150 | this.transport.on('escapeError', (data: Buffer) => log.error('escapeError', data) ); 151 | 152 | 153 | 154 | return this.transport.connect() 155 | .then(() => { 156 | // Assure that config is sent on start, in addition to when the bridge requests it 157 | // Some USB-Serial chips have problems sending the config request on startup, this is a workaround for that 158 | // We wait 2.1 seconds for accounting to most Arduino bootloader's startup time (2s) 159 | setTimeout(() => { 160 | setTimeout(() => this.sendConfig(), 2100); 161 | }, 100); 162 | return null; 163 | }); 164 | 165 | } 166 | 167 | disconnect() { 168 | this.transport.removeAllListeners('data'); 169 | this.transport.removeAllListeners('crcError'); 170 | this.transport.removeAllListeners('framingError'); 171 | this.transport.removeAllListeners('escapeError'); 172 | this.transport.close(); 173 | delete this.transport; 174 | delete this.db; 175 | } 176 | 177 | enterPairMode() { 178 | this.pairMode = true; 179 | let frame = new Buffer([3, 0x03, 0x01]); 180 | this.frameBuffer.push(frame); 181 | this.sendNow(); 182 | } 183 | 184 | exitPairMode() { 185 | this.pairMode = false; 186 | let frame = new Buffer([3, 0x03, 0x00]); 187 | this.frameBuffer.push(frame); 188 | this.sendNow(); 189 | } 190 | 191 | getMode() { 192 | return this.pairMode ? 'pair' : 'normal'; 193 | } 194 | 195 | async handlePairMode(data: Buffer) { 196 | if(data.length < 4) return log.error('Forwarder: got message with not enough data'); 197 | let lqi = data[0]; 198 | let rssi = data[1]; 199 | let len = data[2]; 200 | let msgType = data[3]; 201 | if(msgType !== 0x03) { 202 | if(msgType === 0x00) { 203 | // NACK 204 | //console.log("NACK"); 205 | this.readyToSend = true; 206 | clearTimeout(this.ackTimeout); 207 | this.sendNow(); // Send any remaining messages 208 | } 209 | else if(msgType === 0x01) { 210 | // ACK 211 | //console.log("ACK"); 212 | this.readyToSend = true; 213 | clearTimeout(this.ackTimeout); 214 | this.sendNow(); // Send any remaining messages 215 | } 216 | else return log.error('Forwarder: bad forwarder msg type'); 217 | return; 218 | } 219 | // Parse PAIR REQ 220 | if(data.length < 10) return log.error('Forwarder: got message with not enough data'); 221 | let ctrl = data[4]; 222 | if(ctrl !== 0x02) return log.error('Forwarder: bad message'); 223 | let addr = data.readUInt16LE(5); 224 | if(addr !== 0) return log.error('Forwarder: bad address for pair mode'); 225 | //let len = data[7]; 226 | let paircmd = data [8]; 227 | if(paircmd !== PAIR_CMD) return log.warn("Bad cmd on pair message"); 228 | 229 | let randomId = data[9]; // For managin when multiple devices try to pair, temporal "addressing" 230 | 231 | // Assing address and send 232 | let newAddr 233 | try { 234 | newAddr = await this.db.getNextDeviceAddress(); 235 | } 236 | catch(err) { 237 | return log.error("Error getting next device address from DB.", err); 238 | } 239 | if(newAddr == null || isNaN(newAddr)) return log.warn("WARNING: Max registered devices reached..."); 240 | // Create empty device for occupying the new address 241 | let device = { 242 | address: newAddr, 243 | connected: false, 244 | state: 'disconnected', 245 | waitingPingres: false, 246 | lqi: 0, 247 | rssi: 0, 248 | duration: 10, 249 | lastSeen: new Date(), 250 | willTopic: null, 251 | willMessage: null, 252 | willQoS: null, 253 | willRetain: null 254 | }; 255 | 256 | try { 257 | await this.db.setDevice(device); 258 | } 259 | catch(err) { 260 | return log.error("Error saving Device to DB.", err); 261 | } 262 | 263 | // PAIR RES 264 | let frame = Buffer.from([7, 0x03, 0x03, 0x00, 0x00, 21, 0x03, randomId, newAddr, this.pan]); 265 | let key = Buffer.from(this.key); 266 | frame = Buffer.concat([frame, key]); 267 | //console.log("Pair RES:", frame); 268 | this.frameBuffer.push(frame); 269 | this.sendNow(); 270 | 271 | this.exitPairMode(); 272 | 273 | this.emit("devicePaired", device); 274 | } 275 | 276 | send(addr: number, packet: Buffer) { 277 | // Dont allow sending any message out of pair messages in pair mode 278 | if(this.pairMode) return false; 279 | 280 | // Check for max buffer allowed 281 | if(this.frameBuffer.length >= MAX_BUFFER_ALLOWED) { 282 | log.trace('Forwarder buffer full, packet dropped'); 283 | this.sendNow(); 284 | return false; 285 | } 286 | 287 | // len, msgType, ctrl, addrL, addrH, mqttsnpacket 288 | let addrL = (addr) & 0xFF; 289 | let addrH = (addr>>8) & 0xFF; 290 | let frame = new Buffer([5, 0xFE, 1, addrL, addrH]); 291 | frame = Buffer.concat([frame, packet]); 292 | this.frameBuffer.push(frame); 293 | this.sendNow(); 294 | 295 | return true; 296 | } 297 | 298 | sendNow() { 299 | if(!this.readyToSend) return; 300 | let frame = this.frameBuffer.shift(); 301 | if(typeof(frame) === 'undefined') return; 302 | this.readyToSend = false; 303 | this.transport.write(frame); 304 | this.ackTimeout = setTimeout( () => { 305 | this.readyToSend = true; 306 | this.sendNow(); // Make sure any pending messages are sent 307 | }, ACKTIMEOUT); 308 | } 309 | 310 | sendConfig() { 311 | let frame = Buffer.from([19, CONFIG_CMD, this.pan]); 312 | let key = Buffer.from(this.key); 313 | frame = Buffer.concat([frame, key]) 314 | log.trace("Sending config:", frame); 315 | this.frameBuffer.push(frame); 316 | this.sendNow(); 317 | } 318 | 319 | } 320 | -------------------------------------------------------------------------------- /src/GatewayDB.ts: -------------------------------------------------------------------------------- 1 | import * as loki from 'lokijs'; 2 | import * as path from 'path'; 3 | import * as fs from 'fs'; 4 | import { DBInterface } from './interfaces'; 5 | 6 | export class GatewayDB implements DBInterface { 7 | // Device and topic id pools 8 | // Start from 1, protocol implementation in device interpreets 0 as null 9 | deviceIndex: number = 1; 10 | topicIndex: number = 1; 11 | db: Loki; 12 | 13 | devices: Collection; 14 | topics: Collection; 15 | subscriptions: Collection; 16 | messages: Collection; 17 | 18 | dataPath: string; 19 | 20 | _onSigint: any; 21 | 22 | constructor(dataPath: string) { 23 | // Create directory if not exists 24 | let dataDir = path.dirname(dataPath); 25 | if (!fs.existsSync(dataDir)) { 26 | fs.mkdirSync(dataDir); 27 | } 28 | 29 | this._onSigint = () => { 30 | this.db.close(() => { 31 | console.log('closed'); 32 | process.exit(); 33 | }); 34 | }; 35 | 36 | this.dataPath = dataPath; 37 | } 38 | 39 | destructor() { 40 | this.db.close(() => {}); 41 | process.removeListener('SIGINT', this._onSigint); 42 | } 43 | 44 | connect(): Promise { 45 | return new Promise((resolve, reject) => { 46 | this.db = new loki(this.dataPath, { 47 | autosave: true, 48 | autosaveInterval: 60000, 49 | autoload: true, 50 | autoloadCallback: () => this.loadHandler(resolve) 51 | }); 52 | }); 53 | } 54 | 55 | loadHandler(resolve: Function) { 56 | // devices: 57 | // address: number 58 | // id: string 59 | // connected: bool 60 | // state: string ('active', 'asleep', 'lost', 'awake', 'disconnected') (for sleep support) 61 | // waitingPingres: bool 62 | // lqi: number 63 | // rssi: number 64 | // duration: connect ping timeout 65 | // lastSeen: last seen time 66 | // willTopic: string 67 | // willMessage: string 68 | // willQoS 69 | // willRetain 70 | this.devices = this.db.getCollection('devices'); 71 | if (this.devices === null) this.devices = this.db.addCollection('devices'); 72 | 73 | // Mark all devices as disconnected and waitingPingres: false on startup 74 | this.devices.findAndUpdate( 75 | () => { 76 | return true; 77 | }, 78 | device => { 79 | device.connected = false; 80 | device.waitingPingres = false; 81 | device.state = 'disconnected'; 82 | return device; 83 | } 84 | ); 85 | 86 | // topics: 87 | // device: id 88 | // name: string 89 | // id: topicId 90 | // type: string ('short name', 'normal', 'pre-defined') 91 | this.topics = this.db.getCollection('topics'); 92 | if (this.topics === null) this.topics = this.db.addCollection('topics'); 93 | 94 | // subscriptions: 95 | // device: id 96 | // topic: string // Should connect with topic name in topics, if not preexistent, create 97 | // qos: qos number 98 | this.subscriptions = this.db.getCollection('subscriptions'); 99 | if (this.subscriptions === null) this.subscriptions = this.db.addCollection('subscriptions'); 100 | 101 | // buffered messages 102 | // device: id 103 | // message: buffer 104 | // dup: bool 105 | // retain: bool 106 | // qos: number 107 | // topicId: number 108 | // msgId: number 109 | // topicIdType: string 110 | this.messages = this.db.getCollection('messages'); 111 | if (this.messages === null) this.messages = this.db.addCollection('messages'); 112 | 113 | process.on('SIGINT', this._onSigint); 114 | 115 | // Updating indexes 116 | this.deviceIndex = this.devices.maxId + 1; 117 | this.topicIndex = this.topics.maxId + 1; 118 | 119 | return resolve(null); 120 | } 121 | 122 | // update or create, use for adding wills etc. 123 | setDevice(device): Promise { 124 | let found = null; 125 | if (device.address !== undefined) found = this.devices.findOne({ address: device.address }); 126 | else if (device.id !== undefined) found = this.devices.findOne({ id: device.id }); 127 | if (!found) { 128 | // create new 129 | if (!device.id) { 130 | device.id = this.deviceIndex; 131 | this.deviceIndex++; 132 | } 133 | this.devices.insert(device); 134 | } else { 135 | // update 136 | if (device.address !== undefined) found.address = device.address; 137 | if (device.id !== undefined) found.id = device.id; 138 | if (device.connected !== undefined) found.connected = device.connected; 139 | if (device.waitingPingres !== undefined) found.waitingPingres = device.waitingPingres; 140 | if (device.lqi !== undefined) found.lqi = device.lqi; 141 | if (device.rssi !== undefined) found.rssi = device.rssi; 142 | if (device.duration !== undefined) found.duration = device.duration; 143 | if (device.willTopic !== undefined) found.willTopic = device.willTopic; 144 | if (device.willMessage !== undefined) found.willMessage = device.willMessage; 145 | this.devices.update(found); 146 | } 147 | return Promise.resolve(found); 148 | } 149 | 150 | removeDevice(device: any): Promise { 151 | let found = null; 152 | if (device.address !== undefined) found = this.devices.findOne({ address: device.address }); 153 | else if (device.id !== undefined) found = this.devices.findOne({ id: device.id }); 154 | 155 | if (found == null) return Promise.resolve(false); 156 | 157 | // Cleanup related models 158 | this.topics.removeWhere({ device: found.id }); 159 | this.subscriptions.removeWhere({ device: found.id }); 160 | this.messages.removeWhere({ device: found.id }); 161 | 162 | this.devices.removeWhere({ id: found.id }); 163 | 164 | return Promise.resolve(true); 165 | } 166 | 167 | getDeviceByAddr(addr: number): Promise { 168 | let found = this.devices.findOne({ address: addr }); 169 | return Promise.resolve(found); 170 | } 171 | 172 | getDeviceById(id: number): Promise { 173 | let found = this.devices.findOne({ id: id }); 174 | return Promise.resolve(found); 175 | } 176 | 177 | getAllDevices(): Promise { 178 | let found = this.devices.find(); 179 | return Promise.resolve(found); 180 | } 181 | 182 | getNextDeviceAddress(): Promise { 183 | // Get all devices and order by address 184 | let found = this.devices 185 | .chain() 186 | .find() 187 | .simplesort('address') 188 | .data() 189 | .map(item => { 190 | return item.address; 191 | }); 192 | 193 | let nextIndex = null; 194 | 195 | // Special case when there are no previous devices registered 196 | if (found.length === 0) return Promise.resolve(1); 197 | 198 | // Find lower unused address 199 | for (let i = 0; i < found.length; i++) { 200 | let current = found[i]; 201 | let prev = 0; 202 | if (i != 0) prev = found[i - 1]; 203 | if (current > prev + 1) { 204 | // Found discontinuity, return next value inside discontinuity 205 | nextIndex = prev + 1; 206 | return Promise.resolve(nextIndex); 207 | } 208 | } 209 | // If we reached here, there is no discontinuity, return next value if available 210 | nextIndex = found[found.length - 1] + 1; 211 | // Forbidden addresses, 0xF0 is the bridge 212 | if (nextIndex > 0xfe || nextIndex === 0xf0) return Promise.resolve(null); 213 | return Promise.resolve(nextIndex); 214 | } 215 | 216 | getAllTopics(): Promise { 217 | let found = this.topics.find(); 218 | return Promise.resolve(found); 219 | } 220 | 221 | // accepts id or address as object {id: bla} or {address: bla} 222 | async setTopic( 223 | deviceIdOrAddress: any, 224 | topic: string, 225 | topicId?: number, 226 | type?: string 227 | ): Promise { 228 | if (typeof type === 'undefined' || type === null) type = 'normal'; // default 229 | 230 | if (deviceIdOrAddress.id === undefined) { 231 | if (deviceIdOrAddress.address === undefined) return Promise.resolve(false); 232 | let dev = await this.getDeviceByAddr(deviceIdOrAddress.address); 233 | if (dev) deviceIdOrAddress.id = dev.id; 234 | if (deviceIdOrAddress.id == null) return Promise.resolve(false); 235 | } 236 | 237 | let found = this.topics.findOne({ $and: [{ device: deviceIdOrAddress.id }, { id: topicId }] }); 238 | 239 | if (!found) { 240 | if (!topicId) { 241 | topicId = this.topicIndex; 242 | this.topicIndex++; 243 | } 244 | found = { 245 | device: deviceIdOrAddress.id, 246 | name: topic, 247 | id: topicId, 248 | type: type 249 | }; 250 | this.topics.insert(found); 251 | } else { 252 | found.device = deviceIdOrAddress.id; 253 | found.name = topic; 254 | found.id = topicId; 255 | found.type = type; 256 | this.topics.update(found); 257 | } 258 | 259 | return Promise.resolve(found); 260 | } 261 | 262 | // {id: } or {name:} 263 | async getTopic(deviceIdOrAddress: any, idOrName: any): Promise { 264 | if (deviceIdOrAddress.id === undefined) { 265 | if (deviceIdOrAddress.address === undefined) return Promise.resolve(false); 266 | let dev = await this.getDeviceByAddr(deviceIdOrAddress.address); 267 | if (dev) deviceIdOrAddress.id = dev.id; 268 | if (deviceIdOrAddress.id == null) return Promise.resolve(false); 269 | } 270 | 271 | let query: any = { $and: [{ device: deviceIdOrAddress.id }] }; 272 | if (idOrName.id !== undefined) query.$and.push({ id: idOrName.id }); 273 | if (idOrName.name !== undefined) query.$and.push({ name: idOrName.name }); 274 | 275 | let found = this.topics.findOne(query); 276 | return Promise.resolve(found); 277 | } 278 | 279 | async getTopicsFromDevice(deviceIdOrAddress: any): Promise { 280 | if (deviceIdOrAddress.id === undefined) { 281 | if (deviceIdOrAddress.address === undefined) return Promise.resolve(false); 282 | let dev = await this.getDeviceByAddr(deviceIdOrAddress.address); 283 | if (dev) deviceIdOrAddress.id = dev.id; 284 | if (deviceIdOrAddress.id == null) return Promise.resolve(false); 285 | } 286 | 287 | let query = { device: deviceIdOrAddress.id }; 288 | let found = this.topics.find(query); 289 | return Promise.resolve(found); 290 | } 291 | 292 | getAllSubscriptions(): Promise { 293 | let found = this.subscriptions.find(); 294 | return Promise.resolve(found); 295 | } 296 | 297 | async setSubscription(deviceIdOrAddress: any, topicIdOrName: any, qos: number): Promise { 298 | if (typeof qos === 'undefined' || qos === null) qos = 0; 299 | 300 | if (deviceIdOrAddress.id === undefined) { 301 | if (deviceIdOrAddress.address === undefined) return Promise.resolve(false); 302 | let dev = await this.getDeviceByAddr(deviceIdOrAddress.address); 303 | if (dev) deviceIdOrAddress.id = dev.id; 304 | if (deviceIdOrAddress.id == null) return Promise.resolve(false); 305 | } 306 | 307 | if (topicIdOrName.name === undefined) { 308 | if (topicIdOrName.id === undefined) return Promise.resolve(false); 309 | let topic = await this.getTopic({ id: deviceIdOrAddress.id }, { id: topicIdOrName.id }); 310 | topicIdOrName.name = topic.name; 311 | } 312 | 313 | let found = this.subscriptions.findOne({ 314 | $and: [{ device: deviceIdOrAddress.id }, { topic: topicIdOrName.name }] 315 | }); 316 | 317 | if (!found) { 318 | found = { 319 | device: deviceIdOrAddress.id, 320 | topic: topicIdOrName.name, 321 | qos: qos 322 | }; 323 | this.subscriptions.insert(found); 324 | } else { 325 | found.device = deviceIdOrAddress.id; 326 | found.topic = topicIdOrName.name; 327 | found.qos = qos; 328 | this.subscriptions.update(found); 329 | } 330 | 331 | return Promise.resolve(found); 332 | } 333 | 334 | getSubscriptionsFromTopic(topicName: string): Promise { 335 | let found = this.subscriptions.find({ topic: topicName }); 336 | return Promise.resolve(found); 337 | } 338 | 339 | async getSubscriptionsFromDevice(deviceIdOrAddress: any): Promise { 340 | if (deviceIdOrAddress.id === undefined) { 341 | if (deviceIdOrAddress.address === undefined) return Promise.resolve(false); 342 | let dev = await this.getDeviceByAddr(deviceIdOrAddress.address); 343 | if (dev) deviceIdOrAddress.id = dev.id; 344 | if (deviceIdOrAddress.id == null) return Promise.resolve(false); 345 | } 346 | 347 | let found = this.subscriptions.find({ device: deviceIdOrAddress.id }); 348 | return Promise.resolve(found); 349 | } 350 | 351 | async removeSubscriptionsFromDevice(deviceIdOrAddress: any): Promise { 352 | if (deviceIdOrAddress.id === undefined) { 353 | if (deviceIdOrAddress.address === undefined) return Promise.resolve(false); 354 | let dev = await this.getDeviceByAddr(deviceIdOrAddress.address); 355 | if (dev) deviceIdOrAddress.id = dev.id; 356 | if (deviceIdOrAddress.id == null) return Promise.resolve(false); 357 | } 358 | this.subscriptions.removeWhere({ device: deviceIdOrAddress.id }); 359 | return Promise.resolve(true); 360 | } 361 | 362 | async removeSubscription( 363 | deviceIdOrAddress: any, 364 | topicName: string, 365 | topicType: string 366 | ): Promise { 367 | if (deviceIdOrAddress.id === undefined) { 368 | if (deviceIdOrAddress.address === undefined) return Promise.resolve(false); 369 | let dev = await this.getDeviceByAddr(deviceIdOrAddress.address); 370 | if (dev) deviceIdOrAddress.id = dev.id; 371 | if (deviceIdOrAddress.id == null) return Promise.resolve(false); 372 | } 373 | 374 | this.subscriptions.removeWhere({ 375 | $and: [{ device: deviceIdOrAddress.id }, { topic: topicName }] 376 | }); 377 | return Promise.resolve(true); 378 | } 379 | 380 | pushMessage(message: any): Promise { 381 | this.messages.insert(message); 382 | return Promise.resolve(true); 383 | } 384 | 385 | popMessagesFromDevice(deviceId: number): Promise { 386 | if (typeof deviceId === 'undefined' || deviceId === null) return Promise.resolve(false); 387 | let messages = this.messages.find({ device: deviceId }); 388 | this.messages.removeWhere({ device: deviceId }); 389 | return Promise.resolve(messages); 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /src/GwMonitor.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | import { Gateway } from './Gateway'; 3 | import { log } from './Logger'; 4 | 5 | export class GwMonitor extends EventEmitter { 6 | gateway: Gateway; 7 | prefix: string; 8 | 9 | _onMessage: any; 10 | _onDeviceConnected: any; 11 | _onDeviceDisconnected: any; 12 | _onDevicePaired: any; 13 | 14 | constructor(gateway: Gateway, prefix?: string) { 15 | super(); 16 | 17 | // Monitor topics prefix 18 | this.prefix = prefix; 19 | if (this.prefix == null) this.prefix = 'gw'; 20 | 21 | this.gateway = gateway; 22 | 23 | // "*/get" requests deprecated, please use "*/req" for consistency with "*/res" responses. 24 | this.gateway.client.subscribe(this.prefix + '/devices/get'); 25 | this.gateway.client.subscribe(this.prefix + '/subscriptions/get'); 26 | this.gateway.client.subscribe(this.prefix + '/topics/get'); 27 | this.gateway.client.subscribe(this.prefix + '/forwarder/mode/get'); 28 | 29 | this.gateway.client.subscribe(this.prefix + '/devices/req'); 30 | this.gateway.client.subscribe(this.prefix + '/devices/remove/req'); 31 | this.gateway.client.subscribe(this.prefix + '/subscriptions/req'); 32 | this.gateway.client.subscribe(this.prefix + '/topics/req'); 33 | this.gateway.client.subscribe(this.prefix + '/forwarder/mode/req'); 34 | this.gateway.client.subscribe(this.prefix + '/forwarder/enterpair'); 35 | this.gateway.client.subscribe(this.prefix + '/forwarder/exitpair'); 36 | this.gateway.client.subscribe(this.prefix + '/forwarder/mode/get'); 37 | 38 | this._onMessage = (topic: string, message: Buffer, packet: any) => 39 | this.onMessage(topic, message, packet); 40 | this._onDeviceConnected = (device: any) => this.onDeviceConnected(device); 41 | this._onDeviceDisconnected = (device: any) => this.onDeviceDisconnected(device); 42 | this._onDevicePaired = (device: any) => this.onDevicePaired(device); 43 | 44 | this.gateway.client.on('message', this._onMessage); 45 | this.gateway.on('deviceConnected', this._onDeviceConnected); 46 | this.gateway.on('deviceDisconnected', this._onDeviceDisconnected); 47 | this.gateway.forwarder.on('devicePaired', this._onDevicePaired); 48 | } 49 | 50 | destructor() { 51 | this.gateway.client.unsubscribe(this.prefix + '/devices/get'); 52 | this.gateway.client.unsubscribe(this.prefix + '/subscriptions/get'); 53 | this.gateway.client.unsubscribe(this.prefix + '/topics/get'); 54 | this.gateway.client.unsubscribe(this.prefix + '/forwarder/mode/get'); 55 | 56 | this.gateway.client.unsubscribe(this.prefix + '/devices/req'); 57 | this.gateway.client.unsubscribe(this.prefix + '/devices/remove/req'); 58 | this.gateway.client.unsubscribe(this.prefix + '/subscriptions/req'); 59 | this.gateway.client.unsubscribe(this.prefix + '/topics/req'); 60 | this.gateway.client.unsubscribe(this.prefix + '/forwarder/mode/req'); 61 | this.gateway.client.unsubscribe(this.prefix + '/forwarder/enterpair'); 62 | this.gateway.client.unsubscribe(this.prefix + '/forwarder/exitpair'); 63 | this.gateway.client.unsubscribe(this.prefix + '/forwarder/mode/get'); 64 | 65 | this.gateway.client.removeListener('message', this._onMessage); 66 | this.gateway.removeListener('deviceConnected', this._onDeviceConnected); 67 | this.gateway.removeListener('deviceDisconnected', this._onDeviceDisconnected); 68 | this.gateway.forwarder.removeListener('devicePaired', this._onDevicePaired); 69 | 70 | delete this.gateway; 71 | } 72 | 73 | async onMessage(topic: string, message: Buffer, packet: any) { 74 | if (topic === this.prefix + '/devices/req' || topic === this.prefix + '/devices/get') { 75 | let temp; 76 | try { 77 | temp = await this.gateway.db.getAllDevices(); 78 | } catch (err) { 79 | return log.error(err); 80 | } 81 | let devices = JSON.parse(JSON.stringify(temp)); // make copy, fixes crash with lokijs 82 | // Cleanup 83 | for (let i in devices) { 84 | delete devices[i].meta; 85 | delete devices[i].$loki; 86 | } 87 | this.gateway.client.publish(this.prefix + '/devices/res', JSON.stringify(devices)); 88 | } 89 | 90 | if (topic === this.prefix + '/devices/remove/req') { 91 | let result = false; 92 | let device = null; 93 | try { 94 | device = JSON.parse(message.toString()); 95 | result = await this.gateway.db.removeDevice(device); 96 | } catch (err) { 97 | log.warn(err); 98 | result = false; 99 | } 100 | let response = { 101 | success: result 102 | }; 103 | this.gateway.client.publish(this.prefix + '/devices/remove/res', JSON.stringify(response)); 104 | } 105 | 106 | if ( 107 | topic === this.prefix + '/subscriptions/req' || 108 | topic === this.prefix + '/subscriptions/get' 109 | ) { 110 | let temp; 111 | try { 112 | temp = await this.gateway.db.getAllSubscriptions(); 113 | } catch (err) { 114 | return log.error(err); 115 | } 116 | let subscriptions = JSON.parse(JSON.stringify(temp)); // make copy, fixes crash with lokijs 117 | // Cleanup 118 | for (let i in subscriptions) { 119 | delete subscriptions[i].meta; 120 | delete subscriptions[i].$loki; 121 | } 122 | this.gateway.client.publish( 123 | this.prefix + '/subscriptions/res', 124 | JSON.stringify(subscriptions) 125 | ); 126 | } 127 | 128 | if (topic === this.prefix + '/topics/req' || topic === this.prefix + '/topics/get') { 129 | let temp; 130 | try { 131 | temp = await this.gateway.db.getAllTopics(); 132 | } catch (err) { 133 | return log.error(err); 134 | } 135 | let topics = JSON.parse(JSON.stringify(temp)); // make copy, fixes crash with lokijs 136 | // Cleanup 137 | for (let i in topics) { 138 | delete topics[i].meta; 139 | delete topics[i].$loki; 140 | } 141 | this.gateway.client.publish(this.prefix + '/topics/res', JSON.stringify(topics)); 142 | } 143 | 144 | if (topic === this.prefix + '/forwarder/enterpair') { 145 | this.gateway.forwarder.enterPairMode(); 146 | } 147 | 148 | if (topic === this.prefix + '/forwarder/exitpair') { 149 | this.gateway.forwarder.exitPairMode(); 150 | } 151 | 152 | if ( 153 | topic === this.prefix + '/forwarder/mode/req' || 154 | topic === this.prefix + '/forwarder/mode/get' 155 | ) { 156 | let mode = this.gateway.forwarder.getMode(); 157 | this.gateway.client.publish( 158 | this.prefix + '/forwarder/mode/res', 159 | JSON.stringify({ mode: mode }) 160 | ); 161 | } 162 | } 163 | 164 | onDeviceConnected(device: any) { 165 | let dev = JSON.parse(JSON.stringify(device)); 166 | delete dev.meta; 167 | delete dev.$loki; 168 | this.gateway.client.publish(this.prefix + '/devices/connected', JSON.stringify(dev)); 169 | } 170 | 171 | onDeviceDisconnected(device: any) { 172 | let dev = JSON.parse(JSON.stringify(device)); 173 | delete dev.meta; 174 | delete dev.$loki; 175 | this.gateway.client.publish(this.prefix + '/devices/disconnected', JSON.stringify(dev)); 176 | } 177 | 178 | onDevicePaired(device: any) { 179 | let dev = JSON.parse(JSON.stringify(device)); 180 | delete dev.meta; 181 | delete dev.$loki; 182 | this.gateway.client.publish(this.prefix + '/devices/paired', JSON.stringify(dev)); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/Logger.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as bunyan from 'bunyan'; 3 | 4 | export const log = bunyan.createLogger({ 5 | name: 'aquila-gateway', 6 | level: 'trace' 7 | }); 8 | -------------------------------------------------------------------------------- /src/MQTTTransport.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as Slip from 'node-slip'; 3 | import { EventEmitter } from 'events'; 4 | import * as mqtt from 'mqtt'; 5 | import { log } from './Logger'; 6 | import { calcCrc, checkCrc } from './CrcUtils'; 7 | import { TransportInterface } from './interfaces'; 8 | 9 | export class MQTTTransport extends EventEmitter implements TransportInterface { 10 | 11 | fake: boolean = false; 12 | url: string; 13 | inTopic: string; 14 | outTopic: string; 15 | 16 | // Serial port write buffer control 17 | writing: boolean = false; 18 | writeBuffer: Array = []; 19 | 20 | externalClient: boolean = false; 21 | client: mqtt.Client = null; 22 | parser: any; 23 | 24 | _onClientConnect: any; 25 | _onClientOffline: any; 26 | _onClientReconnect: any; 27 | _onClientMessage: any; 28 | 29 | constructor(url: string, inTopic: string, outTopic: string, client?: mqtt.Client) { 30 | super(); 31 | 32 | this.url = url; 33 | this.inTopic = inTopic; 34 | this.outTopic = outTopic; 35 | 36 | if(client != null) { 37 | this.externalClient = true; 38 | this.client = client; 39 | } 40 | 41 | this._onClientConnect = () => this.onClientConnect(); 42 | this._onClientOffline = () => this.onClientOffline(); 43 | this._onClientReconnect = () => this.onClientReconnect(); 44 | this._onClientMessage = (topic: string, message: Buffer, packet: any) => this.onClientMessage(topic, message, packet); 45 | 46 | let receiver = { 47 | data: (input: Buffer) => { 48 | // Check CRC 49 | let crcOk = checkCrc(input); 50 | // Strip CRC data 51 | let data = input.slice(0, input.length - 2); 52 | 53 | if(crcOk) { 54 | this.emit("data", data); 55 | } 56 | else { 57 | this.emit("crcError", data); 58 | } 59 | 60 | }, 61 | framing: (input: Buffer) => { 62 | this.emit("framingError", input); 63 | }, 64 | escape: (input: Buffer) => { 65 | this.emit("escapeError", input); 66 | } 67 | }; 68 | 69 | this.parser = new Slip.parser(receiver); 70 | } 71 | 72 | onClientConnect() { 73 | log.debug('Connected to MQTT broker (MQTTTransport)'); 74 | // Subscribe to bridge out topic 75 | this.client.subscribe(this.outTopic, { qos: 2 }); 76 | } 77 | 78 | onClientOffline() { 79 | log.warn('MQTT broker offline (MQTTTransport)'); 80 | } 81 | 82 | onClientReconnect() { 83 | log.warn('Trying to reconnect with MQTT broker (MQTTTransport)'); 84 | } 85 | 86 | onClientMessage(topic: string, message: Buffer, packet: any) { 87 | //if(message.length > MAXLEN) return log.warn("message too long"); 88 | if(topic !== this.outTopic) return; //log.error("bad topic"); 89 | // Convert from base64 90 | message = Buffer.from(message.toString(), 'base64'); 91 | //console.log(message, message.toString('utf-8')); 92 | this.parser.write(message); 93 | } 94 | 95 | connect(): Promise { 96 | 97 | if(this.client == null) this.client = mqtt.connect(this.url); 98 | 99 | this.client.on('connect', this._onClientConnect); 100 | 101 | this.client.on('offline', this._onClientOffline); 102 | 103 | this.client.on('reconnect', this._onClientReconnect); 104 | 105 | this.client.on('message', this._onClientMessage); 106 | 107 | if(this.externalClient || this.client.connected) { 108 | // Do connect event for the first time 109 | // Make subscriptions for the first time 110 | this.client.subscribe(this.outTopic, { qos: 2 }); 111 | return Promise.resolve(null); 112 | } 113 | else { 114 | return new Promise((resolve, reject) => { 115 | this.client.once('connect', () => { 116 | // Make subscriptions for the first time 117 | this.client.subscribe(this.outTopic, { qos: 2 }); 118 | resolve(null); 119 | }); 120 | }); 121 | } 122 | 123 | } 124 | 125 | close(callback: Function) { 126 | if(!callback) callback = function(){}; 127 | 128 | this.client.removeListener('connect', this._onClientConnect); 129 | this.client.removeListener('offline', this._onClientOffline); 130 | this.client.removeListener('reconnect', this._onClientReconnect); 131 | this.client.removeListener('message', this._onClientMessage); 132 | 133 | if(this.externalClient) delete this.client; 134 | if(this.client == null || this.externalClient) return; 135 | this.client.end(false, () => { 136 | delete this.client; 137 | callback(); 138 | }); 139 | } 140 | 141 | write(data: any) { 142 | data = new Buffer(data); 143 | // Append CRC 144 | let crc = calcCrc(data); 145 | let crcBuf = new Buffer(2); 146 | 147 | crcBuf.writeUInt16LE(crc, 0); 148 | 149 | let buffer = Buffer.concat([data, crcBuf]); 150 | 151 | // Convert to Slip 152 | let slipData = Slip.generator(buffer); 153 | 154 | // Convert to Base64 155 | slipData = new Buffer(slipData.toString('base64')); 156 | 157 | this.writeBuffer.push(slipData); 158 | this.writeNow(); 159 | } 160 | 161 | writeNow() { 162 | if(this.client == null) return; 163 | 164 | // Nothing to do here 165 | if(this.writeBuffer.length <= 0) return; 166 | // We are busy, do nothing 167 | if(this.writing) return; 168 | this.writing = true; 169 | 170 | // do nothing if we are in fake mode 171 | if(this.fake) { this.writing = false; return; } 172 | 173 | 174 | var data = this.writeBuffer.shift(); 175 | this.client.publish(this.inTopic, data, { 176 | qos: 2, 177 | retain: false 178 | }); 179 | 180 | //if(config.debug) console.log("Sending:", data); 181 | 182 | this.writing = false; 183 | if(this.writeBuffer.length > 0) this.writeNow(); 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /src/SerialTransport.ts: -------------------------------------------------------------------------------- 1 | // SerialTransport.js 2 | 3 | import * as SerialPort from 'serialport'; 4 | import * as Slip from 'node-slip'; 5 | import { EventEmitter } from 'events'; 6 | import { calcCrc, checkCrc } from './CrcUtils'; 7 | import { TransportInterface } from './interfaces'; 8 | 9 | export class SerialTransport extends EventEmitter implements TransportInterface { 10 | 11 | fake: boolean = false; 12 | // Serial port write buffer control 13 | writing: boolean = false; 14 | writeBuffer: Array = []; 15 | 16 | parser: any; 17 | serialPort: SerialPort; 18 | 19 | constructor(baudrate: number, port: string) { 20 | super(); 21 | 22 | const receiver = { 23 | data: (input: Buffer) => { 24 | // Check CRC 25 | let crcOk = checkCrc(input); 26 | // Strip CRC data 27 | let data = input.slice(0, input.length - 2); 28 | 29 | if(crcOk) { 30 | this.emit("data", data); 31 | } 32 | else { 33 | this.emit("crcError", data); 34 | } 35 | }, 36 | framing: (input: Buffer) => { 37 | this.emit("framingError", input); 38 | }, 39 | escape: (input: Buffer) => { 40 | this.emit("escapeError", input); 41 | } 42 | }; 43 | 44 | this.parser = new Slip.parser(receiver); 45 | 46 | this.serialPort = new SerialPort(port, { 47 | baudRate: baudrate, 48 | autoOpen: false 49 | }); 50 | 51 | this.serialPort.on("data", (data) => { 52 | this.parser.write(data); 53 | }); 54 | 55 | this.serialPort.on("open", () => { 56 | this.emit("ready"); 57 | }); 58 | 59 | this.serialPort.on("error", (err) => { 60 | this.emit("error", err); 61 | }); 62 | 63 | this.serialPort.on("disconnect", (err) => { 64 | this.emit("disconnect", err); 65 | }); 66 | 67 | this.serialPort.on("close", () => { 68 | this.emit("close"); 69 | }); 70 | } 71 | 72 | connect(): Promise { 73 | return new Promise((resolve, reject) => { 74 | this.serialPort.open((err: any) => { 75 | if(err) { 76 | this.emit("error", err); 77 | return reject(err); 78 | } 79 | return resolve(null); 80 | }); 81 | }); 82 | } 83 | 84 | close(callback: Function) { 85 | if(!callback) callback = function(){}; 86 | this.serialPort.flush((err) => { 87 | if(err) return callback(err); 88 | this.serialPort.drain((err) => { 89 | if(err) return callback(err); 90 | this.serialPort.close( () => callback() ); 91 | }); 92 | }); 93 | } 94 | 95 | write(data: any) { 96 | data = new Buffer(data); 97 | // Append CRC 98 | let crc = calcCrc(data); 99 | let crcBuf = new Buffer(2); 100 | 101 | crcBuf.writeUInt16LE(crc, 0); 102 | 103 | let buffer = Buffer.concat([data, crcBuf]); 104 | 105 | // Convert to Slip 106 | let slipData = Slip.generator(buffer); 107 | 108 | this.writeBuffer.push(slipData); 109 | this.writeNow(); 110 | } 111 | 112 | writeNow() { 113 | // Nothing to do here 114 | if(this.writeBuffer.length <= 0) return; 115 | // We are busy, do nothing 116 | if(this.writing) return; 117 | this.writing = true; 118 | 119 | // do nothing if we are in fake mode 120 | if(this.fake) { this.writing = false; return; } 121 | 122 | this.serialPort.drain(() => { 123 | let data = this.writeBuffer.shift(); 124 | this.serialPort.write(data); 125 | 126 | //if(config.debug) console.log("Sending:", data); 127 | 128 | this.writing = false; 129 | if(this.writeBuffer.length > 0) this.writeNow(); 130 | }); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/TCPTransport.ts: -------------------------------------------------------------------------------- 1 | // TCPTransport.js 2 | 3 | import * as Slip from 'node-slip'; 4 | import { EventEmitter } from 'events'; 5 | import * as net from 'net'; 6 | import { log } from './Logger'; 7 | import { calcCrc, checkCrc } from './CrcUtils'; 8 | import { TransportInterface } from './interfaces'; 9 | 10 | export class TCPTransport extends EventEmitter implements TransportInterface { 11 | 12 | fake: boolean = false; 13 | port: number; 14 | // Serial port write buffer control 15 | writing: boolean = false; 16 | writeBuffer: Array = []; 17 | 18 | parser: any; 19 | server: any = null; 20 | sock: any = null; 21 | 22 | constructor(port: number) { 23 | super(); 24 | 25 | this.port = port; 26 | 27 | const receiver = { 28 | data: (input: Buffer) => { 29 | // Check CRC 30 | let crcOk = checkCrc(input); 31 | // Strip CRC data 32 | let data = input.slice(0, input.length - 2); 33 | 34 | if(crcOk) { 35 | this.emit("data", data); 36 | } 37 | else { 38 | this.emit("crcError", data); 39 | } 40 | }, 41 | framing: (input: Buffer) => { 42 | this.emit("framingError", input); 43 | }, 44 | escape: (input: Buffer) => { 45 | this.emit("escapeError", input); 46 | } 47 | }; 48 | 49 | this.parser = new Slip.parser(receiver); 50 | } 51 | 52 | connect(): Promise { 53 | if(this.server != null) return Promise.reject(new Error('Already connected')); // Already connected 54 | 55 | log.info("TCP Transport server listening on port", this.port); 56 | 57 | return new Promise((resolve, reject) => { 58 | this.server = net.createServer((sock) => { 59 | log.info('TCP client connected: ' + sock.remoteAddress +':'+ sock.remotePort); 60 | if(this.sock != null) { 61 | log.warn('There is a bridge already connected, ignoring new connection'); 62 | return; 63 | } 64 | 65 | this.sock = sock; 66 | 67 | // TODO: Keep alive not working, try: https://www.npmjs.com/package/net-keepalive 68 | //this.sock.setTimeout(10000); 69 | this.sock.setKeepAlive(true, 0); 70 | 71 | this.sock.on("data", (data: any) => { 72 | this.parser.write(data); 73 | }); 74 | 75 | this.sock.on("connect", () => { 76 | this.emit("ready"); 77 | }); 78 | 79 | this.sock.on("error", (err: any) => { 80 | log.debug("Socket error"); 81 | this.emit("error", err); 82 | }); 83 | 84 | this.sock.on("end", (err: any) => { 85 | log.debug("Socket end"); 86 | this.emit("disconnect", err); 87 | this.sock = null; 88 | }); 89 | 90 | this.sock.on("close", () => { 91 | log.debug("Socket close"); 92 | this.emit("close"); 93 | this.sock = null; 94 | }); 95 | 96 | this.sock.on("timeout", () => { 97 | log.debug("Socket timeout"); 98 | this.sock.end(); 99 | }); 100 | 101 | resolve(null); 102 | 103 | }).listen(this.port); 104 | }); 105 | 106 | } 107 | 108 | close(callback: Function) { 109 | if(!callback) callback = function(){}; 110 | if(this.sock == null) return; 111 | this.sock.close((err: any) => { 112 | if(err) return callback(err); 113 | }); 114 | } 115 | 116 | write(data: any) { 117 | data = new Buffer(data); 118 | // Append CRC 119 | let crc = calcCrc(data); 120 | let crcBuf = new Buffer(2); 121 | 122 | crcBuf.writeUInt16LE(crc, 0); 123 | 124 | let buffer = Buffer.concat([data, crcBuf]); 125 | 126 | // Convert to Slip 127 | let slipData = Slip.generator(buffer); 128 | 129 | this.writeBuffer.push(slipData); 130 | this.writeNow(); 131 | } 132 | 133 | writeNow() { 134 | if(this.sock == null) return; 135 | 136 | // Nothing to do here 137 | if(this.writeBuffer.length <= 0) return; 138 | // We are busy, do nothing 139 | if(this.writing) return; 140 | this.writing = true; 141 | 142 | // do nothing if we are in fake mode 143 | if(this.fake) { this.writing = false; return; } 144 | 145 | 146 | let data = this.writeBuffer.shift(); 147 | this.sock.write(data); 148 | 149 | //if(config.debug) console.log("Sending:", data); 150 | 151 | this.writing = false; 152 | if(this.writeBuffer.length > 0) this.writeNow(); 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as loki from 'lokijs'; 3 | 4 | declare global { 5 | interface LokiResultset { 6 | find(): LokiResultset; 7 | } 8 | } -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | 3 | export interface DBInterface { 4 | destructor(); 5 | connect(): Promise; 6 | setDevice(device): Promise; 7 | removeDevice(device: any): Promise; 8 | getDeviceByAddr(addr: number): Promise; 9 | getDeviceById(id: number): Promise; 10 | getAllDevices(): Promise; 11 | getNextDeviceAddress(): Promise; 12 | getAllTopics(): Promise; 13 | setTopic(deviceIdOrAddress: any, topic: string, topicId?: number, type?: string): Promise; 14 | getTopic(deviceIdOrAddress: any, idOrName: any): Promise; 15 | getTopicsFromDevice(deviceIdOrAddress: any): Promise; 16 | getAllSubscriptions(): Promise; 17 | setSubscription(deviceIdOrAddress: any, topicIdOrName: any, qos: number): Promise; 18 | getSubscriptionsFromTopic(topicName: string): Promise; 19 | getSubscriptionsFromDevice(deviceIdOrAddress: any): Promise; 20 | removeSubscriptionsFromDevice(deviceIdOrAddress: any): Promise; 21 | removeSubscription(deviceIdOrAddress: any, topicName: string, topicType: string): Promise; 22 | pushMessage(message: any): Promise; 23 | popMessagesFromDevice(deviceId: number): Promise; 24 | } 25 | 26 | export interface TransportInterface extends EventEmitter { 27 | connect(): Promise; 28 | close(callback?: Function); 29 | write(data: any); 30 | writeNow(); 31 | } 32 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | 2 | import { TCPTransport } from './TCPTransport'; 3 | import { SerialTransport } from './SerialTransport'; 4 | import { MQTTTransport } from './MQTTTransport'; 5 | import { GatewayDB } from './GatewayDB'; 6 | import { Forwarder } from './Forwarder'; 7 | import { Gateway } from './Gateway'; 8 | import { GwMonitor } from './GwMonitor'; 9 | import { log } from './Logger'; 10 | import * as program from 'commander'; 11 | import * as path from 'path'; 12 | const pjson = require('./../package.json'); 13 | 14 | function parseBool(s: string) 15 | { 16 | if(s === 'false') return false; 17 | return true; 18 | } 19 | 20 | function parseKey(key: string) 21 | { 22 | let keyArr: Array = key.split(','); 23 | if(keyArr.length !== 16) 24 | { 25 | log.warn("Invalid encryption key received, starting without encryption"); 26 | return null; 27 | } 28 | keyArr = keyArr.map((item) => { 29 | return parseInt(item); 30 | }); 31 | return keyArr; 32 | } 33 | 34 | function parseSubnet(s: string) 35 | { 36 | return parseInt(s); 37 | } 38 | 39 | const DEFAULT_DATA_DIR = path.join(process.env[(process.platform === 'win32') ? 'ALLUSERSPROFILE' : 'HOME'], '.aquila-gateway'); 40 | const DEFAULT_DATA_PATH = path.join(DEFAULT_DATA_DIR, 'data.json'); 41 | 42 | program 43 | .version(pjson.version) 44 | .option('-v, --verbose [level]', 'Verbosity level for logging (fatal, error, warn, info, debug, trace) [info]', 'info') 45 | .option('-t, --transport [transport]', 'Forwarder transport type (serial, tcp) [serial]', 'serial') 46 | .option('-p, --port [serial port]', 'Serial Port path if using serial transport, TCP port number if using TCP transport [/dev/tty.SLAB_USBtoUART | 6969]', '/dev/tty.SLAB_USBtoUART') 47 | .option('-b, --broker [url]', 'MQTT broker URL [http://localhost:1883]', 'http://localhost:1883') 48 | .option('-u, --allow-unknown-devices [true/false]', 'Allow connection of previously unknown (not paired) devices [true]', parseBool, true) 49 | .option('-s, --subnet [pan id]', 'PAN subnet number (1 to 254) [1]', parseSubnet, 1) 50 | .option('-k, --key [16 byte array]', '16 byte encryption key [null]', parseKey, null) 51 | .option('-d, --data-path [path]', 'Path to data persist file [' + DEFAULT_DATA_PATH + ']', DEFAULT_DATA_PATH) 52 | .option('-m, --monitor-prefix [prefix]', 'Gateway monitor topics prefix [gw]', 'gw') 53 | .parse(process.argv); 54 | 55 | log.level(program.verbose); 56 | 57 | // Select Forwarder transport 58 | let transport; 59 | if(program.transport === 'tcp') 60 | { 61 | let tcpPort = parseInt(program.port); 62 | if(isNaN(tcpPort)) tcpPort = 6969; 63 | transport = new TCPTransport(tcpPort); 64 | } 65 | else if(program.transport === 'mqtt') 66 | { 67 | transport = new MQTTTransport(program.broker, 68 | "91bbef0fa64c130d0b274c7299c424/bridge/in", 69 | "91bbef0fa64c130d0b274c7299c424/bridge/out"); 70 | } 71 | else 72 | { 73 | transport = new SerialTransport(115200, program.port); 74 | } 75 | 76 | let db = new GatewayDB(program.dataPath); 77 | let gw: Gateway; 78 | 79 | db.connect() 80 | .then(() => { 81 | let forwarder = new Forwarder(db, transport, program.subnet, program.key); 82 | gw = new Gateway(db, forwarder); 83 | return gw.init(program.broker, program.allowUnknownDevices); 84 | }) 85 | .then(() => { 86 | let gwMon = new GwMonitor(gw, program.monitorPrefix); 87 | log.info("Gateway Started"); 88 | }); 89 | 90 | 91 | -------------------------------------------------------------------------------- /tests/TestForwarder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var inherits = require('util').inherits; 4 | var EE = require('events').EventEmitter; 5 | var VirtualNetwork = require('./VirtualNetwork'); 6 | var log = require('./../Logger'); 7 | 8 | /* 9 | Dummy Forwarder for testing 10 | 11 | Events: 12 | data ({lqi, rssi, addr, mqttsnFrame}) 13 | ready 14 | 15 | */ 16 | 17 | var Forwarder = function() 18 | { 19 | var self = this; 20 | self.network = new VirtualNetwork(); 21 | self.network.init(); 22 | }; 23 | 24 | inherits(Forwarder, EE); 25 | 26 | Forwarder.prototype.connect = function() 27 | { 28 | var self = this; 29 | setTimeout(function() 30 | { 31 | self.emit('ready'); 32 | }, 500); 33 | 34 | self.network.on('data', function onData(data) 35 | { 36 | //log.trace('Data: ', data); 37 | 38 | var message = { 39 | lqi: 255, 40 | rssi: 255, 41 | len: data.frame.length + 4, 42 | msgType: 0xFE, 43 | ctrl: 1, 44 | addr: data.addr, 45 | mqttsnFrame: data.frame 46 | } 47 | 48 | self.emit('data', message); 49 | 50 | }); 51 | 52 | }; 53 | 54 | Forwarder.prototype.disconnect = function() 55 | { 56 | var self = this; 57 | log.trace("Test Forwarder Disconnected"); 58 | }; 59 | 60 | Forwarder.prototype.send = function(addr, packet) 61 | { 62 | var self = this; 63 | 64 | self.network.send(addr, packet); 65 | }; 66 | 67 | module.exports = Forwarder; -------------------------------------------------------------------------------- /tests/VirtualDevice.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var inherits = require('util').inherits; 4 | var EE = require('events').EventEmitter; 5 | var mqttsn = require('mqttsn-packet'); 6 | var parser = mqttsn.parser(); 7 | var log = require('./../Logger'); 8 | 9 | var DURATION = 60; 10 | 11 | var VirtualDevice = function(addr, network) 12 | { 13 | var self = this; 14 | self.addr = addr; 15 | self.connected = false; 16 | self.network = network; 17 | 18 | setInterval(function deviceKeepAlive() 19 | { 20 | if(!self.connected) return; 21 | 22 | var data = { 23 | addr: self.addr, 24 | frame: mqttsn.generate({ cmd: 'pingreq', clientId: 'test' }) 25 | } 26 | self.network.emit('data', data); 27 | 28 | }, DURATION*1000); 29 | }; 30 | 31 | inherits(VirtualDevice, EE); 32 | 33 | VirtualDevice.prototype.sendFrame = function(frame) 34 | { 35 | var self = this; 36 | var data = { 37 | addr: self.addr, 38 | frame: frame 39 | }; 40 | self.network.emit('data', data); 41 | }; 42 | 43 | VirtualDevice.prototype.parse = function(frame) 44 | { 45 | var self = this; 46 | var packet = parser.parse(frame); 47 | log.debug("Device", self.addr, "got Packet:", packet); 48 | if(packet.cmd === 'willtopicreq') setTimeout(function(){self.emit('willtopicreq')}, 10); 49 | if(packet.cmd === 'willmsgreq') setTimeout(function(){self.emit('willmsgreq')}, 10); 50 | if(packet.cmd === 'suback') setTimeout(function(){self.emit('suback')}, 10); 51 | if(packet.cmd === 'unsuback') setTimeout(function(){self.emit('unsuback')}, 10); 52 | if(packet.cmd === 'regack') setTimeout(function(){self.emit('regack')}, 10); 53 | if(packet.cmd === 'disconnect') setTimeout(function(){self.emit('disconnect')}, 10); 54 | if(packet.cmd === 'connack') setTimeout(function(){self.connected = true; self.emit('connack')}, 10); 55 | }; 56 | 57 | VirtualDevice.prototype.connect = function(withWill) 58 | { 59 | var self = this; 60 | self.sendFrame(mqttsn.generate({ cmd: 'connect', will: withWill, cleanSession: true, duration: DURATION, clientId: 'test' })); 61 | }; 62 | 63 | VirtualDevice.prototype.waitFor = function(msgType, callback) 64 | { 65 | var self = this; 66 | self.once(msgType, callback); 67 | }; 68 | 69 | VirtualDevice.prototype.willTopic = function() 70 | { 71 | var self = this; 72 | self.sendFrame(mqttsn.generate({ cmd: 'willtopic', willTopic: 'will', qos: 0, retain: false })); 73 | }; 74 | 75 | VirtualDevice.prototype.willMsg = function() 76 | { 77 | var self = this; 78 | self.sendFrame(mqttsn.generate({ cmd: 'willmsg', willMsg: 'last will' })); 79 | }; 80 | 81 | VirtualDevice.prototype.subscribe = function(qos, topicIdType, topicName) 82 | { 83 | var self = this; 84 | self.sendFrame(mqttsn.generate({ cmd: 'subscribe', qos: qos, topicIdType: topicIdType, topicName: topicName })); 85 | }; 86 | 87 | VirtualDevice.prototype.unsubscribe = function(topicIdType, topicName) 88 | { 89 | var self = this; 90 | self.sendFrame(mqttsn.generate({ cmd: 'unsubscribe', topicIdType: topicIdType, topicName: topicName })); 91 | }; 92 | 93 | VirtualDevice.prototype.publish = function(qos, retain, topicIdType, topicId, payload) 94 | { 95 | var self = this; 96 | self.sendFrame(mqttsn.generate({ cmd: 'publish', qos: qos, retain: retain, topicIdType: topicIdType, topicId: topicId, payload: payload })); 97 | }; 98 | 99 | VirtualDevice.prototype.register = function(topic) 100 | { 101 | var self = this; 102 | self.sendFrame(mqttsn.generate({ cmd: 'register', topicName: topic })); 103 | }; 104 | 105 | VirtualDevice.prototype.disconnect = function(duration) 106 | { 107 | var self = this; 108 | self.sendFrame(mqttsn.generate({ cmd: 'disconnect', duration: duration })); 109 | }; 110 | 111 | module.exports = VirtualDevice; 112 | 113 | -------------------------------------------------------------------------------- /tests/VirtualNetwork.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var inherits = require('util').inherits; 4 | var EE = require('events').EventEmitter; 5 | var async = require('async'); 6 | var VirtualDevice = require('./VirtualDevice'); 7 | var suspend = require('suspend'); 8 | 9 | /* 10 | Events: 11 | - data 12 | 13 | Current tests: 14 | two devices on the network 15 | dev1 connects with will 16 | dev2 connects without will 17 | User: check device list from GwMonitor 18 | dev2 subscribes to topic 'test' 19 | dev2 subscribes to topic 'test1' 20 | dev1 subscribes to topic 'test2' 21 | User: check subscription and topic lists from GwMonitor 22 | dev1 registers and publishes topic 'test' 23 | User: check subscription and topic lists from GwMonitor 24 | User: check that only device 2 gets the message (from terminal, packet prints) 25 | User: subscribe to topic 'test' from MQTTLens or similar and check correct message reception 26 | dev2: unsubscribes to topic 'test' 27 | User: check subscription and topic lists from GwMonitor 28 | dev2: disconnects 29 | User: check device lists from GwMonitor 30 | */ 31 | 32 | var VirtualNetwork = function() 33 | { 34 | var self = this; 35 | self.dev1 = new VirtualDevice(1, self); 36 | self.dev2 = new VirtualDevice(2, self); 37 | }; 38 | 39 | inherits(VirtualNetwork, EE); 40 | 41 | VirtualNetwork.prototype.init = function() 42 | { 43 | var self = this; 44 | // Connect test with will 45 | suspend.run(function* test1() 46 | { 47 | yield setTimeout(suspend.resume(), 1000); 48 | console.log("\nStarting test 1..."); 49 | self.dev1.connect(true); 50 | yield self.dev1.waitFor('willtopicreq', suspend.resume()); 51 | self.dev1.willTopic(); 52 | yield self.dev1.waitFor('willmsgreq', suspend.resume()); 53 | self.dev1.willMsg(); 54 | yield self.dev1.waitFor('connack', suspend.resume()); 55 | console.log(":::::::::::Connect Test with will OK"); 56 | }); 57 | // Connect test without will 58 | suspend.run(function* test2() 59 | { 60 | yield setTimeout(suspend.resume(), 2000); 61 | console.log("\nStarting test 2..."); 62 | self.dev2.connect(false); 63 | yield self.dev2.waitFor('connack', suspend.resume()); 64 | console.log(":::::::::::Connect Test without will OK"); 65 | }); 66 | 67 | // Subscribe test 68 | suspend.run(function* test3() 69 | { 70 | yield setTimeout(suspend.resume(), 3000); 71 | console.log("\nStarting test 3..."); 72 | self.dev2.subscribe(0, 'normal', 'test'); // qos, topicIdType, topic 73 | yield self.dev2.waitFor('suback', suspend.resume()); 74 | console.log(":::::::::::Subscribe test OK"); 75 | }); 76 | 77 | // Subscribe test 78 | suspend.run(function* test3_1() 79 | { 80 | yield setTimeout(suspend.resume(), 3000); 81 | console.log("\nStarting test 3.1..."); 82 | self.dev2.subscribe(0, 'normal', 'test1'); // qos, topicIdType, topic 83 | yield self.dev2.waitFor('suback', suspend.resume()); 84 | console.log(":::::::::::Subscribe test OK"); 85 | }); 86 | 87 | // Subscribe test 88 | suspend.run(function* test3_2() 89 | { 90 | yield setTimeout(suspend.resume(), 3000); 91 | console.log("\nStarting test 3.2..."); 92 | self.dev1.subscribe(0, 'normal', 'test2'); // qos, topicIdType, topic 93 | yield self.dev1.waitFor('suback', suspend.resume()); 94 | console.log(":::::::::::Subscribe test OK"); 95 | }); 96 | 97 | // Publish test 98 | suspend.run(function* test4() 99 | { 100 | yield setTimeout(suspend.resume(), 4000); 101 | console.log("\nStarting test 4..."); 102 | // Register topic 103 | self.dev1.register('test'); 104 | yield self.dev1.waitFor('regack', suspend.resume()); 105 | // TODO: get assigned topic id from regack 106 | self.dev1.publish(0, false, 'normal', 4, 'hola test'); // qos, retain, topicIdType, topicId, payload 107 | console.log(":::::::::::Publish test OK"); 108 | }); 109 | 110 | // unsubscribe test 111 | suspend.run(function* test5() 112 | { 113 | yield setTimeout(suspend.resume(), 5000); 114 | console.log("\nStarting test 5..."); 115 | self.dev2.unsubscribe('normal', 'test'); // topicIdType, topic 116 | yield self.dev2.waitFor('unsuback', suspend.resume()); 117 | console.log(":::::::::::Unsubscribe test OK"); 118 | }); 119 | 120 | // disconnect test 121 | suspend.run(function* test6() 122 | { 123 | yield setTimeout(suspend.resume(), 6000); 124 | console.log("\nStarting test 6..."); 125 | self.dev2.disconnect(null); // duration, null = no duration 126 | yield self.dev2.waitFor('disconnect', suspend.resume()); 127 | console.log(":::::::::::Disconnect test OK"); 128 | }); 129 | 130 | }; 131 | 132 | VirtualNetwork.prototype.send = function(addr, frame) 133 | { 134 | var self = this; 135 | //console.log("sending:", frame, addr); 136 | if(addr === self.dev1.addr) self.dev1.parse(frame); 137 | if(addr === self.dev2.addr) self.dev2.parse(frame); 138 | }; 139 | 140 | module.exports = VirtualNetwork; 141 | -------------------------------------------------------------------------------- /tests/testMain.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var GatewayDB = require('./../GatewayDB'); 4 | var Forwarder = require('./TestForwarder'); 5 | var Gateway = require('./../Gateway'); 6 | var GwMonitor = require('./../GwMonitor'); 7 | var log = require('./../Logger'); 8 | var program = require('commander'); 9 | var pjson = require('./../package.json'); 10 | var path = require('path'); 11 | 12 | var DEFAULT_DATA_DIR = path.join(process.env[(process.platform === 'win32') ? 'ALLUSERSPROFILE' : 'HOME'], '.aquila-gateway'); 13 | var DEFAULT_DATA_PATH = path.join(DEFAULT_DATA_DIR, 'data.json'); 14 | 15 | program 16 | .version(pjson.version) 17 | .option('-v, --verbose [level]', 'Verbosity level for logging (fatal, error, warn, info, debug, trace) [debug]', 'debug') 18 | .option('-d, --data-path [path]', 'Path to data persist file [' + DEFAULT_DATA_PATH + ']', DEFAULT_DATA_PATH) 19 | .parse(process.argv); 20 | 21 | log.level(program.verbose); 22 | 23 | var db = new GatewayDB(program.dataPath); 24 | 25 | var forwarder = new Forwarder(); 26 | 27 | var gw = new Gateway(db, forwarder); 28 | 29 | gw.init('http://localhost:1883', true); 30 | 31 | gw.on('ready', function onGwReady() 32 | { 33 | var gwMon = new GwMonitor(gw); 34 | log.info("Gateway Started"); 35 | }); 36 | 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "noImplicitAny": false, 7 | "preserveConstEnums": true, 8 | "removeComments": true, 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "outDir": "dist" 12 | }, 13 | "include": [ 14 | "src/**/*.ts" 15 | ], 16 | "exclude": [ 17 | "node_modules" 18 | ], 19 | "files": [ 20 | "typings/*.d.ts" 21 | ] 22 | } --------------------------------------------------------------------------------