[actiontemplatehli] Initial contribution (#12260)
* [actiontemplatehli] initial contribution Signed-off-by: Miguel Álvarez <miguelwork92@gmail.com>
This commit is contained in:
parent
11aa3207a6
commit
daea9ae5b2
|
@ -388,6 +388,7 @@
|
|||
/bundles/org.openhab.voice.googletts/ @gbicskei
|
||||
/bundles/org.openhab.voice.mactts/ @kaikreuzer
|
||||
/bundles/org.openhab.voice.marytts/ @kaikreuzer
|
||||
/bundles/org.openhab.voice.actiontemplatehli/ @GiviMAD
|
||||
/bundles/org.openhab.voice.picotts/ @FlorianSW
|
||||
/bundles/org.openhab.voice.pollytts/ @hillmanr
|
||||
/bundles/org.openhab.voice.porcupineks/ @GiviMAD
|
||||
|
|
|
@ -1941,6 +1941,11 @@
|
|||
<artifactId>org.openhab.voice.marytts</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.voice.actiontemplatehli</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.voice.picotts</artifactId>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
|
||||
== Third-party Content
|
||||
|
||||
opennlp-tools
|
||||
* License: Apache 2.0 License
|
||||
* Project: https://github.com/apache
|
||||
* Source: https://github.com/apache/opennlp
|
||||
|
||||
jackson
|
||||
* License: Apache 2.0 License
|
||||
* Project: https://github.com/FasterXML/jackson
|
||||
* Source: https://github.com/FasterXML/jackson
|
|
@ -0,0 +1,390 @@
|
|||
# Action Template Interpreter
|
||||
|
||||
A human language interpreter implementation powered by OpenNLP.
|
||||
This is an attempt to provide you with a template system to match text commands to specific items and read its state or send command to them.
|
||||
For doing this the interpreter takes advantage of some nlp techniques.
|
||||
|
||||
The (Apache OpenNLP)[https://opennlp.apache.org] library is a machine learning based toolkit for the processing of natural language text.
|
||||
This human language interpreter aims to have no language dependency as it does nothing out of the box, please report any incompatibility with your language.
|
||||
|
||||
You can find models provided by OpenNLP for some languages [here](https://opennlp.apache.org/models.html) and [here](http://opennlp.sourceforge.net/models-1.5/).
|
||||
Those are not required, as you can use the build-in white space or simple tokenizers (from OpenNLP),they are just required to use the match by lemmas and the optional language tag functionalities.
|
||||
|
||||
There are some examples at the end that you can review if you want a general idea of what can be done.
|
||||
|
||||
## NLP Terminology
|
||||
|
||||
I will briefly explain some terms that will be used:
|
||||
|
||||
* Tokenize: first step of the recognition is to split the text, each of these parts is called token.
|
||||
* Named Entity Recognition (NER): is the process of finding a subset of tokens on the input.
|
||||
* Part Of Speech (POS) tagging: categorizing tokens in a text, depending on the definition of the token and its context.
|
||||
* Lemmatize: is the process of getting a generic representation of the tokens, each of it is called lemma. (Example of one token to lemma conversion: 'is' -> 'be').
|
||||
|
||||
## Action Template Target:
|
||||
|
||||
This interpreter allows two ways to target items:
|
||||
|
||||
* You can link an action to a specific item by adding the custom metadata namespace 'actiontemplatehli' to it.
|
||||
* You can link an action to all items of a type by providing the file '<OPENHAB_USERDATA>/actiontemplatehli/type_actions/<ITEM_TYPE_NAME>.json' (here you can restrict each action by item tags).
|
||||
|
||||
Two important notes:
|
||||
|
||||
* Actions linked to items have prevalence over actions linked to a whole item type.
|
||||
* On actions linked to an item type the itemLabel placeholder will always be applied (explained bellow). If there are multiple item labels detected on the input the actions linked to both will be scored, each using the correct itemLabel value, so there should be no collisions in case a template contains a token that matches an item label.
|
||||
|
||||
## Action Template Scoring
|
||||
|
||||
The scoring mechanism scores each action configuration to look for the best scored.
|
||||
If a token fails the comparison, the action scores 0.
|
||||
If all actions scored 0, none is executed.
|
||||
|
||||
The action configuration field 'type' defines if the template should be compared using tokens or lemmas.
|
||||
Please note that the captured placeholder value is extracted from the tokens not from the lemmas, but the equivalent lemmas are replaced before scoring.
|
||||
|
||||
The action template string is a list of tokens separated by white space.
|
||||
You can use the ';' separator to provide alternative templates and the '|' to provide alternative tokens.
|
||||
Here is an example of string: "what app|application is open on $itemLabel;what app|application is on $itemLabel".
|
||||
|
||||
Take in account that, as this is a token basis comparison, matching depends on the tokenizer you are using as they can produce different tokens for the same text.
|
||||
|
||||
## Action Template Options:
|
||||
|
||||
The location where action configurations are placed changes whether you are targeting an item or many, so take a look to the 'Action Template Target' to understand where to put those configurations.
|
||||
Also, you can check the paths indicated on the examples at the end.
|
||||
|
||||
Actions can read the state from an item or send a command to it.
|
||||
This is defined by the boolean field read which is 'false' by default.
|
||||
|
||||
When read is false:
|
||||
|
||||
* template: action template. (Required)
|
||||
* value: value to be sent. It can be used to capture the transformed placeholder values. (Required unless the target item type is String, in which case silent mode is assumed to be true and the whole text is passed to the item).
|
||||
* type: action template type, either "tokens" or "lemmas".
|
||||
* requiredTags: allow to restrict the items targeted by its tags by ignoring items not having all these tags.
|
||||
* placeholders: defined placeholders that can be used on the template and replaced on the value.
|
||||
* silent: boolean used to avoid confirmation message.
|
||||
* targetMembers: when targeting a Group item, can be used to send the command to its member items instead.
|
||||
|
||||
When read is true:
|
||||
|
||||
* template: action template. (Required)
|
||||
* value: read template, can use the placeholders symbols $itemLabel and $state.
|
||||
* emptyValue: An alternative template. Is used when the state value is empty or NULL after the post transformation. The $itemLabel is available.
|
||||
* type: action template type, either "tokens" or "lemmas".
|
||||
* requiredTags: allow to restrict the items targeted by its tags by ignoring items not having all these tags.
|
||||
* placeholders: only the placeholder with label state will be used, to process its POS transformation on the state.
|
||||
* targetMembers: when targeting a Group item, can be used to access the state of one of its members. In case of multiple matches, a warning is shown and the first one is used.
|
||||
|
||||
## Placeholders
|
||||
|
||||
This configuration allow you to define symbols to use on your templates.
|
||||
You can define the sets of tokens to match using ner, and a transformation using pos.
|
||||
Those are its fields:
|
||||
|
||||
* label: label for the placeholder, is prefixed with '$' and spaces are replaced by '_' to create the symbol you can use on the template (Required).
|
||||
* nerValues: list of strings containing parts of the text to look for. Takes precedence over the ner field.
|
||||
* ner: name for a file under the ner folder (<OPENHAB_USERDATA>/actiontemplatehli/ner), first it will look for a <ner>.bin model and then for a <ner>.xml dictionary for applying ner (prevalence over 'nerValues').
|
||||
* posValues: apply a pos transformation with static values. Takes precedence over the pos field.
|
||||
* pos: name for a file under the pos folder (<OPENHAB_USERDATA>/actiontemplatehli/pos), first it will look for a <pos>.bin model and then for a <pos>.xml dictionary (prevalence over 'posValues').
|
||||
|
||||
The placeholder symbol replaces the text tokens matched using NER (before scoring the actions) and the captured value could be transformed using POS and will be accessible to the value under its symbol.
|
||||
As a summary, using the placeholders you can configure how parts of the speech are converted into valid item values and backward.
|
||||
The examples at the end of the document can help you to see it clearer.
|
||||
|
||||
There are some special placeholders:
|
||||
|
||||
### The 'itemLabel' Placeholder
|
||||
|
||||
The itemLabel placeholder is always applied when scoring actions linked to item types. It's replaced using NER (no case-sensitive) with your item labels and synonyms (collisions will be reported in debug logs). Its value is only available for read actions.
|
||||
|
||||
### The 'groupLabel' Placeholder
|
||||
|
||||
The groupLabel placeholder is only available for read actions when targeting a group member.
|
||||
When it's present, the 'itemLabel' placeholder will take the value of the target member label and the 'groupLabel' the label of the group.
|
||||
|
||||
### The 'state' Placeholder
|
||||
|
||||
It's used to access the value on the read actions, you can configure a POS transformation for it.
|
||||
|
||||
### The 'itemOption' Placeholder
|
||||
|
||||
This placeholder is available for both write and read actions and doesn't need to be configured.
|
||||
|
||||
When read is false, the 'itemOption' placeholder will be computed from the item command description options, or from the state description options if the command description options are not present.
|
||||
|
||||
When read is true, the 'itemOption' placeholder will be computed from the item state description options.
|
||||
|
||||
Note that, when targeting multiple group members, 'ner' (value matching) is done by merging all available member options but 'pos' (value transformation) is done using just the target member item options.
|
||||
|
||||
### The '*' Placeholder (the dynamic placeholder)
|
||||
|
||||
The dynamic placeholder is designed to capture free text. The captured value is exposed to the value under the symbol '$*' as other placeholders.
|
||||
It has some restrictions:
|
||||
|
||||
* Can not be the only token on the template.
|
||||
* Can not be used as an optional token.
|
||||
* Can not be used multiple times on the same template alternative.
|
||||
|
||||
Note that the dynamic placeholder does not score. This way you can use it to fallback other sentences.
|
||||
|
||||
This example can help you to understand how this works:
|
||||
|
||||
You have an action with the template "play $* on living room" and another action with the template "play $musicAuthor on living room" and assuming 'mozart' is a valid value for the placeholder '$musicAuthor'.
|
||||
|
||||
The sentence "play mozart on living room" will score 4 when compared with the template containing the dynamic placeholder and 5 when compared with the one without it.
|
||||
The action with template "play $musicAuthor on living room" will be executed.
|
||||
|
||||
The sentence "play beethoven on living room" will score 4 when compared with the template containing the dynamic placeholder and 0 when compared with the one without it.
|
||||
The action with template "play $* on living room" will be executed.
|
||||
|
||||
### POS Transformation
|
||||
|
||||
POS is a technique which produces tags for each token, here though we are going to use it to match a group of words with a value so we should transform those words to a single token.
|
||||
That's the reason why the whitespace character should be replaced by '__' in the POS dictionaries and static values, you can see some examples bellow.
|
||||
|
||||
### Target members:
|
||||
|
||||
When the target of an action is a group item, you can target its members instead.
|
||||
|
||||
You can use the following fields:
|
||||
|
||||
* itemName: name of the item member to target. If present the other fields are ignored.
|
||||
* itemType: type of the item members to target.
|
||||
* requiredTags: allow to restrict the members targeted by tags when matching by type.
|
||||
* recursive: when matching by itemType, look for group members in a recursive way, default true.
|
||||
* mergeState: on a read action when matching by itemType, merge the item states by performing an AND operation, only allowed for 'Switch' and 'Contact' item types, default false.
|
||||
|
||||
## Text Preprocessing
|
||||
|
||||
The interpreter needs to match the input text with a target item and action configuration, to know what to do.
|
||||
To do so, it needs the tokens and optionally the POS tags and the lemmas.
|
||||
|
||||
### Tokenizer
|
||||
|
||||
You can provide a custom model at '<OPENHAB_USERDATA>/actiontemplatehli/token.bin', otherwise it will use the built-in simple tokenizer or whitespace tokenizer (configurable).
|
||||
|
||||
Here you have an example of the built-in ones:
|
||||
|
||||
* Using the white space tokenizer "What time is it?" produces the tokens "what" "time" "is" "it?"
|
||||
* Using the simple tokenizer "What time is it?" produces the tokens "what" "time" "is" "it" "?"
|
||||
|
||||
Tokenizing the text is enough to use the action type 'tokens' as tokens are the only ones required for scoring (but the option 'optionalLanguageTags' will not take effect unless you have the POS language tags).
|
||||
|
||||
### POSTagger (language tags)
|
||||
|
||||
You need to provide a model for POS tagging at '<OPENHAB_USERDATA>/actiontemplatehli/pos.bin' for your language.
|
||||
This will produce a language tag for each token, that can be used in 'optionalLanguageTags' to make some optional for scoring.
|
||||
Please note that these labels may be different depending on the model, please refer to your model's documentation.
|
||||
As an example:
|
||||
|
||||
The tokens "that,sounds,good" produces the tags "DT,VBZ,JJ".
|
||||
|
||||
Assuming optionalLanguageTags is empty, if we have an action with template "sounds good" it will get a 0 score when compared to the text "that sounds good" because the token "that" is not in the template.
|
||||
|
||||
But if we set optionalLanguageTags to "DT", the action template "sounds good" will score 2 against the text "that sounds good" as the tokens with the tag "DT" are considered optional when scoring.
|
||||
|
||||
Note that if we have another action with the template "that sounds good" it will score 3 and take prevalence.
|
||||
|
||||
You need the correct language tags for the lemmatizer to work.
|
||||
|
||||
### Lemmatizer
|
||||
|
||||
You need to provide a model for the lemmatizer at '<OPENHAB_USERDATA>/actiontemplatehli/lemma.bin' for your language.
|
||||
This will produce a lemma for each token, then you can use the action type 'lemmas'.
|
||||
|
||||
Note that you need the POS language tags for your language, the ones covered on the previous section, for the lemmatizer to work.
|
||||
|
||||
## Interpreter Configuration
|
||||
|
||||
| Config | Group | Type | Default | Description |
|
||||
|-------------------------|----------|---------|----------------------|---------------------------------------------------------------------------------------------------------------|
|
||||
| lowerText | nlp | boolean | false | Convert the input text to lowercase before processing |
|
||||
| caseSensitive | nlp | boolean | false | Enable case sensitivity, do not apply to dictionaries and models, do not apply to the 'itemLabel' placeholder |
|
||||
| useSimpleTokenizer | nlp | boolean | false | Prefer simple tokenizer over white space tokenizer |
|
||||
| detokenizeOptimization | nlp | boolean | true | Enables build-in detokenization based on original text, otherwise string join by space is used |
|
||||
| optionalLanguageTags | nlp | text | | Comma separated POS language tags that will be optional when comparing |
|
||||
| commandSentMessage | messages | text | Done | Message for successful command |
|
||||
| unhandledMessage | messages | text | I can not do that | Message for unsuccessful action |
|
||||
| failureMessage | messages | text | There was an error | Message for error during processing |
|
||||
|
||||
## Examples:
|
||||
|
||||
### String type action configs example:
|
||||
|
||||
This example contains the files to add actions for opening an android application and checking what application is opened.
|
||||
These actions will target all String items with the tag 'launch_android_app'.
|
||||
|
||||
These are the files needed:
|
||||
|
||||
#### File '<OPENHAB_USERDATA>/actiontemplatehli/type_actions/String.json'
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"template": "launch|open $app on $itemLabel",
|
||||
"value": "$app",
|
||||
"type": "tokens",
|
||||
"requiredTags": ["launch_android_app"],
|
||||
"placeholders": [
|
||||
{
|
||||
"label": "app",
|
||||
"ner": "applications",
|
||||
"pos": "application_to_package"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"template": "what app|application is open on $itemLabel;what app|application is on $itemLabel",
|
||||
"read": true,
|
||||
"value": "the open app is $state",
|
||||
"emptyValue": "no app open on $itemLabel",
|
||||
"type": "tokens",
|
||||
"requiredTags": ["launch_android_app"],
|
||||
"placeholders": [
|
||||
{
|
||||
"label": "state",
|
||||
"pos": "package_to_application"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### File '<OPENHAB_USERDATA>/actiontemplatehli/ner/applications.xml'
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<dictionary case_sensitive="false">
|
||||
<entry>
|
||||
<token>youtube</token>
|
||||
</entry>
|
||||
<entry>
|
||||
<token>jellyfin</token>
|
||||
</entry>
|
||||
<entry>
|
||||
<token>amazon</token>
|
||||
<token>video</token>
|
||||
</entry>
|
||||
<entry>
|
||||
<token>netflix</token>
|
||||
</entry>
|
||||
</dictionary>
|
||||
```
|
||||
|
||||
#### File '<OPENHAB_USERDATA>/actiontemplatehli/pos/package_to_application.xml'
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<dictionary>
|
||||
<entry tags="youtube">
|
||||
<token>com.google.android.youtube</token>
|
||||
</entry>
|
||||
<entry tags="netflix">
|
||||
<token>com.netflix.ninja</token>
|
||||
</entry>
|
||||
<entry tags="jellyfin">
|
||||
<token>org.jellyfin.androidtv</token>
|
||||
</entry>
|
||||
<entry tags="amazon__video"> // note the __
|
||||
<token>com.amazon.amazonvideo.livingroom</token>
|
||||
</entry>
|
||||
</dictionary>
|
||||
```
|
||||
|
||||
#### File '<OPENHAB_USERDATA>/actiontemplatehli/pos/application_to_package.xml'
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<dictionary>
|
||||
<entry tags="com.google.android.youtube">
|
||||
<token>youtube</token>
|
||||
</entry>
|
||||
<entry tags="com.netflix.ninja">
|
||||
<token>netflix</token>
|
||||
</entry>
|
||||
<entry tags="org.jellyfin.androidtv">
|
||||
<token>jellyfin</token>
|
||||
</entry>
|
||||
<entry tags="com.amazon.amazonvideo.livingroom">
|
||||
<token>amazon__video</token> // note the __
|
||||
</entry>
|
||||
</dictionary>
|
||||
```
|
||||
|
||||
### Switch type action configs example:
|
||||
|
||||
This example contains the files to add actions for turning a switch on or off.
|
||||
These actions will target all Switch items.
|
||||
|
||||
#### File '<OPENHAB_USERDATA>/actiontemplatehli/type_actions/Switch.json'
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"template": "$onOff $itemLabel",
|
||||
"value": "$onOff",
|
||||
"type": "tokens",
|
||||
"placeholders": [
|
||||
{
|
||||
"label": "onOff",
|
||||
"nerValues": [
|
||||
"turn on",
|
||||
"turn off"
|
||||
],
|
||||
"posValues": {
|
||||
"turn__on": "ON", // note the __
|
||||
"turn__off": "OFF"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"template": "how be the $itemLabel",
|
||||
"read": true,
|
||||
"type": "lemmas",
|
||||
"value": "$itemLabel is $state"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Switch item action configs example:
|
||||
|
||||
This example contains the item metadata to add an action to an item with type 'Switch''.
|
||||
Add a custom metadata 'actiontemplatehli' to the 'Switch' item with the following:
|
||||
|
||||
```yaml
|
||||
value: ""
|
||||
config:
|
||||
placeholders:
|
||||
- label: onOff
|
||||
nerValues:
|
||||
- turn on
|
||||
- turn off
|
||||
posValues:
|
||||
turn__on: ON
|
||||
turn__off: OFF
|
||||
template: $onOff $itemLabel
|
||||
type: tokens
|
||||
value: $onOff
|
||||
```
|
||||
|
||||
### Dynamic placeholder, sending a message example:
|
||||
|
||||
This example contains the item metadata to add an action that uses the dynamic placeholder.
|
||||
Add a custom metadata 'actiontemplatehli' to a String item with the following:
|
||||
|
||||
```yaml
|
||||
value: ""
|
||||
config:
|
||||
placeholders:
|
||||
- label: contact
|
||||
nerValues:
|
||||
- Andrea
|
||||
- Jacob
|
||||
- Raquel
|
||||
silent: true
|
||||
template: send message $* to $contact
|
||||
type: tokens
|
||||
value: $contact:$*
|
||||
```
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.voice.actiontemplatehli</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Voice :: Action Template Interpreter</name>
|
||||
|
||||
<properties>
|
||||
<dep.noembedding>jackson-core,jackson-annotations,jackson-databind</dep.noembedding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.opennlp/opennlp -->
|
||||
<dependency>
|
||||
<groupId>org.apache.opennlp</groupId>
|
||||
<artifactId>opennlp-tools</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.voice.actiontemplatehli-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-voice-actiontemplatehli" description="Action Template Interpreter" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.voice.actiontemplatehli/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.actiontemplatehli.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ActionTemplateInterpreterConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Miguel Álvarez - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ActionTemplateInterpreterConfiguration {
|
||||
/**
|
||||
* Convert the input text to lower case before processing
|
||||
*/
|
||||
public boolean lowerText = false;
|
||||
/**
|
||||
* Enable case sensitivity for pos and ner static values.
|
||||
*/
|
||||
public boolean caseSensitive = false;
|
||||
/**
|
||||
* Message for successful command
|
||||
*/
|
||||
public String commandSentMessage = "Done";
|
||||
/**
|
||||
* Message for unsuccessful processing
|
||||
*/
|
||||
public String unhandledMessage = "I can not do that";
|
||||
/**
|
||||
* Message for error during processing
|
||||
*/
|
||||
public String failureMessage = "There was an error";
|
||||
/**
|
||||
* POS tags that will be optional when comparing
|
||||
*/
|
||||
public String optionalLanguageTags = "";
|
||||
/**
|
||||
* Prefer simple tokenizer over white space tokenizer
|
||||
*/
|
||||
public boolean useSimpleTokenizer = false;
|
||||
/**
|
||||
* Enables build-in detokenization based on original text, otherwise string join by space is used
|
||||
*/
|
||||
public boolean detokenizeOptimization = true;
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.actiontemplatehli.internal;
|
||||
|
||||
import static org.openhab.voice.actiontemplatehli.internal.ActionTemplateInterpreter.getPlaceholderSymbol;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.OpenHAB;
|
||||
|
||||
/**
|
||||
* The {@link ActionTemplateInterpreterConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Miguel Álvarez - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ActionTemplateInterpreterConstants {
|
||||
/**
|
||||
* Service name
|
||||
*/
|
||||
public static final String SERVICE_NAME = "Action Template Interpreter";
|
||||
/**
|
||||
* Service id
|
||||
*/
|
||||
public static final String SERVICE_ID = "actiontemplatehli";
|
||||
|
||||
/**
|
||||
* Service category
|
||||
*/
|
||||
public static final String SERVICE_CATEGORY = "voice";
|
||||
/**
|
||||
* Service pid
|
||||
*/
|
||||
public static final String SERVICE_PID = "org.openhab." + SERVICE_CATEGORY + "." + SERVICE_ID;
|
||||
/**
|
||||
* Root service folder
|
||||
*/
|
||||
public static final String NLP_FOLDER = Path.of(OpenHAB.getUserDataFolder(), "actiontemplatehli").toString();
|
||||
/**
|
||||
* NER folder for dictionaries and models
|
||||
*/
|
||||
public static final String NER_FOLDER = Path.of(NLP_FOLDER, "ner").toString();
|
||||
/**
|
||||
* POS folder for dictionaries and models
|
||||
*/
|
||||
public static final String POS_FOLDER = Path.of(NLP_FOLDER, "pos").toString();
|
||||
/**
|
||||
* Folder for type action configurations
|
||||
*/
|
||||
public static final String TYPE_ACTION_CONFIGS_FOLDER = Path.of(NLP_FOLDER, "type_actions").toString();
|
||||
/**
|
||||
* ItemLabel placeholder name
|
||||
*/
|
||||
public static final String ITEM_LABEL_PLACEHOLDER = "itemLabel";
|
||||
/**
|
||||
* ItemLabel placeholder symbol
|
||||
*/
|
||||
public static final String ITEM_LABEL_PLACEHOLDER_SYMBOL = getPlaceholderSymbol(ITEM_LABEL_PLACEHOLDER);
|
||||
/**
|
||||
* State placeholder name
|
||||
*/
|
||||
public static final String STATE_PLACEHOLDER = "state";
|
||||
/**
|
||||
* State placeholder symbol
|
||||
*/
|
||||
public static final String STATE_PLACEHOLDER_SYMBOL = getPlaceholderSymbol(STATE_PLACEHOLDER);
|
||||
/**
|
||||
* Item option placeholder name
|
||||
*/
|
||||
public static final String ITEM_OPTION_PLACEHOLDER = "itemOption";
|
||||
/**
|
||||
* State placeholder symbol
|
||||
*/
|
||||
public static final String ITEM_OPTION_PLACEHOLDER_SYMBOL = getPlaceholderSymbol(ITEM_OPTION_PLACEHOLDER);
|
||||
/**
|
||||
* Dynamic placeholder name
|
||||
*/
|
||||
public static final String DYNAMIC_PLACEHOLDER = "*";
|
||||
/**
|
||||
* Dynamic placeholder symbol
|
||||
*/
|
||||
public static final String DYNAMIC_PLACEHOLDER_SYMBOL = getPlaceholderSymbol(DYNAMIC_PLACEHOLDER);
|
||||
/**
|
||||
* GroupLabel placeholder name
|
||||
*/
|
||||
public static final String GROUP_LABEL_PLACEHOLDER = "groupLabel";
|
||||
/**
|
||||
* GroupLabel placeholder symbol
|
||||
*/
|
||||
public static final String GROUP_LABEL_PLACEHOLDER_SYMBOL = getPlaceholderSymbol(GROUP_LABEL_PLACEHOLDER);
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.actiontemplatehli.internal.configuration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.items.Metadata;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* The {@link ActionTemplateConfiguration} class represents each configured action
|
||||
*
|
||||
* @author Miguel Álvarez - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ActionTemplateConfiguration {
|
||||
@JsonProperty("type")
|
||||
public String type = "tokens";
|
||||
@JsonProperty("read")
|
||||
public boolean read = false;
|
||||
@JsonProperty(value = "template", required = true)
|
||||
public String template = "";
|
||||
@JsonProperty("value")
|
||||
public @Nullable Object value = null;
|
||||
@JsonProperty("emptyValue")
|
||||
public String emptyValue = "";
|
||||
@JsonProperty("placeholders")
|
||||
public List<ActionTemplatePlaceholder> placeholders = List.of();
|
||||
@JsonProperty("requiredTags")
|
||||
public String[] requiredItemTags = new String[] {};
|
||||
@JsonProperty("silent")
|
||||
public boolean silent = false;
|
||||
@JsonProperty("memberTargets")
|
||||
public @Nullable ActionTemplateGroupTargets memberTargets = null;
|
||||
|
||||
public static ActionTemplateConfiguration[] fromMetadata(Metadata metadata) throws JsonProcessingException {
|
||||
var configuration = metadata.getConfiguration();
|
||||
var multipleValues = configuration.get("multiple");
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
if (multipleValues != null) {
|
||||
return mapper.readValue(mapper.writeValueAsString(multipleValues), ActionTemplateConfiguration[].class);
|
||||
} else {
|
||||
var actionConfig = mapper.readValue(mapper.writeValueAsString(configuration),
|
||||
ActionTemplateConfiguration.class);
|
||||
return new ActionTemplateConfiguration[] { actionConfig };
|
||||
}
|
||||
}
|
||||
|
||||
public static ActionTemplateConfiguration[] fromJSON(File jsonFile) throws IOException {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
return mapper.readValue(jsonFile, ActionTemplateConfiguration[].class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.actiontemplatehli.internal.configuration;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* The {@link ActionTemplateGroupTargets} class filters the item targets when targeting an item group.
|
||||
*
|
||||
* @author Miguel Álvarez - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ActionTemplateGroupTargets {
|
||||
@JsonProperty("itemName")
|
||||
public String itemName = "";
|
||||
@JsonProperty("itemType")
|
||||
public String itemType = "";
|
||||
@JsonProperty("requiredTags")
|
||||
public String[] requiredItemTags = new String[] {};
|
||||
@JsonProperty("mergeState")
|
||||
public boolean mergeState = false;
|
||||
@JsonProperty("recursive")
|
||||
public boolean recursive = true;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.actiontemplatehli.internal.configuration;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* The {@link ActionTemplatePlaceholder} class configures placeholders for the action template
|
||||
*
|
||||
* @author Miguel Álvarez - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ActionTemplatePlaceholder {
|
||||
@JsonProperty(value = "label", required = true)
|
||||
public String label = "";
|
||||
@JsonProperty("ner")
|
||||
public @Nullable String nerFile = null;
|
||||
@JsonProperty("nerValues")
|
||||
public String @Nullable [] nerStaticValues = null;
|
||||
@JsonProperty("pos")
|
||||
public @Nullable String posFile = null;
|
||||
@JsonProperty("posValues")
|
||||
public @Nullable Map<String, String> posStaticValues = null;
|
||||
|
||||
public static ActionTemplatePlaceholder withLabel(String label) {
|
||||
var placeholder = new ActionTemplatePlaceholder();
|
||||
placeholder.label = label;
|
||||
return placeholder;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
|
||||
https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
<config-description uri="voice:actiontemplatehli">
|
||||
<parameter-group name="nlp">
|
||||
<label>Neural Language Processing</label>
|
||||
<description>Configure natural language processing.</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="messages">
|
||||
<label>Response Messages</label>
|
||||
<description>Configure interpreter responses.</description>
|
||||
</parameter-group>
|
||||
<parameter name="lowerText" type="boolean" groupName="nlp">
|
||||
<label>Lower Text</label>
|
||||
<description>Convert the input text to lowercase before processing.</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="caseSensitive" type="boolean" groupName="nlp">
|
||||
<label>Case Sensitive</label>
|
||||
<description>Enable case sensitivity, do not apply to dictionaries and models, do not apply to the 'itemLabel'
|
||||
placeholder.</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="useSimpleTokenizer" type="boolean" groupName="nlp">
|
||||
<label>Use Simple Tokenizer</label>
|
||||
<description>Prefer simple tokenizer over white space tokenizer.</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="detokenizeOptimization" type="boolean" groupName="nlp">
|
||||
<label>Detokenize Optimization</label>
|
||||
<description>Enables build-in detokenization based on original text, otherwise string join by space is used.</description>
|
||||
<default>true</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="optionalLanguageTags" type="text" groupName="nlp">
|
||||
<label>Optional Language Tags</label>
|
||||
<description>Comma separated POS language tags that will be optional when comparing.</description>
|
||||
</parameter>
|
||||
<parameter name="commandSentMessage" type="text" groupName="messages">
|
||||
<label>Command Sent Message</label>
|
||||
<description>Message for successful command.</description>
|
||||
<default>Done</default>
|
||||
</parameter>
|
||||
<parameter name="unhandledMessage" type="text" groupName="messages">
|
||||
<label>Unhandled Message</label>
|
||||
<description>Message for unsuccessful processing.</description>
|
||||
<default>I can not do that</default>
|
||||
</parameter>
|
||||
<parameter name="failureMessage" type="text" groupName="messages">
|
||||
<label>Failure Message</label>
|
||||
<description>Message for error during processing.</description>
|
||||
<default>There was an error</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
|
@ -0,0 +1,24 @@
|
|||
voice.config.actiontemplatehli.caseSensitive.label = Case Sensitive
|
||||
voice.config.actiontemplatehli.caseSensitive.description = Enable case sensitivity, do not apply to dictionaries and models, do not apply to the 'itemLabel' placeholder.
|
||||
voice.config.actiontemplatehli.commandSentMessage.label = Command Sent Message
|
||||
voice.config.actiontemplatehli.commandSentMessage.description = Message for successful command.
|
||||
voice.config.actiontemplatehli.detokenizeOptimization.label = Detokenize Optimization
|
||||
voice.config.actiontemplatehli.detokenizeOptimization.description = Enables build-in detokenization based on original text, otherwise string join by space is used.
|
||||
voice.config.actiontemplatehli.failureMessage.label = Failure Message
|
||||
voice.config.actiontemplatehli.failureMessage.description = Message for error during processing.
|
||||
voice.config.actiontemplatehli.group.messages.label = Response Messages
|
||||
voice.config.actiontemplatehli.group.messages.description = Configure interpreter responses.
|
||||
voice.config.actiontemplatehli.group.nlp.label = Neural Language Processing
|
||||
voice.config.actiontemplatehli.group.nlp.description = Configure natural language processing.
|
||||
voice.config.actiontemplatehli.lowerText.label = Lower Text
|
||||
voice.config.actiontemplatehli.lowerText.description = Convert the input text to lowercase before processing.
|
||||
voice.config.actiontemplatehli.optionalLanguageTags.label = Optional Language Tags
|
||||
voice.config.actiontemplatehli.optionalLanguageTags.description = Comma separated POS language tags that will be optional when comparing.
|
||||
voice.config.actiontemplatehli.unhandledMessage.label = Unhandled Message
|
||||
voice.config.actiontemplatehli.unhandledMessage.description = Message for unsuccessful processing.
|
||||
voice.config.actiontemplatehli.useSimpleTokenizer.label = Use Simple Tokenizer
|
||||
voice.config.actiontemplatehli.useSimpleTokenizer.description = Prefer simple tokenizer over white space tokenizer.
|
||||
|
||||
# service
|
||||
|
||||
service.voice.actiontemplatehli.label = Action Template Interpreter
|
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.voice.actiontemplatehli.internal;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.openhab.voice.actiontemplatehli.internal.ActionTemplateInterpreterConstants.SERVICE_ID;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.Metadata;
|
||||
import org.openhab.core.items.MetadataKey;
|
||||
import org.openhab.core.items.MetadataRegistry;
|
||||
import org.openhab.core.items.events.ItemEventFactory;
|
||||
import org.openhab.core.library.items.NumberItem;
|
||||
import org.openhab.core.library.items.StringItem;
|
||||
import org.openhab.core.library.items.SwitchItem;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.openhab.core.voice.text.InterpretationException;
|
||||
import org.openhab.voice.actiontemplatehli.internal.configuration.ActionTemplateConfiguration;
|
||||
import org.openhab.voice.actiontemplatehli.internal.configuration.ActionTemplateGroupTargets;
|
||||
import org.openhab.voice.actiontemplatehli.internal.configuration.ActionTemplatePlaceholder;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* The {@link ActionTemplateInterpreterTest} class contains the tests for the interpreter
|
||||
*
|
||||
* @author Miguel Álvarez - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ActionTemplateInterpreterTest {
|
||||
private @Mock @NonNullByDefault({}) ItemRegistry itemRegistryMock;
|
||||
private @Mock @NonNullByDefault({}) MetadataRegistry metadataRegistryMock;
|
||||
private @Mock @NonNullByDefault({}) EventPublisher eventPublisherMock;
|
||||
private @NonNullByDefault({}) ActionTemplateInterpreter interpreter;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws IOException {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
// Prepare Switch
|
||||
var switchItem = new SwitchItem("testSwitch");
|
||||
switchItem.setState(OnOffType.OFF);
|
||||
switchItem.setLabel("bedroom light");
|
||||
switchItem.addTag("Light");
|
||||
Mockito.when(itemRegistryMock.get(switchItem.getName())).thenReturn(switchItem);
|
||||
// Prepare Switch Write action
|
||||
var switchNPLWriteAction = new ActionTemplateConfiguration();
|
||||
switchNPLWriteAction.template = "$onOff $itemLabel";
|
||||
switchNPLWriteAction.value = "$onOff";
|
||||
var onOffPlaceholder = new ActionTemplatePlaceholder();
|
||||
onOffPlaceholder.label = "onOff";
|
||||
onOffPlaceholder.nerStaticValues = new String[] { "turn on", "turn off" };
|
||||
onOffPlaceholder.posStaticValues = Map.of("turn__on", "ON", "turn__off", "OFF");
|
||||
switchNPLWriteAction.placeholders = List.of(onOffPlaceholder);
|
||||
// Prepare Switch Read action
|
||||
var switchNPLReadAction = new ActionTemplateConfiguration();
|
||||
switchNPLReadAction.read = true;
|
||||
switchNPLReadAction.template = "how is the $itemLabel";
|
||||
switchNPLReadAction.value = "$itemLabel is $state";
|
||||
// Prepare Group
|
||||
var groupItem = new GroupItem("testGroup");
|
||||
groupItem.setLabel("bedroom");
|
||||
groupItem.addTag("Location");
|
||||
Mockito.when(itemRegistryMock.get(groupItem.getName())).thenReturn(groupItem);
|
||||
// TV channel
|
||||
var numberItem = new NumberItem("testNumber");
|
||||
numberItem.setState(DecimalType.valueOf("1"));
|
||||
numberItem.setLabel("channel");
|
||||
numberItem.addTag("tv_channel");
|
||||
numberItem
|
||||
.setStateDescriptionService((text, locale) -> StateDescriptionFragmentBuilder
|
||||
.create().withOptions(List.of(new StateOption("1", "channel one"),
|
||||
new StateOption("2", "channel two"), new StateOption("3", "channel three")))
|
||||
.build().toStateDescription());
|
||||
Mockito.when(itemRegistryMock.get(numberItem.getName())).thenReturn(numberItem);
|
||||
// Prepare Group Write action
|
||||
var groupNPLWriteAction = new ActionTemplateConfiguration();
|
||||
groupNPLWriteAction.template = "turn on $itemLabel lights";
|
||||
groupNPLWriteAction.requiredItemTags = new String[] { "Location" };
|
||||
groupNPLWriteAction.value = "ON";
|
||||
groupNPLWriteAction.memberTargets = new ActionTemplateGroupTargets();
|
||||
groupNPLWriteAction.memberTargets.itemType = "Switch";
|
||||
groupNPLWriteAction.memberTargets.requiredItemTags = new String[] { "Light" };
|
||||
// Prepare Group Read action
|
||||
var groupNPLReadAction = new ActionTemplateConfiguration();
|
||||
groupNPLReadAction.read = true;
|
||||
groupNPLReadAction.requiredItemTags = new String[] { "Location" };
|
||||
groupNPLReadAction.template = "how is the light in the $itemLabel";
|
||||
groupNPLReadAction.value = "$itemLabel in $groupLabel is $state";
|
||||
var statePlaceholder = new ActionTemplatePlaceholder();
|
||||
statePlaceholder.label = "state";
|
||||
statePlaceholder.posStaticValues = Map.of("ON", "on", "OFF", "off");
|
||||
groupNPLReadAction.placeholders = List.of(statePlaceholder);
|
||||
groupNPLReadAction.memberTargets = new ActionTemplateGroupTargets();
|
||||
groupNPLReadAction.memberTargets.itemName = switchItem.getName();
|
||||
groupNPLReadAction.memberTargets.requiredItemTags = new String[] { "Light" };
|
||||
// Prepare group write action using item option
|
||||
var groupNPLOptionWriteAction = new ActionTemplateConfiguration();
|
||||
groupNPLOptionWriteAction.template = "set $itemLabel channel to $itemOption";
|
||||
groupNPLOptionWriteAction.requiredItemTags = new String[] { "Location" };
|
||||
groupNPLOptionWriteAction.value = "$itemOption";
|
||||
groupNPLOptionWriteAction.memberTargets = new ActionTemplateGroupTargets();
|
||||
groupNPLOptionWriteAction.memberTargets.itemType = "Number";
|
||||
groupNPLOptionWriteAction.memberTargets.requiredItemTags = new String[] { "tv_channel" };
|
||||
// Prepare group read action using item option
|
||||
var groupNPLOptionReadAction = new ActionTemplateConfiguration();
|
||||
groupNPLOptionReadAction.read = true;
|
||||
groupNPLOptionReadAction.requiredItemTags = new String[] { "Location" };
|
||||
groupNPLOptionReadAction.template = "what channel is on the $itemLabel tv";
|
||||
groupNPLOptionReadAction.value = "$groupLabel tv is on $itemOption";
|
||||
groupNPLOptionReadAction.memberTargets = new ActionTemplateGroupTargets();
|
||||
groupNPLOptionReadAction.memberTargets.itemType = "Number";
|
||||
groupNPLOptionReadAction.memberTargets.requiredItemTags = new String[] { "tv_channel" };
|
||||
// Add switch member to group
|
||||
groupItem.addMember(switchItem);
|
||||
// Add number member to group
|
||||
groupItem.addMember(numberItem);
|
||||
// Prepare string
|
||||
var stringItem = new StringItem("testString");
|
||||
stringItem.setLabel("message example");
|
||||
Mockito.when(itemRegistryMock.get(stringItem.getName())).thenReturn(stringItem);
|
||||
// Prepare string write action
|
||||
var stringNPLWriteAction = new ActionTemplateConfiguration();
|
||||
stringNPLWriteAction.template = "send message $* to $contact";
|
||||
stringNPLWriteAction.value = "$contact:$*";
|
||||
stringNPLWriteAction.silent = true;
|
||||
var contactPlaceholder = new ActionTemplatePlaceholder();
|
||||
contactPlaceholder.label = "contact";
|
||||
contactPlaceholder.nerStaticValues = new String[] { "Mark", "Andrea" };
|
||||
contactPlaceholder.posStaticValues = Map.of("Mark", "+34000000000", "Andrea", "+34000000001");
|
||||
stringNPLWriteAction.placeholders = List.of(contactPlaceholder);
|
||||
var stringConfig = mapper.readValue(mapper.writeValueAsString(stringNPLWriteAction), Map.class);
|
||||
// Mock metadata for 'testString'
|
||||
Mockito.when(metadataRegistryMock.get(new MetadataKey(SERVICE_ID, stringItem.getName())))
|
||||
.thenReturn(new Metadata(new MetadataKey(SERVICE_ID, stringItem.getName()), "", stringConfig));
|
||||
// Mock items
|
||||
Mockito.when(itemRegistryMock.getAll()).thenReturn(List.of(switchItem, stringItem, groupItem, numberItem));
|
||||
|
||||
interpreter = new ActionTemplateInterpreter(itemRegistryMock, metadataRegistryMock, eventPublisherMock) {
|
||||
@Override
|
||||
protected ActionTemplateConfiguration[] getTypeActionConfigs(String itemType) {
|
||||
// mock type actions for testing
|
||||
if ("Switch".equals(itemType)) {
|
||||
return new ActionTemplateConfiguration[] { switchNPLWriteAction, switchNPLReadAction };
|
||||
}
|
||||
if ("Group".equals(itemType)) {
|
||||
return new ActionTemplateConfiguration[] { groupNPLWriteAction, groupNPLReadAction,
|
||||
groupNPLOptionReadAction, groupNPLOptionWriteAction };
|
||||
}
|
||||
return new ActionTemplateConfiguration[] {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Test type write action
|
||||
*/
|
||||
@Test
|
||||
public void switchItemOnOffTest() throws InterpretationException {
|
||||
var response = interpreter.interpret(Locale.ENGLISH, "turn on bedroom light");
|
||||
assertThat(response, is("Done"));
|
||||
Mockito.verify(eventPublisherMock).post(ItemEventFactory.createCommandEvent("testSwitch", OnOffType.ON));
|
||||
response = interpreter.interpret(Locale.ENGLISH, "turn off bedroom light");
|
||||
assertThat(response, is("Done"));
|
||||
Mockito.verify(eventPublisherMock).post(ItemEventFactory.createCommandEvent("testSwitch", OnOffType.OFF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test type read action
|
||||
*/
|
||||
@Test
|
||||
public void switchItemReadTest() throws InterpretationException {
|
||||
var response = interpreter.interpret(Locale.ENGLISH, "how is the bedroom light");
|
||||
assertThat(response, is("bedroom light is OFF"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test group write action targeting members
|
||||
*/
|
||||
@Test
|
||||
public void groupItemMemberOnTest() throws InterpretationException {
|
||||
var response = interpreter.interpret(Locale.ENGLISH, "turn on bedroom lights");
|
||||
assertThat(response, is("Done"));
|
||||
Mockito.verify(eventPublisherMock).post(ItemEventFactory.createCommandEvent("testSwitch", OnOffType.ON));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test group read action targeting members
|
||||
*/
|
||||
@Test
|
||||
public void groupItemMemberReadTest() throws InterpretationException {
|
||||
var response = interpreter.interpret(Locale.ENGLISH, "how is the light in the bedroom");
|
||||
assertThat(response, is("bedroom light in bedroom is off"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test target a group member item using the itemOption placeholder
|
||||
*/
|
||||
@Test
|
||||
public void groupItemOptionTest() throws InterpretationException {
|
||||
var response = interpreter.interpret(Locale.ENGLISH, "what channel is on the bedroom tv");
|
||||
assertThat(response, is("bedroom tv is on channel one"));
|
||||
response = interpreter.interpret(Locale.ENGLISH, "set bedroom channel to channel two");
|
||||
assertThat(response, is("Done"));
|
||||
Mockito.verify(eventPublisherMock)
|
||||
.post(ItemEventFactory.createCommandEvent("testNumber", new DecimalType("2")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test write action using the dynamic label
|
||||
*/
|
||||
@Test
|
||||
public void messageTest() throws InterpretationException {
|
||||
var response = interpreter.interpret(Locale.ENGLISH, "send message please turn off the bedroom light to mark");
|
||||
// silent mode is enabled so no response
|
||||
assertThat(response, is(""));
|
||||
Mockito.verify(eventPublisherMock).post(ItemEventFactory.createCommandEvent("testString",
|
||||
new StringType("+34000000000:please turn off the bedroom light")));
|
||||
}
|
||||
}
|
|
@ -408,6 +408,7 @@
|
|||
<module>org.openhab.voice.googletts</module>
|
||||
<module>org.openhab.voice.mactts</module>
|
||||
<module>org.openhab.voice.marytts</module>
|
||||
<module>org.openhab.voice.actiontemplatehli</module>
|
||||
<module>org.openhab.voice.picotts</module>
|
||||
<module>org.openhab.voice.pollytts</module>
|
||||
<module>org.openhab.voice.porcupineks</module>
|
||||
|
|
Loading…
Reference in New Issue