UNPKG

signalk-server

Version:

An implementation of a [Signal K](http://signalk.org) server for boats.

292 lines (291 loc) 12 kB
"use strict"; /* * Copyright 2017 Scott Bender <scott@scottbender.net> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); const debug_1 = require("./debug"); const mdnsPatch_1 = require("./mdnsPatch"); const debug = (0, debug_1.createDebug)('signalk-server:discovery'); const canboatjs = require('@canboat/canboatjs'); const dgram = require('dgram'); (0, mdnsPatch_1.patchAstronautLabsMdns)(); const { Browser } = require('@astronautlabs/mdns'); const { networkInterfaces } = require('os'); module.exports.runDiscovery = function (app) { if (canboatjs.discover) { try { canboatjs.discover(app); } catch (ex) { console.log(ex); } } discoverWLN10(); discoverGoFree(); discoverSignalkWs('ws'); discoverSignalkWs('wss'); function findUDPProvider(port) { return app.config.settings.pipedProviders.find((provider) => { return (provider.pipeElements && provider.pipeElements.length === 1 && provider.pipeElements[0].type === 'providers/simple' && provider.pipeElements[0].options && provider.pipeElements[0].options.type === 'NMEA0183' && provider.pipeElements[0].options.subOptions.type === 'udp' && provider.pipeElements[0].options.subOptions.port === port); }); } function findTCPProvider(host, port) { return app.config.settings.pipedProviders.find((provider) => { return (provider.pipeElements && provider.pipeElements.length === 1 && provider.pipeElements[0].type === 'providers/simple' && provider.pipeElements[0].options && provider.pipeElements[0].options.type === 'NMEA0183' && provider.pipeElements[0].options.subOptions.type === 'tcp' && provider.pipeElements[0].options.subOptions.host === host && provider.pipeElements[0].options.subOptions.port === port); }); } function findWSProvider(ip, wsType, host, port) { return app.config.settings.pipedProviders.find((provider) => { return (provider.pipeElements && provider.pipeElements.length === 1 && provider.pipeElements[0].type === 'providers/simple' && provider.pipeElements[0].options && provider.pipeElements[0].options.type === 'SignalK' && provider.pipeElements[0].options.subOptions.type === wsType && provider.pipeElements[0].options.subOptions.port.toString() === port.toString() && (provider.pipeElements[0].options.subOptions.host === host || provider.pipeElements[0].options.subOptions.host === ip)); }); } function discoverGoFree() { const socket = dgram.createSocket('udp4'); const found = []; socket.on('message', function (buffer) { const msg = buffer.toString('utf8'); if (msg[0] === '{') { try { const json = JSON.parse(msg); const serial = json.SerialNumber; if (json.Services && found.indexOf(serial) === -1) { json.Services.forEach((service) => { if (service.Service === 'nmea-0183' && !findTCPProvider(json.IP, service.Port)) { const id = `${json.Name} (${serial})`; debug('found GoFree %s at %s:%s', id, json.IP, service.Port); found.push(serial); app.emit('discovered', { id: id, pipeElements: [ { type: 'providers/simple', options: { type: 'NMEA0183', subOptions: { type: 'tcp', host: json.IP, port: service.Port } } } ] }); } }); } } catch (err) { debug('discoverGoFree:', err); return; } } }); socket.on('error', (error) => { debug('discoverGoFree:', error); }); socket.on('close', () => { debug('discoverGoFree close'); }); debug('looking for GoFree broadcasting on UDP port 2052'); try { socket.bind(2052, () => { try { socket.addMembership('239.2.1.1'); } catch (ex) { debug('discoverGoFree:', ex); } }); } catch (ex) { debug('discoverGoFree:', ex); } setTimeout(() => { if (socket) { socket.close(); } }, 10000); } function discoverWLN10() { if (!findUDPProvider('2000')) { let socket = dgram.createSocket('udp4'); socket.on('message', function (buffer) { const msg = buffer.toString('utf8'); if (msg[0] === '$') { socket.close(); socket = undefined; app.emit('discovered', { id: 'WLN10', pipeElements: [ { type: 'providers/simple', options: { type: 'NMEA0183', subOptions: { type: 'udp', port: '2000' } } } ] }); } }); socket.on('error', (error) => { debug('discoverWLN10:', error); }); socket.on('close', () => { debug('discoverWLN10 close'); }); debug('looking for a WLN10 broadcasting UDP port 2000'); try { socket.bind(2000); } catch (ex) { debug('discoverWLN10:', ex); } setTimeout(() => { if (socket) { socket.close(); } }, 5000); } } function discoverSignalkWs(wsType) { try { const browser = new Browser('_signalk-' + wsType + '._tcp'); let browserStopped = false; debug('looking for SignalK ' + wsType); browser.on('serviceUp', function onServiceUp(data) { try { // Suppress the local server's own advertisement. data.addresses // can contain a mix of IPv4 and IPv6 entries (including // loopback) depending on the underlying mDNS responder, so a // single address from the list is not enough — any local match // anywhere in the list means this is our own service. const addresses = Array.isArray(data.addresses) ? data.addresses : []; const primaryAddress = addresses[0]; if (primaryAddress && !addresses.some(isOwnAddress) && data.type && data.type.name === 'signalk-' + wsType && !findWSProvider(primaryAddress, wsType, data.host, data.port)) { debug('discoverSignalkWs found data[' + wsType + ']:', data); const providerId = `${wsType}-${data.host}:${data.port} (${primaryAddress})`; app.emit('discovered', { id: providerId, enabled: false, pipeElements: [ { type: 'providers/simple', options: { type: 'SignalK', subOptions: { type: wsType, host: data.host, port: data.port, address: primaryAddress, providerId }, providerId } } ] }); } } catch (err) { debug('discoverSignalkWs:', err); } }); browser.on('error', (err) => { browserStopped = true; debug('discoverSignalkWs:', err); }); browser.start(); setTimeout(() => { try { if (browserStopped) { return; } browserStopped = true; browser.stop(); debug('discoverSignalkWs close'); } catch (err) { debug('discoverSignalkWs:', err); } }, 5000); } catch (err) { debug('discoverSignalkWs:', err); } } // True when `addr` is an address bound to one of our own interfaces, // covering IPv4 (including 127.0.0.0/8), IPv6 (including ::1 and // link-local fe80::*), and the zone-ID suffix mDNS responders append // to link-local IPv6 (e.g. 'fe80::1%eth0'). Used to filter the local // server's own mDNS advertisement out of discovery results. function isOwnAddress(addr) { if (typeof addr !== 'string' || addr.length === 0) return false; const normalized = addr.replace(/%.*$/, '').toLowerCase(); const nets = networkInterfaces(); for (const name of Object.keys(nets)) { for (const net of nets[name] || []) { if (net.address && net.address.toLowerCase() === normalized) { return true; } } } return false; } }; if (require.main === module) { const app = { config: { settings: { pipedProviders: [] } }, emit: (event, p) => { console.log(`found ${JSON.stringify(p)}`); } }; module.exports.runDiscovery(app); } //# sourceMappingURL=discovery.js.map