In addition to voice announcements and voice commands, I also implemented a “dialog” feature, where I can ask for information, and get an answer from the system. Currently, this very limited, I can only ask for the time of day, for temperature and humidity.
In /etc/openhab/things/rhasspy.things we have
Thing mqtt:topic:rh-mq:VoiceQuestion (mqtt:broker:rh-mq) {
Channels:
Type string: Message [ stateTopic="hermes/intent/VoiceQuestion" ]
}
In /etc/openhab/items/rhasspy.items we have
String Rhasspy_Question "Rhasspy Question" { channel="mqtt:topic:rh-mq:VoiceQuestion:Message" }
String vqTime "time" (gVQ) // dummy items picked up by Rhasspy slot program
in other .items files, I have (the details are not relevant for what I am trying to explain here)
Number localCurrentTemperature "temperature [%.0f°C]" <temperature> (gVQ) {...some binding...}
Number localCurrentHumidity "humidity [%.0f%%rH]" <humidity> (gVQ) {...some binding...}
In /etc/openhab/rules/rhasspy.rules we have
rule "Rhasspy VoiceQuestion message"
when
Item Rhasspy_Question received update
then
if (IsLive.state!=ON) { return; }
val String json = newState.toString
val String rawInput = transform("JSONPATH","$.rawInput", json)
val String topic = transform("JSONPATH","$.slots[?(@.entity=='oh_items')].rawValue", json).toLowerCase
val String siteId = transform("JSONPATH","$.siteId", json)
logInfo("voice","Site {} heard '{}', ask about '{}'", siteId, rawInput, topic )
var String answer = " I don't know"
if (topic=="temperature") {
answer = "the temperature is "
+ String::format("%.0f", (localCurrentTemperature.state as DecimalType).floatValue )
+ " degrees outside and "
+ String::format("%.0f", (AZ_Temp.state as DecimalType).floatValue )
+ " degrees inside"
} else if (topic=="humidity") {
answer = "the " + topic + " is "
+ String::format("%.0f", (localCurrentHumidity.state as DecimalType).floatValue )
+ " percent"
} else if (topic == "time") {
answer = "it is " + String.format("%1$tH:%1$tM", now)
}
// where should the answer be heard?
val sinkName = transform("MAP","source_to_sink.map",siteId)
val sinkItem = gSay.members.findFirst[ t | t.name=="say_"+sinkName]
if (sinkItem !== null) {
sinkItem.sendCommand(answer)
}
logInfo("voice","QUESTION '{}' to '{}', ANSWER '{}' to '{}'", topic, siteId, answer, sinkName)
end
This part is a bit ugly, because I have a long if .. else if chain to identify each question topic. There is room for improvement …