Hi!
Iāve just read this:
That might be a solution and quite helpful (if the interpreter delivers good results; as we know this depends from speaker to speaker, from openhab item config to openhab item config etc.).
Iāve read at Multimedia | openHAB and tried it for myself. Unfortunatelly in the docs only DSL rules are mentioned and I receive an error (interpret is not known):
2023-01-14 23:18:30.445 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'cb71c69296' failed: ReferenceError: "interpret" is not defined in <eval> at line number 68
So, at least for today Iāll stop my activities on this one.
Iāve got the script below working (basically the one above updated). So both the specialCommandExecuted
and also executeCommandBySemanticModel
. Not completely but working.
There Iām convinced that a mapping table as you suggested might be a good addition. Important is, that the keys are identical to what is in the semantic model.
Edit, 2023-01-15:
=> That is something I that would be really helpful for me including the keyword spotter. Iām looking forward to have feedback on this one.
Here, enjoy my script (@all):
(Edit, 2023-01-15: The script is not finished yet. Iāll update significant changes after having the working HAB speaker android porting without interaction)
/*
/*
Written by Ornostar, 2023-01-15. V0.9.0
This script will take the a text command from an item and will interpret it and create a result.
There are currently some important things (Todos! & nice to know):
1. you need to program all complicated or non-semantic/slang commands by yourself. See function specialCommandExecuted(.)
2. the evaluation of the semantic model is currently not complete.
a) see evaluateCommand(.): The type will not be evaluated. If you got 2 different items with different types, but same tags (like light dimming + temperature) you'll always get one error.
3. You need to configure the synonyms
4. Of course, you are responsible to deliver the string to interpret. For example look here:
a) see https://community.openhab.org/t/some-new-voice-add-ons-porcupineks-googlestt-watsonstt-voskstt/133500
b) see https://community.openhab.org/t/hab-speaker-dialog-processing-in-the-browser/140655
c) create item for voice command and configuration of using this (here + interpreter settings in openhab->settings)
5. You need to configure the items (currently correct tagging with the synonymsType defined below)
6. relative changes are not implemented (higher, lower, brighter, darker, louder... )
7. Some interpretation issues I have (see above) might be solved by using the build in interpreter.
Thanks @ Miguel for proposing this solution. I didn't tried so far, but could work. See https://www.openhab.org/docs/configuration/multimedia.html.
*/
//import org.openhab.model.script.actions.*;
var logger = Java.type("org.slf4j.LoggerFactory").getLogger("myScript");
var Voice = Java.type("org.openhab.core.model.script.actions.Voice");
/*
input parameter
*/
//var command = items["Sprachbefehl"].toString().toLowerCase();
var command = ("Mach viel Licht k2 aus").toLowerCase();
//var command = event.itemState.toString().toLowerCase();
logger.info("Voice command rule is executed with command: " + command);
//logger.info("Sprachbefehl: event ist " + event);
/*
Configuration parameter. Synonyms have the "meaning" which is the term known in openhab and an array of words that are meant as the meaning (this script will replace these words by the meaning).
important: recognized type (synonmsType) is used as "tag" in items
get your tts and sink by looking via API (developer tools -> API Explorer -> get on those ressources) or via openhab-cli (get sinks, voices)
*/
var volume = new PercentType(80);
var sink = "habspeaker::a4140789f5::sink"; // that is hard coded from the thing that is used byself. This won't work on your side.
var tts = "marytts:bits1hsmm"; // define this
// define these
var knownMisunderstandings = JSON.parse('[' +
'{"meaning": "Licht in K2 an", "words":["Licht im katz feier", "Licht in katz feier", "Licht in kanns feier", "Licht in ganzz bayern", "Licht sind katz feier"]}' +
']');
var synonymsLocation = JSON.parse('[' +
'{"meaning": "K2", "words":["kind2", "k2", "sĆ¼dost", "kampf zweier", "kanns feiern", "sĆ¼dosten", "sĆ¼d ost", "ca zwei", "katz feier"]},' +
'{"meaning": "Wohnen", "words":["wohn", "wohnen"]},' +
'{"meaning": "K1", "words":["kind1", "K1", "sĆ¼dwest"]},' +
'{"meaning": "Bad", "words":["bad", "badezimmer"]}' +
']');
var synonymsType = JSON.parse('[' +
'{"meaning": "Light", "words":["licht", "nicht"]},' +
'{"meaning": "Rollershutter", "words":["RollƤden", "Beschattung", "Rollos"]}' +
']');
var synonymsValue = JSON.parse('[' +
'{"meaning": "ON", "words":["an", "eins", "1", "hell", "ein"]},' +
'{"meaning": "OFF", "words":["aus", "0", "1", "dunkel"]},' +
'{"meaning": "10", "words":["etwas", "Bisschen"]},' +
'{"meaning": "80", "words":["viel"]}' +
']');
/*
a small initialization
*/
var synonyms;
var additionalLocationTagsToExtendSynonymsBySematicModel = ["Location",
"Location_Indoor",
"Location_Indoor_Room_Entry" ,
"Location_Indoor_Room_DiningRoom" ,
"Location_Indoor_Room" ,
"Location_Indoor_Room_Office" ,
"Location_Indoor_Room_BoilerRoom" ,
"Location_Indoor_Room_Kitchen" ,
"Location_Indoor_Room_Bathroom" ,
"Location_Indoor_Room_LivingRoom" ,
"Location_Indoor_Corridor" ,
"Location_Indoor_Room_Bedroom" ,
"Location_Outdoor_Carport" ,
"Location_Outdoor_Garden" ,
"Location_Indoor" ,
"Location_Indoor_Floor_GroundFloor" ,
"Location_Indoor_Floor_SecondFloor" ,
"Location_Indoor_Building"];
synonymsLocation = extendConfiguredSynonymsBySemanticModel(additionalLocationTagsToExtendSynonymsBySematicModel, synonymsLocation);
synonyms = knownMisunderstandings;
synonyms = synonyms.concat(synonymsLocation);
synonyms = synonyms.concat(synonymsType);
synonyms = synonyms.concat(synonymsValue);
/*
begin.
*/
// preparation of command; substitute words with meaning of words
command = updateCommandWithSynonymMeaning(synonyms, command);
//interpret(command, "system", null); // system is Build-In Interpreter. See <Protocol:your OpenhabIP:PORT>/developer/api-explorer
// try to find some fixed special commands
// if not successful go into complete analysis (what is location, what is type, what is value, what is other; last is not used). and trigger are command (not sure: either via semantic model or via known naming schema of items)
if (!specialCommandExecuted(command)){
evaluateCommand(command);
}
function evaluateCommand(command){
//logger.info("command: " + command);
var newCommandAsJSON = convertCommandToJSON(command);
/*if (newCommandAsJSON.locations.length >1 || newCommandAsJSON.type.length > 1){
//ToDo run through reasonable location/type combinations and call executeCommandBySemanticModel(.)
// 1. align first location and first type.
// afterwords aling hte latter (if location is after type, align the next type to the same location / if next is type, align it to the next type ???)
}*/
executeCommandBySemanticModel(newCommandAsJSON);
//logger.info("newCommandAsJSON: " + JSON.stringify(newCommandAsJSON));
};
function executeCommandBySemanticModel(cmdAsJSON){
var allItemsByTag;
// ToDo: Type korrekt identifiziern
// Problem: Der Value ist nicht eindeutig: "10" kann Dimmer und Number sein (Beispiel Lichttemperatur, Lichthelligkeit) oder Rollershutter.
// Ćber Kombination von Licht und Value kann man auch nicht identifzieren, weil Licht auch beides sein kann.
var type = "";
if (isNaN(cmdAsJSON.value)){
type == "Number"
}
if (type == ""){
allItemsByTag = itemRegistry.getItemsByTag([cmdAsJSON.locations, cmdAsJSON.type]);
} else {
allItemsByTag = itemRegistry.getItemsByTagAndType(type, [cmdAsJSON.locations, cmdAsJSON.type]);
}
var message = [];
for (var i in allItemsByTag) {
var state = allItemsByTag[i].getState();
var name = allItemsByTag[i].getName();
logger.info("name: " + name + " und state: " + state);
try {
events.sendCommand(allItemsByTag[i].getName(), cmdAsJSON.value);
if(message.indexOf([cmdAsJSON.type, cmdAsJSON.locations, cmdAsJSON.value].join("|")) == -1) {
message.push([cmdAsJSON.type, cmdAsJSON.locations, cmdAsJSON.value].join("|"));
}
logger.info("Item-Kommando wurde versendet allItemsByTag[i]: " + allItemsByTag[i].getName() + " und cmdAsJSON.value: " + cmdAsJSON.value);
} catch (e) {
logger.info("Item-Kommando wurde versendet allItemsByTag[i]: " + allItemsByTag[i].getName() + " und cmdAsJSON.value: " + cmdAsJSON.value);
}
}
for (var i in message){
//logger.info("message[i]: " + message[i]);
sayVoiceRespones(message[i].split("|")[0], message[i].split("|")[1], message[i].split("|")[2]);
}
};
function extendConfiguredSynonymsBySemanticModel(itemTagsForItemSearch, synonyms){
var retVal = synonyms;
for (var j in itemTagsForItemSearch){
allItemsByTag = itemRegistry.getItemsByTag(itemTagsForItemSearch[j]);
for (var i in allItemsByTag) {
/*logger.info("allItemsByTag[i].getLabel: " + allItemsByTag[i].getLabel());
logger.info("allItemsByTag[i].getName: " + allItemsByTag[i].getName());
logger.info("allItemsByTag[i].getGroupNames: " + allItemsByTag[i].getGroupNames());
logger.info("allItemsByTag[i].getState: " + allItemsByTag[i].getState());
logger.info("allItemsByTag[i].getTags: " + allItemsByTag[i].getTags());
logger.info("allItemsByTag[i].getType: " + allItemsByTag[i].getType());*/
var alreadyAvailable = findIndexInSynonymsByMeaning(allItemsByTag[i].getLabel(), synonyms)
//logger.info("searched for: " + allItemsByTag[i].getLabel() + " and found index is: " + alreadyAvailable);
if (alreadyAvailable != -1){
//logger.info("retVal[alreadyAvailable].words vorher: " + retVal[alreadyAvailable].words);
retVal[alreadyAvailable].words = retVal[alreadyAvailable].words.concat([allItemsByTag[i].getLabel(), allItemsByTag[i].getName()])
//logger.info("retVal[alreadyAvailable].words nachher: " + retVal[alreadyAvailable].words);
} else {
//logger.info("retVal vorher: " + JSON.stringify(retVal));
retVal = retVal.concat(JSON.parse('[' +
'{"meaning": "' + allItemsByTag[i].getLabel() + '", "words":["' + allItemsByTag[i].getLabel() +'","'+ allItemsByTag[i].getName() +'"]}' +
']'));
//logger.info("retVal nachher: " + JSON.stringify(retVal));
}
}
}
return retVal
}
function findIndexInSynonymsByMeaning(searchString, synonyms){
for (var k = 0, len = synonyms.length; k < len; k++){
if (synonyms[k].meaning == searchString){
return k;
}
}
return -1;
}
function assignWordsToSynonymGroup(cmd, synonymGroupToCheck){
var retVal = new Array();
for (var k = 0, len = synonymGroupToCheck.length; k < len; k++){
var searchIndex = cmd.search(synonymGroupToCheck[k].meaning);
//logger.info("Suchbegriff: " + synonymGroupToCheck[k].meaning.toLowerCase() + ". Gefunden: " + searchIndex);
if (searchIndex != -1){
retVal[retVal.length] = synonymGroupToCheck[k].meaning;
}
}
return retVal;
};
function findLocation(cmd){
return assignWordsToSynonymGroup(cmd, synonymsLocation);
};
function findType(cmd){
return assignWordsToSynonymGroup(cmd, synonymsType);
};
function findValue(cmd){
var values = assignWordsToSynonymGroup(cmd, synonymsValue);
// if string and number then remove string (due to grammatically reasons: e.g. "turn smt 10% on")
for (var i = 0, len = values.length; i < len; i++) {
if (!isNaN(values[i])){
/*logger.info("values[i]: " + values[i] + " ist eine number");
logger.info("i: " + i);
logger.info("values.slice(i,i+1): " + values.slice(i,i+1));*/
return values.slice(i,i+1);
}
}
return values;
};
function updateCommandWithSynonymMeaning(synonymlist, command){
var retVal = command.toLowerCase();
for (var i = 0, len = synonymlist.length; i < len; i++) {
//logger.info("retVal.toLowerCase(): " + retVal.toLowerCase());
//logger.info("synonymlist[i].meaning: " + synonymlist[i].meaning);
if (retVal.search(synonymlist[i].meaning.toLowerCase()) > -1){
retVal = retVal.replace(synonymlist[i].meaning.toLowerCase(), synonymlist[i].meaning);
}
for (var j = 0, l = synonymlist[i].words.length; j < l; j++){
// ToDo: Check if a part of a word is replaced or a standalone word.
//logger.info("Wort zu lower Case: " + synonymlist[i].words[j]);
if (retVal.search(synonymlist[i].words[j].toLowerCase()) > -1){
//logger.info("synonymlist[i].words[j] ist "+ synonymlist[i].words[j] +" ersetzen mit: " + synonymlist[i].meaning);
retVal = retVal.replace(synonymlist[i].words[j], synonymlist[i].meaning);
}
}
}
//logger.info("updateCommandWithSynonymMeaning: " + command + " wurde zu "+ retVal);
return retVal;
};
function convertCommandToJSON(command){
var retVal = JSON.parse('{' +
'"locations": [],' +
'"value": [],' +
'"type": [],' +
'"other": []' +
'}');
retVal.locations.push(findLocation(command));
retVal.type.push(findType(command));
retVal.value.push(findValue(command));
logger.info("retVal: " + JSON.stringify(retVal));
return retVal;
};
function sayVoiceRespones(type, loc, val){
var message = "Ich setze " + type;
if (loc != null){
message = message + " in " + loc;
}
message = message +" auf " + val + ".";
Voice.say(message, tts, sink, volume);
};
function specialCommandExecuted(command){
logger.info("specialCommandExecuted wird ausgefĆ¼hrt mit: " + command);
if (command.search("musik", command) != -1 && command.search("an", command) != -1) {
switchMusic("ON", null);
sayVoiceRespones("Musik", null, "OFF");
return true;
} else if (command.search("musik", command) != -1 && command.search("aus", command) != -1)
{
switchMusic("OFF", null);
sayVoiceRespones("Musik", null, "OFF");
return true;
} /*else if (command.search("Light im katz feier", command) != -1 || command.search("Light in katz feier", command) != -1 || command.search("Light in kanns feier", command) != -1 || command.search("Light in gONz bayern", command) != -1 || command.search("Light sind katz feier", command) != -1)
{
events.sendCommand("LichtK2SwitchItem", "ON");
return true;
}*/
return false;
}
function switchMusic(state, location){
if (location == null){
events.sendCommand(ir.getItem("SqueezePlayer1Playpause"), state);
events.sendCommand(ir.getItem("SqueezePlayer2Playpause"), state);
events.sendCommand(ir.getItem("SqueezePlayer3Playpause"), state);
events.sendCommand(ir.getItem("SqueezePlayer4Playpause"), state);
return;
} else {
switch(location) {
case "alles":
events.sendCommand(ir.getItem("SqueezePlayer1Playpause"), state);
events.sendCommand(ir.getItem("SqueezePlayer2Playpause"), state);
events.sendCommand(ir.getItem("SqueezePlayer3Playpause"), state);
events.sendCommand(ir.getItem("SqueezePlayer4Playpause"), state);
break;
case "wohn":
logger.info("case wohn in switchMusic not implemented yet. location: " + location);
break;
case "kĆ¼che":
logger.info("case kĆ¼che in switchMusic not implemented yet. location: " + location);
break;
case "ess":
logger.info("case ess in switchMusic not implemented yet. location: " + location);
break;
case "wc":
logger.info("case wc in switchMusic not implemented yet. location: " + location);
break;
case "drauĆen":
logger.info("case drauĆen in switchMusic not implemented yet. location: " + location);
break;
default:
logger.info("case default in switchMusic not implemented yet. location: " + location);
break;
}
}
};