Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import com.example.ava.esphome.Connected
import com.example.ava.esphome.Disconnected
import com.example.ava.esphome.EspHomeState
import com.example.ava.esphome.voiceassistant.VoiceTimer.Companion.timerFromEvent
import com.example.ava.tasker.StopRingingRunner
import com.example.ava.tasker.WakeSatelliteRunner
import com.example.esphomeproto.api.VoiceAssistantAnnounceRequest
import com.example.esphomeproto.api.VoiceAssistantConfigurationRequest
import com.example.esphomeproto.api.VoiceAssistantEventResponse
Expand Down Expand Up @@ -72,14 +70,22 @@ class VoiceAssistant(
@RequiresPermission(Manifest.permission.RECORD_AUDIO)
fun start() {
startVoiceInput()

// Wire up tasker actions
WakeSatelliteRunner.register { scope.launch { wakeAssistant() } }
StopRingingRunner.register { scope.launch { stopTimer() } }
}

fun subscribe() = subscription.asSharedFlow()

fun wakeAssistant() {
scope.launch { doWakeAssistant() }
}

fun stopAssistant() {
scope.launch { doStopAssistant() }
}

fun stopTimer() {
doStopTimer()
}

@RequiresPermission(Manifest.permission.RECORD_AUDIO)
private fun startVoiceInput() = isConnected
.flatMapLatest { isConnected ->
Expand Down Expand Up @@ -207,21 +213,21 @@ class VoiceAssistant(
// Allow using the wake word to stop the timer.
// TODO: Should the assistant also wake?
if (isRinging) {
stopTimer()
doStopTimer()
} else {
wakeAssistant(wakeWordPhrase)
doWakeAssistant(wakeWordPhrase)
}
}

private suspend fun onStopDetected() {
if (isRinging) {
stopTimer()
doStopTimer()
} else {
stopAssistant()
doStopAssistant()
}
}

private suspend fun wakeAssistant(
private suspend fun doWakeAssistant(
wakeWordPhrase: String = "",
isContinueConversation: Boolean = false
) {
Expand Down Expand Up @@ -257,7 +263,7 @@ class VoiceAssistant(
ended = { onTtsFinished(it) }
)

private suspend fun stopAssistant() {
private suspend fun doStopAssistant() {
// Ignore the stop request if the assistant is idle or currently streaming
// microphone audio as there's either nothing to stop or the stop word was
// used incidentally as part of the voice command.
Expand All @@ -268,7 +274,7 @@ class VoiceAssistant(
voiceOutput.unDuck()
}

private fun stopTimer() {
private fun doStopTimer() {
Timber.d("Stop timer")
if (isRinging) {
_ringingTimer.update { null }
Expand All @@ -281,7 +287,7 @@ class VoiceAssistant(
Timber.d("TTS finished")
if (continueConversation) {
Timber.d("Continuing conversation")
wakeAssistant(isContinueConversation = true)
doWakeAssistant(isContinueConversation = true)
} else {
voiceOutput.unDuck()
}
Expand All @@ -295,7 +301,7 @@ class VoiceAssistant(
scope.launch { onTimerSoundFinished(it) }
}
} else {
stopTimer()
doStopTimer()
}
} else {
voiceOutput.unDuck()
Expand All @@ -316,7 +322,5 @@ class VoiceAssistant(
override fun close() {
scope.cancel()
voiceOutput.close()
WakeSatelliteRunner.unregister()
StopRingingRunner.unregister()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@ import com.example.ava.nsd.NsdRegistration
import com.example.ava.nsd.registerVoiceSatelliteNsd
import com.example.ava.settings.VoiceSatelliteSettings
import com.example.ava.settings.VoiceSatelliteSettingsStore
import com.example.ava.tasker.ActivityConfigAvaActivity
import com.example.ava.tasker.AvaActivityRunner
import com.example.ava.tasker.observeTaskerState
import com.example.ava.utils.translate
import com.example.ava.wakelocks.WifiWakeLock
import com.joaomgcd.taskerpluginlibrary.extensions.requestQuery
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
Expand Down Expand Up @@ -112,11 +110,8 @@ class VoiceSatelliteService() : LifecycleService() {
return super.onStartCommand(intent, flags, startId)
}

private fun startTaskerStateObserver() {
combine(voiceSatelliteState, voiceTimers) { state, timers ->
AvaActivityRunner.updateState(state, timers)
ActivityConfigAvaActivity::class.java.requestQuery(this)
}.launchIn(lifecycleScope)
private fun startTaskerStateObserver() = lifecycleScope.launch {
_voiceSatellite.collectLatest { it?.observeTaskerState(this@VoiceSatelliteService) }
}

private fun updateNotificationOnStateChanges() = _voiceSatellite
Expand All @@ -127,7 +122,7 @@ class VoiceSatelliteService() : LifecycleService() {
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).notify(
2,
createVoiceSatelliteServiceNotification(
this@VoiceSatelliteService,
this,
it.translate(resources)
)
)
Expand All @@ -136,7 +131,7 @@ class VoiceSatelliteService() : LifecycleService() {

private fun registerVoiceSatelliteNsd(settings: VoiceSatelliteSettings) =
registerVoiceSatelliteNsd(
context = this@VoiceSatelliteService,
context = this,
name = settings.name,
port = settings.serverPort,
macAddress = settings.macAddress
Expand Down
37 changes: 37 additions & 0 deletions app/src/main/java/com/example/ava/tasker/TaskerRegistration.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.example.ava.tasker

import android.content.Context
import com.example.ava.esphome.EspHomeDevice
import com.joaomgcd.taskerpluginlibrary.extensions.requestQuery
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart

/**
* Registers Tasker actions and passes device state to Tasker conditions until cancelled.
*/
suspend fun EspHomeDevice.observeTaskerState(context: Context) = combine(
voiceAssistant.state,
voiceAssistant.allTimers
) { state, timers ->
AvaActivityRunner.updateState(state, timers)
ActivityConfigAvaActivity::class.java.requestQuery(context)
}.onStart {
registerTaskerActions()
}.onCompletion {
unregisterTaskerActions()
}
// Although implemented as a flow, conceptually this method is just a long running
// background task as nothing is ever emitted, collect hides the implementation
// detail and surfaces a cancellable suspend function instead
.collect { }

private fun EspHomeDevice.registerTaskerActions() {
WakeSatelliteRunner.register { voiceAssistant.wakeAssistant() }
StopRingingRunner.register { voiceAssistant.stopTimer() }
}

private fun unregisterTaskerActions() {
WakeSatelliteRunner.unregister()
StopRingingRunner.unregister()
}
2 changes: 2 additions & 0 deletions app/src/test/java/com/example/ava/TaskerPluginsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class TaskerPluginsTest {
val voiceAssistant = createVoiceAssistant(voiceInput = voiceInput)
val sentMessages = mutableListOf<MessageLite>()
val messageJob = voiceAssistant.subscribe().onEach { sentMessages.add(it) }.launchIn(this)
WakeSatelliteRunner.register { voiceAssistant.wakeAssistant() }
advanceUntilIdle()

val result = WakeSatelliteRunner().run(dummyContext, TaskerInput(Unit))
Expand Down Expand Up @@ -75,6 +76,7 @@ class TaskerPluginsTest {
}
}
val voiceAssistant = createVoiceAssistant(voiceOutput = voiceOutput)
StopRingingRunner.register { voiceAssistant.stopTimer() }

// Make it ring by sending a timer finished event
voiceAssistant.handleMessage(voiceAssistantTimerEventResponse {
Expand Down
Loading