Skip to content

Commit

Permalink
Merge 622223a into e50646e
Browse files Browse the repository at this point in the history
  • Loading branch information
svrooij committed Aug 12, 2020
2 parents e50646e + 622223a commit 0e07431
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 53 deletions.
22 changes: 12 additions & 10 deletions examples/events.js
Expand Up @@ -7,13 +7,9 @@ kantoor.AlarmClockService.Events.on(ServiceEvents.Data, data => {
console.log('AlarmClock data %s', JSON.stringify(data))
})

// kantoor.ZoneGroupTopologyService.Events.on(ServiceEvents.Data, data => {
// console.log('ZoneGroupTopology data %s', JSON.stringify(data))
// })

// kantoor.ZoneGroupTopologyService.Events.on(ServiceEvents.Data, data => {
// console.log('ZoneGroupTopology lastchange %s', JSON.stringify(data, null, 2))
// })
kantoor.ZoneGroupTopologyService.Events.on(ServiceEvents.Data, data => {
console.log('ZoneGroupTopology data %s', JSON.stringify(data))
})

kantoor.AVTransportService.Events.on(ServiceEvents.Data, data => {
console.log('AVTransport lastchange %s', JSON.stringify(data, null, 2))
Expand All @@ -23,11 +19,17 @@ kantoor.RenderingControlService.Events.on(ServiceEvents.Data, data => {
console.log('RenderingControl lastchange %s', JSON.stringify(data, null, 2))
})

setInterval(async () => {
const result = await kantoor.RefreshEventSubscriptions();
console.log('Succesfully refreshed the events %s', result)
}, 300 * 1000)

process.on('SIGINT', () => {
console.log('Hold-on cancelling all subscriptions')
// kantoor.AVTransportService.Events.removeAllListeners(ServiceEvents.Data)
// kantoor.RenderingControlService.Events.removeAllListeners(ServiceEvents.Data)
// kantoor.ZoneGroupTopologyService.Events.removeAllListeners(ServiceEvents.Data)
kantoor.AlarmClockService.Events.removeAllListeners(ServiceEvents.Data)
kantoor.AVTransportService.Events.removeAllListeners(ServiceEvents.Data)
kantoor.RenderingControlService.Events.removeAllListeners(ServiceEvents.Data)
kantoor.ZoneGroupTopologyService.Events.removeAllListeners(ServiceEvents.Data)

setTimeout(() => {
process.exit(0)
Expand Down
4 changes: 4 additions & 0 deletions examples/use-manager.js
Expand Up @@ -48,6 +48,10 @@ manager.InitializeFromDevice(process.env.SONOS_HOST || '192.168.96.56')
})
.catch(console.error)

manager.onNewDevice((device) => {
console.log('New device found %s %s', device.Name, device.Uuid);
})

process.on('SIGINT', () => {
manager.Devices.forEach(d => {
d.CancelEvents()
Expand Down
14 changes: 14 additions & 0 deletions src/generator/templates/sonos-base.template.hbs
Expand Up @@ -63,4 +63,18 @@ export default class SonosDeviceBase {

{{/unless}}
{{/each}}

/**
* Refresh all service event subscriptions, if you were already subscribed.
*
* @returns {Promise<boolean>} returns true if at least one service subscription is refreshed.
* @memberof SonosDeviceBase
*/
public async RefreshEventSubscriptions(): Promise<boolean> {
let result = false;
{{#each services}}
if (this.{{pName}} !== undefined) result = await this.{{pName}}.CheckEventListener() || result;
{{/each}}
return result;
}
}
103 changes: 61 additions & 42 deletions src/services/base-service.ts
Expand Up @@ -292,7 +292,7 @@ export default abstract class BaseService <TServiceEvent> {
// #region Events
private sid?: string;

private eventRenewTimeout?: NodeJS.Timeout;
private eventRenewInterval?: NodeJS.Timeout;

/**
* Events allowes you access to the events generated by this service.
Expand All @@ -309,21 +309,20 @@ export default abstract class BaseService <TServiceEvent> {
this.debug('Listener removed');
if (this.events === undefined) return;
const events = this.events.eventNames().filter((e) => e !== 'removeListener' && e !== 'newListener');
if (this.sid !== undefined && events.length === 0) await this.cancelSubscription(this.sid);
if (this.sid !== undefined && events.length === 0) await this.cancelSubscription();
});
this.events.on('newListener', async () => {
this.debug('Listener added');
if (this.sid === undefined && process.env.SONOS_DISABLE_EVENTS === undefined) {
this.debug('Subscribing to events');
const sid = await this.subscribeForEvents();
SonosEventListener.DefaultInstance.RegisterSubscription(sid, this);
await this.subscribeForEvents();
}
});
}
return this.events;
}

private async subscribeForEvents(): Promise<string> {
private async subscribeForEvents(): Promise<boolean> {
const callback = SonosEventListener.DefaultInstance.GetEndpoint(this.uuid, this.serviceNane);
const resp = await fetch(new Request(
`http://${this.host}:${this.port}${this.eventSubUrl}`,
Expand All @@ -341,56 +340,76 @@ export default abstract class BaseService <TServiceEvent> {
throw new Error('No subscription id received');
}
this.sid = sid;
this.eventRenewTimeout = setTimeout(async () => {
if (this.sid !== undefined) {
await this.renewEventSubscription(this.sid);
}
}, 3500 * 1000);
if (this.eventRenewInterval === undefined) {
this.eventRenewInterval = setInterval(async () => {
await this.renewEventSubscription();
}, 600 * 1000); // Renew events every 10 minutes.
}
SonosEventListener.DefaultInstance.RegisterSubscription(sid, this);

return sid;
return true;
}

private async renewEventSubscription(sid: string): Promise<boolean> {
private async renewEventSubscription(): Promise<boolean> {
this.debug('Renewing event subscription');
const resp = await fetch(new Request(
`http://${this.host}:${this.port}${this.eventSubUrl}`,
{
method: 'SUBSCRIBE',
headers: {
SID: sid,
Timeout: 'Second-3600',
if (this.sid !== undefined) {
const resp = await fetch(new Request(
`http://${this.host}:${this.port}${this.eventSubUrl}`,
{
method: 'SUBSCRIBE',
headers: {
SID: this.sid,
Timeout: 'Second-3600',
},
},
},
));
if (resp.ok) {
this.debug('Renewed event subscription');
this.eventRenewTimeout = setTimeout(async () => {
if (this.sid !== undefined) await this.renewEventSubscription(this.sid);
}, 3500 * 1000);
return true;
));
if (resp.ok) {
this.debug('Renewed event subscription');
return true;
}
}

this.debug('Renew event subscription failed, trying to resubscribe');
const newSid = await this.subscribeForEvents();
SonosEventListener.DefaultInstance.RegisterSubscription(newSid, this);
await this.subscribeForEvents();
return true;
}

private async cancelSubscription(sid: string): Promise<boolean> {
private async cancelSubscription(): Promise<boolean> {
this.debug('Cancelling event subscription');
if (this.eventRenewTimeout !== undefined) clearTimeout(this.eventRenewTimeout);
this.sid = undefined;
const resp = await fetch(new Request(
`http://${this.host}:${this.port}${this.eventSubUrl}`,
{
method: 'UNSUBSCRIBE',
headers: {
SID: sid,
if (this.eventRenewInterval !== undefined) {
clearInterval(this.eventRenewInterval);
}

if (this.sid !== undefined) {
const resp = await fetch(new Request(
`http://${this.host}:${this.port}${this.eventSubUrl}`,
{
method: 'UNSUBSCRIBE',
headers: {
SID: this.sid,
},
},
},
));
this.debug('Cancelled event subscription success %o', resp.ok);
return resp.ok;
));
this.sid = undefined;
this.debug('Cancelled event subscription success %o', resp.ok);
return resp.ok;
}

this.debug('No subscription to cancel');
return false;
}

/**
* Force refresh the event subscription
*
* @returns {Promise<boolean>} returns true on success and false if you weren't listening for events.
* @memberof BaseService
*/
public async CheckEventListener(): Promise<boolean> {
if (this.sid !== undefined) {
return await this.renewEventSubscription();
}
return false;
}

/**
Expand Down
27 changes: 27 additions & 0 deletions src/sonos-device-base.ts
Expand Up @@ -239,4 +239,31 @@ export default class SonosDeviceBase {
if (this.zonegrouptopologyservice === undefined) this.zonegrouptopologyservice = new ZoneGroupTopologyService(this.host, this.port, this.uuid);
return this.zonegrouptopologyservice;
}

/**
* Refresh all service event subscriptions, if you were already subscribed.
*
* @returns {Promise<boolean>} returns true if at least one service subscription is refreshed.
* @memberof SonosDeviceBase
*/
public async RefreshEventSubscriptions(): Promise<boolean> {
let result = false;
if (this.avtransportservice !== undefined) result = await this.avtransportservice.CheckEventListener() || result;
if (this.alarmclockservice !== undefined) result = await this.alarmclockservice.CheckEventListener() || result;
if (this.audioinservice !== undefined) result = await this.audioinservice.CheckEventListener() || result;
if (this.connectionmanagerservice !== undefined) result = await this.connectionmanagerservice.CheckEventListener() || result;
if (this.contentdirectoryservice !== undefined) result = await this.contentdirectoryservice.CheckEventListener() || result;
if (this.devicepropertiesservice !== undefined) result = await this.devicepropertiesservice.CheckEventListener() || result;
if (this.groupmanagementservice !== undefined) result = await this.groupmanagementservice.CheckEventListener() || result;
if (this.grouprenderingcontrolservice !== undefined) result = await this.grouprenderingcontrolservice.CheckEventListener() || result;
if (this.htcontrolservice !== undefined) result = await this.htcontrolservice.CheckEventListener() || result;
if (this.musicservicesservice !== undefined) result = await this.musicservicesservice.CheckEventListener() || result;
if (this.qplayservice !== undefined) result = await this.qplayservice.CheckEventListener() || result;
if (this.queueservice !== undefined) result = await this.queueservice.CheckEventListener() || result;
if (this.renderingcontrolservice !== undefined) result = await this.renderingcontrolservice.CheckEventListener() || result;
if (this.systempropertiesservice !== undefined) result = await this.systempropertiesservice.CheckEventListener() || result;
if (this.virtuallineinservice !== undefined) result = await this.virtuallineinservice.CheckEventListener() || result;
if (this.zonegrouptopologyservice !== undefined) result = await this.zonegrouptopologyservice.CheckEventListener() || result;
return result;
}
}
38 changes: 37 additions & 1 deletion src/sonos-manager.ts
Expand Up @@ -93,7 +93,22 @@ export default class SonosManager {
private handleZoneEventData(data: ZoneGroupTopologyServiceEvent): void {
if (data.ZoneGroupState !== undefined) {
data.ZoneGroupState.forEach((g) => {
const coordinator = this.devices.find((d) => d.Uuid === g.coordinator.uuid) || new SonosDevice(g.coordinator.host, g.coordinator.port, g.coordinator.uuid, g.coordinator.name);
let coordinator = this.devices.find((d) => d.Uuid === g.coordinator.uuid);
if (coordinator === undefined) {
coordinator = new SonosDevice(g.coordinator.host, g.coordinator.port, g.coordinator.uuid, g.coordinator.name, { coordinator: undefined, name: g.name, managerEvents: this.events });
this.devices.push(coordinator);
this.events.emit('NewDevice', coordinator);
}

// New members
g.members
.filter((m) => !this.devices.some((d) => d.Uuid === m.uuid))
.forEach((m) => {
const newDevice = new SonosDevice(m.host, m.port, m.uuid, m.name, { coordinator: m.uuid === g.coordinator.uuid ? undefined : coordinator, name: g.name, managerEvents: this.events });
this.devices.push(newDevice);
this.events.emit('NewDevice', coordinator);
});

g.members.forEach((m) => {
this.events.emit(m.uuid, { coordinator: g.coordinator.uuid === m.uuid ? undefined : coordinator, name: g.name });
});
Expand Down Expand Up @@ -122,6 +137,16 @@ export default class SonosManager {
return this.devices;
}

/**
* Subscribe to receive new devices.
*
* @param {(device: SonosDevice) => void} listener
* @memberof SonosManager
*/
public OnNewDevice(listener: (device: SonosDevice) => void): void {
this.events.on('NewDevice', listener);
}

/**
* Play a notification on all groups, without changing the current groups (for now).
*
Expand Down Expand Up @@ -171,4 +196,15 @@ export default class SonosManager {

return await this.PlayNotification(notificationOptions);
}

/**
* Check the event subscriptions for all known devices.
*
* @returns {Promise<void>}
* @memberof SonosManager
*/
public async CheckAllEventSubscriptions(): Promise<void> {
await this.zoneService?.CheckEventListener();
await Promise.all(this.devices.map((device) => device.RefreshEventSubscriptions()));
}
}

0 comments on commit 0e07431

Please sign in to comment.