1
+ package io.bimmergestalt.reader.carapp
2
+
3
+ import android.util.Log
4
+ import io.bimmergestalt.idriveconnectkit.rhmi.RHMIApplication
5
+ import io.bimmergestalt.idriveconnectkit.rhmi.RHMIEvent
6
+ import io.bimmergestalt.idriveconnectkit.rhmi.RHMIModel
7
+ import kotlinx.coroutines.flow.MutableStateFlow
8
+ import kotlinx.coroutines.flow.combine
9
+ import kotlin.math.max
10
+ import kotlin.math.min
11
+
12
+
13
+ data class HMITTS ( // the json decoder object
14
+ val TTSState : TTSState
15
+ )
16
+ data class TTSState ( // the actual state
17
+ val state : Int? ,
18
+ val currentblock : Int? ,
19
+ val blocks : Int? ,
20
+ val type : String? ,
21
+ val languageavailable : Int? ,
22
+ ) {
23
+ val stateName: ReadoutState
24
+ get() = ReadoutState .fromValue(state)
25
+
26
+ override fun toString (): String {
27
+ return " $type ${stateName.name} - $currentblock /$blocks "
28
+ }
29
+ }
30
+ enum class ReadoutState (val value : Int ) { // enum name for the state int
31
+ UNDEFINED (0 ),
32
+ IDLE (1 ),
33
+ PAUSED (2 ),
34
+ ACTIVE (3 ),
35
+ BUSY (4 );
36
+
37
+ companion object {
38
+ fun fromValue (value : Int? ): ReadoutState {
39
+ return values().firstOrNull { it.value == value } ? : UNDEFINED
40
+ }
41
+ }
42
+ }
43
+ enum class ReadoutCommand (val value : String ) {
44
+ PAUSE (" STR_READOUT_PAUSE" ),
45
+ STOP (" STR_READOUT_STOP" ),
46
+ PREV_BLOCK (" STR_READOUT_PREV_BLOCK" ),
47
+ NEXT_BLOCK (" STR_READOUT_NEXT_BLOCK" ),
48
+ RESTART (" STR_READOUT_JUMP_TO_BEGIN" ),
49
+ }
50
+
51
+ class ReadoutController (val name : String , val speechEvent : RHMIEvent .ActionEvent , val commandEvent : RHMIEvent .ActionEvent ) {
52
+ val speechList = speechEvent.getAction()?.asLinkAction()?.getLinkModel()?.asRaListModel()!!
53
+ val commandList = commandEvent.getAction()?.asLinkAction()?.getLinkModel()?.asRaListModel()!!
54
+
55
+ companion object {
56
+ fun build (app : RHMIApplication , name : String ): ReadoutController {
57
+ val events = app.events.values.filterIsInstance<RHMIEvent .ActionEvent >().filter {
58
+ it.getAction()?.asLinkAction()?.actionType == " readout"
59
+ }
60
+ if (events.size != 2 ) {
61
+ throw IllegalArgumentException (" UI Description is missing 2 readout events" )
62
+ }
63
+ return ReadoutController (name, events[0 ], events[1 ])
64
+ }
65
+ }
66
+
67
+ var desiredState = ReadoutState .IDLE
68
+ val activeState = MutableStateFlow (ReadoutState .IDLE ) // whether we are currently talking
69
+ private var currentState = TTSState (0 , null , null , null , null )
70
+ val debugText = MutableStateFlow (currentState.toString())
71
+ val isActive: Boolean
72
+ get() = currentState.type == name &&
73
+ (currentState.stateName == ReadoutState .ACTIVE || currentState.stateName == ReadoutState .BUSY )
74
+
75
+ private val lineIndex = MutableStateFlow (0 )
76
+ private var nextLineIndex = - 1 // next line index to read at the next IDLE state
77
+ private var lines = MutableStateFlow (emptyList<String >())
78
+ val currentLine = lines.combine(lineIndex) { lines, i ->
79
+ lines.getOrNull(i) ? : " "
80
+ }
81
+
82
+ fun onTTSEvent (ttsState : TTSState ) {
83
+ currentState = ttsState
84
+ debugText.value = currentState.toString()
85
+ Log .d(TAG , " TTSEvent: currentState:${ttsState.stateName} currentName:${ttsState.type} currentBlock:${ttsState.currentblock} /${ttsState.blocks} " )
86
+
87
+ if (desiredState == ReadoutState .ACTIVE && ttsState.stateName == ReadoutState .IDLE ) {
88
+ if (nextLineIndex >= 0 ) {
89
+ lineIndex.value = nextLineIndex
90
+ readLine()
91
+ return // don't update activeState
92
+ } else {
93
+ desiredState = ReadoutState .IDLE // automatically stop
94
+ }
95
+ }
96
+ // we aren't automatically continuing on, update the public activeState
97
+ activeState.value = if (ttsState.type == name) {
98
+ when (ttsState.stateName) {
99
+ ReadoutState .ACTIVE -> ReadoutState .ACTIVE
100
+ ReadoutState .BUSY -> ReadoutState .ACTIVE
101
+ ReadoutState .PAUSED -> ReadoutState .PAUSED
102
+ else -> ReadoutState .IDLE
103
+ }
104
+ } else { // some other TTS app is speaking
105
+ ReadoutState .IDLE
106
+ }
107
+ }
108
+
109
+ fun readLines (lines : List <String >) {
110
+ loadLines(lines)
111
+ play()
112
+ }
113
+
114
+ fun loadLines (lines : List <String >) {
115
+ this .lines.value = lines
116
+ this .lineIndex.value = 0
117
+ nextLineIndex = 0
118
+ desiredState = ReadoutState .IDLE
119
+ }
120
+
121
+ fun play () {
122
+ desiredState = ReadoutState .ACTIVE
123
+ readLine()
124
+ }
125
+
126
+
127
+ fun prevLine () {
128
+ nextLineIndex = max(0 , lineIndex.value - 1 )
129
+ lineIndex.value = nextLineIndex
130
+ _stop ()
131
+ }
132
+
133
+ fun nextLine () {
134
+ nextLineIndex = min(lines.value.size - 1 , lineIndex.value + 1 )
135
+ lineIndex.value = nextLineIndex
136
+ _stop ()
137
+ }
138
+
139
+ private fun readLine () {
140
+ val line = lines.value.getOrNull(lineIndex.value) ? : " "
141
+ val data = RHMIModel .RaListModel .RHMIListConcrete (2 )
142
+ data.addRow(arrayOf(line, name))
143
+ Log .d(TAG , " Starting readout from $name : ${data[0 ][0 ]} " )
144
+ speechList.setValue(data, 0 , 1 , 1 )
145
+ speechEvent.triggerEvent()
146
+
147
+ // cue the next line to play
148
+ if (lineIndex.value < lines.value.size - 1 ) {
149
+ nextLineIndex = lineIndex.value + 1
150
+ } else {
151
+ nextLineIndex = - 1
152
+ }
153
+ }
154
+
155
+ fun pause () {
156
+ desiredState = ReadoutState .PAUSED
157
+
158
+ Log .d(TAG , " Pausing $name readout" )
159
+ val data = RHMIModel .RaListModel .RHMIListConcrete (2 ).apply {
160
+ addRow(arrayOf(ReadoutCommand .PAUSE .value, name))
161
+ }
162
+ commandList.setValue(data, 0 , 1 , 1 )
163
+ commandEvent.triggerEvent()
164
+ }
165
+ fun stop () {
166
+ desiredState = ReadoutState .IDLE
167
+ _stop ()
168
+ }
169
+ private fun _stop () {
170
+ Log .d(TAG , " Cancelling $name readout" )
171
+ val data = RHMIModel .RaListModel .RHMIListConcrete (2 ).apply {
172
+ addRow(arrayOf(ReadoutCommand .STOP .value, name))
173
+ }
174
+ commandList.setValue(data, 0 , 1 , 1 )
175
+ commandEvent.triggerEvent()
176
+ }
177
+ }
0 commit comments