Skip to content

Commit

Permalink
Adding mDNS discovery and removing UPnP as it is no longer available. F…
Browse files Browse the repository at this point in the history
…ixes #221
  • Loading branch information
peter-murray committed Aug 13, 2022
1 parent 1103393 commit d54b973
Show file tree
Hide file tree
Showing 10 changed files with 908 additions and 196 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const discovery = require('../../dist/cjs').discovery
;

async function getBridge() {
const results = await discovery.upnpSearch();
const results = await discovery.mdnsSearch();

// Results will be an array of bridges that were found
console.log(JSON.stringify(results, null, 2));
Expand Down
914 changes: 746 additions & 168 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"dependencies": {
"@peter-murray/hue-bridge-model": "^2.0.1",
"bonjour": "^3.5.0",
"bottleneck": "^2.19.5",
"node-fetch": "^2.6.1"
},
Expand All @@ -38,6 +39,7 @@
"@types/mocha": "^9.0.0",
"@types/node": "^14.14.31",
"@types/node-fetch": "^2.5.12",
"@types/bonjour": "^3.5.10",
"chai": "~4.3.4",
"mocha": "^9.1.3",
"ts-node": "^10.4.0",
Expand Down
47 changes: 47 additions & 0 deletions src/api/discovery/DiscoveryLogger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import EventEmitter from 'events';

const DEBUG: boolean = /node-hue-discovery/.test(process.env['NODE_DEBUG'] || '');

export class DiscoveryLogger {

readonly name: string;

constructor(name: string) {
this.name = name;
}

static install(name: string, browser: EventEmitter) {
if (DiscoveryLogger.isDebug()) {
const logger = new DiscoveryLogger(name);
logger.log(`Installing discovery logger`);

browser.on('up', (val: any) => {
logger.log(`**up: ${JSON.stringify(val)}`);
});

browser.on('down', (val: any) => {
logger.log(`**down: ${JSON.stringify(val)}`);
});

browser.on('error', (val: any) => {
logger.log(`**error: ${JSON.stringify(val)}`);
});
}
}

static isDebug(): boolean {
return DEBUG;
}

log(event: string, payload?: any) {
if (DiscoveryLogger.isDebug()) {
let detail = `${event}`;

if (payload) {
detail += `\n${JSON.stringify(payload)}`;
}

console.log(`DiscoveryLogger [${this.name}] :: ${detail}`);
}
}
}
22 changes: 7 additions & 15 deletions src/api/discovery/bridge-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,13 @@ export function getBridgeDescription(bridge: DiscoveryBridgeDefinition, timeout?
const ipAddress = bridge.internalipaddress;

return request({
method: 'GET',
url: `http://${ipAddress}/description.xml`,
timeout: timeout || DATA_TIMEOUT,
headers: {
accept: 'text/xml'
}
})
// return axios.request({
// method: 'GET',
// url: `http://${ipAddress}/description.xml`,
// timeout: timeout | DATA_TIMEOUT,
// headers: {
// accept: 'text/xml'
// }
// })
method: 'GET',
url: `http://${ipAddress}/description.xml`,
timeout: timeout || DATA_TIMEOUT,
headers: {
accept: 'text/xml'
}
})
.catch(err => {
throw new ApiError(`Failed to resolve the XML Description for the bridge at ${ipAddress}; ${err.message}`);
})
Expand Down
10 changes: 5 additions & 5 deletions src/api/discovery/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { BridgeConfigData } from './discoveryTypes';

