UNPKG

domotz-remote-pawn

Version:

Domotz Agent

184 lines (156 loc) 6.96 kB
/** This file is part of Domotz Agent. * Copyright (C) 2016 Domotz UK LLP * * Domotz Agent is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Domotz Agent is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Domotz Agent. If not, see <http://www.gnu.org/licenses/>. **/ /** * * Created by Massimiliano Cuzzoli <massi@domotz.com> on 26/08/16. * */ const PROTOCOL_NAME = 'upnp'; const UPNP_SEMAPHORE = 'UPNP_SEMAPHORE'; const UPNP_INSPECTION_TIMEOUT = 15000; // milliseconds const UPNP_DISCOVERY_TIMEOUT = 5000; // milliseconds const SEARCH_ATTEMPT_INTERVAL = 500; // milliseconds const MAX_SEARCH_ATTEMPTS = 5; var upnpDiscoveryFactory = function (resourceLocator, messageBuilder, responseParser, deferredRequestHandler) { "use strict"; var q = resourceLocator.q; var ssdp = resourceLocator.ssdp; var mutex = resourceLocator.utils.mutex; var isLoopbackLocation = resourceLocator.utils.web.isLoopbackUrl; var ssdpClient; // A new client is instantiated when the discovery starts var upnpDiscoveryWorkingData; // This data structure is cleaned up when the discovery starts var upnpDiscoverySnapshotData = {}; var upnpDiscoveryStartTimestamp; // Timestamp of last discovery start var lock_discovery = function () { return mutex.lock(UPNP_SEMAPHORE, 1); }; var unlock_discovery = function () { mutex.unlock(UPNP_SEMAPHORE, 1); }; var getDeviceServices = function (device_address) { var promiseList = []; var locationList = upnpDiscoveryWorkingData[device_address]; console.debug('Inspecting upnp device ' + device_address); for (var location in locationList) { if (locationList.hasOwnProperty(location) && !isLoopbackLocation(location)) { (function (location_arg) { var resultHandler = function (data) { locationList[location_arg]['result'] = data; return device_address; }; var errorHandler = resultHandler; console.debug('Inspecting upnp location ' + location_arg); promiseList.push(deferredRequestHandler.inspect_upnp_resource({ url: location_arg, method: 'get', timeout: UPNP_INSPECTION_TIMEOUT }) .then(responseParser.parse_xml_response) .then(resultHandler) .catch(errorHandler)); })(location); } } return promiseList; }; var storeUpnpInformationForDevice = function (data) { if (!data) { console.warn('Upnp: Empty device address list'); return; } var deviceAddress = data[0]; upnpDiscoverySnapshotData[deviceAddress] = messageBuilder.buildUpnpDeviceMessageForEngine( upnpDiscoveryWorkingData[deviceAddress]); }; var collectUpnpInformation = function (callback, err_callback) { var data_completion_promises = []; ssdpClient.stop(); for (var deviceAddress in upnpDiscoveryWorkingData) { if (upnpDiscoveryWorkingData.hasOwnProperty(deviceAddress)) { var data_gather_promise, device_promises; device_promises = getDeviceServices(deviceAddress); data_gather_promise = q.all(device_promises) .then(storeUpnpInformationForDevice) .catch(function (err) { console.error('Upnp Discovery ERROR: ' + JSON.stringify(err)); }); data_completion_promises.push(data_gather_promise); } } q.all(data_completion_promises) .then(function () { unlock_discovery(); if (callback) callback(upnpDiscoverySnapshotData) }) .catch(function (err) { unlock_discovery(); if (err_callback) err_callback(err) }); }; var onSsdpResponse = function (headers, statusCode, rinfo) { if (upnpDiscoveryWorkingData[rinfo.address] === undefined) { upnpDiscoveryWorkingData[rinfo.address] = {}; } if (upnpDiscoveryWorkingData[rinfo.address][headers.LOCATION] === undefined) { upnpDiscoveryWorkingData[rinfo.address][headers.LOCATION] = {uuid: {}} } upnpDiscoveryWorkingData[rinfo.address][headers.LOCATION].uuid[headers.USN] = true; console.debug('SSDP response: (location=' + headers.LOCATION + ', uuid=' + headers.USN + ')'); }; var discover = function (callback, err_callback) { if (!lock_discovery()){ console.warn("WARNING: UPNP Discovery service ALREADY STARTED, ignoring call !"); return; } ssdpClient = new ssdp.Client(); upnpDiscoveryWorkingData = {}; upnpDiscoveryStartTimestamp = Math.floor(new Date() / 1000); ssdpClient.on('response', onSsdpResponse); for (var i = 0; i < MAX_SEARCH_ATTEMPTS; i++) { setTimeout(function () { ssdpClient.search('ssdp:all') }, SEARCH_ATTEMPT_INTERVAL * i); } setTimeout(function () { collectUpnpInformation(callback, err_callback) }, UPNP_DISCOVERY_TIMEOUT); }; var applyFunctionToSnapshotEntries = function (functionToApply) { for (var device_address in upnpDiscoverySnapshotData) { if (upnpDiscoverySnapshotData.hasOwnProperty(device_address)) { functionToApply(device_address, upnpDiscoverySnapshotData[device_address], PROTOCOL_NAME); } } }; var searchEntryIntoSnapshot = function (device_address) { return upnpDiscoverySnapshotData[device_address]; }; return { discover: discover, isDiscoveryInProgress: function () { return mutex.isLocked(UPNP_SEMAPHORE); }, applyFunctionToSnapshotEntries: applyFunctionToSnapshotEntries, searchEntryIntoSnapshot: searchEntryIntoSnapshot, _getUpnpWorkingData: function () { return upnpDiscoveryWorkingData }, getProtocolName: function () { return PROTOCOL_NAME }, getProtocolNameUpperCase: function () { return PROTOCOL_NAME.toUpperCase(); } }; }; module.exports.factory = upnpDiscoveryFactory;