UNPKG

@mathquis/node-assistant-protocol

Version:
774 lines (751 loc) 27.7 kB
const File = require('fs') const Path = require('path') const UUID = require('uuid').v4 const Topics = require('./topics') const Capabilities = require('./capabilities') module.exports = (hermes, options) => { options || (options = {}) const logger = options.logger || hermes.logger || console // Traits const loadable = require('./traits/loadable')(hermes, logger) const stateful = require('./traits/stateful')(hermes, logger) const failable = require('./traits/failable')(hermes, logger) const volumeChangeable = require('./traits/volumeChangeable')(hermes, logger) const volumeSetable = require('./traits/volumeSetable')(hermes, logger) const volumeOffsetable = require('./traits/volumeOffsetable')(hermes, logger) const muteSetable = require('./traits/muteSetable')(hermes, logger) const muteChangeable = require('./traits/muteChangeable')(hermes, logger) const applyMixins = (mixins, object) => { return mixins.reduce((obj, [mixin, topic]) => { return mixin(topic, obj) }, object) } const assistant = { Topics, Capabilities, dialogue: applyMixins([ [ loadable, Topics.DIALOGUE_LOAD ], [ stateful, Topics.DIALOGUE_STATE ] ], { name: 'Dialogue' } ), core: applyMixins([ [ loadable, Topics.CORE_LOAD ], [ stateful, Topics.CORE_STATE ] ], { name: 'Core' } ), brain: applyMixins([ [ loadable, Topics.BRAIN_LOAD ], [ stateful, Topics.BRAIN_STATE ] ], { name: 'Brain' } ), device: { load: async (siteId) => { logger.debug('Device on site "%s" is loaded', siteId) await hermes.publish(hermes.format(Topics.DEVICE_LOAD, {siteId}), hermes.serialize({})) }, onLoad: handler => { const wrapper = (topic, payload) => { let match if ( match = topic.match(/^assistant\/device\/(.+?)\/load$/)) { const [, siteId] = match payload || (payload = {}) handler(topic, {...payload, siteId}) } } return hermes.on(hermes.format(Topics.DEVICE_LOAD, {siteId: '+'}), wrapper) }, reboot: async (siteId) => { id = UUID() logger.debug('Requesting "%s" reboot on site "%s"', id, siteId) await hermes.publish(hermes.format(Topics.DEVICE_REBOOT, {siteId}), hermes.serialize({id})) }, onReboot: (siteId, handler) => { const wrapper = (topic, payload) => { let match if ( match = topic.match(/^assistant\/device\/(.+?)\/reboot$/)) { const [m, siteId] = match handler(topic, {siteId, ...payload}) } } return hermes.on(hermes.format(Topics.DEVICE_REBOOT, {siteId}), wrapper) }, shutdown: async (siteId) => { id = UUID() logger.debug('Requesting "%s" shutdown on site "%s"', id, siteId) await hermes.publish(hermes.format(Topics.DEVICE_SHUTDOWN, {siteId}), hermes.serialize({id})) }, onShutdown: (siteId, handler) => { const wrapper = (topic, payload) => { let match if ( match = topic.match(/^assistant\/device\/(.+?)\/shutdown$/)) { const [m, siteId] = match handler(topic, {siteId, ...payload}) } } return hermes.on(hermes.format(Topics.DEVICE_SHUTDOWN, {siteId}), wrapper) }, state: async (siteId, online = false, attributes = {}, state = {}) => { logger.debug('Device "%s" state (online: %s)', siteId, online) const payload = { online, siteId, attributes, state } const options = { retain: true } await hermes.publish(hermes.format(Topics.DEVICE_STATE, {siteId}), hermes.serialize(payload), options) }, onState: handler => { const wrapper = (topic, payload) => { let match if ( match = topic.match(/^assistant\/device\/(.+?)\/state$/)) { const [, siteId] = match payload || (payload = {}) handler(topic, {...payload, siteId}) } } return hermes.on(hermes.format(Topics.DEVICE_STATE, {siteId: '+'}), wrapper) }, getLastWill: siteId => { return { topic: hermes.format(Topics.DEVICE_STATE, {siteId}), payload: hermes.serialize({online: false, attributes: {}, state: {}}), retain: true } } }, train: applyMixins([ [ failable, Topics.TRAIN_ERROR ] ], { name: 'Train', perform: async (dataset, timeout) => { id = UUID() logger.debug('Requesting training "%s"', id) let p if ( timeout ) p = hermes.train.waitForComplete(id, timeout) await hermes.publish(Topics.TRAIN_PERFORM, hermes.serialize({ id, dataset })) await p }, onPerform: handler => { return on(Topics.TRAIN_PERFORM, handler) }, complete: async (requestId) => { logger.debug('Training "%s" complete', requestId) await hermes.publish(Topics.TRAIN_COMPLETE, hermes.serialize({ requestId })) }, onComplete: (handler) => { return on(Topics.TRAIN_COMPLETE, handler) }, waitForComplete: (requestId, timeout) => { logger.debug('Waiting for training "%s" completed for %d ms', requestId, timeout) return waitFor(Topics.TRAIN_COMPLETE, (topic, payload) => { if ( payload.requestId != requestId ) return logger.debug('Training "%s" complete', requestId) return true }, timeout) } } ), wakeword: { keywordAudioData: async (keyword, siteId, audioData) => { logger.debug('Sending detected keyword "%s" audio data', keyword) await hermes.publish(hermes.format(Topics.WAKEWORD_KEYWORD_AUDIO_DATA, {keyword, siteId}), hermes.noop(audioData)) }, onKeywordAudioData: (keyword = '+', siteId = '+', handler) => { const wrapper = (topic, payload) => { let match if ( match = topic.match(/^assistant\/wakeword\/(.+?)\/(.+?)\/audioData$/)) { const [m, k, s] = match handler(topic, {keyword: k, siteId: s, bytes: payload}) } } return hermes.on(hermes.format(Topics.WAKEWORD_KEYWORD_AUDIO_DATA, {keyword, siteId}), wrapper, hermes.noop) } }, alert: applyMixins([ [ failable, Topics.ALERT_ERROR ] ], { name: 'Alert', alertStarted: async (siteId, alert) => { logger.debug('Alert "%s" started on site "%s"', alertId, siteId) await hermes.publish(Topics.ALERT_ALERT_STARTED, hermes.serialize({ siteId, alert })) }, onAlertStarted: (handler) => { return hermes.on(Topics.ALERT_ALERT_STARTED, handler) }, setVolume: async (siteId, volume) => { volume = parseInt(volume) if ( isNaN(volume) ) throw new Error('Invalid volume "' + volume + '"') volume = Math.max(0, Math.min(100, volume)) logger.debug('Setting alert volume on site "%s" to %d', siteId, volume) await hermes.publish(Topics.ALERT_SET_VOLUME, hermes.serialize({ siteId, volume })) }, onSetVolume: (handler) => { return hermes.on(Topics.ALERT_SET_VOLUME, handler) }, volumeChanged: async (siteId, volume) => { logger.debug('Alert volume on site "%s" changed to %d', siteId, volume) await hermes.publish(Topics.ALERT_VOLUME_CHANGED, hermes.serialize({ siteId, volume })) }, onVolumeChanged: (handler) => { return hermes.on(Topics.ALERT_VOLUME_CHANGED, handler) }, setAlert: async (siteId, alert, timeout) => { const requestId = UUID() logger.debug('Requesting "%s" alert on site "%s"', requestId, siteId) let p if ( timeout ) p = assistant.alert.waitForSetAlertResult(requestId, timeout) hermes.publish(Topics.ALERT_SET_ALERT, hermes.serialize({ requestId, siteId, alert })) await p return requestId }, onSetAlert: (handler) => { return hermes.on(Topics.ALERT_SET_ALERT, handler) }, waitForSetAlertResult: (id, timeout) => { return hermes.waitForEither( Topics.ALERT_SET_ALERT_SUCCEEDED, (topic, payload) => payload.requestId === id, Topics.ALERT_SET_ALERT_FAILED, (topic, payload) => payload.requestId === id, timeout ) }, alertSet: async (siteId, alert) => { logger.debug('Alert "%s" set on site "%s"', alertId, siteId) await hermes.publish(Topics.ALERT_SET, hermes.serialize({ siteId, alert })) }, setAlertSucceeded: async (requestId, siteId) => { logger.debug('Request "%s" has set alert successfully on site "%s"', requestId, siteId) await hermes.publish(Topics.ALERT_SET_ALERT_SUCCEEDED, hermes.serialize({ requestId, siteId })) }, onSetAlertSucceeded: (handler) => { return hermes.on(Topics.ALERT_SET_ALERT_SUCCEEDED, handler) }, setAlertFailed: async (requestId, siteId, error) => { logger.debug('Request "%s" failed setting alert on site "%s": error', requestId, siteId, error) await hermes.publish(Topics.ALERT_SET_ALERT_FAILED, hermes.serialize({ requestId, siteId, error })) }, onSetAlertFailed: (handler) => { return hermes.on(Topics.ALERT_SET_ALERT_FAILED, handler) }, snoozeAlert: async (siteId, type, duration, timeout) => { const requestId = UUID() logger.debug('Request "%s" has asked to snooze alert on site "%s" for %d seconds', requestId, siteId, duration) let p if ( timeout ) p = assistant.alert.waitForSnoozeAlertResult(requestId, timeout) hermes.publish(Topics.ALERT_SNOOZE_ALERT, hermes.serialize({ requestId, siteId, type, duration })) await p return requestId }, onSnoozeAlert: (handler) => { return hermes.on(Topics.ALERT_SNOOZE_ALERT, handler) }, waitForSnoozeAlertResult: (id, timeout) => { return hermes.waitForEither( Topics.ALERT_SNOOZE_ALERT_SUCCEEDED, (topic, payload) => payload.requestId === id, Topics.ALERT_SNOOZE_ALERT_FAILED, (topic, payload) => payload.requestId === id, timeout ) }, alertSnoozed: async (siteId, alert) => { logger.debug('Alert "%s" snoozed on site "%s"', alertId, siteId) await hermes.publish(Topics.ALERT_ALERT_SNOOZED, hermes.serialize({ siteId, alert })) }, onAlertSnoozed: (handler) => { return hermes.on(Topics.ALERT_ALERT_SNOOZED, handler) }, snoozeAlertSucceeded: async (requestId, siteId) => { logger.debug('Request "%s" has snoozed alert successfully on site "%s"', requestId, siteId) await hermes.publish(Topics.ALERT_SNOOZE_ALERT_SUCCEEDED, hermes.serialize({ requestId, siteId })) }, onSnoozeAlertSucceeded: (handler) => { return hermes.on(Topics.ALERT_SNOOZE_ALERT_SUCCEEDED, handler) }, snoozeAlertFailed: async (requestId, siteId, error) => { logger.debug('Request "%s" has failed to snooze alert on site "%s"', requestId, siteId) await hermes.publish(Topics.ALERT_SNOOZE_ALERT_FAILED, hermes.serialize({ requestId, siteId, error })) }, onSnoozeAlertFailed: (handler) => { return hermes.on(Topics.ALERT_SNOOZE_ALERT_FAILED, handler) }, cancelAlert: async (siteId, type, period, timeout) => { const requestId = UUID() logger.debug('Request "%s" has asked to cancel alert on site "%s"', requestId, siteId) let p if ( timeout ) p = assistant.alert.waitForCancelAlertResult(requestId, timeout) hermes.publish(Topics.ALERT_CANCEL_ALERT, hermes.serialize({ requestId, type, period })) await p return requestId }, onCancelAlert: (handler) => { return hermes.on(Topics.ALERT_CANCEL_ALERT, handler) }, waitForCancelAlertResult: (id, timeout) => { return hermes.waitForEither( Topics.ALERT_CANCEL_ALERT_SUCCEEDED, (topic, payload) => payload.requestId === id, Topics.ALERT_CANCEL_ALERT_FAILED, (topic, payload) => payload.requestId === id, timeout ) }, alertCanceled: async (siteId, alert) => { logger.debug('Alert "%s" canceled on site "%s"', alertId, siteId) await hermes.publish(Topics.ALERT_ALERT_CANCELED, hermes.serialize({ siteId, alert })) }, onAlertCanceled: (handler) => { return hermes.on(Topics.ALERT_ALERT_CANCELED, handler) }, cancelAlertSucceeded: async (requestId, siteId) => { logger.debug('Request "%s" has canceled alerts successfully on site "%s"', requestId, siteId) await hermes.publish(Topics.ALERT_CANCEL_ALERT_SUCCEEDED, hermes.serialize({ requestId, siteId })) }, onCancelAlertSucceeded: (handler) => { return hermes.on(Topics.ALERT_CANCEL_ALERT_SUCCEEDED, handler) }, cancelAlertFailed: async (requestId, siteId, error) => { logger.debug('Request "%s" has failed to cancel alerts on site "%s"', requestId, siteId) await hermes.publish(Topics.ALERT_CANCEL_ALERT_FAILED, hermes.serialize({ requestId, siteId, error })) }, onCancelAlertFailed: (handler) => { return hermes.on(Topics.ALERT_CANCEL_ALERT_FAILED, handler) }, stopAlert: async (siteId, type, options, timeout) => { const requestId = UUID() logger.debug('Request "%s" has asked to stop alert on site "%s"', requestId, siteId) let p if ( timeout ) p = assistant.alert.waitForStopAlertResult(requestId, timeout) hermes.publish(Topics.ALERT_STOP_ALERT, hermes.serialize({ requestId, siteId, type, options })) await p return requestId }, onStopAlert: (handler) => { return hermes.on(Topics.ALERT_STOP_ALERT, handler) }, waitForStopAlertResult: (id, timeout) => { return hermes.waitForEither( Topics.ALERT_STOP_ALERT_SUCCEEDED, (topic, payload) => payload.requestId === id, Topics.ALERT_STOP_ALERT_FAILED, (topic, payload) => payload.requestId === id, timeout ) }, alertStopped: async (siteId, alert) => { logger.debug('Alert "%s" stopped on site "%s"', alertId, siteId) await hermes.publish(Topics.ALERT_ALERT_STOPPED, hermes.serialize({ siteId, alert })) }, onAlertStopped: (handler) => { return hermes.on(Topics.ALERT_ALERT_STOPPED, handler) }, stopAlertSucceeded: async (requestId, siteId) => { logger.debug('Request "%s" has stopped alerts successfully on site "%s"', requestId, siteId) await hermes.publish(Topics.ALERT_STOP_ALERT_SUCCEEDED, hermes.serialize({ requestId, siteId })) }, onStopAlertSucceeded: (handler) => { return hermes.on(Topics.ALERT_STOP_ALERT_SUCCEEDED, handler) }, stopAlertFailed: async (requestId, siteId, error) => { logger.debug('Request "%s" has failed to stop alerts on site "%s"', requestId, siteId) await hermes.publish(Topics.ALERT_STOP_ALERT_FAILED, hermes.serialize({ requestId, siteId, error })) }, onStopAlertFailed: (handler) => { return hermes.on(Topics.ALERT_STOP_ALERT_FAILED, handler) }, deleteAlert: async (siteId, type, options, timeout) => { const requestId = UUID() logger.debug('Request "%s" has asked to ddelete alert "%s" at "%s"', requestId, siteId) let p if ( timeout ) p = assistant.alert.waitForDeleteAlertResult(requestId, timeout) hermes.publish(Topics.ALERT_DELETE_ALERT, hermes.serialize({ requestId, siteId, type, options })) await p return requestId }, onDeleteAlert: (handler) => { return hermes.on(Topics.ALERT_DELETE_ALERT, handler) }, waitForDeleteAlertResult: (id, timeout) => { return hermes.waitForEither( Topics.ALERT_DELETE_ALERT_SUCCEEDED, (topic, payload) => payload.requestId === id, Topics.ALERT_DELETE_ALERT_FAILED, (topic, payload) => payload.requestId === id, timeout ) }, alertDeleted: async (siteId, alert) => { logger.debug('Alert "%s" deleted on site "%s"', alertId, siteId) await hermes.publish(Topics.ALERT_ALERT_DELETED, hermes.serialize({ siteId, alert })) }, onAlertDeleted: (handler) => { return hermes.on(Topics.ALERT_ALERT_DELETED, handler) }, deleteAlertSucceeded: async (requestId, siteId) => { logger.debug('Request "%s" has deleted alerts successfully on site "%s"', requestId, siteId) await hermes.publish(Topics.ALERT_DELETE_ALERT_SUCCEEDED, hermes.serialize({ requestId, siteId })) }, onDeleteAlertSucceeded: (handler) => { return hermes.on(Topics.ALERT_DELETE_ALERT_SUCCEEDED, handler) }, deleteAlertFailed: async (requestId, siteId, error) => { logger.debug('Request "%s" has failed to delete alerts on site "%s"', requestId, siteId) await hermes.publish(Topics.ALERT_DELETE_ALERT_FAILED, hermes.serialize({ requestId, siteId, error })) }, onDeleteAlertFailed: (handler) => { return hermes.on(Topics.ALERT_DELETE_ALERT_FAILED, handler) }, deleteAlerts: async (siteId, type, timeout) => { const requestId = UUID() logger.debug('Deleting alert "%s" on site "%s"', requestId, siteId) let p if ( timeout ) p = assistant.alert.waitForDeleteAlertsResult(requestId, timeout) hermes.publish(Topics.ALERT_DELETE_ALERTS, hermes.serialize({ requestId, siteId, type })) await p return requestId }, onDeleteAlerts: (handler) => { return hermes.on(Topics.ALERT_DELETE_ALERTS, handler) }, waitForDeleteAlertsResult: (id, timeout) => { return hermes.waitForEither( Topics.ALERT_DELETE_ALERTS_SUCCEEDED, (topic, payload) => payload.requestId === id, Topics.ALERT_DELETE_ALERTS_FAILED, (topic, payload) => payload.requestId === id, timeout ) }, deleteAlertsSucceeded: async (requestId, siteId) => { logger.debug('Request "%s" has deleted alerts successfully on site "%s"', requestId, siteId) await hermes.publish(Topics.ALERT_DELETE_ALERTS_SUCCEEDED, hermes.serialize({ requestId, siteId })) }, onDeleteAlertsSucceeded: (handler) => { return hermes.on(Topics.ALERT_DELETE_ALERTS_SUCCEEDED, handler) }, deleteAlertsFailed: async (requestId, siteId, error) => { logger.debug('Request "%s" has failed to delete alerts on site "%s"', requestId, siteId) await hermes.publish(Topics.ALERT_DELETE_ALERTS_FAILED, hermes.serialize({ requestId, siteId, error })) }, onDeleteAlertsFailed: (handler) => { return hermes.on(Topics.ALERT_DELETE_ALERTS_FAILED, handler) }, alertError: async (id, error, context) => { logger.debug('Alert error:', error) await hermes.publish(Topics.ALERT_ALERT_ERROR, serialize({ id, error, context })) }, onAlertError: handler => { return on(Topics.ALERT_ALERT_ERROR, handler) } } ), audioPlayer: applyMixins([ [ failable, Topics.AUDIO_PLAYER_ERROR ] ], { name: 'AudioPlayer', play: async (siteId, context, zone, timeout) => { const requestId = UUID() logger.debug('Audio player playing "%s" in zone "%s" from site "%s"', context.id, zone, siteId) let p if ( timeout ) p = assistant.audioPlayer.waitForPlayResult(requestId, timeout) hermes.publish(Topics.AUDIO_PLAYER_PLAY, hermes.serialize({ requestId, siteId, context, zone })) await p return requestId }, onPlay: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_PLAY, handler) }, waitForPlayResult: (id, timeout) => { return hermes.waitForEither( Topics.AUDIO_PLAYER_PLAY_SUCCEEDED, (topic, {requestId}) => requestId === id, Topics.AUDIO_PLAYER_PLAY_FAILED, (topic, {requestId}) => requestId === id, timeout ) }, playSucceeded: async (requestId) => { logger.debug('Audio player playing "%s" succeeded', requestId) await hermes.publish(Topics.AUDIO_PLAYER_PLAY_SUCCEEDED, hermes.serialize({ requestId })) }, onPlaySucceeded: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_PLAY_SUCCEEDED, handler) }, playFailed: async (requestId, error) => { logger.debug('Audio player playing "%s" failed', requestId) await hermes.publish(Topics.AUDIO_PLAYER_PLAY_FAILED, hermes.serialize({ requestId, error })) }, onPlayFailed: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_PLAY_FAILED, handler) }, pause: async (siteId, zone) => { logger.debug('Audio player pausing site "%s" (zone: %s)', siteId, zone) await hermes.publish(Topics.AUDIO_PLAYER_PAUSE, hermes.serialize({ siteId, zone })) }, onPause: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_PAUSE, handler) }, resume: async (siteId, zone) => { logger.debug('Audio player resuming site "%s" (zone: %s)', siteId, zone) await hermes.publish(Topics.AUDIO_PLAYER_RESUME, hermes.serialize({ siteId, zone })) }, onResume: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_RESUME, handler) }, previous: async (siteId, zone) => { logger.debug('Audio player previous on site "%s" (zone: %s)', siteId, zone) await hermes.publish(Topics.AUDIO_PLAYER_PREVIOUS, hermes.serialize({ siteId, zone })) }, onPrevious: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_PREVIOUS, handler) }, next: async (siteId, zone) => { logger.debug('Audio player next on site "%s" (zone: %s)', siteId, zone) await hermes.publish(Topics.AUDIO_PLAYER_NEXT, hermes.serialize({ siteId, zone })) }, onNext: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_NEXT, handler) }, seek: async (siteId, zone, position) => { logger.debug('Audio player seeking on site "%s" to position %d (zone: %s)', siteId, position, zone) await hermes.publish(Topics.AUDIO_PLAYER_SEEK, hermes.serialize({ siteId, zone, position })) }, onSeek: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_SEEK, handler) }, offset: async (siteId, zone, offset) => { logger.debug('Audio player offsetting on site "%s" with offset %d (zone: %s)', siteId, offset, zone) await hermes.publish(Topics.AUDIO_PLAYER_OFFSET, hermes.serialize({ siteId, zone, offset })) }, onOffset: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_OFFSET, handler) }, stop: async (siteId, zone) => { logger.debug('Audio player stopping on zone "%s" from site "%s" (zone: %s)', zone, siteId, zone) await hermes.publish(Topics.AUDIO_PLAYER_STOP, hermes.serialize({ siteId, zone })) }, onStop: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_STOP, handler) }, setShuffle: async (siteId, zone, state) => { state = !!state logger.debug('Audio player setting shuffle on site "%s" to %s (zone: %s)', siteId, state, zone) await hermes.publish(Topics.AUDIO_PLAYER_SET_SHUFFLE, hermes.serialize({ siteId, zone, state })) }, onSetShuffle: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_SET_SHUFFLE, handler) }, setRepeat: async (siteId, zone, state) => { state = !!state logger.debug('Audio player setting repeat on site "%s" to %s (zone: %s)', siteId, state, zone) await hermes.publish(Topics.AUDIO_PLAYER_SET_REPEAT, hermes.serialize({ siteId, zone, state })) }, onSetRepeat: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_SET_REPEAT, handler) }, playbackStarted: async (siteId, state) => { logger.debug('Audio player started playback of context "%s" on %s', state.id, siteId) await hermes.publish(Topics.AUDIO_PLAYER_PLAYBACK_STARTED, hermes.serialize({ siteId, state })) }, onPlaybackStarted: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_PLAYBACK_STARTED, handler) }, playbackPaused: async (siteId, state) => { logger.debug('Audio player paused playback of context "%s" on %s', state.id, siteId) await hermes.publish(Topics.AUDIO_PLAYER_PLAYBACK_PAUSED, hermes.serialize({ siteId, state })) }, onPlaybackPaused: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_PLAYBACK_PAUSED, handler) }, playbackStopped: async (siteId) => { logger.debug('Audio player stopped playback on %s', siteId) await hermes.publish(Topics.AUDIO_PLAYER_PLAYBACK_STOPPED, hermes.serialize({ siteId })) }, onPlaybackStopped: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_PLAYBACK_STOPPED, handler) }, playbackProgress: async (siteId, state) => { logger.debug('Audio player reported playback progress of context "%s" on %s', state.id, siteId) await hermes.publish(Topics.AUDIO_PLAYER_PLAYBACK_PROGRESS, hermes.serialize({ siteId, state })) }, onPlaybackProgress: (handler) => { return hermes.on(Topics.AUDIO_PLAYER_PLAYBACK_PROGRESS, handler) } } ), speaker: applyMixins([ [ volumeSetable, Topics.SPEAKER_SET_VOLUME ], [ volumeOffsetable, Topics.SPEAKER_OFFSET_VOLUME ], [ volumeChangeable, Topics.SPEAKER_VOLUME_CHANGED ], [ muteSetable, Topics.SPEAKER_SET_MUTE ], [ muteChangeable, Topics.SPEAKER_MUTE_CHANGED ], [ failable, Topics.SPEAKER_ERROR ] ], { name: 'Speaker' } ), playback: applyMixins([ [ volumeSetable, Topics.PLAYBACK_SET_VOLUME ], [ volumeOffsetable, Topics.PLAYBACK_OFFSET_VOLUME ], [ volumeChangeable, Topics.PLAYBACK_VOLUME_CHANGED ], [ muteSetable, Topics.PLAYBACK_SET_MUTE ], [ muteChangeable, Topics.PLAYBACK_MUTE_CHANGED ], [ failable, Topics.PLAYBACK_ERROR ] ], { name: 'Playback' } ), voice: applyMixins([ [ loadable, Topics.VOICE_LOAD ], [ stateful, Topics.VOICE_STATE ], [ volumeSetable, Topics.VOICE_SET_VOLUME ], [ volumeOffsetable, Topics.VOICE_OFFSET_VOLUME ], [ volumeChangeable, Topics.VOICE_VOLUME_CHANGED ], [ muteSetable, Topics.VOICE_SET_MUTE ], [ muteChangeable, Topics.VOICE_MUTE_CHANGED ], [ failable, Topics.VOICE_ERROR ] ], { name: 'Voice' } ), doNotDisturb: applyMixins([ [ failable, Topics.DND_ERROR ] ], { name: 'DnD', toggleOn: async (siteId) => { logger.debug('Setting do not disturb "ON" on site "%s"', siteId) await hermes.publish(Topics.DND_TOGGLE_ON, hermes.serialize({ siteId })) }, onToggleOn: (handler) => { return hermes.on(Topics.DND_TOGGLE_ON, handler) }, toggleOff: async (siteId) => { logger.debug('Setting do not disturb "OFF" on site "%s"', siteId) await hermes.publish(Topics.DND_TOGGLE_OFF, hermes.serialize({ siteId })) }, onToggleOff: (handler) => { return hermes.on(Topics.DND_TOGGLE_OFF, handler) }, stateChanged: async (siteId, state) => { logger.debug('Do not disturb state on site "%s" changed to %s', siteId, state) await hermes.publish(Topics.DND_STATE_CHANGED, hermes.serialize({ siteId, state })) }, onStateChanged: (handler) => { return hermes.on(Topics.DND_STATE_CHANGED, handler) } } ) } return { ...hermes, assistant } }