describe('discovery', () => {

describe('#nupnpSearch()', () => {
describe('#nupnpSearch()', function () {

this.timeout(10000);

it('should discover a bridge', async () => {
const results = await discovery.nupnpSearch();
Expand All @@ -21,13 +23,12 @@ describe('discovery', () => {
});
});


describe('#upnpSearch()', function () {
describe('#mdnsSearch()', function () {

this.timeout(10000);

it('should discover a bridge', async () => {
const results = await discovery.upnpSearch(3000);
const results = await discovery.mdnsSearch();

expect(results).to.be.instanceOf(Array);
expect(results).to.have.length.greaterThan(0);
Expand All @@ -42,7 +43,6 @@ describe('discovery', () => {
});
});


describe('#description()', function () {

this.timeout(10000);
Expand Down
13 changes: 9 additions & 4 deletions src/api/discovery/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import * as bridgeValidator from './bridge-validation';
import { nupnp } from './nupnp';
import { SSDPSearch } from './UPnP';
import { mDNSSearch } from './mDNS';
import {
BridgeConfigError, BridgeDiscoveryResponse, DiscoveryBridgeDefinition, DiscoveryBridgeDescription
} from './discoveryTypes';


export function upnpSearch(timeout?: number): Promise<DiscoveryBridgeDescription[]> {
const upnp = new SSDPSearch();
return upnp.search(timeout).then(loadDescriptions);
export function mdnsSearch(timeout?: number): Promise<DiscoveryBridgeDescription[]> {
const mDNSearch = new mDNSSearch();

return mDNSearch.search(timeout)
.then((data) => {
mDNSearch.finished();
return loadDescriptions(data);
});
}

export function nupnpSearch(): Promise<BridgeDiscoveryResponse[]> {
Expand Down
29 changes: 29 additions & 0 deletions src/api/discovery/mDNS.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect } from 'chai';
import { mDNSSearch } from './mDNS';

describe('mDNS', function () {

this.timeout(40 * 1000);

let mdns: mDNSSearch;

beforeEach(() => {
mdns = new mDNSSearch();
});

afterEach(() => {
if (mdns) {
mdns.finished();
}
})

it('should discover a bridge on the network', async () => {
// const results = await mdns.search(15 * 1000);
const results = await mdns.search();

expect(results).to.be.instanceOf(Array);
expect(results).to.have.length.greaterThan(0);
expect(results[0]).to.have.property('id');
expect(results[0]).to.have.property('internalipaddress');
});
});
57 changes: 57 additions & 0 deletions src/api/discovery/mDNS.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { DiscoveryBridgeDefinition } from './discoveryTypes';

import * as bonjour from 'bonjour';
import { DiscoveryLogger } from './DiscoveryLogger';
import { Service } from 'bonjour';

export class mDNSSearch {

private mdns: any;

private browser: any;

constructor() {
this.mdns = bonjour.default({})
}

search(timeout?: number): Promise<DiscoveryBridgeDefinition[]> {
const self = this;

// Queue up a search for services immediately
this.browser = this.mdns.find({
type: 'hue',
protocol: 'tcp',
});
DiscoveryLogger.install('mDNS', this.browser);

return new Promise((resolve) => {
this.browser.start();

// Await our timeout before returning any results
setTimeout(() => {
const allServices = self.browser.services;

let results: DiscoveryBridgeDefinition[] = [];
if (allServices) {
resolve(allServices.map((service: Service) => {
if (service.addresses) {
return {
internalipaddress: service.addresses[0],
id: service.fqdn,
}
}
}))
}

self.browser.stop();
resolve(results);
}, timeout || 5000);
});
}

finished() {
if (this.mdns) {
this.mdns.destroy();
}
}
}
8 changes: 5 additions & 3 deletions src/v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@ import { model } from '@peter-murray/hue-bridge-model';
import { v3Model } from './v3Model';
import { deprecatedFunction } from './util';
import * as api from './api';
import { description, nupnpSearch, upnpSearch } from './api/discovery';
import { description, nupnpSearch, mdnsSearch } from './api/discovery';


// Definition of the v3 API for node-hue-api
const v3 = {
api: api,

discovery: {
// Have overridden this with mDNS searching as UPnP is no more and was removed from the bridge
upnpSearch: (timeout: number) => {
deprecatedFunction(
'6.x',
`require('node-hue-api').v3.discovery.upnpSearch()`,
`Use require('node-hue-api').discovery.upnpSearch()`);
return upnpSearch(timeout);
`Use require('node-hue-api').discovery.mdnsSearch()`);
return mdnsSearch(timeout);
},

nupnpSearch: () => {
Expand Down

0 comments on commit d54b973

Please sign in to comment.