package org.openhab.domain.command;
import org.openhab.domain.IOpenHABWidgetControl;
import org.openhab.domain.IOpenHABWidgetProvider;
import org.openhab.domain.IPopularNameProvider;
import org.openhab.domain.IRoomProvider;
import org.openhab.domain.ITextToSpeechProvider;
import org.openhab.domain.OpenHABWidgetProvider;
import org.openhab.domain.model.ApplicationMode;
import org.openhab.domain.model.OpenHABItemType;
import org.openhab.domain.model.OpenHABWidget;
import org.openhab.domain.model.OpenHABWidgetType;
import org.openhab.domain.model.Room;
import org.openhab.domain.util.ILogger;
import org.openhab.domain.util.IRegularExpression;
import org.openhab.domain.util.StringHandler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import static org.openhab.domain.command.OpenHABWidgetCommandType.GetStatus;
import static org.openhab.domain.command.OpenHABWidgetCommandType.values;
/**
* Created by Tony Alpskog in 2014.
*/
public class CommandAnalyzer implements ICommandAnalyzer {
private static final String TAG = "CommandAnalyzer";
private final IRoomProvider mRoomProvider;
private final IOpenHABWidgetProvider mOpenHABWidgetProvider;
private final IOpenHABWidgetControl mWidgetControl;
private final IRegularExpression mRegularExpression;
private final IPopularNameProvider mPopularNameProvider;
private final ICommandPhrasesProvider mCommandPhrasesProvider;
private final ILogger mLogger;
private final ICommandColorProvider mCommandColorProvider;
protected ITextToSpeechProvider mTextToSpeechProvider;
protected Map<String, List<OpenHABWidgetType>> mWidgetTypeTagMapping = new HashMap<String, List<OpenHABWidgetType>>();
protected Map<String, List<OpenHABItemType>> mItemTypeTagMapping = new HashMap<String, List<OpenHABItemType>>();
protected Map<String, String> mCommandTagsRegex = new HashMap<String, String>();
private OnShowRoomListener mOnShowRoomListener;
@Inject
public CommandAnalyzer(IRoomProvider roomProvider,
OpenHABWidgetProvider openHABWidgetProvider,
IOpenHABWidgetControl widgetControl,
IRegularExpression regularExpression,
IPopularNameProvider popularNameProvider,
ICommandPhrasesProvider commandPhrasesProvider,
ILogger logger,
ICommandColorProvider commandColorProvider) {
mRoomProvider = roomProvider;
mOpenHABWidgetProvider = openHABWidgetProvider;
mWidgetControl = widgetControl;
mRegularExpression = regularExpression;
mPopularNameProvider = popularNameProvider;
mCommandPhrasesProvider = commandPhrasesProvider;
mLogger = logger;
mCommandColorProvider = commandColorProvider;
initializeWidgetTypeTagMapping();
initializeCommandTagMapping();
}
@Override
public SpeechAnalyzerResult executeSpeech(ArrayList<String> speechResult, ApplicationMode applicationMode) {
return SpeechAnalyzerResult.ContinueListening;
}
@Override
public SpeechAnalyzerResult analyzeRoomNavigation(ArrayList<String> speechResult, ApplicationMode applicationMode) {
// Fill the list view with the strings the recognizer thought it
// could have heard
List<Room> roomList = new ArrayList<Room>();
if(mRoomProvider != null)
roomList.addAll(mRoomProvider.getAllRooms());
Iterator<Room> iterator = roomList.iterator();
Map<String, Room> roomNameMap = new HashMap<String, Room>();
while (iterator.hasNext()) {
Room nextRoom = iterator.next();
roomNameMap.put(nextRoom.getName().toUpperCase(), nextRoom);
}
if(applicationMode == ApplicationMode.RoomFlipper) {
for(String match : speechResult) {
if(roomNameMap.keySet().contains(match.toUpperCase())) {
//Got a speech match.
Room roomToShow = roomNameMap.get(match.toUpperCase());
mLogger.d(TAG, "showRoom() - Show room<" + roomToShow.getId() + ">");
if(mOnShowRoomListener != null)
mOnShowRoomListener.onShowRoom(roomToShow);
if(mTextToSpeechProvider != null)
mTextToSpeechProvider.speakText(roomToShow.getName());
else
mLogger.e(TAG, "TextToSpeechProvider is NULL!");
}
}
}
for(String match : speechResult) {
if("hej då huset".equalsIgnoreCase(match)) {
//End HAB application
mLogger.d(TAG, "Got a match: '" + match + "'");
break;
}
}
return SpeechAnalyzerResult.ContinueListening;
}
@Override
public CommandAnalyzerResult analyzeCommand(List<String> commandPhrases, ApplicationMode applicationMode) {
List<CommandPhraseMatchResult> commandMatchResultList = getCommandsFromPhrases(commandPhrases);
Map<CommandPhraseMatchResult, WidgetPhraseMatchResult> unitMatchResult = getHighestWidgetsFromCommandMatchResult(commandMatchResultList);
if(unitMatchResult.size() == 0)
return null;
int topScore = 0;
CommandPhraseMatchResult bestKeySoFar = null;
for (CommandPhraseMatchResult cpmr : unitMatchResult.keySet()) {
if(unitMatchResult.get(cpmr) == null)
continue;
int currentScore = (cpmr.getPoint() * 30) + unitMatchResult.get(cpmr).getMatchPercent();
if (currentScore > topScore) {
topScore = currentScore;
bestKeySoFar = cpmr;
}
}
final OpenHABWidgetCommandType commandType = bestKeySoFar != null ? bestKeySoFar.getCommandType() : null;
String commandReply = null;
if(bestKeySoFar == null)
return null;
if(bestKeySoFar.getCommandType() != GetStatus) {
String value = getCommandValue(bestKeySoFar);
if(value != null) {
mWidgetControl.sendItemCommand(unitMatchResult.get(bestKeySoFar).getWidget(), value);
commandReply = value;
}
}
if (commandReply != null)
return new CommandAnalyzerResult(null, unitMatchResult.get(bestKeySoFar).getWidget(), topScore, commandReply, commandType);
else
return new CommandAnalyzerResult(null, unitMatchResult.get(bestKeySoFar).getWidget(), topScore, unitMatchResult.get(bestKeySoFar).getWidget().getLabelValue(), commandType);
}
@Override
public void setOnShowRoomListener(OnShowRoomListener listener) {
mOnShowRoomListener = listener;
}
public String getCommandValue(CommandPhraseMatchResult commandPhraseMatchResult) {
String valueTypeToLookFor;
switch (commandPhraseMatchResult.getCommandType()) {
case AdjustSetpoint: valueTypeToLookFor = "<decimal>";
break;
case SliderSetPercentage: valueTypeToLookFor = "<integer>";
break;
case SwitchOn: return "ON";
case SwitchOff: return "OFF";
case RollerShutterDown: return "DOWN";
case RollerShutterUp: return "UP";
default: return null;
//TODO - Extend this list of supported item type commands
}
for(int i = 0; i < commandPhraseMatchResult.getTags().length; i++ ) {
if(commandPhraseMatchResult.getTags()[i].equalsIgnoreCase(valueTypeToLookFor))
return commandPhraseMatchResult.getTagPhrases()[i];
}
return null;
}
public String getCommandReply(CommandAnalyzerResult commandAnalyzerResult) {
String result;
if(commandAnalyzerResult.getCommandType() == GetStatus) {
result = mPopularNameProvider.getPopularNameFromWidgetLabel(commandAnalyzerResult.getOpenHABWidget().getLabel()) + " is " + commandAnalyzerResult.getOpenHABItemState();//TODO add language support
} else {
result = mPopularNameProvider.getPopularNameFromWidgetLabel(commandAnalyzerResult.getOpenHABWidget().getLabel()) + " was set to " + commandAnalyzerResult.getOpenHABItemState();//TODO add language support
}
return result;
}
protected List<Room> getRoomsFromPhrases(ArrayList<String> speechResult, ApplicationMode applicationMode) {
List<Room> resultList = new ArrayList<Room>();
Map<String, Room> roomNameMap = mRoomProvider.getMapOfRoomNames();
if(applicationMode == ApplicationMode.RoomFlipper) {
for(String roomName : roomNameMap.keySet()) {
for (String match : speechResult) {
if(match.toUpperCase().contains(roomName.toUpperCase()) && !resultList.contains(roomNameMap.get(roomName.toUpperCase())))
{
//Got a room match.
Room foundRoom = roomNameMap.get(roomName.toUpperCase());
resultList.add(foundRoom);
//Log.d(HABApplication.getLogTag(), "Found room in command phrase <" + foundRoom.getName() + ">");
}
}
}
}
return resultList;
}
protected List<OpenHABWidget> getUnitsFromPhrases(List<String> commandPhrases, List<Room> listOfRooms) {
List<OpenHABWidget> resultList = new ArrayList<OpenHABWidget>();
// Fill the list view with the strings the recognizer thought it
// could have heard
List<OpenHABWidget> widgetList = mOpenHABWidgetProvider.getListOfWidgetsFromListOfRooms(listOfRooms);
if(widgetList.size() == 0) {
widgetList = mOpenHABWidgetProvider.getWidgetList((Set<OpenHABWidgetType>) null);
}
//Create widget name Map
Iterator<OpenHABWidget> iterator = widgetList.iterator();
Map<String, OpenHABWidget> widgetNameMap = new HashMap<String, OpenHABWidget>();
while (iterator.hasNext()) {
OpenHABWidget nextWidget = iterator.next();
widgetNameMap.put(nextWidget.getLabel().toUpperCase(), nextWidget);
}
//Look for match
for(String match : commandPhrases) {
if(widgetNameMap.keySet().contains(match.toUpperCase())) {
//Got a unit match.
OpenHABWidget foundWidget = widgetNameMap.get(match.toUpperCase());
resultList.add(foundWidget);
mLogger.d(TAG, "Found unit in command phrase <" + foundWidget.getLabel() + ">");
}
}
return resultList;
}
protected List<OpenHABWidget> getUnitsFromPhrases2(List<String> commandPhrases) {
List<OpenHABWidget> resultList = new ArrayList<OpenHABWidget>();
List<OpenHABWidget> widgetList = mOpenHABWidgetProvider.getWidgetList((Set<OpenHABWidgetType>) null);
Iterator<OpenHABWidget> iterator = widgetList.iterator();
Map<String, OpenHABWidget> widgetNameMap = new HashMap<String, OpenHABWidget>();
while (iterator.hasNext()) {
OpenHABWidget nextWidget = iterator.next();
widgetNameMap.put(mPopularNameProvider.getPopularNameFromWidgetLabel(nextWidget.getLabel()).toUpperCase(), nextWidget);
}
//Look for phrases
for(String phrase : commandPhrases) {
for(String unitName : widgetNameMap.keySet()) {
if (!StringHandler.isNullOrEmpty(unitName) && unitName.contains(phrase.toUpperCase())) {
//Got a unit match.
OpenHABWidget foundWidget = widgetNameMap.get(unitName);
resultList.add(foundWidget);
mLogger.d("CommandAnalyzer", "Found unit in command phrase <" + foundWidget.getLabel() + ">");
}
}
}
return resultList;
}
public String replaceCommandTagsWithRegEx(String source) {
String regexCommand = StringHandler.replaceSubStrings(source, "<", ">", "(.+)").toUpperCase() + "\\z";
List<String> tags = mRegularExpression.getAllNextMatchAsList(regexCommand, source, true).GroupList;
String result = source;
for(String tag : tags) {
if(mCommandTagsRegex.containsKey(tag.toLowerCase()))
result = result.replaceAll(tag, mCommandTagsRegex.get(tag.toLowerCase()));
}
return "\\A" + result + "\\z";
}
public List<String> getMatchingRegExGroups(String regex, String target) {
return mRegularExpression.getAllNextMatchAsList(regex, target, true).GroupList;
}
/**
* Example commandPhrases = "Turn on Kitchen ceiling lights" will result in a single mapping
* of "KITCHEN CEILING LIGHTS" as key and OpenHABWidgetCommandType.SwitchOn as value.
* CommandPhraseMatchResult objects with the highest points will be placed first in list.
* @param commandPhrases
* @return a Map of keys as upper case phrase strings with the command excluded and command types as values.
*/
public List<CommandPhraseMatchResult> getCommandsFromPhrases(List<String> commandPhrases) {
final List<CommandPhraseMatchResult> commandPhraseMatchResultList = new ArrayList<CommandPhraseMatchResult>();
for(String phrase : commandPhrases) {
if(phrase == null)
continue;
commandPhraseMatchResultList.addAll(getCommandsFromPhrase(phrase.toUpperCase()));
}
return commandPhraseMatchResultList;
}
private List<CommandPhraseMatchResult> getCommandsFromPhrase(String phrase) {
final List<CommandPhraseMatchResult> commandPhraseMatchResultList = new ArrayList<CommandPhraseMatchResult>();
for(OpenHABWidgetCommandType commandType : values()) {
if(commandType == null)
continue;
commandPhraseMatchResultList.addAll(getCommandsFromCommandType(phrase, commandType));
}
return commandPhraseMatchResultList;
}
private List<CommandPhraseMatchResult> getCommandsFromCommandType(String phrase, OpenHABWidgetCommandType commandType) {
final List<CommandPhraseMatchResult> commandPhraseMatchResultList = new ArrayList<CommandPhraseMatchResult>();
for(String commandAsText : mCommandPhrasesProvider.getCommandPhrases(commandType)) {
commandAsText = commandAsText.toUpperCase();
final int matchPoints = StringHandler.replaceSubStrings(commandAsText, "<", ">", "").split("\\s+").length;
final String regexCommand = "\\A" + StringHandler.replaceSubStrings(commandAsText, "<", ">", "(.+)").toUpperCase() + "\\z";
final Pattern pattern = Pattern.compile(replaceCommandTagsWithRegEx(commandAsText), Pattern.CASE_INSENSITIVE);
final Matcher matcher = pattern.matcher(phrase);
if (!matcher.find())
continue;
final List<String> tagPhrases = new ArrayList<String>();
for(int i = 1; i <= matcher.groupCount(); i++)
if(matcher.group(i) != null && !matcher.group(i).isEmpty())
tagPhrases.add(matcher.group(i));
final List<String> tags = getMatchingRegExGroups(regexCommand, commandAsText);
int listIndex = 0;
if(commandPhraseMatchResultList.size() > 0) {
for(; listIndex < commandPhraseMatchResultList.size();) {
if(commandPhraseMatchResultList.get(listIndex).getPoint() < matchPoints)
break;
listIndex++;
}
}
commandPhraseMatchResultList.add(listIndex, new CommandPhraseMatchResult(commandType,
tags.toArray(new String[tags.size()]),
tagPhrases.toArray(new String[tagPhrases.size()]),
matchPoints));
}
return commandPhraseMatchResultList;
}
public Map<CommandPhraseMatchResult, WidgetPhraseMatchResult> getHighestWidgetsFromCommandMatchResult(List<CommandPhraseMatchResult> listOfCommandResult) {
//TODO - Compare the tag type with item and widget type. No tag<->type match = No unit result.
Map<CommandPhraseMatchResult, WidgetPhraseMatchResult> resultMap = new HashMap<CommandPhraseMatchResult, WidgetPhraseMatchResult>();
for (CommandPhraseMatchResult commandPhraseMatchResult : listOfCommandResult) {
WidgetPhraseMatchResult widgetMatch = getMostProbableWidgetFromCommandMatchResult(commandPhraseMatchResult);
//TODO - Implement call to method that compares the tag type with item and widget type. No tag<->type match = No unit result
String[] valueTags = getValueTagType(commandPhraseMatchResult);
if (valueTags.length > 1)
mLogger.w(TAG, "Command phrase contains more than one value tag: " + valueTags.length);
if (widgetMatch != null && (valueTags.length > 0 && doesTagTypeMatchWidgetType(valueTags[0], widgetMatch.getWidget())) || valueTags.length == 0)
resultMap.put(commandPhraseMatchResult, widgetMatch);
}
return resultMap;
}
protected String[] getValueTagType(CommandPhraseMatchResult commandPhraseMatchResult) {
List<String> tagList = new ArrayList<String>();
for(String tag : commandPhraseMatchResult.getTags()) {
if(!tag.equalsIgnoreCase("<unit>"))
tagList.add(tag);
}
return tagList.toArray(new String[tagList.size()]);
}
public boolean doesTagTypeMatchWidgetType(String tag, OpenHABWidget widget) {
//TODO - What about if the tag is missing in the Map members? -> Third return value type.
if(widget.hasItem() && mItemTypeTagMapping.containsKey(tag))
return mItemTypeTagMapping.get(tag).contains(widget.getItem().getType());
else if(mWidgetTypeTagMapping.containsKey(tag))
return mWidgetTypeTagMapping.get(tag).contains(widget.getType());
return true;
}
public Map<CommandPhraseMatchResult, List<WidgetPhraseMatchResult>> getAllWidgetsFromCommandMatchResult(List<CommandPhraseMatchResult> listOfCommandResult) {
Map<CommandPhraseMatchResult, List<WidgetPhraseMatchResult>> resultMap = new HashMap<CommandPhraseMatchResult, List<WidgetPhraseMatchResult>>();
for (CommandPhraseMatchResult commandPhraseMatchResult : listOfCommandResult) {
resultMap.put(commandPhraseMatchResult, getWidgetsFromCommandMatchResult(commandPhraseMatchResult));
}
return resultMap;
}
public List<WidgetPhraseMatchResult> getWidgetsFromCommandMatchResult(CommandPhraseMatchResult commandPhraseMatchResult) {
String unitTagPhrase = getUnitPhrase(commandPhraseMatchResult);
//Add all found widgets no matter their match points.
return mOpenHABWidgetProvider.getWidgetByLabel(unitTagPhrase);
}
public WidgetPhraseMatchResult getMostProbableWidgetFromCommandMatchResult(CommandPhraseMatchResult commandPhraseMatchResult) {
String unitTagPhrase = getUnitPhrase(commandPhraseMatchResult);
//Only add the widget with the highest match point to the resulting list.
int matchPoint = 0;
WidgetPhraseMatchResult widgetMatch = null;
WidgetPhraseMatchResult matchResult = null;
List<WidgetPhraseMatchResult> widgetList = mOpenHABWidgetProvider.getWidgetByLabel(unitTagPhrase);
for (WidgetPhraseMatchResult aWidgetList : widgetList) {
matchResult = aWidgetList;
if (matchResult.getMatchPercent() > matchPoint) {
matchPoint = matchResult.getMatchPercent();
widgetMatch = new WidgetPhraseMatchResult(matchPoint, matchResult.getWidget());
}
}
return widgetMatch;
}
public String getUnitPhrase(CommandPhraseMatchResult commandPhraseMatchResult) {
String unitTagPhrase = null;
for(int i = 0; i < commandPhraseMatchResult.getTags().length; i++) {
if(commandPhraseMatchResult.getTags()[i].equalsIgnoreCase("<unit>")) {
unitTagPhrase = commandPhraseMatchResult.getTagPhrases()[i];
break;
}
}
return unitTagPhrase;
}
protected String getRegExMatch(String source, Pattern pattern) {
String result = "";
Matcher matcher = pattern.matcher(source);
if(matcher.find()) {
for (int i = 1; i <= matcher.groupCount(); i++)
result += matcher.group(i) + " ";
}
return result.trim();
}
protected void initializeWidgetTypeTagMapping() {
mItemTypeTagMapping.put("<integer>", Arrays.asList(OpenHABItemType.Dimmer, OpenHABItemType.Number));
mItemTypeTagMapping.put("<decimal>", Arrays.asList(OpenHABItemType.Number));
mItemTypeTagMapping.put("<color>", Arrays.asList(OpenHABItemType.Color));
mItemTypeTagMapping.put("<text>", Arrays.asList(OpenHABItemType.String));
}
protected void initializeCommandTagMapping() {
mCommandTagsRegex.put("<integer>", "([0-9]+)");
mCommandTagsRegex.put("<decimal>", "([0-9.,]+)");
mCommandTagsRegex.put("<text>", "(.+)");
mCommandTagsRegex.put("<unit>", "(.+)");
mCommandTagsRegex.put("<selection>", "(.+)");
//<color> is a bit special...
String[] colorNames = mCommandColorProvider.getColorNames();
StringBuilder colorRegEx = new StringBuilder();
for(String colorName : colorNames)
colorRegEx.append(colorRegEx.length() == 0? colorName : "|" + colorName);
mCommandTagsRegex.put("<color>", "(" + colorRegEx.toString() + ")");
}
}