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

Add Ability to customize favourites/inputs list #218

Closed
wants to merge 3 commits into from
Closed
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
out
npm-debug.log
.DS_Store
17 changes: 15 additions & 2 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,20 @@
},
"excludeAirPlay": {
"title": "Exclude AirPlay 2",
"description": "Exclude AirPlay 2 zone players that are already exposed to Apple's Home app.",
"description": "Exclude AirPlay 2 zone players that are already exposed to Apple's Home app. (Please Note: If your system is S2 only, enabling this option will remove all zones from Homebridge. Use with caution.)",
"type": "boolean"
},
"filterFavorites": {
"title": "Filter Favorites",
"description": "Filter out non-favorite audio inputs (AirPlay, Line In, TV, etc.) from the favorites list.",
"type": "boolean",
"default": false
},
"forceS2": {
"description": "Force S2 compatibility.",
"type": "boolean",
"default": false
},
"heartrate": {
"description": "Interval (in seconds) to poll zone player. Default: disabled.",
"type": "integer"
Expand Down Expand Up @@ -124,7 +135,8 @@
"functionBody": "return model.leds"
}
},
"excludeAirPlay"
"excludeAirPlay",
"filterFavorites"
]
},
{
Expand All @@ -143,6 +155,7 @@
"tv",
{
"key": "maxFavourites",
"type": "text",
"condition": {
"functionBody": "return model.tv"
}
Expand Down
30 changes: 16 additions & 14 deletions lib/ZpPlatform.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ class ZpPlatform extends homebridgeLib.Platform {
timeout: 15, // seconds
tvIdPrefix: 'TV',
SpeakerService: this.Services.hap.Switch,
VolumeCharacteristic: this.Characteristics.hap.Volume
VolumeCharacteristic: this.Characteristics.hap.Volume,
filterFavorites: false // Default value
}
const optionParser = new homebridgeLib.OptionParser(this.config, true)
optionParser
Expand All @@ -82,6 +83,7 @@ class ZpPlatform extends homebridgeLib.Platform {
.boolKey('alarms')
.boolKey('brightness')
.boolKey('excludeAirPlay')
.boolKey('filterFavorites')
.intKey('heartrate', 1, 60)
.boolKey('leds')
.intKey('maxFavourites', 16, 96)
Expand Down Expand Up @@ -178,27 +180,27 @@ class ZpPlatform extends homebridgeLib.Platform {
}

async handleMdnsMessage (message) {
const id = message.txt.info.split('/')[4]
const address = message.referer.address
this.debug('mdns: found %s at %s', id, address)
const household = message.hhid
const bootseq = parseInt(message.txt.bootseq)
try {
const id = message.txt.info.split('/')[4]
const address = message.referer.address
this.debug('mdns: found %s at %s', id, address)
const household = message.hhid
const bootseq = parseInt(message.txt.bootseq)
const zpClient = await this.createZpClient(id, address, household)
await zpClient.handleAliveMessage({ id, address, household, bootseq })
} catch (error) { this.error(error) }
}

async handleUpnpMessage (address, message) {
const id = message.usn.split(':')[1]
if (message.st != null) {
this.debug('upnp: found %s at %s', id, address)
} else {
this.debug('upnp: %s is alive at %s', id, address)
}
const household = message['x-rincon-household']
const bootseq = parseInt(message['x-rincon-bootseq'])
try {
const id = message.usn.split(':')[1]
if (message.st != null) {
this.debug('upnp: found %s at %s', id, address)
} else {
this.debug('upnp: %s is alive at %s', id, address)
}
const household = message['x-rincon-household']
const bootseq = parseInt(message['x-rincon-bootseq'])
const zpClient = await this.createZpClient(id, address, household)
await zpClient.handleAliveMessage({ id, address, household, bootseq })
} catch (error) { this.error(error) }
Expand Down
209 changes: 97 additions & 112 deletions lib/ZpService.js
Original file line number Diff line number Diff line change
Expand Up @@ -1167,131 +1167,116 @@ class Tv extends ZpService {
)
return identifier
}
favouritesUpdated() {
const favs = this.zpHousehold.favourites;
if (!favs) return;

favouritesUpdated () {
const favs = this.zpHousehold.favourites
if (favs == null) {
return
}
this.sources = []
this.configureInputSource('n/a', null, false)
this.updateGroupInputSource(true)
this.configureInputSource(
'AirPlay', 'x-sonos-vli:' + this.zpClient.id + ':1', false
)
this.configureInputSource(
'Audio In', 'x-rincon-stream:' + this.zpClient.id, this.zpClient.audioIn
)
this.configureInputSource(
'TV', 'x-sonos-htastream:' + this.zpClient.id + ':spdif',
this.zpClient.tvIn
)
for (const key in favs) {
const fav = favs[key]
this.configureInputSource(
key.slice(0, 64), fav.uri, true, fav.container, fav.meta
)
}
for (
let index = this.sources.length;
index < this.platform.config.maxFavourites - 1;
index++
) {
this.configureInputSource(`Input ${index + 1}`, null, false)
}
this.configureInputSource('Sonos Chime', 'x-rincon-buzzer:0', true)
this.sources = [];
this.configureInputSource('n/a', null, false);
this.updateGroupInputSource(true);
this.configureInputSource('AirPlay', `x-sonos-vli:${this.zpClient.id}:1`, false);
this.configureInputSource('Audio In', `x-rincon-stream:${this.zpClient.id}`, this.zpClient.audioIn);
this.configureInputSource('TV', `x-sonos-htastream:${this.zpClient.id}:spdif`, this.zpClient.tvIn);

for (const key in favs) {
const fav = favs[key];
this.configureInputSource(key.slice(0, 64), fav.uri, true, fav.container, fav.meta);
}

for (let index = this.sources.length; index < this.platform.config.maxFavourites - 1; index++) {
this.configureInputSource(`Input ${index + 1}`, null, false);
}

this.configureInputSource('Sonos Chime', 'x-rincon-buzzer:0', true);
this.log(
'input sources: %j',
this.sources
.filter((source) => source.visible)
.map((source) => source.configuredName)
);

if (this.notYetInitialised) {
delete this.notYetInitialised;
this.emit('initialised');
}

this.values.activeIdentifier = this.activeIdentifier(this.sonosValues.uri);
}

updateGroupInputSource(silent = false) {
const index = 0;
const source = this.sources[index];
const inputSource = this.inputSources[index];
if (!source || !inputSource) return;

const platformCoordinatorId = this.sonosValues.platformCoordinatorId;
const zpClient = this.platform.zpClients[platformCoordinatorId];
let configuredName = 'n/a';
let uri;
let visible = false;

if (this.sonosValues.sonosGroup && this.sonosValues.sonosGroup !== this.zpClient.zoneName) {
configuredName = 'Leave ' + this.sonosValues.sonosGroup;
visible = true;
} else if (platformCoordinatorId && platformCoordinatorId !== this.zpClient.id && zpClient) {
configuredName = 'Join ' + zpClient.zoneGroupShortName;
uri = `x-rincon:${platformCoordinatorId}`;
visible = true;
}

source.configuredName = configuredName;
source.uri = uri;
source.visible = visible;
inputSource.values.configuredName = configuredName;
inputSource.values.isConfigured = visible
? this.Characteristics.hap.IsConfigured.CONFIGURED
: this.Characteristics.hap.IsConfigured.NOT_CONFIGURED;
inputSource.values.targetVisibilityState = visible
? this.Characteristics.hap.TargetVisibilityState.SHOWN
: this.Characteristics.hap.TargetVisibilityState.HIDDEN;

if (!silent) {
this.log(
'input sources: %j',
this.sources.filter((source) => {
return source.visible
}).map((source) => {
return source.configuredName
})
)
if (this.notYetInitialised) {
delete this.notYetInitialised
this.emit('initialised')
}
this.values.activeIdentifier = this.activeIdentifier(this.sonosValues.uri)
'Input Sources: %j',
this.sources
.filter((source) => source.visible)
.map((source) => source.configuredName)
);
}
}

updateGroupInputSource (silent = false) {
const index = 0
const source = this.sources[index]
const inputSource = this.inputSources[index]
if (source == null || inputSource == null) {
return
configureInputSource(configuredName, uri, visible, container, meta) {
if (this.platform.config.filterFavorites) {
if (configuredName === 'AirPlay' || configuredName === 'Audio In' || configuredName === 'TV') {
visible = false;
}
}

const platformCoordinatorId = this.sonosValues.platformCoordinatorId
const zpClient = this.platform.zpClients[platformCoordinatorId]
let configuredName = 'n/a'
let uri
let visible = false
if (
this.sonosValues.sonosGroup != null &&
this.sonosValues.sonosGroup !== this.zpClient.zoneName
) {
configuredName = 'Leave ' + this.sonosValues.sonosGroup
visible = true
} else if (
platformCoordinatorId != null &&
platformCoordinatorId !== this.zpClient.id &&
zpClient != null
) {
configuredName = 'Join ' + zpClient.zoneGroupShortName
uri = 'x-rincon:' + platformCoordinatorId
visible = true
}
source.configuredName = configuredName
source.uri = uri
source.visible = visible
inputSource.values.configuredName = configuredName
if (configuredName === 'Sonos Chime') {
visible = false;
}

this.sources.push({ configuredName, uri, visible, container, meta });
const identifier = this.sources.length;
if (identifier <= this.platform.config.maxFavourites) {
const inputSource = this.inputSources[identifier - 1];
inputSource.values.configuredName = configuredName;
inputSource.values.isConfigured = visible
? this.Characteristics.hap.IsConfigured.CONFIGURED
: this.Characteristics.hap.IsConfigured.NOT_CONFIGURED
: this.Characteristics.hap.IsConfigured.NOT_CONFIGURED;
inputSource.values.targetVisibilityState = visible
? this.Characteristics.hap.TargetVisibilityState.SHOWN
: this.Characteristics.hap.TargetVisibilityState.HIDDEN
if (!silent) {
this.log(
'Input Sources: %j',
this.sources.filter((source) => {
return source.visible
}).map((source) => {
return source.configuredName
})
)
}
}
: this.Characteristics.hap.TargetVisibilityState.HIDDEN;

configureInputSource (configuredName, uri, visible, container, meta) {
this.sources.push({ configuredName, uri, visible, container, meta })
const identifier = this.sources.length
if (identifier <= this.platform.config.maxFavourites) {
const inputSource = this.inputSources[identifier - 1]
inputSource.values.configuredName = configuredName
inputSource.values.isConfigured = visible
? this.Characteristics.hap.IsConfigured.CONFIGURED
: this.Characteristics.hap.IsConfigured.NOT_CONFIGURED
if (configuredName === 'Sonos Chime') {
visible = false
}
inputSource.values.targetVisibilityState = visible
? this.Characteristics.hap.TargetVisibilityState.SHOWN
: this.Characteristics.hap.TargetVisibilityState.HIDDEN
if (configuredName === 'AirPlay') {
inputSource.values.inputSourceType =
this.Characteristics.hap.InputSourceType.OTHER
} else if (configuredName === 'TV') {
inputSource.values.inputSourceType =
this.Characteristics.hap.InputSourceType.HDMI
} else if (uri != null && uri.startsWith('x-sonosapi-stream:')) {
inputSource.values.inputSourceType =
this.Characteristics.hap.InputSourceType.TUNER
}
if (configuredName === 'AirPlay') {
inputSource.values.inputSourceType = this.Characteristics.hap.InputSourceType.OTHER;
} else if (configuredName === 'TV') {
inputSource.values.inputSourceType = this.Characteristics.hap.InputSourceType.HDMI;
} else if (uri && uri.startsWith('x-sonosapi-stream:')) {
inputSource.values.inputSourceType = this.Characteristics.hap.InputSourceType.TUNER;
}
}
}

static get Speaker () { return TvSpeaker }

Expand Down