homebridge-soundtouch-plugins
Version:
Homebridge SoundTouch plugins
191 lines (183 loc) • 5.53 kB
text/typescript
import {
API,
APIDiscovery,
compactMap,
Info,
SourceStatus,
} from '@bossoq/soundtouch-api'
import { apiNotFoundWithName } from './errors'
import { isVerboseInConfigs, stringUpperCaseFirst } from './utils'
import {
AccessoryConfig,
GlobalConfig,
PresetConfig,
SoundTouchDevice,
SoundTouchPreset,
SoundTouchSource,
SourceConfig,
VolumeMode,
} from './types'
import { Logging } from 'homebridge'
export const searchAllDevices = async (
globalConfig: GlobalConfig,
accessoryConfigs: AccessoryConfig[],
log: Logging
): Promise<SoundTouchDevice[]> => {
const apis = await APIDiscovery.search()
return Promise.all(
apis.map(async (api) => {
const info = await api.getInfo()
const accessoryConfig = accessoryConfigs.find(
(ac) => ac.room === info.name || ac.ip === api['host']
)
return _deviceFromApi(api, info, globalConfig, accessoryConfig || {}, log)
})
)
}
export const deviceFromConfig = async (
globalConfig: GlobalConfig,
accessoryConfig: AccessoryConfig,
log: Logging
): Promise<SoundTouchDevice> => {
let api: API
if (accessoryConfig.ip) {
api = new API(accessoryConfig.ip, accessoryConfig.port)
} else if (accessoryConfig.room) {
api = await APIDiscovery.find(accessoryConfig.room)
if (!api) throw apiNotFoundWithName(accessoryConfig.name)
}
return _deviceFromApi(
api,
await api.getInfo(),
globalConfig,
accessoryConfig,
log
)
}
export const _deviceFromApi = async (
api: API,
info: Info,
globalConfig: GlobalConfig,
accessoryConfig: AccessoryConfig,
log: Logging
): Promise<SoundTouchDevice> => {
const displayName = accessoryConfig.name || info.name
const isVerbose = isVerboseInConfigs(globalConfig, accessoryConfig)
const pollingInterval =
accessoryConfig.pollingInterval || globalConfig.pollingInterval
if (isVerbose) log(`[${displayName}] found device`)
const component = info.components.find(
(c) => c.serialNumber.toLowerCase() === info.deviceId.toLowerCase()
)
const presets = await _availablePresets(
api,
displayName,
accessoryConfig.presets,
globalConfig.presets,
isVerbose ? log : undefined
)
const sources = await _availableSources(
api,
displayName,
accessoryConfig.sources,
globalConfig.sources,
isVerbose ? log : undefined
)
const globalVolume = globalConfig.volume || {}
const accessoryVolume = accessoryConfig.volume || {}
const onValue = globalVolume.onValue || accessoryVolume.onValue
return {
api: api,
name: displayName,
id: info.deviceId,
model: info.type,
version: component ? component.softwareVersion : undefined,
verbose: isVerbose,
pollingInterval: pollingInterval,
volumeSettings: {
onValue: onValue || -1,
maxValue: globalVolume.maxValue || accessoryVolume.maxValue || 100,
unmuteValue:
globalVolume.unmuteValue ||
accessoryVolume.unmuteValue ||
onValue ||
35,
mode: globalVolume.mode || accessoryVolume.mode || VolumeMode.lightbulb,
},
presets: presets,
sources: sources,
}
}
export const deviceIsOn = async (
device: SoundTouchDevice
): Promise<boolean> => {
const nowPlaying = await device.api.getNowPlaying()
return nowPlaying.source !== SourceStatus.standBy
}
const _availablePresets = async (
api: API,
deviceName: string,
accessoryPresets: PresetConfig[],
globalPresets: PresetConfig[],
log?: Logging
): Promise<SoundTouchPreset[]> => {
const presets = (await api.getPresets()) || []
return compactMap(presets, (preset) => {
const presetConfig = _findConfig(
(p) => p.index === preset.id,
accessoryPresets,
globalPresets
) || { index: preset.id }
if (log !== undefined)
log(
`[${deviceName}] found preset n°${preset.id} '${preset.contentItem.itemName}' on device`
)
if ((<PresetConfig>presetConfig).enabled === false) return undefined
return {
name: (<PresetConfig>presetConfig).name || preset.contentItem.itemName,
index: preset.id,
}
})
}
const _availableSources = async (
api: API,
deviceName: string,
accessorySources?: SourceConfig[],
globalSources?: SourceConfig[],
log?: Logging
): Promise<SoundTouchSource[]> => {
const sources = await api.getSources()
const localSources = sources.items.filter((s) => s.isLocal)
return localSources.map((source) => {
if (log !== undefined)
log(
`[${deviceName}] found local source '${source.source}' with account '${source.sourceAccount}' on device`
)
const sourceConfig = _findConfig(
(p) =>
p.source === source.source &&
(p.account !== undefined ? p.account === source.sourceAccount : true),
accessorySources,
globalSources
) || { source: source.source }
return {
name:
(<SourceConfig>sourceConfig).name ||
`${deviceName} ${
source.name ? source.name : stringUpperCaseFirst(sourceConfig.source)
}`,
source: sourceConfig.source,
account: (<SourceConfig>sourceConfig).account,
enabled: (<SourceConfig>sourceConfig).enabled,
}
})
}
const _findConfig = <Config>(
predicate: (config: Config) => boolean,
accessoryConfigs?: Config[],
globalConfigs?: Config[]
): Config | undefined => {
const config = accessoryConfigs ? accessoryConfigs.find(predicate) : undefined
if (config !== undefined) return config
return globalConfigs ? globalConfigs.find(predicate) : undefined
}