Description
This is a simple but extensible voice control tool for openHAB. It uses the OH Android App to send voice commands to OH and interpret them using custom rules that can be easily extended in the rule script. Apart from the voice command itself, no OH internal data needs to be sent to any external API.
The voice command is split in tokens, which are tried to be matched for the pre-defined rules.
Features:
- Supported languages: English, German
- Send ON/OFF or UP/DOWN command to a single item (matched by label or synonym)
-
turn off the kitchen light
-
lower the living room blinds
- Assuming in above examples that
kitchen light
andliving room blinds
are the labels of single items.
-
- Turn all lights on/off or raise/lower all roller shutters in a group, e.g. a location.
- All descendant items of the respective group can be matched.
-
turn off the light in the first floor
- This will match all
Switch
items with theLight
tag.
- This will match all
-
raise all shutters in the living room
- Assuming in above examples that
first floor
andliving room
are group items.
- Item matching via the item label or
Synonym
metadata. - Create custom rules, including custom script execution upon matching.
Examples (English)
Light Controls
Turning lights on/off in a specific location:
- “Turn on all the lights in the living room.”
- “Switch the light off in the kitchen.”
- “Turn all lights off in the bedroom.”
- “Switch the lights on.”
Turning lights on/off with labels:
- “Turn the kitchen light off.”
- “Switch on the bedroom lights.”
- “Turn off the living room lights.”
Rollershutter Controls
Raising or lowering rollershutters:
- “Put the rollershutter up in the office.”
- “Pull all the blinds down in the living room.”
- “Lower the shutters in the bedroom.”
- “Raise all the roller blinds in the kitchen.”
Specifying rollershutters by label:
- “Lower the kitchen shutters.”
- “Raise the office blinds.”
- “Pull the living room blinds down.”
- “Put the bathroom roller shutter up.”
Generic Item Commands
Turning items on/off:
- “Turn the fan on.”
- “Switch the heater off.”
- “Turn the lamp off.”
Adjusting items up/down:
- “Pull the screen down.”
- “Lower the projector.”
- “Raise the curtain.”
Examples (German)
Lichtsteuerung (Light Controls)
Schalten von Lichtern ein/aus in einem bestimmten Raum:
- “Schalte alle Lichter im Wohnzimmer ein.”
- “Mach das Licht in der Küche aus.”
- “Schalt alle Lampen im Schlafzimmer aus.”
- “Mach die Lichter im Flur an.”
Schalten von Lichtern mit Labels:
- “Schalte die Wohnzimmerlampe aus.”
- “Mach das Küchenlicht an.”
- “Schalt das Schlafzimmerlicht ein.”
- “Mach die Flurlampe aus.”
Rolladensteuerung (Rollershutter Controls)
Hoch- oder Herunterfahren von Rollos in einem Raum:
- “Fahr die Rollos im Wohnzimmer hoch.”
- “Mach das Rollo im Schlafzimmer runter.”
- “Fahre die Rolladen in der Küche hoch.”
- “Fahr die Rolläden im Büro herunter.”
Hoch- oder Herunterfahren von Rollos mit Labels:
- “Fahr das Küchenrollo hoch.”
- “Mach den Wohnzimmerrolladen runter.”
- “Fahre die Schlafzimmerrolläden herunter.”
- “Fahr die Bürorollos hoch.”
Generische Befehle (Generic Commands)
Ein- und Ausschalten von Geräten:
- “Schalte den Ventilator ein.”
- “Mach die Heizung aus.”
- “Schalt die Lampe an.”
Hoch- oder Herunterfahren von Geräten:
- “Fahre den Bildschirm runter.”
- “Mach die Leinwand hoch.”
- “Fahr die Jalousie herunter.”
Requirements
Language: JavaScript, Scripting with openhab-js
Dependencies:
- JavaScript Scripting add-on
- openhab-js 4.5.1 or later
- openHAB Android App, connected to your OH instance and configured to send voice commands to OH
- A voice command String item, configured under Rule Voice Interpreter
How to setup the voice command item
-
Create the item, called e.g. “VoiceCommand”
-
Configure it in the settings as target for the rule voice interpreter: Settings->Rule Voice Interpreter->Voice Command Item
→
Create Custom Rules
As of now, custom rules must be defined in the inline script.
At the very bottom of the script, the // RULES
section defines the rules. Below, there is a section for your custom rules:
// ** CUSTOM RULES *********************
// Add your rules here
// *************************************
You can define variables for common expressions or reuse the ones from the pre-defined rules and add the rule to the rbi
object (the instance of the RuleBasedInterpreter
class), e.g.
let the = opt("the");
let lowerRaise = alt(cmd("lower", DOWN),cmd("raise", UP));
rbi.addRule(seq(lowerRaise, the, itemLabel()));
/**
* Adds a rule.
* Either the expression must contain a function to execute (e.g. send a command) or a specific function must be given.
* @param {Expression} expression
* @param {function} executeFunction If a specific function is given, it will override any function from the expression.
*/
addRule(expression, executeFunction)
The following expressions are available to define a rule:
/**
* Creates an alternative expression. All given expressions can be used alternatively, i.e. using an OR logic.
* @param {...Expression} expressions Any expression types.
*/
function alt(...expressions)
/**
* Creates a sequential expression. All given expressions are processed sequentially in that order.
* @param {...Expression} expressions Any expression types.
*/
function seq(...expressions)
/**
* Creates an optional expression. The given expression is not mandatory for a match.
* @param {Expression} expression
*/
function opt(expression)
/**
* Creates a command expression.
* If the given expression is matched, the given command is sent to all found items of that rule.
* @param {Expression} expression
* @param {string} command
*/
function cmd(expression, command)
/**
* Creates an item label expression.
* It will try to match an item's label or its synonyms to the tokens at this point.
* Only a single item must be matched.
* The found item can be included in the final execution parameter, e.g. to send a command to that item.
* @param {boolean} includeInExecuteParameter Default: true.
* @param {boolean} isGroup Default: false. If true, only group items (type: "Group") are matched.
* @returns
*/
function itemLabel(includeInExecuteParameter, isGroup)
/**
* Creates an item properties expression.
* It tries to filter items according to their properties: tags, item type or parent group.
* If none of filter properties are given, then all items in the registry will be matched.
* All matched items will be included in the final execution parameter, e.g. to send a command to these items.
* @param {Expression} expression The expression to match.
* @param {string[]} tags Default: []. Only items that have all the given tags will be matched.
* @param {boolean} groupRequired Default: true. If true, the expression must contain a group Item Label Expression. Only descendants of that group will be matched.
* @param {string} itemType Default: null. If a type is given, then only items of that type will be matched.
*/
function itemProperties(expression, tags, groupRequired, itemType)
Future Improvements
- Add more out of the box rules, including other languages
- Verify rules in various scenarios
- Include custom rules from a separate script, so the update of the rule template is easier without the risk of data loss
Changelog
Version 0.1
- initial release
Resources
uid: jm:rule_based_voice_interpreter
label: Rule Based Voice Interpreter
description: This is a simple but extensible voice control tool for openHAB. It uses the OH Android App to send voice commands to OH and interpret them using custom rules that can be easily extended in the rule script.
configDescriptions:
- name: VoiceCommandItem
label: Voice Command Item
description: The item that stores the voice command
type: TEXT
context: item
filterCriteria:
- name: type
value: String
required: true
triggers:
- id: "1"
configuration:
itemName: "{{VoiceCommandItem}}"
type: core.ItemStateUpdateTrigger
conditions: []
actions:
- inputs: {}
id: "2"
configuration:
type: application/javascript
script: >-
(function (data) {
console.log("data:")
console.log(data);
// ***
// Interpreter
// ***
const { items } = require('openhab');
class Expression {
/**
* @param {string[]} tokens
* @returns {EvaluationResult}
*/
evaluate(tokens) {
return new EvaluationResult(false, tokens);
}
}
/**
* Creates an alternative expression. All given expressions can be used alternatively, i.e. using an OR logic.
* @param {...Expression} expressions Any expression types.
*/
function alt(...expressions) {return new AlternativeExp(...expressions);}
class AlternativeExp extends Expression {
/**
* @param {...Expression} expressions
*/
constructor(...expressions) {
super();
this.value = expressions;
}
evaluate(tokens) {
console.debug("eval alt: " + stringify(this.value));
console.debug("for tokens: " + stringify(tokens));
var success = false;
var executeFunction = null;
var remainingTokens = tokens;
var executeParameter = null;
let groupItem = null;
for (var index = 0; index < this.value.length; index++) {
var subexp = this.value[index];
console.debug("alt index: " + index + "; subexp: " + stringify(subexp));
var result = evaluateExpressionOrString(subexp, tokens.slice());
if (result.success) {
success = true;
remainingTokens = result.remainingTokens;
executeFunction = result.executeFunction || executeFunction;
executeParameter = result.executeParameter || executeParameter;
groupItem = result.groupItem || groupItem;
break;
}
}
console.debug("eval alt: " + success)
return new EvaluationResult(success, remainingTokens, executeFunction, executeParameter);
}
}
/**
* Creates a sequential expression. All given expressions are processed sequentially in that order.
* @param {...Expression} expressions Any expression types.
*/
function seq(...expressions) {return new SequenceExp(...expressions);}
class SequenceExp extends Expression {
/**
* @param {...Expression} expressions
*/
constructor(...expressions) {
super();
this.value = expressions;
}
evaluate(tokens) {
console.debug("eval seq: " + stringify(this.value));
var success = true;
var executeFunction = null;
var executeParameter = null;
let groupItem = null;
var remainingTokens = tokens.slice();
for (var index = 0; index < this.value.length; index++) {
var subexp = this.value[index];
console.debug("eval subexp " + index + "; subexp: " + stringify(subexp))
var result = evaluateExpressionOrString(subexp, remainingTokens);
if (!result.success) {
console.debug("eval subexp " + index + "failed");
success = false;
break;
}
remainingTokens = result.remainingTokens;
executeFunction = result.executeFunction || executeFunction;
executeParameter = result.executeParameter || executeParameter;
groupItem = result.groupItem || groupItem;
}
console.debug("eval seq: " + success)
return new EvaluationResult(success, remainingTokens, executeFunction, executeParameter, groupItem);
}
}
/**
* Creates an optional expression. The given expression is not mandatory for a match.
* @param {Expression} expression
*/
function opt(expression) {return new OptionalExp(expression);}
class OptionalExp extends Expression {
/**
*
* @param {Expression} expression
*/
constructor(expression) {
super();
this.value = expression;
}
evaluate(tokens) {
console.debug("eval opt: " + stringify(this.value))
var result = evaluateExpressionOrString(this.value, tokens.slice());
if (result.success) {
console.debug("eval opt success")
// only return the reduced token array and other parameters if optional expression was successful.
return new EvaluationResult(true, result.remainingTokens, result.executeFunction, result.executeParameter);
}
console.debug("eval opt fail")
// otherwise still return successful, but nothing from the optional expression result
return new EvaluationResult(true, tokens, null, null);
}
}
/**
* Creates a command expression.
* If the given expression is matched, the given command is sent to all found items of that rule.
* @param {Expression} expression
* @param {string} command
*/
function cmd(expression, command) {return new CommandExp(expression, command);}
class CommandExp extends Expression {
/**
* @param {Expression} expression
* @param {string} command
*/
constructor(expression, command) {
super();
this.value = expression;
this.command = command;
}
evaluate(tokens) {
console.debug("eval cmd: " + stringify(this.value));
var result = evaluateExpressionOrString(this.value, tokens);
console.debug("eval cmd result: " + result.success)
if (!result.success) {
return new EvaluationResult(false, tokens, null, null);
}
let commandToExecute = this.command;
var executeFunction = function(parameter) {
if (!parameter || typeof(parameter) != "object") {
console.debug("Trying to send a command, but no proper object parameter found")
return;
}
if (!parameter.items) {
console.debug("Trying to send a command, but no items parameter found")
return;
}
parameter.items.forEach(item => {
item.sendCommand(commandToExecute);
});
}
return new EvaluationResult(true, result.remainingTokens, executeFunction, result.executeParameter);
}
}
/**
* Creates an item label expression.
* It will try to match an item's label or its synonyms to the tokens at this point.
* Only a single item must be matched.
* The found item can be included in the final execution parameter, e.g. to send a command to that item.
* @param {boolean} includeInExecuteParameter Default: true.
* @param {boolean} isGroup Default: false. If true, only group items (type: "Group") are matched.
* @returns
*/
function itemLabel(includeInExecuteParameter, isGroup) {return new ItemLabelExp(includeInExecuteParameter, isGroup);}
class ItemLabelExp extends Expression {
/**
* @param {boolean} includeInExecuteParameter Default: true.
* @param {boolean} isGroup Default: false.
*/
constructor(includeInExecuteParameter, isGroup) {
super();
this.isGroup = isGroup ??= null;
this.includeInExecuteParameter = includeInExecuteParameter ??= true;
}
evaluate(tokens) {
console.debug("eval item label with tokens: " + stringify(tokens))
if (tokens.length < 1) {
console.debug("no tokens, eval item label fail")
return new EvaluationResult(false, tokens, null, null);
}
let remainingItems = items.getItems();
if (this.isGroup != null) {
remainingItems = remainingItems.filter(item => item.type === "Group");
}
let matchResult = getItemByLabelOrSynonym(remainingItems, tokens);
if (!matchResult) {
console.debug("eval item label fail");
return new EvaluationResult(false, tokens, null, null);
}
console.debug("eval item label success")
let groupItem = null;
if (this.isGroup) {
groupItem = matchResult.matchedItem;
}
let executeParameter = null;
if (this.includeInExecuteParameter) {
executeParameter = {items: [matchResult.matchedItem]};
}
return new EvaluationResult(true, matchResult.remainingTokens, null, executeParameter, groupItem);
}
}
/**
* Creates an item properties expression.
* It tries to filter items according to their properties: tags, item type or parent group.
* If none of filter properties are given, then all items in the registry will be matched.
* All matched items will be included in the final execution parameter, e.g. to send a command to these items.
* @param {Expression} expression The expression to match.
* @param {string[]} tags Default: []. Only items that have all the given tags will be matched.
* @param {boolean} groupRequired Default: true. If true, the expression must contain a group Item Label Expression. Only descendants of that group will be matched.
* @param {string} itemType Default: null. If a type is given, then only items of that type will be matched.
*/
function itemProperties(expression, tags, groupRequired, itemType) {return new ItemPropertiesExp(expression, tags, groupRequired, itemType);}
class ItemPropertiesExp extends Expression {
/**
* @param {Expression} expression
* @param {string[]} tags
* @param {boolean} groupRequired
* @param {string} itemType
*/
constructor(expression, tags, groupRequired, itemType) {
super();
this.value = expression;
this.tags = tags ??= [];
this.groupRequired = groupRequired ??= true;
this.itemType = itemType ??= null;
}
evaluate(tokens) {
console.debug("eval item properties with tokens: " + stringify(tokens))
if (tokens.length < 1) {
console.debug("no tokens, eval item properties fail")
return new EvaluationResult(false, tokens, null, null);
}
let expResult = evaluateExpressionOrString(this.value, tokens.slice());
if (!expResult.success) {
console.debug("eval item properties: inner expression eval fail");
return new EvaluationResult(false, tokens, null, null);
}
if (this.groupRequired && expResult.groupItem == null) {
console.debug("eval item properties fail: group required but not found");
return new EvaluationResult(false, tokens, null, null);
}
console.debug("eval item properties: search items");
let remainingItems = null;
if (this.tags.length == 0) {
remainingItems = items.getItems();
console.debug("eval item properties: get all items");
} else {
remainingItems = items.getItemsByTag(...this.tags);
console.debug("eval item properties: filter items by tags: " + this.tags + "; remaining: " + remainingItems.length);
}
if (this.itemType != null) {
remainingItems = remainingItems.filter(item => item.type == this.itemType);
console.debug("eval item properties: filter items by type: " + this.itemType + "; remaining: " + remainingItems.length);
}
if (this.groupRequired) {
remainingItems = remainingItems.filter(item => itemIsInSubGroup(item, expResult.groupItem));
console.debug("eval item properties: filter items by group: " + expResult.groupItem.name + "; remaining: " + remainingItems.length);
}
if (!expResult.executeParameter) {
expResult.executeParameter = { items: [] };
}
if (!expResult.executeParameter.items) {
expResult.executeParameter.items = [];
}
expResult.executeParameter.items = expResult.executeParameter.items.concat(remainingItems);
return new EvaluationResult(true, expResult.remainingTokens, expResult.executeFunction, expResult.executeParameter);
}
}
class EvaluationResult {
/**
*
* @param {boolean} success if evaluation was successful or not
* @param {string[]} remainingTokens
* @param {function} executeFunction the function to execute in the end
* @param {object} executeParameter the parameter inserted in the executeFunction. Should be a single object that can hold multiple parameters in its key/value pairs.
* @param {object} groupItem
*/
constructor(success, remainingTokens, executeFunction, executeParameter, groupItem) {
this.success = success;
this.remainingTokens = remainingTokens || [];
this.executeFunction = executeFunction;
this.executeParameter = executeParameter;
this.groupItem = groupItem;
}
}
class RuleBasedInterpreter {
constructor() {
this.rules = [];
}
// ***
// FUNCTIONS
// ***
/**
* Adds a rule.
* Either the expression must contain a function to execute (e.g. send a command) or a specific function must be given.
* @param {Expression} expression
* @param {function} executeFunction If a specific function is given, it will override any function from the expression.
*/
addRule(expression, executeFunction) {
this.rules.push({
expression: expression,
executeFunction: executeFunction
});
}
/**
* Clears all saved rules.
*/
clearRules() {
this.rules = [];
}
/**
* Tries to interpret the given utterance by matching all saved rules.
* @param {string} utterance
* @returns
*/
interpretUtterance(utterance) {
if (!utterance) {
return;
}
var normalizedUtterance = normalizeUtterance(utterance);
var tokens = tokenizeUtterance(normalizedUtterance);
console.debug("input normalized utterance: " + normalizedUtterance);
console.debug("input tokens: " + stringify(tokens));
for (var index = 0; index < this.rules.length; index++) {
console.debug("check rule " + index);
var rule = this.rules[index];
console.debug(stringify(rule));
var result = evaluateExpressionOrString(rule.expression, tokens.slice());
if (result.success) {
var executeFunction = result.executeFunction || rule.executeFunction;
if (!executeFunction) {
console.debug("rule matched, but no function to execute found, continue");
continue;
}
console.debug("execute function with parameters: " + stringify(result.executeParameter.items));
executeFunction(result.executeParameter);
break;
}
}
}
}
/**
*
* @param {Expression} expression
* @param {string[]} tokens
* @returns {EvaluationResult}
*/
function evaluateExpressionOrString(expression, tokens) {
if (tokens.length < 1) {
return new EvaluationResult(true, tokens, null);
}
if (typeof(expression) == "string") {
return evaluateStringExpression(expression, tokens);
}
return expression.evaluate(tokens);
}
function evaluateStringExpression(expression, tokens) {
if (tokens.length < 1) {
return new EvaluationResult(false, tokens, null, null);
}
console.debug("eval string: " + expression)
console.debug("token: " + tokens[0]);
var hasMatch = tokens[0] === expression; //tokens[0].match(expression) != null;
console.debug("hasMatch: " + hasMatch)
return new EvaluationResult(hasMatch, tokens.slice(1), null, null);
}
function getItemsBySemanticType(itemList, semanticType) {
return itemList.filter(item => item.semantics.semanticType == semanticType);
}
function getItemByLabelOrSynonym(itemList, tokens) {
// normalize and tokenize the label for easier comparison
let allItems = itemList
.map(function(i){
return {
item: i,
labelTokens: tokenizeUtterance(normalizeUtterance(i.label))
}
});
let checkTokens = function(tokensToCheck, tokensTarget) {
if (tokensToCheck.length > tokensTarget.length) {
return false;
}
for (let index = 0; index < tokensToCheck.length; index++) {
if (tokensToCheck[index] != tokensTarget[index]) {
return false;
}
}
return true;
}
// we need a single exact match
// first try the regular labels
let checkLabels = function(remainingItems) {
let tokenIndex = 0;
while (remainingItems.length > 1) {
if (tokens.length < tokenIndex + 1) {
// no tokens left, but still multiple possible items -> abort
break;
}
remainingItems = remainingItems.filter(function(entry) {
return (entry.labelTokens.length >= tokenIndex + 1) && entry.labelTokens[tokenIndex] == tokens[tokenIndex];
});
tokenIndex++;
}
// if one item is left, ensure that it really has a fully matching label (i.e. each token)
// because one item could be remaining but not all tokens have been checked to match (e.g. single item in overall registry)
if (remainingItems.length == 1) {
if (checkTokens(remainingItems[0].labelTokens, tokens)) {
tokenIndex = remainingItems[0].labelTokens.length;
} else {
remainingItems.pop();
}
}
return {remainingItems: remainingItems, tokenIndex: tokenIndex};
}
let matchResult = checkLabels(allItems.slice());
console.debug("get item by label: found matched labels: " + matchResult.remainingItems.length);
if (matchResult.remainingItems.length == 0) {
// either none or multiple matches found. Let's try the synonyms.
let checkSynonyms = function(allItems) {
let remainingItems = allItems.map(function(i){
return {
item: i.item,
synonyms: getSynonyms(i.item).map(function(s){ return tokenizeUtterance(normalizeUtterance(s));})
}
});
// remove items without synonyms
remainingItems = remainingItems.filter(function(i) {
return i.synonyms.length > 0;
});
let tokenIndex = 0;
while (remainingItems.length > 1) {
if (tokens.length < tokenIndex + 1) {
// no tokens left, but still multiple possible items -> abort
break;
}
// remove synonyms with fewer or non-matching tokens
remainingItems = remainingItems.map(function(i) {
i.synonyms = i.synonyms.filter(function(synonymTokens) {
return (synonymTokens.length >= tokenIndex + 1) && (synonymTokens[tokenIndex] == tokens[tokenIndex]);
});
return i;
});
// remove items without synonyms
remainingItems = remainingItems.filter(function(i) {
return i.synonyms.length > 0;
});
tokenIndex++;
}
// if one item is left, ensure that it really has a fully matching synonym (i.e. each token)
// because one item could be remaining but not all tokens have been checked to match (e.g. single item in overall registry)
if (remainingItems.length == 1) {
let matchingSynonyms = remainingItems[0].synonyms.filter(function(synonymTokens) {
return checkTokens(synonymTokens, tokens);
});
if (matchingSynonyms.length > 0) {
tokenIndex = matchingSynonyms[0].length;
} else {
remainingItems.pop();
}
}
return {remainingItems: remainingItems, tokenIndex: tokenIndex};
}
matchResult = checkSynonyms(allItems.slice());
console.debug("get item by label: found matched synonyms: " + matchResult.remainingItems.length);
}
if (matchResult.remainingItems.length == 1) {
console.debug("get item by label: success");
return {matchedItem: matchResult.remainingItems[0].item, remainingTokens: tokens.slice(matchResult.tokenIndex)};
}
console.debug("get item by label: fail");
return null;
}
function normalizeUtterance(utterance) {
return utterance.toLowerCase();
}
function tokenizeUtterance(utterance) {
return utterance.split(" ").filter(Boolean);
}
function stringify(obj) {
return JSON.stringify(obj, null, 2);
}
function stringIsNullOrEmpty(str) {
return str === undefined || str === null || str === "";
}
function getSynonyms(item) {
var meta = item.getMetadata("synonyms");
if (!meta || stringIsNullOrEmpty(meta.value)) {
return [];
}
return meta.value.split(",");
}
function itemIsInSubGroup(item, targetGroupItem) {
let checkedGroupNames = [];
let groupStack = [];
groupStack.push(item);
while (groupStack.length > 0) {
let groupItem = groupStack.pop();
if (groupItem.name == targetGroupItem.name) {
return true;
}
groupItem.groupNames.forEach(groupName => {
if (!checkedGroupNames.includes(groupName)) {
checkedGroupNames.push(groupName);
groupStack.push(items.getItem(groupName));
}
});
}
return false;
}
// ***
// RULES
// ***
const { ON, OFF, UP, DOWN } = require("@runtime");
let rbi = new RuleBasedInterpreter();
function interpretUtterance(utterance) {
rbi.interpretUtterance(utterance);
}
// ** ENGLISH **************************
let onOff = alt(cmd("on", ON), cmd("off", OFF));
let turn = alt("turn", "switch");
let put = alt("put", "bring", "pull");
let the = opt("the");
let inOfThe = seq(alt("in", "of"), the);
let allThe = alt(seq("all", the), the);
let upDown = alt(cmd("up", UP), cmd("down", DOWN));
let lowerRaise = alt(cmd("lower", DOWN),cmd("raise", UP));
// turn lights on off in location
let lights = alt("light", "lights");
rbi.addRule(seq(
turn,
opt(onOff),
allThe,
itemProperties(
seq(
lights,
opt(onOff),
inOfThe,
itemLabel(false, true)),
["Light"], true, "Switch"),
opt(onOff))
);
rbi.addRule(seq(
turn,
opt(onOff),
allThe,
itemProperties(
seq(
itemLabel(false, true),
lights),
["Light"], true, "Switch"),
opt(onOff))
);
// rollershutters up/down in location
let rollershutters = alt("rollershutter", "rollershutters", seq("roller", alt("shutter", "blind")), seq("roller", alt("shutters", "blinds")), "shutter", "shutters", "blind", "blinds");
rbi.addRule(
seq(
put,
opt(upDown),
allThe,
itemProperties(
seq(
rollershutters,
opt(upDown),
inOfThe,
itemLabel(false, true)
),
null, true, "Rollershutter"
),
opt(upDown),
)
);
rbi.addRule(
seq(
lowerRaise,
allThe,
itemProperties(
seq(
rollershutters,
inOfThe,
itemLabel(false, true)
),
null, true, "Rollershutter"
)
)
);
rbi.addRule(
seq(
put,
opt(upDown),
allThe,
itemProperties(
seq(
itemLabel(false, true),
rollershutters
),
null, true, "Rollershutter"
),
opt(upDown),
)
);
rbi.addRule(
seq(
lowerRaise,
allThe,
itemProperties(
seq(
itemLabel(false, true),
rollershutters
),
null, true, "Rollershutter"
)
)
);
// ON OFF type
rbi.addRule(seq(turn, opt(onOff), the, itemLabel(), opt(onOff)));
// UP DOWN type
rbi.addRule(seq(put, opt(upDown), the, itemLabel(), opt(upDown)));
rbi.addRule(seq(lowerRaise, the, itemLabel()));
// *************************************
// ** GERMAN ***************************
var denDieDas = opt(alt("den", "die", "das"));
var einAnAus = alt(cmd(alt("ein", "an"), ON), cmd("aus", OFF));
var schalte = alt("schalte", "mache", "schalt", "mach");
var fahre = alt("fahre", "fahr", "mache", "mach");
var hochRunter = alt(cmd(alt("hoch", "auf"), UP), cmd(alt("runter", "herunter", "zu"), DOWN));
let imIn = alt("im", seq("in", opt(alt("der", "dem"))));
// ON OFF type
rbi.addRule(seq(schalte, denDieDas, itemLabel(), einAnAus));
// UP DOWN type
rbi.addRule(seq(fahre, denDieDas, itemLabel(), hochRunter));
let alleDie = alt("alle", denDieDas);
// turn lights on off in location
let lichter = alt("licht", "lichter", "lampen");
rbi.addRule(seq(
schalte,
alleDie,
itemProperties(
seq(
lichter,
opt(einAnAus),
imIn,
itemLabel(false, true)),
["Light"], true, "Switch"),
opt(einAnAus))
);
// rollershutters up/down in location
let rollos = alt("rollo", "rollos", "rolladen", "rolläden");
rbi.addRule(
seq(
fahre,
denDieDas,
itemProperties(
seq(
rollos,
imIn,
itemLabel(false, true)
),
null, true, "Rollershutter"
),
hochRunter
)
);
// *************************************
// ** CUSTOM RULES *********************
// Add your rules here
// *************************************
let vcString = items.getItem("{{VoiceCommandItem}}").state;
interpretUtterance(vcString);
})(this.event);
type: script.ScriptAction