Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript types #519

Merged
merged 6 commits into from
Oct 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .generator/node/all-services.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ const {{serviceName}} = require('./{{#if data.filename}}{{data.filename}}{{else}
* @class AllServices
*/
class AllServices {
/**
*
* @param {string} host Sonos host
* @param {number} port Sonos port, default `1400`
*/
constructor (host, port) {
this.host = host
this.port = port
this.port = port || 1400
}

{{#each services}}
/**
* Get instance of {{name}} service
*
* @returns {{serviceName}}
* @returns { {{~ serviceName ~}} }
*/
{{serviceName}} () {
if (!this.{{lower serviceName}}) {
Expand Down
9 changes: 8 additions & 1 deletion .generator/node/service.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ const Service = require('./Service')
* @extends {Service}
*/
class {{serviceName}} extends Service {
/**
*
* @param {string} host Sonos host
* @param {number} port Sonos port, default `1400`
*/
constructor (host, port) {
super()
this.name = '{{name}}'
Expand All @@ -38,7 +43,9 @@ class {{serviceName}} extends Service {
* @remarks {{{remarks}}}
{{/if}}
{{#if outputs}}
* @returns {Object} response object, with these properties {{#each outputs}}'{{name}}'{{#unless @last}}, {{/unless}}{{/each}}
* @returns {Promise<{ {{#each outputs}}{{name}}: {{relatedStateVariable.dataType}}{{#unless @last}}, {{/unless}}{{/each ~}} }>} response object.
{{else}}
* @returns {Promise<Boolean>} request succeeded
{{/if}}
*/
{{#if inputs}}
Expand Down
31 changes: 18 additions & 13 deletions examples/deviceDiscovery.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
const Sonos = require('../')

console.log('Searching for Sonos devices for 5 seconds...')
console.log('Searching for Sonos devices for 10 seconds...')

const discovery = new Sonos.AsyncDeviceDiscovery()

discovery.discover().then((device, model) => {
console.log('Found one sonos device %s getting all groups', device.host)
return device.getAllGroups().then((groups) => {
console.log('Groups %s', JSON.stringify(groups, null, 2))
return groups[0].CoordinatorDevice().togglePlayback()
// discovery.discover({ port: 50333, timeout: 10000 }).then((device, model) => {
// console.log('Found one sonos device %s getting all groups', device.host)
// return device.getAllGroups().then((groups) => {
// console.log('Groups %s', JSON.stringify(groups, null, 2))
// //return groups[0].CoordinatorDevice().togglePlayback()
// })
// }).catch(e => {
// console.warn(' Error in discovery %o', e)
// })

discovery.discoverMultiple({ port: 50333, timeout: 10000 }).then((devices) => {
console.log('Found %d sonos devices', devices.length)
devices.forEach(device => {
console.log('Device IP: %s', device.host)
})
}).catch(e => {
console.warn(' Error in discovery %j', e)
})

// discovery.discoverMultiple({ timeout: 5000 }).then((devices) => {
// console.log('Found %d sonos devices', devices.length)
// devices.forEach(device => {
// console.log('Device IP: %s', device.host)
// })
// }).catch(e => {
// console.warn(' Error in discovery %j', e)
// const discovery = new Sonos.DeviceDiscovery({}, (device, model) => {
// console.log('Found one sonos device %s', device.host)
// process.exit(0);
// })
14 changes: 13 additions & 1 deletion lib/asyncDeviceDiscovery.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
const DeviceDiscovery = require('./deviceDiscovery')
class AsyncDeviceDiscovery {
/**
* Discover one device, will return first device found
* @param {object} options
* @param {number} options.timeout Milliseconds to timeout
* @returns {Promise<{device: import("./sonos").Sonos, model: string}>}
*/
async discover (options = { timeout: 5000 }) {
return new Promise((resolve, reject) => {
const discovery = DeviceDiscovery(options)
discovery.once('DeviceAvailable', (device, model) => {
discovery.destroy()
resolve(device, model)
resolve({ device, model })
})

discovery.once('timeout', () => {
Expand All @@ -14,6 +20,12 @@ class AsyncDeviceDiscovery {
})
}

/**
* Discover multiple devices, will return after timeout
* @param {object} options
* @param {number} options.timeout Milliseconds to timeout
* @returns {Promise<import("./sonos").Sonos[]>}
*/
async discoverMultiple (options = { timeout: 5000 }) {
return new Promise((resolve, reject) => {
const discovery = DeviceDiscovery(options)
Expand Down
146 changes: 84 additions & 62 deletions lib/deviceDiscovery.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,79 +7,101 @@
*/

const dgram = require('dgram')
const util = require('util')
const EventEmitter = require('events').EventEmitter
const Sonos = require('./sonos').Sonos

const PLAYER_SEARCH = Buffer.from(['M-SEARCH * HTTP/1.1',
'HOST: 239.255.255.250:1900',
'MAN: ssdp:discover',
'MX: 1',
'ST: urn:schemas-upnp-org:device:ZonePlayer:1'].join('\r\n'))

/**
* Create a new instance of DeviceDiscovery
* @class DeviceDiscovery
* @emits 'DeviceAvailable' on a Sonos Component Discovery
*/
const DeviceDiscovery = function DeviceDiscovery (options) {
const self = this
self.foundSonosDevices = {}
self.onTimeout = function () {
clearTimeout(self.pollTimer)
}
const PLAYER_SEARCH = Buffer.from(['M-SEARCH * HTTP/1.1',
'HOST: 239.255.255.250:1900',
'MAN: ssdp:discover',
'MX: 1',
'ST: urn:schemas-upnp-org:device:ZonePlayer:1'].join('\r\n'))
const sendDiscover = function () {
['239.255.255.250', '255.255.255.255'].forEach(function (addr) {
self.socket.send(PLAYER_SEARCH, 0, PLAYER_SEARCH.length, 1900, addr)
class DeviceDiscovery extends EventEmitter {
constructor (options) {
super()
// this.options = options
this.foundSonosDevices = {}
this.socket = dgram.createSocket('udp4')

this.socket.on('message', (buffer, rinfo) => {
const data = buffer.toString()
if (data.match(/.+Sonos.+/)) {
const modelCheck = data.match(/SERVER.*\((.*)\)/)
const model = (modelCheck.length > 1 ? modelCheck[1] : null)
const addr = rinfo.address
if (!(addr in this.foundSonosDevices)) {
const sonos = this.foundSonosDevices[addr] = new Sonos(addr)
this.emit('DeviceAvailable', sonos, model)
}
}
})

this.socket.on('close', () => {
if (this.searchTimer) {
clearTimeout(this.searchTimer)
}
clearTimeout(this.pollTimer)
})

this.socket.on('error', (err) => {
this.emit('error', err)
})

this.socket.on('listening', () => {
// console.log('UDP port %d opened for discovery', this.socket.address().port)
this.socket.setBroadcast(true)
this.sendDiscover()
})

this.socket.bind(options.port)

if (options && options.timeout) {
this.searchTimer = setTimeout(() => {
this.socket.close()
this.emit('timeout')
}, options.timeout)
}
}

onTimeout () {
clearTimeout(this.pollTimer)
}

sendDiscover () {
this.sendDiscoveryOnAddress('239.255.255.250')
this.sendDiscoveryOnAddress('255.255.255.255')

// Periodically send discover packet to find newly added devices
self.pollTimer = setTimeout(sendDiscover, 10000)
this.pollTimer = setTimeout(() => {
this.sendDiscover()
}, 10000)
// Remove the on timeout listener and add back in every iteration
self.removeListener('timeout', self.onTimeout)
self.on('timeout', self.onTimeout)
this.removeListener('timeout', this.onTimeout)
this.on('timeout', this.onTimeout)
}
this.socket = dgram.createSocket('udp4', function (buffer, rinfo) {
buffer = buffer.toString()
if (buffer.match(/.+Sonos.+/)) {
const modelCheck = buffer.match(/SERVER.*\((.*)\)/)
const model = (modelCheck.length > 1 ? modelCheck[1] : null)
const addr = rinfo.address
if (!(addr in self.foundSonosDevices)) {
const sonos = self.foundSonosDevices[addr] = new Sonos(addr)
self.emit('DeviceAvailable', sonos, model)

sendDiscoveryOnAddress (address) {
this.socket.send(PLAYER_SEARCH, 0, PLAYER_SEARCH.length, 1900, address, (err, bytes) => {
if (err) {
console.error(err)
}
})
}

destroy () {
if (this.searchTimer) {
clearTimeout(this.searchTimer)
}
})
this.socket.on('close', function () {
if (self.searchTimer) {
clearTimeout(self.searchTimer)
if (this.pollTimer) {
clearTimeout(this.pollTimer)
}
clearTimeout(self.pollTimer)
})
this.socket.on('error', function (err) {
self.emit('error', err)
})
this.socket.bind(options, function () {
self.socket.setBroadcast(true)
sendDiscover()
})
if (options.timeout) {
self.searchTimer = setTimeout(function () {
self.socket.close()
self.emit('timeout')
}, options.timeout)
this.socket.close()
}
return this
}
util.inherits(DeviceDiscovery, EventEmitter)

/**
* Destroys DeviceDiscovery class, stop searching, clean up
* @param {Function} callback ()
*/
DeviceDiscovery.prototype.destroy = function (callback) {
clearTimeout(this.searchTimer)
clearTimeout(this.pollTimer)
this.socket.close(callback)
}

/**
Expand All @@ -89,18 +111,18 @@ DeviceDiscovery.prototype.destroy = function (callback) {
* (in milliseconds).
* Set 'port' to use a specific inbound UDP port,
* rather than a randomly assigned one
* @param {Function} listener Optional 'DeviceAvailable' listener (sonos)
* @param {(device: Sonos, model: string) => void | undefined} listener Optional 'DeviceAvailable' listener (sonos)
* @return {DeviceDiscovery}
*/
const deviceDiscovery = function (options, listener) {
const deviceDiscovery = function (options, listener = undefined) {
if (typeof options === 'function') {
listener = options
options = null
options = undefined
}
options = options || {}
listener = listener || null
listener = listener || undefined
const search = new DeviceDiscovery(options)
if (listener !== null) {
if (listener !== undefined) {
search.on('DeviceAvailable', listener)
}
return search
Expand Down
Loading