From fd0f50dc5fd6afb5b9316411418fdbeda4acdb1c Mon Sep 17 00:00:00 2001 From: grog Date: Wed, 29 Nov 2023 12:01:25 -0800 Subject: [PATCH 001/106] Proposal to be able to set botname or username in aiml --- src/main/java/org/myrobotlab/service/ProgramAB.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/myrobotlab/service/ProgramAB.java b/src/main/java/org/myrobotlab/service/ProgramAB.java index db06861aaa..304c806ce2 100644 --- a/src/main/java/org/myrobotlab/service/ProgramAB.java +++ b/src/main/java/org/myrobotlab/service/ProgramAB.java @@ -1201,6 +1201,12 @@ synchronized public void onChangePredicate(Chat chat, String predicateName, Stri if (s.chat == chat) { // found session saving predicates invoke("publishPredicate", s, predicateName, result); + if ("botname".equals(predicateName)) { + setCurrentBotName(result); + } + if ("username".equals(predicateName)) { + setCurrentUserName(result); + } s.savePredicates(); return; } From 7562b39f173ad521ed41a5b7ac7fb26f04538e63 Mon Sep 17 00:00:00 2001 From: grog Date: Wed, 29 Nov 2023 14:01:49 -0800 Subject: [PATCH 002/106] updated version --- src/main/java/org/myrobotlab/service/ProgramAB.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/ProgramAB.java b/src/main/java/org/myrobotlab/service/ProgramAB.java index 304c806ce2..4555cb6a87 100644 --- a/src/main/java/org/myrobotlab/service/ProgramAB.java +++ b/src/main/java/org/myrobotlab/service/ProgramAB.java @@ -1201,10 +1201,12 @@ synchronized public void onChangePredicate(Chat chat, String predicateName, Stri if (s.chat == chat) { // found session saving predicates invoke("publishPredicate", s, predicateName, result); - if ("botname".equals(predicateName)) { + // botname is the name of the bot currentBotName is the aiml folder that is + // mostly equivalent to its "type" + if ("currentBotName".equals(predicateName)) { setCurrentBotName(result); } - if ("username".equals(predicateName)) { + if ("name".equals(predicateName)) { setCurrentUserName(result); } s.savePredicates(); @@ -1400,6 +1402,11 @@ public Utterance publishUtterance(Utterance utterance) { return utterance; } + /** + * New topic published when it changes + * @param topicChange + * @return + */ public TopicChange publishTopic(TopicChange topicChange) { return topicChange; } From b38e50f097fe13a4bd22d3357377036e7c5404c6 Mon Sep 17 00:00:00 2001 From: grog Date: Tue, 5 Dec 2023 10:58:43 -0800 Subject: [PATCH 003/106] init push --- .../org/myrobotlab/programab/BotInfo.java | 17 +- .../myrobotlab/programab/MrlSraixHandler.java | 211 ----- .../org/myrobotlab/programab/OOBPayload.java | 176 ----- .../java/org/myrobotlab/programab/Oob.java | 14 - .../myrobotlab/programab/PredicateEvent.java | 31 - .../org/myrobotlab/programab/Response.java | 10 +- .../org/myrobotlab/programab/Session.java | 155 ++-- .../org/myrobotlab/programab/XmlParser.java | 31 + .../programab/handlers/oob/OobProcessor.java | 93 +++ .../handlers/sraix/MrlSraixHandler.java | 54 ++ .../myrobotlab/programab/models/Event.java | 65 ++ .../org/myrobotlab/programab/models/Mrl.java | 14 + .../org/myrobotlab/programab/models/Oob.java | 14 + .../myrobotlab/programab/models/Sraix.java | 10 + .../myrobotlab/programab/models/Template.java | 51 ++ .../org/myrobotlab/service/ProgramAB.java | 745 +++++++++++------- .../service/config/ProgramABConfig.java | 27 +- .../service/data/SearchResults.java | 17 + .../myrobotlab/service/data/TopicChange.java | 44 -- .../service/meta/ProgramABMeta.java | 3 +- .../org/myrobotlab/service/ProgramABTest.java | 374 +++++---- 21 files changed, 1129 insertions(+), 1027 deletions(-) delete mode 100755 src/main/java/org/myrobotlab/programab/MrlSraixHandler.java delete mode 100644 src/main/java/org/myrobotlab/programab/OOBPayload.java delete mode 100644 src/main/java/org/myrobotlab/programab/Oob.java delete mode 100644 src/main/java/org/myrobotlab/programab/PredicateEvent.java create mode 100644 src/main/java/org/myrobotlab/programab/XmlParser.java create mode 100644 src/main/java/org/myrobotlab/programab/handlers/oob/OobProcessor.java create mode 100755 src/main/java/org/myrobotlab/programab/handlers/sraix/MrlSraixHandler.java create mode 100644 src/main/java/org/myrobotlab/programab/models/Event.java create mode 100644 src/main/java/org/myrobotlab/programab/models/Mrl.java create mode 100644 src/main/java/org/myrobotlab/programab/models/Oob.java create mode 100644 src/main/java/org/myrobotlab/programab/models/Sraix.java create mode 100644 src/main/java/org/myrobotlab/programab/models/Template.java delete mode 100644 src/main/java/org/myrobotlab/service/data/TopicChange.java diff --git a/src/main/java/org/myrobotlab/programab/BotInfo.java b/src/main/java/org/myrobotlab/programab/BotInfo.java index 8fb3546513..3c89130d3e 100644 --- a/src/main/java/org/myrobotlab/programab/BotInfo.java +++ b/src/main/java/org/myrobotlab/programab/BotInfo.java @@ -12,6 +12,7 @@ import org.alicebot.ab.Bot; import org.myrobotlab.io.FileIO; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.programab.handlers.sraix.MrlSraixHandler; import org.myrobotlab.service.ProgramAB; import org.slf4j.Logger; @@ -23,7 +24,7 @@ public class BotInfo { transient public final static Logger log = LoggerFactory.getLogger(BotInfo.class); - public String name; + public String botType; public File path; public Properties properties = new Properties(); private transient Bot bot; @@ -36,16 +37,16 @@ public class BotInfo { public String img; public BotInfo(ProgramAB programab, File path) { - this.name = path.getName(); + this.botType = path.getName(); this.path = path; this.programab = programab; - programab.info("found bot %s", name); + programab.info("found bot %s", botType); try { FileInputStream fis = new FileInputStream(FileIO.gluePaths(path.getAbsolutePath(), "manifest.txt")); properties.load(new InputStreamReader(fis, Charset.forName("UTF-8"))); log.info("loaded properties"); } catch (FileNotFoundException e) { - programab.warn("bot %s does not have a manifest.txt", name); + programab.warn("bot %s does not have a manifest.txt", botType); } catch (Exception e) { log.error("BotInfo threw", e); } @@ -61,13 +62,13 @@ public synchronized Bot getBot() { if (bot == null) { // lazy loading of bot - created on the first use if (properties.containsKey("locale")) { - bot = new Bot(name, path.getAbsolutePath(), java.util.Locale.forLanguageTag((String) properties.get("locale"))); + bot = new Bot(botType, path.getAbsolutePath(), java.util.Locale.forLanguageTag((String) properties.get("locale"))); bot.listener = programab; } else { if (programab.getLocaleTag() == null) { - bot = new Bot(name, path.getAbsolutePath()); + bot = new Bot(botType, path.getAbsolutePath()); } else { - bot = new Bot(name, path.getAbsolutePath(), java.util.Locale.forLanguageTag(programab.getLocaleTag())); + bot = new Bot(botType, path.getAbsolutePath(), java.util.Locale.forLanguageTag(programab.getLocaleTag())); } bot.listener = programab; } @@ -130,7 +131,7 @@ public void removeProperty(String name2) { @Override public String toString() { - return String.format("%s - %s", name, path); + return String.format("%s - %s", botType, path); } } diff --git a/src/main/java/org/myrobotlab/programab/MrlSraixHandler.java b/src/main/java/org/myrobotlab/programab/MrlSraixHandler.java deleted file mode 100755 index d1b2808b2e..0000000000 --- a/src/main/java/org/myrobotlab/programab/MrlSraixHandler.java +++ /dev/null @@ -1,211 +0,0 @@ -package org.myrobotlab.programab; - -import java.util.ArrayList; -import java.util.Locale; -import java.util.regex.Matcher; - -import org.alicebot.ab.Chat; -import org.alicebot.ab.Sraix; -import org.alicebot.ab.SraixHandler; -import org.myrobotlab.codec.CodecUtils; -import org.myrobotlab.framework.Message; -import org.myrobotlab.framework.interfaces.ServiceInterface; -import org.myrobotlab.logging.LoggerFactory; -import org.myrobotlab.service.ProgramAB; -import org.myrobotlab.service.Runtime; -import org.myrobotlab.service.data.SearchResults; -import org.myrobotlab.service.interfaces.SearchPublisher; -import org.myrobotlab.string.StringUtil; -// import org.nd4j.shade.jackson.dataformat.xml.XmlMapper; -import org.slf4j.Logger; - -import com.fasterxml.jackson.dataformat.xml.XmlMapper; - - -public class MrlSraixHandler implements SraixHandler { - transient public final static Logger log = LoggerFactory.getLogger(MrlSraixHandler.class); - - private ProgramAB programab = null; - - public MrlSraixHandler() { - - } - - public MrlSraixHandler(ProgramAB programab) { - this.programab = programab; - } - - @Override - public String sraix(Chat chatSession, String input, String defaultResponse, String hint, String host, String botid, String apiKey, String limit, Locale locale) { - log.debug("MRL Sraix handler! Input {}", input); - - // FIXME - "list of AIs in priority order to attempt to handle request - // best synopsis of sraix I've found - https://gist.github.com/onlurking/f6431e672cfa202c09a7c7cf92ac8a8b - try { - XmlMapper xmlMapper = new XmlMapper(); - Oob oob = xmlMapper.readValue(input, Oob.class); - StringBuilder responseText = new StringBuilder(); - if (oob.mrljson != null) { - Message[] msgs = CodecUtils.fromJson(oob.mrljson, Message[].class); - for (Message msg: msgs) { - msg.sender = programab.getName(); - msg.sendingMethod = "sraix"; - // buffered asynchronous - use invoke synchronous - // programab.in(msg); - // invoking to keep it synchronous - ServiceInterface si = Runtime.getService(msg.getName()); - Object ret = si.invoke(msg.method, msg.data); - if (ret != null) { - responseText.append(ret.toString()); - } - } - return responseText.toString(); - } - log.info("found oob {}", oob); - } catch (Exception e) { - // programab.error("threw on input %s", input); - } - - // the INPUT has the string we care about. if this is an OOB tag, let's - // evaluate it and return the result. - if (containsOOB(input)) { - String response = processInlineOOB(input); - return response; - } else if (programab != null && programab.getPeer("search") != null) { - try { - SearchPublisher search = (SearchPublisher) programab.getPeer("search"); - if (search != null) { - SearchResults results = search.search(input); - String searchResponse = results.getTextAndImages(); - - if (searchResponse == null || searchResponse.length() == 0) { - Session session = programab.getSession(); - // TODO - perhaps more rich codes for details of failure - // Response r = session.getResponse("SRAIXFAILED_WIKIPEDIA " + input); - Response r = session.getResponse("SRAIXFAILED " + input); - return r.msg; - } - return searchResponse; - } else { - // TODO - perhaps more rich codes for details of failure - // Response r = programab.getResponse("SRAIXFAILED_WIKIPEDIA_NOT_AVAILABLE"); - Session session = programab.getSession(); - Response r = session.getResponse("SRAIXFAILED " + input); - return r.msg; - } - - } catch (Exception e) { - return "sorry, I cannot search now " + e.getMessage(); - } - } else { - // fall back to default behavior of pannous / pandorabots? - // TODO: expose pandora bots here if botid is set? - // TODO: enable call out to an official MRL hosted NLU service/ knowedge - // service. - - String response = Sraix.sraixPannous(input, hint, chatSession, locale); - if (StringUtil.isEmpty(response)) { - return defaultResponse; - } else { - // clean up the response a bit. - response = cleanPannousResponse(response); - return response; - } - } - } - - private String cleanPannousResponse(String response) { - String clean = response.replaceAll("\\(Answers.com\\)", "").trim(); - return clean; - } - - private boolean containsOOB(String text) { - Matcher oobMatcher = OOBPayload.oobPattern.matcher(text); - return oobMatcher.matches(); - } - - // TODO override it inside programAB to share methods and publish OOB - private String processInlineOOB(String text) { - // Find any oob tags - StringBuilder responseBuilder = new StringBuilder(); - ArrayList payloads = new ArrayList(); - Matcher oobMatcher = OOBPayload.oobPattern.matcher(text); - int start = 0; - while (oobMatcher.find()) { - // We found some OOB text. - // assume only one OOB in the text? - // everything from the start to the end of this - responseBuilder.append(text.substring(start, oobMatcher.start())); - // update the end to be - // next segment is from the end of this one to the start of the next one. - start = oobMatcher.end(); - String oobPayload = oobMatcher.group(0); - Matcher mrlMatcher = OOBPayload.mrlPattern.matcher(oobPayload); - while (mrlMatcher.find()) { - String mrlPayload = mrlMatcher.group(0); - OOBPayload payload = parseOOB(mrlPayload); - Object result = invokeOOBPayloads(payloads, mrlPayload, payload); - if (result != null && result.getClass().isArray()) { - Object[] objects = (Object[]) result; - for (Object o : objects) { - responseBuilder.append(o.toString() + " "); - } - } else { - - if (result != null) { - responseBuilder.append(result); - } - } - log.info("OOB PROCESSING RESULT: {}", result); - } - } - // append the last part. (assume the start is set to the end of the last - // match.. - // or zero if no matches found. - responseBuilder.append(text.substring(start)); - return responseBuilder.toString(); - } - - private Object invokeOOBPayloads(ArrayList payloads, String mrlPayload, OOBPayload payload) { - payloads.add(payload); - // grab service and invoke method. - ServiceInterface s = Runtime.getService(payload.getServiceName()); - if (s == null) { - log.warn("Service name in OOB/MRL tag unknown. {}", mrlPayload); - return null; - } - Object result = null; - if (payload.getParams() != null) { - result = s.invoke(payload.getMethodName(), payload.getParams().toArray()); - } else { - result = s.invoke(payload.getMethodName()); - } - return result; - } - - private OOBPayload parseOOB(String oobPayload) { - - // TODO: fix the damn double encoding issue. - // we have user entered text in the service/method and params values. - // grab the service - Matcher serviceMatcher = OOBPayload.servicePattern.matcher(oobPayload); - serviceMatcher.find(); - String serviceName = serviceMatcher.group(1); - - Matcher methodMatcher = OOBPayload.methodPattern.matcher(oobPayload); - methodMatcher.find(); - String methodName = methodMatcher.group(1); - - Matcher paramMatcher = OOBPayload.paramPattern.matcher(oobPayload); - ArrayList params = new ArrayList(); - while (paramMatcher.find()) { - // We found some OOB text. - // assume only one OOB in the text? - String param = paramMatcher.group(1); - params.add(param); - } - OOBPayload payload = new OOBPayload(serviceName, methodName, params); - return payload; - - } -} diff --git a/src/main/java/org/myrobotlab/programab/OOBPayload.java b/src/main/java/org/myrobotlab/programab/OOBPayload.java deleted file mode 100644 index c9984a6929..0000000000 --- a/src/main/java/org/myrobotlab/programab/OOBPayload.java +++ /dev/null @@ -1,176 +0,0 @@ -package org.myrobotlab.programab; - -import java.util.ArrayList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.lang3.StringUtils; -import org.myrobotlab.framework.Message; -import org.myrobotlab.framework.interfaces.ServiceInterface; -import org.myrobotlab.logging.LoggerFactory; -import org.myrobotlab.service.ProgramAB; -import org.myrobotlab.service.Runtime; -import org.slf4j.Logger; - -public class OOBPayload { - - transient public final static Logger log = LoggerFactory.getLogger(OOBPayload.class); - // TODO: something better than regex to parse the xml. (Problem is that the - // service/method/param values - // could end up double encoded ... So we had to switch to hamd crafting the - // aiml for the oob/mrl tag. - public transient static final Pattern oobPattern = Pattern.compile(".*?", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE); - public transient static final Pattern mrlPattern = Pattern.compile(".*?", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE); - public transient static final Pattern servicePattern = Pattern.compile("(.*?)", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE); - public transient static final Pattern methodPattern = Pattern.compile("(.*?)", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE); - public transient static final Pattern paramPattern = Pattern.compile("(.*?)", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE); - - private String serviceName; - private String methodName; - private ArrayList params; - - public OOBPayload() { - // TODO: remove the default constructor - }; - - public OOBPayload(String serviceName, String methodName, ArrayList params) { - this.serviceName = serviceName; - this.methodName = methodName; - this.params = params; - } - - public String getMethodName() { - return methodName; - } - - public ArrayList getParams() { - return params; - } - - public String getServiceName() { - return serviceName; - } - - public void setMethodName(String methodName) { - this.methodName = methodName; - } - - public void setParams(ArrayList params) { - this.params = params; - } - - public void setServiceName(String serviceName) { - this.serviceName = serviceName; - } - - public static String asOOBTag(OOBPayload payload) { - // TODO: this isn't really safe as XML/AIML.. but we don't want to end up - // double encoding things like - // the important tags... So, for now, it's just wrapped in the tags. - StringBuilder oobBuilder = new StringBuilder(); - oobBuilder.append(""); - oobBuilder.append(""); - oobBuilder.append(""); - oobBuilder.append(payload.getServiceName()); - oobBuilder.append(""); - oobBuilder.append(""); - oobBuilder.append(payload.getMethodName()); - oobBuilder.append(""); - for (String param : payload.params) { - oobBuilder.append(""); - // TODO: this could be problematic if the param contains XML chars that - // are not AIML ... - oobBuilder.append(param); - oobBuilder.append(""); - } - oobBuilder.append(""); - oobBuilder.append(""); - return oobBuilder.toString(); - } - - public static String asBlockingOOBTag(OOBPayload oobTag) { - return "" + OOBPayload.asOOBTag(oobTag) + ""; - } - - public static OOBPayload fromString(String oobPayload) { - - // TODO: fix the damn double encoding issue. - // we have user entered text in the service/method - // and params values. - // grab the service - - Matcher serviceMatcher = servicePattern.matcher(oobPayload); - serviceMatcher.find(); - String serviceName = serviceMatcher.group(1); - - Matcher methodMatcher = methodPattern.matcher(oobPayload); - methodMatcher.find(); - String methodName = methodMatcher.group(1); - - Matcher paramMatcher = paramPattern.matcher(oobPayload); - ArrayList params = new ArrayList(); - while (paramMatcher.find()) { - // We found some OOB text. - // assume only one OOB in the text? - String param = paramMatcher.group(1); - params.add(param); - } - OOBPayload payload = new OOBPayload(serviceName, methodName, params); - // log.info(payload.toString()); - return payload; - } - - public static boolean invokeOOBPayload(OOBPayload payload, String sender, boolean blocking) { - ServiceInterface s = Runtime.getService(payload.getServiceName()); - // the service must exist and the method name must be set. - if (s == null || StringUtils.isEmpty(payload.getMethodName())) { - return false; - } - - if (!blocking) { - s.in(Message.createMessage(sender, payload.getServiceName(), payload.getMethodName(), payload.getParams().toArray())); - // non-blocking.. fire and forget! - return true; - } - - // TODO: should you be able to be synchronous for this - // execution? - Object result = null; - if (payload.getParams() != null) { - result = s.invoke(payload.getMethodName(), payload.getParams().toArray()); - } else { - result = s.invoke(payload.getMethodName()); - } - log.info("OOB PROCESSING RESULT: {}", result); - return true; - } - - public static ArrayList extractOOBPayloads(String text, ProgramAB programAB) { - ArrayList payloads = new ArrayList(); - Matcher oobMatcher = OOBPayload.oobPattern.matcher(text); - while (oobMatcher.find()) { - // We found some OOB text. - // assume only one OOB in the text? - String oobPayload = oobMatcher.group(0); - Matcher mrlMatcher = OOBPayload.mrlPattern.matcher(oobPayload); - while (mrlMatcher.find()) { - String mrlPayload = mrlMatcher.group(0); - OOBPayload payload = OOBPayload.fromString(mrlPayload); - payloads.add(payload); - // TODO: maybe we dont' want this? - // Notifiy endpoints - programAB.invoke("publishOOBText", mrlPayload); - // grab service and invoke method. - - } - } - return payloads; - } - - public static String removeOOBFromString(String res) { - Matcher matcher = OOBPayload.oobPattern.matcher(res); - res = matcher.replaceAll(""); - return res; - } - -} diff --git a/src/main/java/org/myrobotlab/programab/Oob.java b/src/main/java/org/myrobotlab/programab/Oob.java deleted file mode 100644 index d6c8d2bf81..0000000000 --- a/src/main/java/org/myrobotlab/programab/Oob.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.myrobotlab.programab; - -public class Oob { - - public class Mrl { - public String service; - public String method; - public Object[] param; - } - - public String mrljson; - public String mrl; -} - diff --git a/src/main/java/org/myrobotlab/programab/PredicateEvent.java b/src/main/java/org/myrobotlab/programab/PredicateEvent.java deleted file mode 100644 index bf256fa143..0000000000 --- a/src/main/java/org/myrobotlab/programab/PredicateEvent.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.myrobotlab.programab; - -/** - * Pojo for state change of one of ProgramAB's state info - * @author GroG - * - */ -public class PredicateEvent { - /** - * unique identifier for the session user & bot - */ - public String id; - /** - * name of the predicate changed - */ - public String name; - - // public String previousValue; - - /** - * new value - */ - public String value; - public String botName; - public String userName; - - @Override - public String toString() { - return String.format("%s %s=%s", id, name, value); - } -} diff --git a/src/main/java/org/myrobotlab/programab/Response.java b/src/main/java/org/myrobotlab/programab/Response.java index 128ae8cfc0..a2802196b7 100644 --- a/src/main/java/org/myrobotlab/programab/Response.java +++ b/src/main/java/org/myrobotlab/programab/Response.java @@ -3,6 +3,8 @@ import java.util.Date; import java.util.List; +import org.myrobotlab.programab.models.Mrl; + /** * FIXME - this class should become a more generalized AI response data object * in org.myrobotlab.data so that other AI systems (and search engines) can fill @@ -29,12 +31,14 @@ public class Response { /** * filtered oob data */ - public List payloads; + public List payloads; - public Response(String userName, String botName, String msg, List payloads) { + public Response(String userName, String botName, String msg, List payloads) { this.botName = botName; this.userName = userName; this.msg = msg; + + // what is this for ? this.payloads = payloads; } @@ -48,7 +52,7 @@ public String toString() { str.append("Msg:" + msg + ", "); str.append("Payloads:["); if (payloads != null) { - for (OOBPayload payload : payloads) { + for (Mrl payload : payloads) { str.append(payload.toString() + ", "); } } diff --git a/src/main/java/org/myrobotlab/programab/Session.java b/src/main/java/org/myrobotlab/programab/Session.java index a234310e57..c529044592 100644 --- a/src/main/java/org/myrobotlab/programab/Session.java +++ b/src/main/java/org/myrobotlab/programab/Session.java @@ -3,8 +3,8 @@ import java.io.File; import java.io.FileWriter; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.TreeSet; @@ -13,6 +13,10 @@ import org.alicebot.ab.Predicates; import org.myrobotlab.io.FileIO; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.programab.handlers.oob.OobProcessor; +import org.myrobotlab.programab.models.Event; +import org.myrobotlab.programab.models.Mrl; +import org.myrobotlab.programab.models.Template; import org.myrobotlab.service.ProgramAB; import org.myrobotlab.service.config.ProgramABConfig; import org.slf4j.Logger; @@ -27,25 +31,55 @@ public class Session { transient public final static Logger log = LoggerFactory.getLogger(ProgramAB.class); - public String userName; - public boolean processOOB = true; + /** + * name of the user that owns this session + */ + public String username; + + /** + * last time the bot responded + */ public Date lastResponseTime = null; + + /** + * bot will prompt users if enabled trolling is true after + * maxConversationDelay has passed + */ public boolean enableTrolling = false; - // Number of milliseconds before the robot starts talking on its own. + + /** + * Number of milliseconds before the robot starts talking on its own. + */ public int maxConversationDelay = 5000; - // FIXME - could be transient ?? - transient public BotInfo botInfo; + /** + * general bot information + */ + public transient BotInfo botInfo; + + /** + * interface to program-ab + */ public transient Chat chat; - transient ProgramAB programab; + /** + * service that manages this session + */ + private transient ProgramAB programab; + /** + * current file associated with this user and session + */ public File predicatesFile; - // public Map predicates = new TreeMap<>(); - public Predicates predicates = null; + /** + * predicate data associated with this session + */ + protected Predicates predicates = null; - // current topic of this session + /** + * current topic of this session + */ public String currentTopic = null; /** @@ -61,35 +95,37 @@ public class Session { */ public Session(ProgramAB programab, String userName, BotInfo botInfo) { this.programab = programab; - this.userName = userName; + this.username = userName; this.botInfo = botInfo; + this.chat = loadChat(); + predicates = chat.predicates; + + Event event = new Event(programab.getName(), userName, null, null); + programab.invoke("publishSession", event); + + ProgramABConfig config = programab.getConfig(); + if (config.startTopic != null) { + chat.predicates.put("topic", config.startTopic); + } + + this.maxConversationDelay = config.maxConversationDelay; + this.enableTrolling = config.enableTrolling; } - /** - * lazy loading chat - * - * task to save predicates and getting responses will eventually call getBot - * we don't want initialization to create 2 when only one is needed - * - * @return - */ private synchronized Chat getChat() { - if (chat == null) { - chat = new Chat(botInfo.getBot()); - // loading predefined predicates - if they exist - File userPredicates = new File(FileIO.gluePaths(botInfo.path.getAbsolutePath(), String.format("config/%s.predicates.txt", userName))); - if (userPredicates.exists()) { - predicatesFile = userPredicates; - chat.predicates.getPredicateDefaults(userPredicates.getAbsolutePath()); - } - - ProgramABConfig config = (ProgramABConfig)programab.getConfig(); - if (config.startTopic != null){ - chat.predicates.put("topic", config.startTopic); - } + return chat; + } + + private Chat loadChat() { + Chat chat = new Chat(botInfo.getBot()); + // loading predefined predicates - if they exist + File userPredicates = new File(FileIO.gluePaths(botInfo.path.getAbsolutePath(), String.format("config/%s.predicates.txt", username))); + if (userPredicates.exists()) { + predicatesFile = userPredicates; + chat.predicates.getPredicateDefaults(userPredicates.getAbsolutePath()); } - predicates = chat.predicates; + return chat; } @@ -103,9 +139,9 @@ public void savePredicates() { sb.append(predicate + ":" + value + "\n"); } } - File predicates = new File(FileIO.gluePaths(botInfo.path.getAbsolutePath(), String.format("config/%s.predicates.txt", userName))); + File predicates = new File(FileIO.gluePaths(botInfo.path.getAbsolutePath(), String.format("config/%s.predicates.txt", username))); predicates.getParentFile().mkdirs(); - log.info("Bot : {} User : {} Predicates Filename : {} ", botInfo.name, userName, predicates); + log.info("bot : {} user : {} saving predicates filename : {} ", botInfo.botType, username, predicates); try { FileWriter writer = new FileWriter(predicates, StandardCharsets.UTF_8); writer.write(sb.toString()); @@ -118,6 +154,7 @@ public void savePredicates() { /** * Get all current predicate names and values + * * @return */ public Map getPredicates() { @@ -127,38 +164,28 @@ public Map getPredicates() { } public Response getResponse(String inText) { + try { + String returnText = getChat().multisentenceRespond(inText); + String xml = String.format("", returnText); + Template template = XmlParser.parseTemplate(xml); - String text = getChat().multisentenceRespond(inText); - - // Find any oob tags - ArrayList oobTags = OOBPayload.extractOOBPayloads(text, programab); + OobProcessor handler = OobProcessor.getInstance(programab); + handler.process(template.oob, true); // block by default - // invoke them all if configured to do so - if (processOOB) { - for (OOBPayload payload : oobTags) { - // assumption is this is non blocking invoking! - boolean oobRes = OOBPayload.invokeOOBPayload(payload, programab.getName(), false); - if (!oobRes) { - // there was a failure invoking - log.warn("Failed to invoke OOB/MRL tag : {}", OOBPayload.asOOBTag(payload)); - } - } - } - - // strip any oob tags if found - if (oobTags.size() > 0) { - text = OOBPayload.removeOOBFromString(text).trim(); + List mrl = template.oob != null ? template.oob.mrl : null; + // returned all text inside template but outside oob + Response response = new Response(username, botInfo.botType, template.text, mrl); + return response; + } catch (Exception e) { + programab.error(e); } - - Response response = new Response(userName, botInfo.name, text, oobTags); - return response; - + return new Response(username, botInfo.botType, "", null); } public Chat reload() { botInfo.reload(); - chat = null; - return getChat(); + chat = loadChat(); + return chat; } public void remove(String predicateName) { @@ -173,4 +200,12 @@ public String getPredicate(String predicateName) { return getChat().predicates.get(predicateName); } + public String getUsername() { + return username; + } + + public Object getBotType() { + return botInfo.botType; + } + } diff --git a/src/main/java/org/myrobotlab/programab/XmlParser.java b/src/main/java/org/myrobotlab/programab/XmlParser.java new file mode 100644 index 0000000000..51051af7db --- /dev/null +++ b/src/main/java/org/myrobotlab/programab/XmlParser.java @@ -0,0 +1,31 @@ +package org.myrobotlab.programab; + +import org.myrobotlab.programab.models.Sraix; +import org.myrobotlab.programab.models.Template; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + +/** + * Thread safe fasterjackson xml parser. + * + * @author GroG + * + */ +public class XmlParser { + + public static Template parseTemplate(String xml) throws JsonMappingException, JsonProcessingException { + ThreadLocal xmlMapperThreadLocal = ThreadLocal.withInitial(XmlMapper::new); + XmlMapper xmlMapper = xmlMapperThreadLocal.get(); + Template template = xmlMapper.readValue(xml, Template.class); + return template; + } + + public static Sraix parseSraix(String xml) throws JsonMappingException, JsonProcessingException { + ThreadLocal xmlMapperThreadLocal = ThreadLocal.withInitial(XmlMapper::new); + XmlMapper xmlMapper = xmlMapperThreadLocal.get(); + return xmlMapper.readValue(xml, Sraix.class); + } + +} diff --git a/src/main/java/org/myrobotlab/programab/handlers/oob/OobProcessor.java b/src/main/java/org/myrobotlab/programab/handlers/oob/OobProcessor.java new file mode 100644 index 0000000000..a34e5aa347 --- /dev/null +++ b/src/main/java/org/myrobotlab/programab/handlers/oob/OobProcessor.java @@ -0,0 +1,93 @@ +package org.myrobotlab.programab.handlers.oob; + +import java.util.List; + +import org.myrobotlab.codec.CodecUtils; +import org.myrobotlab.framework.Message; +import org.myrobotlab.programab.models.Mrl; +import org.myrobotlab.programab.models.Oob; +import org.myrobotlab.service.ProgramAB; + +public class OobProcessor { + + private static OobProcessor instance; + private transient ProgramAB programab; + protected int maxBlockTime = 2000; + + private OobProcessor() { + } + + public static OobProcessor getInstance(ProgramAB programab) { + if (instance == null) { + instance = new OobProcessor(); + instance.programab = programab; + } + return instance; + } + + public Message toMsg(Mrl mrl) { + Object[] data = null; + if (mrl.params != null) { + data = new Object[mrl.params.size()]; + for (int i = 0; i < data.length; ++i) { + data[i] = mrl.params.get(i).trim(); + } + } + String service = mrl.service == null?null:mrl.service.trim(); + return Message.createMessage(programab.getName(), service, mrl.method.trim(), data); + } + + public String process(Oob oob, boolean block) { + StringBuilder sb = new StringBuilder(); + + // FIXME dynamic way of registering oobs + if (oob != null) { + // Process + if (oob.mrl != null) { + List mrls = oob.mrl; + for (Mrl mrl : mrls) { + if (!block) { + // programab.out(toMsg(mrl)); + programab.info("sending without blocking %s", toMsg(mrl)); + programab.send(toMsg(mrl)); + } else { + try { + programab.info("sendingBlocking without blocking %s", toMsg(mrl)); + Object o = programab.sendBlocking(toMsg(mrl), maxBlockTime); + if (o != null) { + sb.append(o); + } + } catch (Exception e) { + programab.error(e); + } + } + } + } // for each mrl + } + + // Process + if (oob != null && oob.mrljson != null) { + + Message[] msgs = CodecUtils.fromJson(oob.mrljson, Message[].class); + if (msgs != null) { + for (Message msg : msgs) { + + if (!block) { + programab.send(msg); + } else { + try { + Object o = programab.sendBlocking(msg, maxBlockTime); + if (o != null) { + sb.append(o); + } + } catch (Exception e) { + programab.error(e); + } + } + } // for each msg + } + } + + return sb.toString(); + } +} diff --git a/src/main/java/org/myrobotlab/programab/handlers/sraix/MrlSraixHandler.java b/src/main/java/org/myrobotlab/programab/handlers/sraix/MrlSraixHandler.java new file mode 100755 index 0000000000..eef8e68498 --- /dev/null +++ b/src/main/java/org/myrobotlab/programab/handlers/sraix/MrlSraixHandler.java @@ -0,0 +1,54 @@ +package org.myrobotlab.programab.handlers.sraix; + +import java.util.Locale; + +import org.alicebot.ab.Chat; +import org.alicebot.ab.SraixHandler; +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.programab.XmlParser; +import org.myrobotlab.programab.handlers.oob.OobProcessor; +import org.myrobotlab.programab.models.Sraix; +import org.myrobotlab.service.ProgramAB; +import org.myrobotlab.service.data.SearchResults; +import org.myrobotlab.service.interfaces.SearchPublisher; +import org.slf4j.Logger; + +public class MrlSraixHandler implements SraixHandler { + transient public final static Logger log = LoggerFactory.getLogger(MrlSraixHandler.class); + + private ProgramAB programab = null; + + public MrlSraixHandler(ProgramAB programab) { + this.programab = programab; + } + + @Override + public String sraix(Chat chatSession, String input, String defaultResponse, String hint, String host, String botid, String apiKey, String limit, Locale locale) { + try { + log.debug("MRL Sraix handler! Input {}", input); + String xml = String.format("%s", input); + // Template template = XmlParser.parseTemplate(xml); + Sraix sraix = XmlParser.parseSraix(xml); + + if (sraix.oob != null) { + OobProcessor handler = OobProcessor.getInstance(programab); + String ret = handler.process(sraix.oob, true); // block by default + return ret; + } else if (sraix.search != null) { + log.info("search now"); + // if my default "search" peer key has a name .. use it ? + SearchPublisher search = (SearchPublisher)programab.getPeer("search"); + SearchResults results = search.search(sraix.search); + // return results.getTextAndImages(); + return results.getHtml(); + } + } catch (Exception e) { + programab.error(e); + } + if (defaultResponse != null) { + return defaultResponse; + } + return ""; + } + +} diff --git a/src/main/java/org/myrobotlab/programab/models/Event.java b/src/main/java/org/myrobotlab/programab/models/Event.java new file mode 100644 index 0000000000..93e85c4086 --- /dev/null +++ b/src/main/java/org/myrobotlab/programab/models/Event.java @@ -0,0 +1,65 @@ +package org.myrobotlab.programab.models; + +/** + * Pojo for state change of one of ProgramAB's state info + * @author GroG + * + */ +public class Event { + /** + * the botName in this state change - typically + * current session botName + */ + public String botname; + /** + * unique identifier for the session user & bot + */ + public String id; + + /** + * name of the predicate changed + */ + public String name; + + /** + * service this topic change came from + */ + public String src; + + /** + * new topic or state name in this transition + */ + public String topic; + + /** + * timestamp + */ + public long ts = System.currentTimeMillis(); + + /** + * the user name in this state change - usually + * current session userName + */ + public String user; + + /** + * new value + */ + public String value; + + public Event() { + } + + public Event(String src, String userName, String botName, String topic) { + this.src = src; + this.user = userName; + this.botname = botName; + this.topic = topic; + } + + + @Override + public String toString() { + return String.format("%s %s=%s", id, name, value); + } +} diff --git a/src/main/java/org/myrobotlab/programab/models/Mrl.java b/src/main/java/org/myrobotlab/programab/models/Mrl.java new file mode 100644 index 0000000000..04c1bf79bb --- /dev/null +++ b/src/main/java/org/myrobotlab/programab/models/Mrl.java @@ -0,0 +1,14 @@ +package org.myrobotlab.programab.models; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; + +public class Mrl { + public String service; + public String method; + @JacksonXmlElementWrapper(useWrapping = false) + @JsonProperty("param") + public List params; +} \ No newline at end of file diff --git a/src/main/java/org/myrobotlab/programab/models/Oob.java b/src/main/java/org/myrobotlab/programab/models/Oob.java new file mode 100644 index 0000000000..833bab5a0f --- /dev/null +++ b/src/main/java/org/myrobotlab/programab/models/Oob.java @@ -0,0 +1,14 @@ +package org.myrobotlab.programab.models; + +import java.util.List; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; + +public class Oob { + + public String mrljson; + + @JacksonXmlElementWrapper(useWrapping = false) + public List mrl; +} + diff --git a/src/main/java/org/myrobotlab/programab/models/Sraix.java b/src/main/java/org/myrobotlab/programab/models/Sraix.java new file mode 100644 index 0000000000..99b0639cb6 --- /dev/null +++ b/src/main/java/org/myrobotlab/programab/models/Sraix.java @@ -0,0 +1,10 @@ +package org.myrobotlab.programab.models; + +// FIXME add attributes and internal tags +public class Sraix { + + public String search; + + public Oob oob; + +} diff --git a/src/main/java/org/myrobotlab/programab/models/Template.java b/src/main/java/org/myrobotlab/programab/models/Template.java new file mode 100644 index 0000000000..91f8e5de51 --- /dev/null +++ b/src/main/java/org/myrobotlab/programab/models/Template.java @@ -0,0 +1,51 @@ +package org.myrobotlab.programab.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; + +//@JacksonXmlRootElement(localName = "template") +//@JsonIgnoreProperties(ignoreUnknown = true) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Template { + // @JacksonXmlElementWrapper(useWrapping = false) + + @JacksonXmlProperty(localName = "template") + + @JacksonXmlText + public String text; + + +public Oob oob; + +// @JsonProperty("ignorable") +// public List oob; +// +// public List getOob() { +// return oob; +// } +// +// public void setOob(List oob) { +// this.oob = oob; +// } + + public static void main(String[] args) { + + try { + + // String xml = ""; + // String xml = ""; + String xml = ""; + + XmlMapper xmlMapper = new XmlMapper(); + Template template = xmlMapper.readValue(xml, Template.class); + + System.out.println(template); + + } catch(Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/org/myrobotlab/service/ProgramAB.java b/src/main/java/org/myrobotlab/service/ProgramAB.java index 4555cb6a87..fa90421d4e 100644 --- a/src/main/java/org/myrobotlab/service/ProgramAB.java +++ b/src/main/java/org/myrobotlab/service/ProgramAB.java @@ -16,11 +16,12 @@ import org.alicebot.ab.Bot; import org.alicebot.ab.Category; import org.alicebot.ab.Chat; -import org.alicebot.ab.MagicBooleans; import org.alicebot.ab.ProgramABListener; import org.apache.commons.lang3.StringUtils; +import org.myrobotlab.framework.Message; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.interfaces.Attachable; +import org.myrobotlab.generics.SlidingWindowList; import org.myrobotlab.image.Util; import org.myrobotlab.io.FileIO; import org.myrobotlab.logging.LoggerFactory; @@ -28,13 +29,12 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.logging.SimpleLogPublisher; import org.myrobotlab.programab.BotInfo; -import org.myrobotlab.programab.PredicateEvent; import org.myrobotlab.programab.Response; import org.myrobotlab.programab.Session; +import org.myrobotlab.programab.models.Event; import org.myrobotlab.service.config.ProgramABConfig; import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.Locale; -import org.myrobotlab.service.data.TopicChange; import org.myrobotlab.service.data.Utterance; import org.myrobotlab.service.interfaces.LocaleProvider; import org.myrobotlab.service.interfaces.LogPublisher; @@ -72,6 +72,11 @@ public class ProgramAB extends Service private static final long serialVersionUID = 1L; + /** + * history of topic changes + */ + protected List topicHistory = new SlidingWindowList<>(100); + /** * useGlobalSession true will allow the sleep member to control session focus */ @@ -85,7 +90,7 @@ public class ProgramAB extends Service Map bots = new TreeMap<>(); /** - * Mapping a bot to a userName and chat session + * Mapping a bot to a username and chat session */ Map sessions = new TreeMap<>(); @@ -140,7 +145,7 @@ public List scanForBots(String path) { if (checkIfValid(file)) { info("found %s bot directory", file.getName()); botDirs.add(file); - addBotPath(file.getAbsolutePath()); + addBot(file.getAbsolutePath()); } } return botDirs; @@ -183,7 +188,7 @@ public void addTextPublisher(TextPublisher service) { } public int getMaxConversationDelay() { - return getCurrentSession().maxConversationDelay; + return getConfig().maxConversationDelay; } /** @@ -196,105 +201,101 @@ public int getMaxConversationDelay() { * */ public Response getResponse(String text) { - return getResponse(getCurrentUserName(), text); + return getResponse(getUsername(), text); } /** * This method has the side effect of switching which bot your are currently * chatting with. * - * @param userName + * @param username * - the query string to the bot brain * @param text * - the user that is sending the query * @return the response for a user from a bot given the input text. */ - public Response getResponse(String userName, String text) { - return getResponse(userName, getCurrentBotName(), text); + public Response getResponse(String username, String text) { + return getResponse(username, getBotType(), text); } /** * Full get response method . Using this method will update the current * user/bot name if different from the current session. * - * @param userName + * @param username * username - * @param botName + * @param botType * bot name * @param text * utterace * @return programab response to utterance * */ - public Response getResponse(String userName, String botName, String text) { - return getResponse(userName, botName, text, true); + public Response getResponse(String username, String botType, String text) { + return getResponse(username, botType, text, true); } /** * Gets a response and optionally update if this is the current bot session * that's active globally. * - * @param userName - * username - * @param botName - * botname + * @param username + * - user request a response + * + * @param botType + * - bot type providing the response + * * @param text - * utterance + * - query * * @param updateCurrentSession - * (specify if the currentbot/currentuser name should be updated in - * the programab service.) - * @return the response + * - switch the current focus, so that the current session is the + * username and bot type in the parameter, publishSession will + * publish the new session if different * - * TODO - no one cares about starting sessions, starting a new session - * could be as simple as providing a different username, or botname in - * getResponse and a necessary session could be created + * @return the response * */ - public Response getResponse(String userName, String botName, String text, boolean updateCurrentSession) { - Session session = getSession(userName, botName); + public Response getResponse(String username, String botType, String text, boolean updateCurrentSession) { + Session session = getSession(username, botType); // if a session with this user and bot does not exist // attempt to create it if (session == null) { - session = startSession(userName, botName); + session = startSession(username, botType, updateCurrentSession); if (session == null) { - error("username or bot name not valid %s %s", userName, botName); + error("username or bot name not valid %s %s", username, botType); return null; } } - // update the current session if we want to change which bot is at - // attention. - if (updateCurrentSession) { - setCurrentUserName(userName); - setCurrentBotName(botName); + if (updateCurrentSession && (!getUsername().equals(username) || !getBotType().equals(botType))) { + setUsername(username); + setBotType(botType); } - // Get the actual bots aiml based response for this session log.info("getResponse({})", text); Response response = session.getResponse(text); - // EEK! clean up the API! - invoke("publishRequest", text); // publisher used by uis + invoke("publishRequest", text); invoke("publishResponse", response); invoke("publishText", response.msg); return response; } - private Bot getBot(String botName) { - return bots.get(botName).getBot(); + private Bot getBot(String botType) { + return bots.get(botType).getBot(); } - private BotInfo getBotInfo(String botName) { - if (botName == null) { + private BotInfo getBotInfo(String botType) { + if (botType == null) { error("getBotinfo(null) not valid"); return null; } - BotInfo botInfo = bots.get(botName); + BotInfo botInfo = bots.get(botType); if (botInfo == null) { - error("botInfo(%s) is null", botName); + error("botInfo(%s) is null", botType); return null; } @@ -312,12 +313,24 @@ public void repetitionCount(int val) { org.alicebot.ab.MagicNumbers.repetition_count = val; } + /** + * get the "current" session if it exists + * + * @return + */ public Session getSession() { - return getSession(getCurrentUserName(), getCurrentBotName()); + return getSession(getUsername(), getBotType()); } - public Session getSession(String userName, String botName) { - String sessionKey = getSessionKey(userName, botName); + /** + * get a specific user & botType session + * + * @param user + * @param botType + * @return + */ + public Session getSession(String user, String botType) { + String sessionKey = getSessionKey(user, botType); if (sessions.containsKey(sessionKey)) { return sessions.get(sessionKey); } else { @@ -325,12 +338,27 @@ public Session getSession(String userName, String botName) { } } - public void removePredicate(String userName, String predicateName) { - removePredicate(userName, getCurrentBotName(), predicateName); + /** + * remove a specific user and current bot types predicate + */ + public void removePredicate(String user, String predicateName) { + removePredicate(user, getBotType(), predicateName); } - public void removePredicate(String userName, String botName, String predicateName) { - getSession(userName, botName).remove(predicateName); + /** + * remove an explicit user and botType's predicate + * + * @param user + * @param botType + * @param name + */ + public void removePredicate(String user, String botType, String name) { + Session session = getSession(user, botType); + if (session != null) { + session.remove(name); + } else { + error("could not remove predicate %s from session %s<->%s session does not exist", user, botType, name); + } } /** @@ -342,8 +370,15 @@ public void removePredicate(String userName, String botName, String predicateNam * value to add to the set */ public void addToSet(String setName, String setValue) { + if (setName == null || setValue == null) { + error("addToSet(%s,%s) cannot have name or value null", setName, setValue); + return; + } + setName = setName.toLowerCase().trim(); + setValue = setValue.trim(); + // add to the set for the bot. - Bot bot = getBot(getCurrentBotName()); + Bot bot = getBot(getBotType()); AIMLSet updateSet = bot.setMap.get(setName); setValue = setValue.toUpperCase().trim(); if (updateSet != null) { @@ -371,8 +406,17 @@ public void addToSet(String setName, String setValue) { * - the value */ public void addToMap(String mapName, String key, String value) { + + if (mapName == null || key == null || value == null) { + error("addToMap(%s,%s,%s) mapname, key or value cannot be null", mapName, key, value); + return; + } + mapName = mapName.toLowerCase().trim(); + key = key.toUpperCase().trim(); + + // add an entry to the map. - Bot bot = getBot(getCurrentBotName()); + Bot bot = getBot(getBotType()); AIMLMap updateMap = bot.mapMap.get(mapName); key = key.toUpperCase().trim(); if (updateMap != null) { @@ -388,41 +432,87 @@ public void addToMap(String mapName, String key, String value) { } } - public void setPredicate(String predicateName, String predicateValue) { - setPredicate(getCurrentUserName(), predicateName, predicateValue); + public void setPredicate(String name, String value) { + setPredicate(getUsername(), name, value); } - public void setPredicate(String userName, String predicateName, String predicateValue) { - setPredicate(userName, getCurrentBotName(), predicateName, predicateValue); + /** + * Sets a specific user and current bot predicate to a value. Useful when + * setting predicate values of a session, when the user previously was an + * unknown human to a new or previously known user. + * + * @param username + * @param name + * @param value + */ + public void setPredicate(String username, String name, String value) { + setPredicate(username, getBotType(), name, value); } - public void setPredicate(String userName, String botName, String predicateName, String predicateValue) { - Session session = getSession(userName, botName); + /** + * Sets a predicate for a session keyed by username and bottype. If the + * session does not currently exist, it will make a new session for that user. + * + * @param username + * @param botType + * @param name + * @param value + */ + public void setPredicate(String username, String botType, String name, String value) { + Session session = getSession(username, botType); if (session != null) { - session.setPredicate(predicateName, predicateValue); + session.setPredicate(name, value); + } else { + // attempt to create a session if it doesn't exist + session = startSession(username, botType, false); + if (session != null) { + session.setPredicate(name, value); + } else { + error("could not create session"); + } } } - @Deprecated - public void unsetPredicate(String userName, String predicateName) { - removePredicate(userName, getCurrentBotName(), predicateName); + @Deprecated /* use removePredicate */ + public void unsetPredicate(String username, String predicateName) { + removePredicate(username, getBotType(), predicateName); } + /** + * Get a predicate's value for the current session + * + * @param predicateName + * @return + */ public String getPredicate(String predicateName) { - return getPredicate(getCurrentUserName(), predicateName); + return getPredicate(getUsername(), predicateName); } - public String getPredicate(String userName, String predicateName) { - return getPredicate(userName, getCurrentBotName(), predicateName); + /** + * get a specified users's predicate value for the current botType session + * + * @param username + * @param predicateName + * @return + */ + public String getPredicate(String username, String predicateName) { + return getPredicate(username, getBotType(), predicateName); } - public String getPredicate(String userName, String botName, String predicateName) { - Session s = getSession(userName, botName); + /** + * With a session key, get a specific predicate value + * + * @param username + * @param botType + * @param predicateName + * @return + */ + public String getPredicate(String username, String botType, String predicateName) { + Session s = getSession(username, botType); if (s == null) { - // If that session doesn't currently exist, let's start it. - s = startSession(userName, botName); + s = startSession(username, botType, false); if (s == null) { - log.warn("Error starting programAB session between bot {} and user {}", userName, botName); + log.warn("Error starting programAB session between bot {} and user {}", username, botType); return null; } } @@ -432,8 +522,8 @@ public String getPredicate(String userName, String botName, String predicateName /** * Only respond if the last response was longer than delay ms ago * - * @param userName - * - current userName + * @param username + * - current username * @param text * - text to get a response * @param delay @@ -442,11 +532,11 @@ public String getPredicate(String userName, String botName, String predicateName * @throws IOException * boom */ - public Response troll(String userName, String text, Long delay) throws IOException { - Session session = getSession(userName, getCurrentBotName()); + public Response troll(String username, String text, Long delay) throws IOException { + Session session = getSession(username, getBotType()); long delta = System.currentTimeMillis() - session.lastResponseTime.getTime(); if (delta > delay) { - return getResponse(userName, text); + return getResponse(username, text); } else { log.info("Skipping response, minimum delay since previous response not reached."); return null; @@ -461,13 +551,13 @@ public boolean isEnableAutoConversation() { * Return a list of all patterns that the current AIML Bot knows to match * against. * - * @param botName + * @param botType * the bots name from which to return it's patterns. * @return a list of all patterns loaded into the aiml brain */ - public ArrayList listPatterns(String botName) { + public ArrayList listPatterns(String botType) { ArrayList patterns = new ArrayList(); - Bot bot = getBot(botName); + Bot bot = getBot(botType); for (Category c : bot.brain.getCategories()) { patterns.add(c.getPattern()); } @@ -508,6 +598,9 @@ public Response publishResponse(Response response) { @Override public String publishText(String text) { + if (text == null || text.length() == 0) { + return ""; + } // TODO: this should not be done here. // clean up whitespaces & cariage return text = text.replaceAll("\\n", " "); @@ -535,21 +628,21 @@ public String publishOOBText(String oobText) { /** * This method will close the current bot, and reload it from AIML It then - * will then re-establish only the session associated with userName. + * will then re-establish only the session associated with username. * - * @param userName + * @param username * username for the session - * @param botName + * @param botType * the bot name being chatted with * @throws IOException * boom * */ - public void reloadSession(String userName, String botName) throws IOException { - Session session = getSession(userName, botName); + public void reloadSession(String username, String botType) throws IOException { + Session session = getSession(username, botType); if (session != null) { session.reload(); - info("reloaded session %s <-> %s ", userName, botName); + info("reloaded session %s <-> %s ", username, botType); } } @@ -559,7 +652,7 @@ public void reloadSession(String userName, String botName) throws IOException { * @return */ public Map getPredicates() { - return getPredicates(config.currentUserName, config.currentBotName); + return getPredicates(config.username, config.botType); } /** @@ -567,8 +660,8 @@ public Map getPredicates() { * * @return */ - public Map getPredicates(String userName, String botName) { - Session session = getSession(userName, botName); + public Map getPredicates(String username, String botType) { + Session session = getSession(username, botType); if (session != null) { return session.getPredicates(); } @@ -585,15 +678,15 @@ public void savePredicates() { } public void setEnableAutoConversation(boolean enableAutoConversation) { - getSession().enableTrolling = enableAutoConversation; + getConfig().enableTrolling = enableAutoConversation; } - public void setMaxConversationDelay(int maxConversationDelay) { - getSession().maxConversationDelay = maxConversationDelay; + public boolean getEnableAutoConversation() { + return getConfig().enableTrolling; } - public void setProcessOOB(boolean processOOB) { - getSession().processOOB = processOOB; + public void setMaxConversationDelay(int maxConversationDelay) { + getConfig().maxConversationDelay = maxConversationDelay; } /** @@ -605,128 +698,105 @@ public void setProcessOOB(boolean processOOB) { * value to set for current bot/session */ public void setBotProperty(String name, String value) { - setBotProperty(getCurrentBotName(), name, value); + setBotProperty(getBotType(), name, value); } /** * set a bot property - the result will be serialized to config/properties.txt * - * @param botName + * @param botType * bot name * @param name * bot property name * @param value * value to set the property too */ - public void setBotProperty(String botName, String name, String value) { - info("setting %s property %s:%s", getCurrentBotName(), name, value); - BotInfo botInfo = getBotInfo(botName); + public void setBotProperty(String botType, String name, String value) { + info("setting %s property %s:%s", getBotType(), name, value); + BotInfo botInfo = getBotInfo(botType); name = name.trim(); value = value.trim(); botInfo.setProperty(name, value); } public void removeBotProperty(String name) { - removeBotProperty(getCurrentBotName(), name); + removeBotProperty(getBotType(), name); } - public void removeBotProperty(String botName, String name) { - info("removing %s property %s", getCurrentBotName(), name); - BotInfo botInfo = getBotInfo(botName); + public void removeBotProperty(String botType, String name) { + info("removing %s property %s", getBotType(), name); + BotInfo botInfo = getBotInfo(botType); botInfo.removeProperty(name); } - public Session startSession() throws IOException { - return startSession(config.currentUserName); - } - - // FIXME - it should just set the current userName only - public Session startSession(String userName) throws IOException { - return startSession(userName, getCurrentBotName()); - } - - public Session startSession(String userName, String botName) { - return startSession(null, userName, botName, MagicBooleans.defaultLocale); - } - - @Deprecated /* path included for legacy */ - public Session startSession(String path, String userName, String botName) { - return startSession(path, userName, botName, MagicBooleans.defaultLocale); + /** + * Setting a session is only setting a key, to the active user and bot, its + * not starting a session, which is a different process done threw + * startSession. + * + * Sets username and botType. The session will be started if it can be when a + * getResponse is processed. "Active" session is just where the session key + * exists and is currently set via username and botType + * + * @param username + * @param botType + * @return + */ + public void setSession(String username, String botType) { + // replacing "focus" so + // current name and bottype is the + // one that will be used + setUsername(username); + setBotType(botType); } /** * Load the AIML 2.0 Bot config and start a chat session. This must be called - * after the service is created. + * after the service is created. If the session does not exist it will be + * created. If the session does exist then that session will be used. * - * @param path - * - the path to the ProgramAB directory where the bots aiml and - * config reside - * @param userName + * config.username and config.botType will be set in memory the specified + * values. The "current" session will be this session. + * + * @param username * - The new user name - * @param botName + * @param botType * - The name of the bot to load. (example: alice2) - * @param locale - * - The locale of the bot to ensure the aiml is loaded (mostly for - * Japanese support) FIXME - local is defined in the bot, - * specifically config/mrl.properties * - * reasons to deprecate: - * - * 1. I question the need to expose this externally at all - if the - * user uses getResponse(username, botname, text) then a session can - * be auto-started - there is really no reason not to auto-start. - * - * 2. path is completely invalid here - * - * 3. Locale is completely invalid - it is now part of the bot - * description in mrl.properties and shouldn't be defined externally, - * unles its pulled from Runtime * @return the session that is started */ - public Session startSession(String path, String userName, String botName, java.util.Locale locale) { - - /* - * not wanted or needed if (path != null) { addBotPath(path); } - */ - - Session session = getSession(userName, botName); + public Session startSession(String username, String botType, boolean setAsCurrent) { - if (session != null) { - log.info("session {} already exists - will use it", getSessionKey(userName, botName)); - setCurrentSession(userName, botName); - return session; + if (username == null || botType == null) { + error("username nor bot type can be null"); + return null; } - // create a new session - log.info("creating new sessions"); - BotInfo botInfo = getBotInfo(botName); - if (botInfo == null) { - error("cannot create session %s is not a valid botName", botName); + if (!bots.containsKey(botType)) { + error("bot type %s is not valid, list of possible types are %s", botType, bots.keySet()); return null; } - session = new Session(this, userName, botInfo); - sessions.put(getSessionKey(userName, botName), session); + if (setAsCurrent) { + // really sets the key of the active session username <-> botType + // but next getResponse will use this session + setSession(username, botType); + } - log.info("Started session for bot botName:{} , userName:{}", botName, userName); - setCurrentSession(userName, botName); - return session; - } + String sessionKey = getSessionKey(username, botType); + if (sessions.containsKey(sessionKey)) { + log.info("session exists returning existing"); + return sessions.get(sessionKey); + } - /** - * setting the current session is equivalent to setting current user name and - * current bot name - * - * @param userName - * username - * @param botName - * botname - * - */ - public void setCurrentSession(String userName, String botName) { - setCurrentUserName(userName); - setCurrentBotName(botName); + log.info("creating new session {}<->{} replacing {}", username, botType, setAsCurrent); + BotInfo botInfo = getBotInfo(botType); + Session session = new Session(this, username, botInfo); + sessions.put(sessionKey, session); + + // get session + return getSession(); } /** @@ -736,7 +806,7 @@ public void setCurrentSession(String userName, String botName) { * @param c */ public void addCategory(Category c) { - Bot bot = getBot(getCurrentBotName()); + Bot bot = getBot(getBotType()); bot.brain.addCategory(c); } @@ -758,16 +828,18 @@ public void addCategory(String pattern, String template, String that) { public void addCategory(String pattern, String template) { addCategory(pattern, template, "*"); } - + /** - * Verifies and adds a new path to the search directories for bots + * Verifies and adds a new path to the search directories for bots. Bots of + * aiml live in directories which represent their "type" The directory names + * must be unique. * * @param path * the path to add a bot from * @return the path if successful. o/w null * */ - public String addBotPath(String path) { + public String addBot(String path) { // verify the path is valid File botPath = new File(path); File verifyAiml = new File(FileIO.gluePaths(path, "aiml")); @@ -783,12 +855,12 @@ public String addBotPath(String path) { BotInfo botInfo = new BotInfo(this, botPath); - // key'ing on "path" probably would be better and only displaying "name" - // then there would be no put/collisions only duplicate names - // (preferrable) + if (bots.containsKey(botInfo.botType)) { + log.info("replacing bot %s with new bot definition", botInfo.botType); + } - bots.put(botInfo.name, botInfo); - botInfo.img = getBotImage(botInfo.name); + bots.put(botInfo.botType, botInfo); + botInfo.img = getBotImage(botInfo.botType); broadcastState(); } else { @@ -798,41 +870,79 @@ public String addBotPath(String path) { return path; } - @Deprecated /* for legacy - use addBotsDir */ - public String setPath(String path) { - // This method is not good, because it doesn't take the full path - // from input and there is a buried "hardcoded" value which no one knows - // about - addBotsDir(path + File.separator + "bots"); - - return path; + @Deprecated /* use setBotType */ + public void setCurrentBotName(String botType) { + setBotType(botType); } - public void setCurrentBotName(String botName) { - config.currentBotName = botName; - invoke("getBotImage", botName); - broadcastState(); + /** + * Sets the current bot type to a set of aiml folders previously added via + * configuration or through the addBot(path) function. + * + * You can get a list of possible configured bot types through the method + * getBots() + * + * @param botType + */ + public void setBotType(String botType) { + if (botType == null) { + error("bot type cannot be null"); + return; + } + + if (bots.size() == 0) { + error("bot paths must be set before a bot type is set"); + } + + if (!bots.containsKey(botType)) { + error("cannot set bot %s, no valid type found, possible values are %s", botType, bots.keySet()); + return; + } + String prev = config.botType; + config.botType = botType; + if (!botType.equals(prev)) { + invoke("getBotImage", botType); + broadcastState(); + } } - public void setCurrentUserName(String currentUserName) { - config.currentUserName = currentUserName; - broadcastState(); + public void setUsername(String username) { + if (username == null) { + error("username cannot be null"); + return; + } + String prev = config.username; + config.username = username; + if (!username.equals(prev)) { + broadcastState(); + } } - public Session getCurrentSession() { - return sessions.get(getSessionKey(getCurrentUserName(), getCurrentBotName())); + public String getSessionKey(String username, String botType) { + return String.format("%s <-> %s", username, botType); } - public String getSessionKey(String userName, String botName) { - return String.format("%s <-> %s", userName, botName); + /** + * Simple preferred way to get the user's name + * + * @return + */ + public String getUsername() { + return config.username; } + @Deprecated /* of course it will be "current" - use getUser() */ public String getCurrentUserName() { - return config.currentUserName; + return getUsername(); } + @Deprecated /* use getBotType() */ public String getCurrentBotName() { - return config.currentBotName; + return getBotType(); + } + + public String getBotType() { + return config.botType; } /** @@ -930,17 +1040,44 @@ public boolean setPeerSearch(boolean b) { @Override public void startService() { - super.startService(); + try { + super.startService(); - logPublisher = new SimpleLogPublisher(this); - logPublisher.filterClasses(new String[] { "org.alicebot.ab.Graphmaster", "org.alicebot.ab.MagicBooleans", "class org.myrobotlab.programab.MrlSraixHandler" }); - Logging logging = LoggingFactory.getInstance(); - logging.setLevel("org.alicebot.ab.Graphmaster", "DEBUG"); - logging.setLevel("org.alicebot.ab.MagicBooleans", "DEBUG"); - logging.setLevel("class org.myrobotlab.programab.MrlSraixHandler", "DEBUG"); - logPublisher.start(); + // scan for bots + if (config.botDir != null) { + scanForBots(config.botDir); + } - scanForBots(getResourceDir()); + // explicitly setting bots overrides scans + if (config.bots != null && config.bots.size() > 0) { + for (String botPath : config.bots) { + addBot(botPath); + } + } + + if (config.username != null) { + setUsername(config.username); + } + + if (config.botType != null) { + setBotType(config.botType); + } + + if (config.startTopic != null) { + setTopic(config.startTopic); + } + + logPublisher = new SimpleLogPublisher(this); + logPublisher.filterClasses(new String[] { "org.alicebot.ab.Graphmaster", "org.alicebot.ab.MagicBooleans", "class org.myrobotlab.programab.MrlSraixHandler" }); + Logging logging = LoggingFactory.getInstance(); + logging.setLevel("org.alicebot.ab.Graphmaster", "DEBUG"); + logging.setLevel("org.alicebot.ab.MagicBooleans", "DEBUG"); + logging.setLevel("class org.myrobotlab.programab.MrlSraixHandler", "DEBUG"); + logPublisher.start(); + + } catch (Exception e) { + error(e); + } } @@ -992,7 +1129,7 @@ public String publishLog(String msg) { } public BotInfo getBotInfo() { - return getBotInfo(config.currentBotName); + return getBotInfo(config.botType); } /** @@ -1002,21 +1139,25 @@ public BotInfo getBotInfo() { * boom * */ - public void reload() throws IOException { - reloadSession(getCurrentUserName(), getCurrentBotName()); + public void reload() { + try { + reloadSession(getUsername(), getBotType()); + } catch (Exception e) { + error(e); + } } public String getBotImage() { - return getBotImage(getCurrentBotName()); + return getBotImage(getBotType()); } - public String getBotImage(String botName) { + public String getBotImage(String botType) { BotInfo botInfo = null; String path = null; try { - botInfo = getBotInfo(botName); + botInfo = getBotInfo(botType); if (botInfo != null) { path = FileIO.gluePaths(botInfo.path.getAbsolutePath(), "bot.png"); File check = new File(path); @@ -1026,16 +1167,16 @@ public String getBotImage(String botName) { } } catch (Exception e) { - info("image for %s cannot be found %s", botName, e.getMessage()); + info("image for %s cannot be found %s", botType, e.getMessage()); } return getResourceImage("default.png"); } - public String getAimlFile(String botName, String name) { - BotInfo botInfo = getBotInfo(botName); + public String getAimlFile(String botType, String name) { + BotInfo botInfo = getBotInfo(botType); if (botInfo == null) { - error("cannot get bot %s", botName); + error("cannot get bot %s", botType); return null; } @@ -1053,10 +1194,10 @@ public String getAimlFile(String botName, String name) { return ret; } - public void saveAimlFile(String botName, String filename, String data) { - BotInfo botInfo = getBotInfo(botName); + public void saveAimlFile(String botType, String filename, String data) { + BotInfo botInfo = getBotInfo(botType); if (botInfo == null) { - error("cannot get bot %s", botName); + error("cannot get bot %s", botType); return; } @@ -1090,44 +1231,10 @@ public ProgramABConfig getConfig() { return config; } - @Override - public ProgramABConfig apply(ProgramABConfig c) { - super.apply(c); - if (c.bots != null && c.bots.size() > 0) { - // bots.clear(); - for (String botPath : c.bots) { - addBotPath(botPath); - } - } - - if (c.botDir == null) { - c.botDir = getResourceDir(); - } - - List botsFromScanning = scanForBots(c.botDir); - for (File file : botsFromScanning) { - addBotPath(file.getAbsolutePath()); - } - - if (c.currentUserName != null) { - setCurrentUserName(c.currentUserName); - } - - if (c.currentBotName != null) { - setCurrentBotName(c.currentBotName); - } - - if (c.startTopic != null) { - setTopic(c.startTopic); - } - - - return c; - } - public static void main(String args[]) { try { LoggingFactory.init("INFO"); + Runtime.startConfig("dev"); // Runtime.start("gui", "SwingGui"); Runtime runtime = Runtime.getInstance(); @@ -1164,7 +1271,7 @@ public static void main(String args[]) { } } - public void addBotsDir(String path) { + public void addBots(String path) { if (path == null) { error("set path can not be null"); @@ -1182,14 +1289,14 @@ public void addBotsDir(String path) { if (check.exists() && check.isDirectory()) { log.info("found %d possible bot directories", check.listFiles().length); for (File f : check.listFiles()) { - addBotPath(f.getAbsolutePath()); + addBot(f.getAbsolutePath()); } } } @Override - synchronized public void onChangePredicate(Chat chat, String predicateName, String result) { - log.info("{} on predicate change {}={}", chat.bot.name, predicateName, result); + synchronized public void onChangePredicate(Chat chat, String predicateName, String value) { + log.info("{} on predicate change {}={}", chat.bot.name, predicateName, value); // a little janky because program-ab doesn't know the predicate filename, // because it does know the "user" @@ -1200,15 +1307,7 @@ synchronized public void onChangePredicate(Chat chat, String predicateName, Stri for (Session s : sessions.values()) { if (s.chat == chat) { // found session saving predicates - invoke("publishPredicate", s, predicateName, result); - // botname is the name of the bot currentBotName is the aiml folder that is - // mostly equivalent to its "type" - if ("currentBotName".equals(predicateName)) { - setCurrentBotName(result); - } - if ("name".equals(predicateName)) { - setCurrentUserName(result); - } + invoke("publishPredicate", s, predicateName, value); s.savePredicates(); return; } @@ -1229,17 +1328,19 @@ synchronized public void onChangePredicate(Chat chat, String predicateName, Stri * - new value of predicate * @return */ - public PredicateEvent publishPredicate(Session session, String name, String value) { - PredicateEvent event = new PredicateEvent(); - event.id = String.format("%s<->%s", session.userName, session.botInfo.name); - event.userName = session.userName; - event.botName = session.botInfo.name; + public Event publishPredicate(Session session, String name, String value) { + Event event = new Event(); + event.id = String.format("%s<->%s", session.username, session.botInfo.botType); + event.user = session.username; + event.botname = session.botInfo.botType; event.name = name; event.value = value; if ("topic".equals(name) && value != null && !value.equals(session.currentTopic)) { - invoke("publishTopic", new TopicChange(session.userName, session.botInfo.name, value, session.currentTopic)); + Event topicChange = new Event(getName(), session.username, session.botInfo.botType, value); + invoke("publishTopic", topicChange); session.currentTopic = value; + topicHistory.add(topicChange); } return event; @@ -1310,14 +1411,14 @@ public void sleep() { @Override public void onUtterance(Utterance utterance) throws Exception { - + log.info("Utterance Received " + utterance); boolean talkToBots = false; // TODO: reconcile having different name between the discord bot username // and the programab bot name. Mr. Turing is not actually Alice.. and vice // versa. - String botName = utterance.channelBotName; + String botType = utterance.channelBotName; // prevent bots going off the rails if (utterance.isBot && talkToBots) { @@ -1326,7 +1427,7 @@ public void onUtterance(Utterance utterance) throws Exception { } // Don't talk to myself, though I should be a bot.. - if (utterance.username.contentEquals(botName)) { + if (utterance.username.contentEquals(botType)) { log.info("Don't talk to myself."); return; } @@ -1340,7 +1441,7 @@ public void onUtterance(Utterance utterance) throws Exception { // TODO: don't talk to bots.. it won't go well.. // TODO: the discord api can provide use the list of mentioned users. // for now.. we'll just see if we see Mr. Turing as a substring. - config.sleep = (config.sleep || utterance.text.contains("@")) && !utterance.text.contains(botName); + config.sleep = (config.sleep || utterance.text.contains("@")) && !utterance.text.contains(botType); if (!config.sleep) { shouldIRespond = true; } @@ -1354,7 +1455,7 @@ public void onUtterance(Utterance utterance) throws Exception { String utteranceDisp = utterance.text; // let's strip the @+botname from the beginning of the utterance i guess. // Strip the botname from the utterance passed to programab. - utteranceDisp = utteranceDisp.replace("@" + botName, ""); + utteranceDisp = utteranceDisp.replace("@" + botType, ""); Response resp = getResponse(utterance.username, utteranceDisp); if (resp != null && !StringUtils.isEmpty(resp.msg)) { // Ok.. now what? respond to the user ... @@ -1375,17 +1476,18 @@ public void onUtterance(Utterance utterance) throws Exception { } } } - + /** * This receiver can take a config published by another service and sync * predicates from it + * * @param cfg */ public void onConfig(ServiceConfig cfg) { - Yaml yaml = new Yaml(); + Yaml yaml = new Yaml(); String yml = yaml.dumpAsMap(cfg); Map cfgMap = yaml.load(yml); - + for (Map.Entry entry : cfgMap.entrySet()) { if (entry.getValue() == null) { setPredicate("cfg_" + entry.getKey(), null); @@ -1393,7 +1495,7 @@ public void onConfig(ServiceConfig cfg) { setPredicate("cfg_" + entry.getKey(), entry.getValue().toString()); } } - + invoke("getPredicates"); } @@ -1404,27 +1506,74 @@ public Utterance publishUtterance(Utterance utterance) { /** * New topic published when it changes + * * @param topicChange * @return */ - public TopicChange publishTopic(TopicChange topicChange) { + public Event publishTopic(Event topicChange) { return topicChange; } - public String getTopic() { - return getPredicate(getCurrentUserName(), "topic"); + public String getTopic() { + return getPredicate(getUsername(), "topic"); } - - public String getTopic(String username) { + + public String getTopic(String username) { return getPredicate(username, "topic"); } - - public void setTopic(String username, String topic) { + + public void setTopic(String username, String topic) { setPredicate(username, "topic", topic); } - - public void setTopic(String topic) { - setPredicate(getCurrentUserName(), "topic", topic); + + public void setTopic(String topic) { + setPredicate(getUsername(), "topic", topic); + } + + /** + * Published when a new session is created + * + * @param session + * @return + */ + public Event publishSession(Event session) { + return session; + } + + /** + * clear all sessions + */ + public void clear() { + log.info("clearing sessions"); + sessions.clear(); + } + + /** + *
+   * A mechanism to publish a message directly from aiml.
+   * The subscriber can interpret the message and do something with it.
+   * In the case of InMoov for example, the unaddressed messages are processed
+   * as python method calls. This remove direct addressing from the aiml!
+   * And allows a great amount of flexibility on how the messages are
+   * interpreted, without polluting the aiml or ProgramAB.
+   * 
+   * The oob syntax is:
+   *  <oob>
+   *    <mrljson>
+   *        [{method:on_new_user, data:[{"name":"<star/>"}]}]
+   *    </mrljson>
+   * </oob>
+   * 
+   * 
+   * Full typed parameters are supported without conversions.
+   * 
+   * 
+ * + * @param msg + * @return + */ + public Message publishMessage(Message msg) { + return msg; } } diff --git a/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java b/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java index ce9ae14033..0012b6864b 100644 --- a/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java +++ b/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java @@ -6,14 +6,11 @@ import org.myrobotlab.framework.Plan; public class ProgramABConfig extends ServiceConfig { - - @Deprecated /* unused text filters */ - public String[] textFilters; - + /** - * a directory ProgramAB will scan for new bots + * a directory ProgramAB will scan for new bots on startup */ - public String botDir; + public String botDir = "resource/ProgramAB/"; /** * explicit bot directories @@ -24,13 +21,13 @@ public class ProgramABConfig extends ServiceConfig { * current sessions bot name, it must match a botname that was scanned * currently with ProgramAB Alice, Dr.Who, Mr. Turing and Ency */ - public String currentBotName = "Alice"; + public String botType = "Alice"; /** * User name currently interacting with the bot. Setting it here will * default it. */ - public String currentUserName = "human"; + public String username = "human"; /** * sleep current state of the sleep if globalSession is used true : ProgramAB @@ -45,7 +42,19 @@ public class ProgramABConfig extends ServiceConfig { * a new session if available, this means a config/{username}.predicates.txt * will need to exist with a topic field */ - public String startTopic = "unknown"; + public String startTopic = null; + + /** + * bot will prompt users if enabled trolling is true + * after maxConversationDelay has passed + */ + public boolean enableTrolling = false; + + + /** + * Number of milliseconds before the robot starts talking on its own. + */ + public int maxConversationDelay = 5000; @Override public Plan getDefault(Plan plan, String name) { diff --git a/src/main/java/org/myrobotlab/service/data/SearchResults.java b/src/main/java/org/myrobotlab/service/data/SearchResults.java index 3981693c71..8a24b94674 100644 --- a/src/main/java/org/myrobotlab/service/data/SearchResults.java +++ b/src/main/java/org/myrobotlab/service/data/SearchResults.java @@ -32,6 +32,23 @@ public String getText() { } return sb.toString(); } + + // FIXME - probably should not do this - but leave it up to the service + // that created it and keep this data object more simple + public String getHtml() { + StringBuilder sb = new StringBuilder(); + for (String t : text) { + sb.append(t); + } + + for (ImageData img : images) { + sb.append(""); + sb.append("\n"); + } + return sb.toString(); + } public String getTextAndImages() { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/org/myrobotlab/service/data/TopicChange.java b/src/main/java/org/myrobotlab/service/data/TopicChange.java deleted file mode 100644 index af25c05f06..0000000000 --- a/src/main/java/org/myrobotlab/service/data/TopicChange.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.myrobotlab.service.data; - -/** - * Purpose of this class is to be a simple data POJO - * for ProgramAB topic changes. This will be useful to - * interface the state machine implemented in AIML where - * topic changes represent changes of state. - * - * @author GroG - * - */ -public class TopicChange { - - /** - * previous topic or state in this state transition - */ - public String oldTopic; - - /** - * new topic or state name in this transition - */ - public String newTopic; - - /** - * the user name in this state change - usually - * current session userName - */ - public String userName; - - /** - * the botName in this state change - typically - * current session botName - */ - public String botName; - - public TopicChange(String userName, String botName, String newTopic, String oldTopic) { - this.userName = userName; - this.botName = botName; - this.newTopic = newTopic; - this.oldTopic = oldTopic; - } - - -} diff --git a/src/main/java/org/myrobotlab/service/meta/ProgramABMeta.java b/src/main/java/org/myrobotlab/service/meta/ProgramABMeta.java index 7430f362dd..94ce962ed2 100644 --- a/src/main/java/org/myrobotlab/service/meta/ProgramABMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/ProgramABMeta.java @@ -32,7 +32,8 @@ public ProgramABMeta() { // TODO: This is for CJK support in ProgramAB move this into the published // POM for ProgramAB so they are pulled in transiently. addDependency("org.apache.lucene", "lucene-analysis-common", "9.4.2"); - addDependency("org.apache.lucene", "lucene-analysis-kuromoji", "9.4.2"); + addDependency("org.apache.lucene", "lucene-analysis-kuromoji", "9.4.2"); + addDependency("org.openjdk.nashorn", "nashorn-core", "15.4"); addCategory("ai", "control"); } diff --git a/src/test/java/org/myrobotlab/service/ProgramABTest.java b/src/test/java/org/myrobotlab/service/ProgramABTest.java index a72f9e7b00..d3af491020 100644 --- a/src/test/java/org/myrobotlab/service/ProgramABTest.java +++ b/src/test/java/org/myrobotlab/service/ProgramABTest.java @@ -1,79 +1,132 @@ package org.myrobotlab.service; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import java.util.Map; +import org.junit.After; +import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; -import org.myrobotlab.framework.Service; import org.myrobotlab.io.FileIO; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.programab.BotInfo; import org.myrobotlab.programab.Response; +import org.myrobotlab.programab.Session; import org.myrobotlab.service.data.Locale; import org.slf4j.Logger; -public class ProgramABTest extends AbstractServiceTest { +public class ProgramABTest { public final static Logger log = LoggerFactory.getLogger(ProgramABTest.class); - private String botname = "lloyd"; + + static protected final String PIKACHU = "pikachu"; + + static protected final String LLOYD = "lloyd"; + // TODO: move this to test resources private String testResources = "src/test/resources/ProgramAB"; - private String path = null; - private ProgramAB testService; + + static private ProgramAB lloyd; + + static private ProgramAB pikachu; private String username = "testUser"; - public void addCategoryTest() throws IOException { - testService.addCategory("BOOG", "HOWDY"); - Response resp = testService.getResponse(username, "BOOG"); - assertTrue(resp.msg.equals("HOWDY")); + // This method runs once before any test method in the class + @BeforeClass + public static void setUpClass() { + System.out.println("BeforeClass - Runs once before any test method"); + lloyd = (ProgramAB) Runtime.start(LLOYD, "ProgramAB"); + pikachu = (ProgramAB) Runtime.start(PIKACHU, "ProgramAB"); + + // very first inits - all should work ! + assertEquals("4 standard", 4, lloyd.getBots().size()); + // should require very little to start ! - this is a requirement ! + Response response = lloyd.getResponse("Hi"); + + // expect Alice's aiml processed + assertTrue(response.msg.startsWith("Hi")); + + Session session = lloyd.getSession(); + assertEquals("default user should be human", "human", session.getUsername()); + assertEquals("default botType should be Alice", "Alice", session.getBotType()); + + } + + // This method runs once after all test methods in the class have been + // executed + @AfterClass + public static void tearDownClass() { + System.out.println("AfterClass - Runs once after all test methods"); + Runtime.release(LLOYD); + Runtime.release(PIKACHU); } - public Service createService() { - try { - // LoggingFactory.init("INFO"); - log.info("Setting up the Program AB Service ########################################"); - // Load the service under test - // a test robot - // TODO: this should probably be created by Runtime, - // OOB tags might not know what the service name is ?! - testService = (ProgramAB) Runtime.start(botname, "ProgramAB"); - testService.setPath(path); - // testService = new ProgramAB("simple"); - // testService.setPath("c:/mrl/develop/ProgramAB"); - - // start the service. - testService.startService(); - // load the bot brain for the chat with the user - testService.startSession(username, botname); - // clean out any aimlif the bot that might - // have been saved in a previous test run! - String aimlIFPath = path + "/bots/" + botname + "/aimlif"; - File aimlIFPathF = new File(aimlIFPath); - if (aimlIFPathF.isDirectory()) { - for (File f : aimlIFPathF.listFiles()) { - // if there's a file here. - log.info("Deleting pre-existing AIMLIF files : {}", f.getAbsolutePath()); - f.delete(); - } + // This method runs before each test method + @Before + public void setUp() { + System.out.println("Before - Runs before each test method"); + // Perform setup tasks specific to each test method + + // add a couple test bots + List bots = lloyd.scanForBots(testResources + "/bots"); + assertEquals("2 test bots", bots.size(), 2); + assertEquals("6 bots total", 6, lloyd.getBots().size()); + + pikachu.scanForBots(testResources + "/bots"); + + // validate newly created programab can by default start a session + Session session = lloyd.getSession(); + assertNotNull(session); + + lloyd.setBotType("lloyd"); + assertEquals("lloyd", lloyd.getBotType()); + + // validate error is called when invalid bot type set + + // load the bot brain for the chat with the user + lloyd.setSession(username, LLOYD); + assertEquals(username, lloyd.getUsername()); + // clean out any aimlif the bot that might + // have been saved in a previous test run! + String aimlIFPath = testResources + "/bots/" + LLOYD + "/aimlif"; + File aimlIFPathF = new File(aimlIFPath); + if (aimlIFPathF.isDirectory()) { + for (File f : aimlIFPathF.listFiles()) { + // if there's a file here. + log.info("Deleting pre-existing AIMLIF files : {}", f.getAbsolutePath()); + f.delete(); } - // TODO: same thing for predicates! (or other artifacts from a previous - // aiml - // test run) - } catch (Exception e) { - log.error("createService threw", e); } - return testService; } - public void listPatternsTest() { - ArrayList res = testService.listPatterns(botname); + // This method runs after each test method + @After + public void tearDown() { + System.out.println("After - Runs after each test method"); + } + + @Test + public void testAddCategoryTest() throws IOException { + lloyd.addCategory("ABCDEF", "ABCDEF"); + // String username = lloyd.getUsername(); + // username, + Response resp = lloyd.getResponse("ABCDEF"); + assertTrue(resp.msg.equals("ABCDEF")); + } + + @Test + public void testListPatterns() { + ArrayList res = lloyd.listPatterns(LLOYD); assertTrue(res.size() > 0); } @@ -82,42 +135,30 @@ public void listPatternsTest() { // stuff // @Test public void pannousTest() throws IOException { - Response resp = testService.getResponse(username, "SHOW ME INMOOV"); + Response resp = lloyd.getResponse(username, "SHOW ME INMOOV"); // System.out.println(resp); boolean contains = resp.msg.contains("http"); assertTrue(contains); } - @Before - public void setUp() { - // TODO: set the location for the temp folder via : - // System.getProperty("java.io.tmpdir") - // LoggingFactory.init("INFO"); - // testFolder.getRoot().getAbsolutePath() - try { - this.path = testFolder.getRoot().getAbsolutePath() + File.separator + "ProgramAB"; - FileIO.copy(testResources, path); - } catch (IOException e) { - log.warn("Error extracting resources for test. {}", testResources); - Assert.assertNotNull(e); - } - } - - public void sraixOOBTest() throws IOException { + @Test + public void testSraixOOB() throws IOException { // Response resp = testService.getResponse(username, "MRLSRAIX"); // System.out.println(resp); // boolean contains = resp.msg.contains("foobar"); // assertTrue(contains); - Response resp = testService.getResponse(username, "OOBMRLSRAIX"); + Response resp = lloyd.getResponse(username, "OOBMRLSRAIX"); // System.out.println(resp); boolean contains = resp.msg.contains("You are talking to lloyd"); assertTrue(contains); } - public void sraixTest() throws IOException { + @Test + public void testSraix() throws IOException { if (Runtime.hasInternet()) { - Response resp = testService.getResponse(username, "MRLSRAIX"); - //Response resp = testService.getResponse(username, "Why is the sky blue?"); + Response resp = lloyd.getResponse(username, "MRLSRAIX"); + // Response resp = testService.getResponse(username, "Why is the sky + // blue?"); // System.out.println(resp); // System.out.println(resp); boolean contains = resp.msg.contains("information"); @@ -125,75 +166,76 @@ public void sraixTest() throws IOException { } } + @Test public void testAddEntryToSetAndMaps() throws IOException { // TODO: This does NOT work yet! - Response resp = testService.getResponse(username, "Add Jabba to the starwarsnames set"); + Response resp = lloyd.getResponse(username, "Add Jabba to the starwarsnames SET"); assertEquals("Ok...", resp.msg); - resp = testService.getResponse(username, "Add jabba equals Jabba the Hut to the starwars map"); + resp = lloyd.getResponse(username, "Add jabba equals Jabba the Hut to the starwars MAP"); assertEquals("Ok...", resp.msg); - resp = testService.getResponse(username, "DO YOU LIKE Jabba?"); + resp = lloyd.getResponse(username, "DO YOU LIKE Jabba?"); assertEquals("Jabba the Hut is awesome.", resp.msg); // TODO : re-enable this one? // now test creating a new set. - resp = testService.getResponse(username, "Add bourbon to the whiskey set"); + resp = lloyd.getResponse(username, "Add bourbon to the whiskey SET"); assertEquals("Ok...", resp.msg); - resp = testService.getResponse(username, "NEWSETTEST bourbon"); + resp = lloyd.getResponse(username, "NEWSETTEST bourbon"); // assertEquals("bourbon is a whiskey", resp.msg); } @Test public void testJapanese() throws IOException { - - ProgramAB pikachu = (ProgramAB) Runtime.start("pikachu", "ProgramAB"); - pikachu.setPath(path); - // pikachu the service. - pikachu.startService(); + pikachu.scanForBots(testResources + "/bots"); + pikachu.setBotType("pikachu"); + // setting Japanese locality + pikachu.setLocale("ja"); // load the bot brain for the chat with the user - pikachu.startSession(path, username, "pikachu", new java.util.Locale("ja")); + pikachu.setSession(username, PIKACHU); Response resp = pikachu.getResponse("私はケビンです"); assertEquals("あなたに会えてよかったケビン", resp.msg); - pikachu.releaseService(); + Runtime.release(PIKACHU); } + @Test public void testLearn() throws IOException { // Response resp1 = testService.getResponse(session, "SET FOO BAR"); // System.out.println(resp1.msg); - Response resp = testService.getResponse(username, "LEARN AAA IS BBB"); + Response resp = lloyd.getResponse(username, "LEARN AAA IS BBB"); // System.out.println(resp.msg); - resp = testService.getResponse(username, "WHAT IS AAA"); + resp = lloyd.getResponse(username, "WHAT IS AAA"); assertEquals("BBB", resp.msg); } @Test public void testMultiSession() throws IOException { ProgramAB lloyd = (ProgramAB) Runtime.start("lloyd", "ProgramAB"); - lloyd.setPath(path); - // pikachu the service. - lloyd.startService(); + lloyd.setBotType("lloyd"); // load the bot brain for the chat with the user - lloyd.startSession(path, "user1", "lloyd"); + lloyd.setSession("user1", "lloyd"); Response res = lloyd.getResponse("My name is Kevin"); System.out.println(res); - lloyd.startSession(path, "user2", "lloyd"); + lloyd.setSession("user2", "lloyd"); res = lloyd.getResponse("My name is Grog"); System.out.println(res); - lloyd.startSession(path, "user1", "lloyd"); + lloyd.setSession("user1", "lloyd"); Response respA = lloyd.getResponse("What is my name?"); System.out.println(respA); - lloyd.startSession(path, "user2", "lloyd"); + lloyd.setSession("user2", "lloyd"); Response respB = lloyd.getResponse("What is my name?"); System.out.println(respB); - + lloyd.setSession(username, LLOYD); assertEquals("Kevin", respA.msg); assertEquals("Grog", respB.msg); - - // release this service. - lloyd.releaseService(); - } + @Test public void testOOBTags() throws Exception { - Response resp = testService.getResponse(username, "OOB TEST"); + + // Response resp = testService.getResponse(username, "OOB TEST"); + + ProgramAB lloyd = (ProgramAB) Runtime.start("lloyd", "ProgramAB"); + Response resp = lloyd.getResponse(username, "OOB TEST"); + assertEquals("OOB Tag Test", resp.msg); // TODO figure a mock object that can wait on a callback to let us know the @@ -201,161 +243,149 @@ public void testOOBTags() throws Exception { // wait up to 5 seconds for python service to start long maxWait = 6000; int i = 0; - Runtime.start("python", "Python"); - while (Runtime.getService("python") == null) { + + while (Runtime.getService("oobclock") == null) { Thread.sleep(100); - log.info("Waiting for python to start..."); + log.info("waiting for oobclock to start..."); i++; if (i > maxWait) { - Assert.assertFalse("Took too long to process OOB tag", i > maxWait); + Assert.assertFalse("took too long to process OOB tag", i > maxWait); } } - Assert.assertNotNull(Runtime.getService("python")); - + Assert.assertNotNull(Runtime.getService("oobclock")); + Runtime.release("oobclock"); } + @Test public void testPredicates() { // test removing the predicate if it exists - testService.setPredicate(username, "name", "foo1"); - String name = testService.getPredicate(username, "name"); + lloyd.setPredicate(username, "name", "foo1"); + String name = lloyd.getPredicate(username, "name"); // validate it's set properly assertEquals("foo1", name); - testService.removePredicate(username, "name"); + lloyd.removePredicate(username, "name"); // validate the predicate doesn't exist - name = testService.getPredicate(username, "name"); + name = lloyd.getPredicate(username, "name"); // TODO: is this valid? one would expect it would return null. assertEquals("unknown", name); // set a predicate - testService.setPredicate(username, "name", "foo2"); - name = testService.getPredicate(username, "name"); + lloyd.setPredicate(username, "name", "foo2"); + name = lloyd.getPredicate(username, "name"); // validate it's set properly assertEquals("foo2", name); } + @Test public void testProgramAB() throws Exception { // a response - Response resp = testService.getResponse(username, "UNIT TEST PATTERN"); + Response resp = lloyd.getResponse(username, "UNIT TEST PATTERN"); // System.out.println(resp.msg); assertEquals("Unit Test Pattern Passed", resp.msg); } + @Test public void testSavePredicates() throws IOException { long uniqueVal = System.currentTimeMillis(); String testValue = String.valueOf(uniqueVal); - Response resp = testService.getResponse(username, "SET FOO " + testValue); + Response resp = lloyd.getResponse(username, "SET FOO " + testValue); assertEquals(testValue, resp.msg); - testService.savePredicates(); - testService.reloadSession(username, botname); - resp = testService.getResponse(username, "GET FOO"); + lloyd.savePredicates(); + lloyd.reloadSession(username, LLOYD); + resp = lloyd.getResponse(username, "GET FOO"); assertEquals("FOO IS " + testValue, resp.msg); } - @Override - public void testService() throws Exception { - // run each of the test methods. - testProgramAB(); - testOOBTags(); - testSavePredicates(); - testPredicates(); - testLearn(); - testSets(); - testSetsAndMaps(); - testAddEntryToSetAndMaps(); - testTopicCategories(); - umlautTest(); - listPatternsTest(); - // This following test is known to be busted.. - // pannousTest(); - addCategoryTest(); - sraixOOBTest(); - sraixTest(); // this should call out to wikipedia for info about Claude Shannon. - // on pannous bots - testUppercase(); - } - + @Test public void testUppercase() { // test a category where the aiml tag is uppercased. - Response resp = testService.getResponse(username, "UPPERCASE"); + Response resp = lloyd.getResponse(username, "UPPERCASE"); assertEquals("Passed", resp.msg); } + @Test public void testSets() throws IOException { - Response resp = testService.getResponse(username, "SETTEST CAT"); + Response resp = lloyd.getResponse(username, "SETTEST CAT"); assertEquals("An Animal.", resp.msg); - resp = testService.getResponse(username, "SETTEST MOUSE"); + resp = lloyd.getResponse(username, "SETTEST MOUSE"); assertEquals("An Animal.", resp.msg); - resp = testService.getResponse(username, "SETTEST DOG"); + resp = lloyd.getResponse(username, "SETTEST DOG"); // System.out.println(resp.msg); assertEquals("An Animal.", resp.msg); } + @Test public void testSetsAndMaps() throws IOException { - Response resp = testService.getResponse(username, "DO YOU LIKE Leah?"); + Response resp = lloyd.getResponse(username, "DO YOU LIKE Leah?"); assertEquals("Princess Leia Organa is awesome.", resp.msg); - resp = testService.getResponse(username, "DO YOU LIKE Princess Leah?"); + resp = lloyd.getResponse(username, "DO YOU LIKE Princess Leah?"); assertEquals("Princess Leia Organa is awesome.", resp.msg); } + @Test public void testTopicCategories() throws IOException { + lloyd.removePredicate(username, "topic"); + String topic = lloyd.getTopic(); + assertEquals("unknown", topic); // Top level definition - Response resp = testService.getResponse(username, "TESTTOPICTEST"); + Response resp = lloyd.getResponse(username, "TESTTOPICTEST"); assertEquals("TOPIC IS unknown", resp.msg); - resp = testService.getResponse(username, "SET TOPIC TEST"); - resp = testService.getResponse(username, "TESTTOPICTEST"); + resp = lloyd.getResponse(username, "SET TOPIC TEST"); + resp = lloyd.getResponse(username, "TESTTOPICTEST"); assertEquals("TEST TOPIC RESPONSE", resp.msg); // maybe we can still fallback to non-topic responses. - resp = testService.getResponse(username, "HI"); + resp = lloyd.getResponse(username, "HI"); assertEquals("Hello user!", resp.msg); // TODO: how the heck do we unset a predicate from AIML? - testService.unsetPredicate(username, "topic"); - resp = testService.getResponse(username, "TESTTOPICTEST"); + lloyd.removePredicate(username, "topic"); + resp = lloyd.getResponse(username, "TESTTOPICTEST"); assertEquals("TOPIC IS unknown", resp.msg); } - public void umlautTest() throws IOException { - Response resp = testService.getResponse(username, "Lars Ümlaüt"); + @Test + public void testUmlaut() throws IOException { + Response resp = lloyd.getResponse(username, "Lars Ümlaüt"); // @GroG says - "this is not working" assertEquals("He's a character from Guitar Hero!", resp.msg); } @Test public void testLocales() { - // have locales - ProgramAB lloyd = (ProgramAB)Runtime.start("pikachu", "ProgramAB"); - // lloyd.setPath(path); - lloyd.addBotsDir(path + File.separator + "bots"); - lloyd.setCurrentBotName("pikachu"); + ProgramAB lloyd = (ProgramAB) Runtime.start("pikachu", "ProgramAB"); + lloyd.addBots(testResources + "/" + "bots"); + lloyd.setBotType("pikachu"); Map locales = lloyd.getLocales(); - assertTrue(locales.size() > 0); assertTrue(locales.containsKey("ja")); } @Test - public void testReload() { - // FIXME - TODO - // reload bot creates a new bot leaves old references :( - // verify reload - /* - * Preferably with default bot ProgramAB lloyd = - * (ProgramAB)Runtime.start("lloyd", "ProgramAB"); // did not work because - * lloyd is lame // lloyd.getResponse("my name is george"); Response - * response = lloyd.getResponse("what is my name?"); - * - * BotInfo botInfo = lloyd.getBotInfo(); Bot oldBot = botInfo.getBot(); - * lloyd.reload(); Bot newBotInfo = botInfo.getBot(); - * assertNotEquals(oldBot, newBotInfo); - * - * response = lloyd.getResponse("what is my name?"); - * assertTrue(response.msg.contains("george")); - */ + public void testReload() throws IOException { + lloyd.getResponse("my name is george"); + Response response = lloyd.getResponse("what is my name?"); + + BotInfo botInfo = lloyd.getBotInfo(); + + String newFile = botInfo.path.getAbsolutePath() + File.separator + "aiml" + File.separator + "newFileCategory.aiml"; + String newFileCategory = "RELOAD"; + FileIO.toFile(newFile, newFileCategory); + + lloyd.reload(); + + response = lloyd.getResponse("RELOAD"); + assertTrue(response.msg.contains("I have reloaded")); + + response = lloyd.getResponse("what is my name?"); + assertTrue(response.msg.contains("george")); + + // clean out file + new File(newFile).delete(); + } @Test public void testDefaultSession() throws IOException { // minimal startup - create the service get a response ProgramAB lloyd = (ProgramAB) Runtime.start("lloyd", "ProgramAB"); - lloyd.setPath(path); - lloyd.setCurrentBotName("lloyd"); + lloyd.setBotType("lloyd"); assertTrue(lloyd.getBots().size() > 0); // test for a response @@ -370,7 +400,7 @@ public void testDefaultSession() throws IOException { // TODO - tests // ProgramAB starts - it should find its own bot info's // set username = default - // set botname = what is available if NOT set + // set LLOYD = what is available if NOT set // getResponse() -> if current session doesn't exist - get bot // if current bot doesn't exist - attempt to activate it // test - absolute minimal setup and getResponse ... 2 lines ? 1? From 37675a8d17fa3f0c2d52be228455b7d9b23e2ce5 Mon Sep 17 00:00:00 2001 From: grog Date: Tue, 5 Dec 2023 11:02:47 -0800 Subject: [PATCH 004/106] dragged in from deps --- .../java/org/myrobotlab/service/InMoov2.java | 1419 ++++++++++++----- .../java/org/myrobotlab/service/Lloyd.java | 20 +- .../service/config/InMoov2Config.java | 372 ++++- 3 files changed, 1354 insertions(+), 457 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 7ef89d2465..c1b384bb86 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -3,14 +3,19 @@ import java.io.File; import java.io.FilenameFilter; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.TreeMap; import java.util.TreeSet; +import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.io.FilenameUtils; +import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.Message; import org.myrobotlab.framework.Plan; import org.myrobotlab.framework.Platform; @@ -24,29 +29,33 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.opencv.OpenCVData; -import org.myrobotlab.programab.PredicateEvent; import org.myrobotlab.programab.Response; +import org.myrobotlab.programab.models.Event; +import org.myrobotlab.service.Log.LogEntry; import org.myrobotlab.service.abstracts.AbstractSpeechRecognizer; import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; import org.myrobotlab.service.config.InMoov2Config; import org.myrobotlab.service.config.OpenCVConfig; import org.myrobotlab.service.config.SpeechSynthesisConfig; import org.myrobotlab.service.data.JoystickData; -import org.myrobotlab.service.data.LedDisplayData; import org.myrobotlab.service.data.Locale; +import org.myrobotlab.service.data.SensorData; import org.myrobotlab.service.interfaces.IKJointAngleListener; import org.myrobotlab.service.interfaces.JoystickListener; import org.myrobotlab.service.interfaces.LocaleProvider; import org.myrobotlab.service.interfaces.ServiceLifeCycleListener; import org.myrobotlab.service.interfaces.ServoControl; import org.myrobotlab.service.interfaces.Simulator; +import org.myrobotlab.service.interfaces.SpeechListener; import org.myrobotlab.service.interfaces.SpeechRecognizer; import org.myrobotlab.service.interfaces.SpeechSynthesis; import org.myrobotlab.service.interfaces.TextListener; import org.myrobotlab.service.interfaces.TextPublisher; import org.slf4j.Logger; -public class InMoov2 extends Service implements ServiceLifeCycleListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, IKJointAngleListener { +public class InMoov2 extends Service + implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, + IKJointAngleListener { public final static Logger log = LoggerFactory.getLogger(InMoov2.class); @@ -56,16 +65,22 @@ public class InMoov2 extends Service implements ServiceLifeCycleL static String speechRecognizer = "WebkitSpeechRecognition"; + /** + * number of times waited in boot state + */ + protected int bootCount = 0; + /** * This method will load a python file into the python interpreter. * * @param file - * file to load + * file to load * @return success/failure */ @Deprecated /* use execScript - this doesn't handle resources correctly */ public static boolean loadFile(String file) { File f = new File(file); + // FIXME cannot be casting to Python Python p = (Python) Runtime.getService("python"); log.info("Loading Python file {}", f.getAbsolutePath()); if (p == null) { @@ -95,6 +110,11 @@ public static boolean loadFile(String file) { return true; } + /** + * the config that was processed before booting, if there was one. + */ + protected String bootedConfig = null; + protected transient ProgramAB chatBot; protected List configList; @@ -109,12 +129,28 @@ public static boolean loadFile(String file) { protected transient SpeechRecognizer ear; + protected List errors = new ArrayList<>(); + + /** + * The finite state machine is core to managing state of InMoov2. There is + * very little benefit gained in having the interactions pub/sub. Therefore, + * there will be a direct reference to the fsm. If different state graph is + * needed, then the fsm can provide that service. + */ + private transient FiniteStateMachine fsm = null; // waiting controable threaded gestures we warn user protected boolean gestureAlreadyStarted = false; protected Set gestures = new TreeSet(); + /** + * Prevents actions or events from happening when InMoov2 is first booted + */ + protected boolean hasBooted = false; + + protected boolean isPirOn = false; + protected transient HtmlFilter htmlFilter; protected transient ImageDisplay imageDisplay; @@ -138,10 +174,27 @@ public static boolean loadFile(String file) { protected transient Python python; + protected long stateLastIdleTime = System.currentTimeMillis(); + + protected long stateLastRandomTime = System.currentTimeMillis(); + protected String voiceSelected; + /** + * Generalized memory, used for normalizing data from different services into + * one centralized + * place. Not the same as configuration, as this definition is owned by what the + * user + * needs vs configuration is what the service needs and understands. + * Python, ProgramAB and the InMoov2 service can all normalize their data here + * with one way or two way bindings + */ + protected Map memory = new TreeMap<>(); + public InMoov2(String n, String id) { super(n, id); + locales = Locale.getLocaleMap("en-US", "fr-FR", "es-ES", "de-DE", "nl-NL", "ru-RU", "hi-IN", "it-IT", "fi-FI", + "pt-PT", "tr-TR"); } // should be removed in favor of general listeners @@ -155,7 +208,7 @@ public InMoov2Config apply(InMoov2Config c) { super.apply(c); try { - locales = Locale.getLocaleMap("en-US", "fr-FR", "es-ES", "de-DE", "nl-NL", "ru-RU", "hi-IN", "it-IT", "fi-FI", "pt-PT", "tr-TR"); + Runtime.start("python"); if (c.locale != null) { setLocale(c.locale); @@ -163,14 +216,6 @@ public InMoov2Config apply(InMoov2Config c) { setLocale(getSupportedLocale(Runtime.getInstance().getLocale().toString())); } - loadAppsScripts(); - - loadInitScripts(); - - if (c.loadGestures) { - loadGestures(); - } - if (c.heartbeat) { startHeartbeat(); } else { @@ -183,8 +228,6 @@ public InMoov2Config apply(InMoov2Config c) { return c; } - - @Override public void attachTextListener(String name) { addListener("publishText", name); @@ -383,21 +426,15 @@ public void enable() { * @param pythonCode * @return */ - public boolean exec(String pythonCode) { - try { - Python p = (Python) Runtime.start("python", "Python"); - return p.exec(pythonCode, true); - } catch (Exception e) { - error("unable to execute script %s", pythonCode); - return false; - } + public void exec(String pythonCode) { + send("python", "exec", pythonCode); } /** * This method will try to launch a python command with error handling * * @param gesture - * the gesture + * the gesture * @return gesture result */ public String execGesture(String gesture) { @@ -407,6 +444,7 @@ public String execGesture(String gesture) { subscribe("python", "publishStatus", this.getName(), "onGestureStatus"); startedGesture(gesture); lastGestureExecuted = gesture; + // FIXME cannot be casting to Python Python python = (Python) Runtime.getService("python"); if (python == null) { error("python service not started"); @@ -415,28 +453,36 @@ public String execGesture(String gesture) { return python.evalAndWait(gesture); } + /** + * Possible pub/sub way to interface with python - no blocking though + * + * @param code + * @return + */ + public String publishPython(String code) { + return code; + } + /** * FIXME - I think there was lots of confusion of executing resources or just * a file on the file system ... "execScript" I would expect to be just a file * on the file system. * + * FIXME - this is a mess - the UI uses this function exhaustively, it should + * not ! it should be appropriately named to execResource or execResourcefile + * + * * If resource semantics are needed there should be a execResourceScript which * adds the context and calls the underlying execScript "which only" executes * a filesystem file :P * * @param someScriptName - * execute a resource script + * execute a resource script * @return success or failure */ - public boolean execScript(String someScriptName) { - try { - Python p = (Python) Runtime.start("python", "Python"); - String script = getResourceAsString(someScriptName); - return p.exec(script, true); - } catch (Exception e) { - error("unable to execute script %s", someScriptName); - return false; - } + public void execScript(String someScriptName) { + String script = getResourceAsString(someScriptName); + send("python", "exec", script); } public void finishedGesture() { @@ -455,7 +501,23 @@ public void finishedGesture(String nameOfGesture) { // FIXME - this isn't the callback for fsm - why is it needed here ? public void fire(String event) { - invoke("publishEvent", event); + // systemEvent(event); + fsm.fire(event); + } + + /** + * used to configure a flashing event - could use configuration to signal + * different colors and states + * + * @return + */ + public void flash() { + invoke("publishFlash", "default"); + } + + public String flash(String name) { + invoke("publishFlash", name); + return name; } public void fullSpeed() { @@ -467,13 +529,41 @@ public void fullSpeed() { sendToPeer("torso", "fullSpeed"); } - // FIXME - remove all of this form of localization + /** + * Generalized memory get/set + * will probably need to save at some point as well + * + * @param key + * @return + */ public String get(String key) { - String ret = localize(key); - if (ret != null) { - return ret; + Object ret = memory.get(key); + if (ret == null) { + return null; } - return "not yet translated"; + return ret.toString(); + } + + /** + * Generalized memory setter + * + * @param key + * @param data + * @return + */ + public Object set(String key, Object data) { + return memory.put(key, data); + } + + /** + * rebroadcasted from chatBot whenever predicates change + * + * @param predicate + * @return + */ + public Event publishPredicate(Event predicate) { + predicate.src = getName(); + return predicate; } public InMoov2Arm getArm(String side) { @@ -502,30 +592,39 @@ public InMoov2Head getHead() { * @return the timestamp of the last activity time. */ public Long getLastActivityTime() { - try { - - Long lastActivityTime = 0L; - - Long head = (Long) sendToPeerBlocking("head", "getLastActivityTime", getName()); - Long leftArm = (Long) sendToPeerBlocking("leftArm", "getLastActivityTime", getName()); - Long rightArm = (Long) sendToPeerBlocking("rightArm", "getLastActivityTime", getName()); - Long leftHand = (Long) sendToPeerBlocking("leftHand", "getLastActivityTime", getName()); - Long rightHand = (Long) sendToPeerBlocking("rightHand", "getLastActivityTime", getName()); - Long torso = (Long) sendToPeerBlocking("torso", "getLastActivityTime", getName()); - - lastActivityTime = Math.max(head, leftArm); - lastActivityTime = Math.max(lastActivityTime, rightArm); - lastActivityTime = Math.max(lastActivityTime, leftHand); - lastActivityTime = Math.max(lastActivityTime, rightHand); - lastActivityTime = Math.max(lastActivityTime, torso); - - return lastActivityTime; - - } catch (Exception e) { - error(e); - return null; + Long head = (InMoov2Head) getPeer("head") != null ? ((InMoov2Head) getPeer("head")).getLastActivityTime() : null; + Long leftArm = (InMoov2Arm) getPeer("leftArm") != null ? ((InMoov2Arm) getPeer("leftArm")).getLastActivityTime() + : null; + Long rightArm = (InMoov2Arm) getPeer("rightArm") != null ? ((InMoov2Arm) getPeer("rightArm")).getLastActivityTime() + : null; + Long leftHand = (InMoov2Hand) getPeer("leftHand") != null + ? ((InMoov2Hand) getPeer("leftHand")).getLastActivityTime() + : null; + Long rightHand = (InMoov2Hand) getPeer("rightHand") != null + ? ((InMoov2Hand) getPeer("rightHand")).getLastActivityTime() + : null; + Long torso = (InMoov2Torso) getPeer("torso") != null ? ((InMoov2Torso) getPeer("torso")).getLastActivityTime() + : null; + + Long lastActivityTime = null; + + if (head != null || leftArm != null || rightArm != null || leftHand != null || rightHand != null || torso != null) { + lastActivityTime = 0L; + if (head != null) + lastActivityTime = Math.max(lastActivityTime, head); + if (leftArm != null) + lastActivityTime = Math.max(lastActivityTime, leftArm); + if (rightArm != null) + lastActivityTime = Math.max(lastActivityTime, rightArm); + if (leftHand != null) + lastActivityTime = Math.max(lastActivityTime, leftHand); + if (rightHand != null) + lastActivityTime = Math.max(lastActivityTime, rightHand); + if (torso != null) + lastActivityTime = Math.max(lastActivityTime, torso); } + return lastActivityTime; } public InMoov2Arm getLeftArm() { @@ -574,6 +673,13 @@ public InMoov2Hand getRightHand() { return (InMoov2Hand) getPeer("rightHand"); } + public String getState() { + if (fsm == null) { + return null; + } + return fsm.getCurrent(); + } + /** * matches on language only not variant expands language match to full InMoov2 * bot locale @@ -604,10 +710,6 @@ public InMoov2Torso getTorso() { return (InMoov2Torso) getPeer("torso"); } - public InMoov2Config getTypedConfig() { - return (InMoov2Config) config; - } - public void halfSpeed() { sendToPeer("head", "setSpeed", 25.0, 25.0, 25.0, 25.0, 100.0, 25.0); sendToPeer("rightHand", "setSpeed", 30.0, 30.0, 30.0, 30.0, 30.0, 30.0); @@ -648,7 +750,7 @@ public void loadAppsScripts() throws IOException { loadScripts(getResourceDir() + fs + "gestures/InMoovApps/Rock_Paper_Scissors"); loadScripts(getResourceDir() + fs + "gestures/InMoovApps/Kids_WordsGame"); } - + public void loadGestures() { loadGestures(getResourceDir() + fs + "gestures"); } @@ -659,11 +761,11 @@ public void loadGestures() { * file should contain 1 method definition that is the same as the filename. * * @param directory - * - the directory that contains the gesture python files. + * - the directory that contains the gesture python files. * @return true/false */ public boolean loadGestures(String directory) { - invoke("publishEvent", "LOAD GESTURES"); + systemEvent("LOAD GESTURES"); // iterate over each of the python files in the directory // and load them into the python interpreter. @@ -693,7 +795,7 @@ public boolean loadGestures(String directory) { info("%s Gestures loaded, %s Gestures with error", totalLoaded, totalError); broadcastState(); if (totalError > 0) { - invoke("publishEvent", "GESTURE_ERROR"); + systemEvent("GESTURE_ERROR"); return false; } return true; @@ -709,7 +811,7 @@ public void loadScripts(String directory) throws IOException { File dir = new File(directory); if (!dir.exists() || !dir.isDirectory()) { - invoke("publishEvent", "LOAD SCRIPTS ERROR"); + systemEvent("LOAD SCRIPTS ERROR"); return; } @@ -724,17 +826,24 @@ public boolean accept(File dir, String name) { if (files != null) { for (File file : files) { - Python p = (Python) Runtime.start("python", "Python"); - if (p != null) { - p.execFile(file.getAbsolutePath()); - } + send("python", "execFile", file.getAbsolutePath()); } } } } public void moveArm(String which, Double bicep, Double rotate, Double shoulder, Double omoplate) { - invoke("publishMoveArm", which, bicep, rotate, shoulder, omoplate); + HashMap map = new HashMap<>(); + Optional.ofNullable(bicep).ifPresent(value -> map.put("bicep", value)); + Optional.ofNullable(rotate).ifPresent(value -> map.put("rotate", value)); + Optional.ofNullable(shoulder).ifPresent(value -> map.put("shoulder", value)); + Optional.ofNullable(omoplate).ifPresent(value -> map.put("omoplate", value)); + + if ("left".equals(which)) { + invoke("publishMoveLeftArm", map); + } else { + invoke("publishMoveRightArm", map); + } } public void moveEyelids(Double eyelidleftPos, Double eyelidrightPos) { @@ -749,8 +858,21 @@ public void moveHand(String which, Double thumb, Double index, Double majeure, D moveHand(which, thumb, index, majeure, ringFinger, pinky, null); } - public void moveHand(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { - invoke("publishMoveHand", which, thumb, index, majeure, ringFinger, pinky, wrist); + public void moveHand(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, + Double wrist) { + HashMap map = new HashMap<>(); + Optional.ofNullable(thumb).ifPresent(value -> map.put("thumb", value)); + Optional.ofNullable(index).ifPresent(value -> map.put("index", value)); + Optional.ofNullable(majeure).ifPresent(value -> map.put("majeure", value)); + Optional.ofNullable(ringFinger).ifPresent(value -> map.put("ringFinger", value)); + Optional.ofNullable(pinky).ifPresent(value -> map.put("pinky", value)); + Optional.ofNullable(wrist).ifPresent(value -> map.put("wrist", value)); + + if ("left".equals(which)) { + invoke("publishMoveLeftHand", map); + } else { + invoke("publishMoveRightHand", map); + } } public void moveHead(Double neck, Double rothead) { @@ -766,13 +888,24 @@ public void moveHead(Double neck, Double rothead, Double eyeX, Double eyeY, Doub } public void moveHead(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, Double rollNeck) { - invoke("publishMoveHead", neck, rothead, eyeX, eyeY, jaw, rollNeck); + HashMap map = new HashMap<>(); + Optional.ofNullable(neck).ifPresent(value -> map.put("neck", value)); + Optional.ofNullable(rothead).ifPresent(value -> map.put("rothead", value)); + Optional.ofNullable(eyeX).ifPresent(value -> map.put("eyeX", value)); + Optional.ofNullable(eyeY).ifPresent(value -> map.put("eyeY", value)); + Optional.ofNullable(jaw).ifPresent(value -> map.put("jaw", value)); + Optional.ofNullable(rollNeck).ifPresent(value -> map.put("rollNeck", value)); + invoke("publishMoveHead", map); } public void moveHead(Integer neck, Integer rothead, Integer rollNeck) { moveHead((double) neck, (double) rothead, null, null, null, (double) rollNeck); } + public void moveHeadBlocking(Integer neck, Integer rothead) { + moveHeadBlocking((double) neck, (double) rothead, null); + } + public void moveHeadBlocking(Double neck, Double rothead) { moveHeadBlocking(neck, rothead, null); } @@ -801,8 +934,10 @@ public void moveLeftHand(Double thumb, Double index, Double majeure, Double ring moveHand("left", thumb, index, majeure, ringFinger, pinky, wrist); } - public void moveLeftHand(Integer thumb, Integer index, Integer majeure, Integer ringFinger, Integer pinky, Integer wrist) { - moveHand("left", (double) thumb, (double) index, (double) majeure, (double) ringFinger, (double) pinky, (double) wrist); + public void moveLeftHand(Integer thumb, Integer index, Integer majeure, Integer ringFinger, Integer pinky, + Integer wrist) { + moveHand("left", (double) thumb, (double) index, (double) majeure, (double) ringFinger, (double) pinky, + (double) wrist); } public void moveRightArm(Double bicep, Double rotate, Double shoulder, Double omoplate) { @@ -813,13 +948,18 @@ public void moveRightHand(Double thumb, Double index, Double majeure, Double rin moveHand("right", thumb, index, majeure, ringFinger, pinky, wrist); } - public void moveRightHand(Integer thumb, Integer index, Integer majeure, Integer ringFinger, Integer pinky, Integer wrist) { - moveHand("right", (double) thumb, (double) index, (double) majeure, (double) ringFinger, (double) pinky, (double) wrist); + public void moveRightHand(Integer thumb, Integer index, Integer majeure, Integer ringFinger, Integer pinky, + Integer wrist) { + moveHand("right", (double) thumb, (double) index, (double) majeure, (double) ringFinger, (double) pinky, + (double) wrist); } public void moveTorso(Double topStom, Double midStom, Double lowStom) { - // the "right" way - invoke("publishMoveTorso", topStom, midStom, lowStom); + HashMap map = new HashMap<>(); + Optional.ofNullable(topStom).ifPresent(value -> map.put("topStom", value)); + Optional.ofNullable(midStom).ifPresent(value -> map.put("midStom", value)); + Optional.ofNullable(lowStom).ifPresent(value -> map.put("lowStom", value)); + invoke("publishMoveTorso", map); } public void moveTorsoBlocking(Double topStom, Double midStom, Double lowStom) { @@ -827,10 +967,10 @@ public void moveTorsoBlocking(Double topStom, Double midStom, Double lowStom) { sendToPeer("torso", "moveToBlocking", topStom, midStom, lowStom); } - public PredicateEvent onChangePredicate(PredicateEvent event) { + public Event onChangePredicate(Event event) { log.error("onChangePredicate {}", event); if (event.name.equals("topic")) { - invoke("publishEvent", String.format("TOPIC CHANGED TO %s", event.value)); + systemEvent(String.format("TOPIC CHANGED TO %s", event.value)); } // depending on configuration .... // call python ? @@ -839,11 +979,17 @@ public PredicateEvent onChangePredicate(PredicateEvent event) { return event; } + public void onConfigFinished(String configName) { + log.info("onConfigFinished"); + configStarted = false; + invoke("publishBoot"); + } + /** * comes in from runtime which owns the config list * * @param configList - * list of configs + * list of configs */ public void onConfigList(List configList) { this.configList = configList; @@ -857,8 +1003,8 @@ public void onCreated(String fullname) { public void onFinishedConfig(String configName) { log.info("onFinishedConfig"); - // invoke("publishEvent", "configFinished"); - invoke("publishFinishedConfig", configName); + // systemEvent("configFinished"); + invoke("publishConfigFinished", configName); } public void onGestureStatus(Status status) { @@ -870,6 +1016,170 @@ public void onGestureStatus(Status status) { unsubscribe("python", "publishStatus", this.getName(), "onGestureStatus"); } + protected long heartbeatCount = 0; + + /** + * Allows or prevents sensor input from processing. + * Initial boot prevents sensor data from interfering with booting. + */ + protected boolean allowSensorInput = false; + + /** + * A generalized recurring event which can perform checks and various other + * methods or tasks. Heartbeats will not start until after boot stage. + */ + public void onHeartbeat(String name) { + try { + heartbeatCount++; + // heartbeats can start before config is + // done processing - so the following should + // not be dependent on config + Runtime runtime = Runtime.getInstance(); + + // no longer having problems with synchronization of other + // services - boot is immutable - no scripts can run until it is done + // and it will run InMoov2.py + if (fsm == null || "boot".equals(getState())) { + // need to keep trying to boot + boot(); + log.warn("not ready to leave boot - runtime still processing config"); + return; + } + // prepare report + if (!hasBooted) { + log.info("boot hasn't completed, will not process heartbeat"); + return; + } + + Long lastActivityTime = getLastActivityTime(); + + // FIXME lastActivityTime != 0 is bogus - the value should be null if + // never set + if (config.stateIdleInterval != null && lastActivityTime != null && lastActivityTime != 0 + && lastActivityTime + (config.stateIdleInterval * 1000) < System.currentTimeMillis()) { + stateLastIdleTime = lastActivityTime; + } + + // will fire an idle event if there has been no activity + if (System.currentTimeMillis() > stateLastIdleTime + (config.stateIdleInterval * 1000)) { + fire("idle"); + stateLastIdleTime = System.currentTimeMillis(); + } + + // interval event firing + if (config.stateRandomInterval != null + && System.currentTimeMillis() > stateLastRandomTime + (config.stateRandomInterval * 1000)) { + // fire("random"); + stateLastRandomTime = System.currentTimeMillis(); + } + + // FIXME publishInactivit(long time) + // FIXME publishLastActivity + + if (config.flashOnPir) { + Pir pir = (Pir) getPeer("pir"); + if (pir != null && pir.isActive()) { + flash("pir"); + } + } + + } catch (Exception e) { + error(e); + } + + // if (config.pirOnFlash && isPeerStarted("pir") && isPirOn) { + // flash("pir"); + // } + + if (config.batteryInSystem) { + double batteryLevel = Runtime.getBatteryLevel(); + invoke("publishBatteryLevel", batteryLevel); + // FIXME - thresholding should always have old value or state + // so we don't pump endless errors + if (batteryLevel < 5) { + error("battery level < 5 percent"); + // systemEvent(BATTERY ERROR) + } else if (batteryLevel < 10) { + warn("battery level < 10 percent"); + // systemEvent(BATTERY WARN) + } + } + + // flash error until errors are cleared + if (config.flashOnErrors) { + if (errors.size() > 0) { + invoke("publishFlash", "error"); + } else { + // invoke("publishFlash", "heartbeat"); + } + } + + // has processed system events + // if led report ... + if (isPeerStarted("neoPixel") && !isSpeaking) { + // FIXME - publishLedMatrix + // FIXME direct type is bad :( + NeoPixel neo = (NeoPixel) getPeer("neoPixel"); + if (neo != null) { + // neo.clearPixelSet(); + + String state = getState(); + if (state != null) { + // log.info("hashcode {} {}", state, state.hashCode()); + // base bottom blue square + String color = CodecUtils.hashcodeToHex(state.hashCode()); + log.info("hashcode {} {} {}", state, color, state.hashCode()); + neo.setPixel(134, color); + neo.setPixel(135, color); + neo.setPixel(136, color); + neo.setPixel(137, color); + } + + if (heartbeatCount % 2 == 0) { + // top heartbeat square off + neo.setPixel(132, 0, 0, 0); + neo.setPixel(133, 0, 0, 0); + neo.setPixel(138, 0, 0, 0); + neo.setPixel(139, 0, 0, 0); + } else { + // top heartbeat square on + neo.setPixel(132, 12, 180, 212); + neo.setPixel(133, 12, 180, 212); + neo.setPixel(138, 12, 180, 212); + neo.setPixel(139, 12, 180, 212); + } + + // FIXME anywhere exposing type is bad + Pir pir = (Pir) getPeer("pir"); + if (pir != null && pir.isActive()) { + neo.setPixel(130, 225, 254, 0); + neo.setPixel(131, 225, 254, 0); + neo.setPixel(140, 225, 254, 0); + neo.setPixel(141, 225, 254, 0); + } else { + neo.setPixel(130, 0, 0, 0); + neo.setPixel(131, 0, 0, 0); + neo.setPixel(140, 0, 0, 0); + neo.setPixel(141, 0, 0, 0); + + } + neo.writeMatrix(); + } + } + + } + + public void onInactivity() { + log.info("onInactivity"); + + // powerDown ? + + } + + /** + * Central hub of input motion control. Potentially, all input from joysticks, + * quest2 controllers and headset, or any IK service could be sent here + */ @Override public void onJointAngles(Map angleMap) { log.debug("onJointAngles {}", angleMap); @@ -887,7 +1197,65 @@ public void onJointAngles(Map angleMap) { public void onJoystickInput(JoystickData input) throws Exception { // TODO timer ? to test and not send an event // switches to manual control ? - invoke("publishEvent", "joystick"); + systemEvent("joystick"); + } + + /** + * Centralized logging system will have all logging from all services, + * including lower level logs that do not propegate as statuses + * + * @param log + * - flushed log from Log service + */ + public void onLogEvents(List log) { + // scan for warn or errors + for (LogEntry entry : log) { + if ("ERROR".equals(entry.level) && errors.size() < 100) { + errors.add(entry); + } + } + } + + public void onMoveHead(Map map) { + InMoov2Head head = (InMoov2Head) getPeer("head"); + if (head != null) { + head.onMove(map); + } + } + + public void onMoveLeftArm(Map map) { + InMoov2Arm leftArm = (InMoov2Arm) getPeer("leftArm"); + if (leftArm != null) { + leftArm.onMove(map); + } + } + + public void onMoveLeftHand(Map map) { + InMoov2Hand leftHand = (InMoov2Hand) getPeer("leftHand"); + if (leftHand != null) { + leftHand.onMove(map); + } + } + + public void onMoveRightArm(Map map) { + InMoov2Arm rightArm = (InMoov2Arm) getPeer("rightArm"); + if (rightArm != null) { + rightArm.onMove(map); + } + } + + public void onMoveRightHand(Map map) { + InMoov2Hand rightHand = (InMoov2Hand) getPeer("rightHand"); + if (rightHand != null) { + rightHand.onMove(map); + } + } + + public void onMoveTorso(Map map) { + InMoov2Torso torso = (InMoov2Torso) getPeer("torso"); + if (torso != null) { + torso.onMove(map); + } } public String onNewState(String state) { @@ -911,16 +1279,16 @@ public OpenCVData onOpenCVData(OpenCVData data) { // FIXME - publish event with or without data ? String file reference return data; } - + /** * onPeak volume callback TODO - maybe make it variable with volume ? * * @param volume */ public void onPeak(double volume) { - if (config.neoPixelFlashWhenSpeaking && !configStarted) { + if (config.neoPixelFlashWhenSpeaking && !"boot".equals(getState())) { if (volume > 0.5) { - invoke("publishSpeakingFlash", "speaking"); + invoke("publishSpeakingFlash", "speaking"); } } } @@ -930,14 +1298,25 @@ public void onPeak(double volume) { * onPirOn flash neopixel */ public void onPirOn() { - // FIXME flash on config.flashOnBoot - invoke("publishFlash", "pir"); - ProgramAB chatBot = (ProgramAB)getPeer("chatBot"); - if (chatBot != null) { - String botState = chatBot.getPredicate("botState"); - if ("sleeping".equals(botState)) { - invoke("publishEvent", "WAKE"); - } + isPirOn = true; + // one advantage of re-publishing from inmoov - getName parameter can be + // added + if (allowSensorInput && !"boot".equals(getState())) { + SensorData pir = new SensorData(getName(), "Pir", isPirOn); + invoke("publishSensorData", pir); + } + } + + /** + * Pir off callback - FIXME NEEDS WORK + */ + public void onPirOff() { + isPirOn = false; + // one advantage of re-publishing from inmoov - getName parameter can be + // added + if (allowSensorInput && !"boot".equals(getState())) { + SensorData pir = new SensorData(getName(), "Pir", isPirOn); + invoke("publishSensorData", pir); } } @@ -972,9 +1351,9 @@ public boolean onSense(boolean b) { // setEvent("pir-sense-on" .. also sets it in config ? // config.handledEvents["pir-sense-on"] if (b) { - invoke("publishEvent", "PIR ON"); + systemEvent("PIR ON"); } else { - invoke("publishEvent", "PIR OFF"); + systemEvent("PIR OFF"); } return b; } @@ -986,7 +1365,7 @@ public boolean onSense(boolean b) { */ public void onStartConfig(String configName) { log.info("onStartConfig"); - invoke("publishStartConfig", configName); + invoke("publishConfigStarted", configName); } /** @@ -998,8 +1377,6 @@ public void onStartConfig(String configName) { */ @Override public void onStarted(String name) { - InMoov2Config c = (InMoov2Config) config; - log.info("onStarted {}", name); try { @@ -1008,7 +1385,7 @@ public void onStarted(String name) { // BAD IDEA - better to ask for a system report or an error report // if (runtime.isProcessingConfig()) { - // invoke("publishEvent", "CONFIG STARTED"); + // systemEvent("CONFIG STARTED"); // } String peerKey = getPeerKey(name); @@ -1018,11 +1395,11 @@ public void onStarted(String name) { } if (runtime.isProcessingConfig() && !configStarted) { - invoke("publishEvent", "CONFIG STARTED " + runtime.getConfigName()); + systemEvent("CONFIG STARTED " + runtime.getConfigName()); configStarted = true; } - invoke("publishEvent", "STARTED " + peerKey); + systemEvent("STARTED " + peerKey); switch (peerKey) { case "audioPlayer": @@ -1127,6 +1504,7 @@ public void onStarted(String name) { @Override public void onStopped(String name) { + log.info("service {} has stopped"); // using release peer for peer releasing // FIXME - auto remove subscriptions of peers? } @@ -1143,6 +1521,9 @@ public void onText(String text) { // TODO FIX/CHECK this, migrate from python land public void powerDown() { + // publishFlash(maxInactivityTimeSeconds, maxInactivityTimeSeconds, + // maxInactivityTimeSeconds, maxInactivityTimeSeconds, + // maxInactivityTimeSeconds, maxInactivityTimeSeconds) rest(); purgeTasks(); @@ -1185,15 +1566,210 @@ public void publish(String name, String method, Object... data) { invoke("publishMessage", msg); } - public String publishStartConfig(String configName) { + public String publishConfigStarted(String configName) { info("config %s started", configName); - invoke("publishEvent", "CONFIG STARTED " + configName); + systemEvent("CONFIG STARTED " + configName); return configName; } - public String publishFinishedConfig(String configName) { + public double publishBatteryLevel(double d) { + return d; + } + + /** + * At boot all services specified through configuration have started, or if no + * configuration has started minimally the InMoov2 service has started. During + * the processing of config and starting other services data will have + * accumulated, and at boot, some of data may now be inspected and processed + * in a synchronous single threaded way. With reporting after startup, vs + * during, other peer services are not needed (e.g. audioPlayer is no longer + * needed to be started "before" InMoov2 because when boot is called + * everything that is wanted has been started. + * + */ + public void boot() { + try { + // if ! ready + // FIXME don't overwrite if exists + Runtime runtime = Runtime.getInstance(); + runtime.saveDefault("InMoovDefault", getName(), "InMoov2", true); + + bootCount++; + log.info("boot count {}", bootCount); + + // thinking you shouldn't "boot" twice ? + if (hasBooted) { + log.warn("will not boot again"); + return; + } + + if (runtime.isProcessingConfig()) { + log.warn("runtime still processing config set {}, waiting ....", runtime.getConfigName()); + return; + } + + ServiceInterface python = Runtime.getService("python"); + if (python == null || !python.isReady()) { + log.warn("python not ready, waiting ...."); + return; + } + + // TODO - MAKE BOOT REPORT !!!! deliver it on a heartbeat + + // get service start and release life cycle events + // FIXME reduce to onStart + runtime.attachServiceLifeCycleListener(getName()); + + List services = Runtime.getServices(); + for (ServiceInterface si : services) { + if ("Servo".equals(si.getSimpleName())) { + send(si.getFullName(), "setAutoDisable", true); + } + } + + // get events of new services and shutdown + subscribe("runtime", "shutdown"); + // power up loopback subscription + addListener(getName(), "powerUp"); + + subscribe("runtime", "publishConfigList"); + if (runtime.isProcessingConfig()) { + systemEvent("configStarted"); + } + + // subscribe to config processing events + // runtime callbacks publish the same a local + // subscribe("runtime", "publishConfigStarted", "publishConfigStarted"); + subscribe("runtime", "publishConfigFinished", "publishConfigFinished"); + + // chatbot getresponse attached to publishEvent + addListener("publishEvent", getPeerName("chatBot"), "getResponse"); + + // copy config if it doesn't already exist + String resourceBotDir = FileIO.gluePaths(getResourceDir(), "config"); + List files = FileIO.getFileList(resourceBotDir); + for (File f : files) { + String botDir = "data/config/" + f.getName(); + File bDir = new File(botDir); + if (bDir.exists() || !f.isDirectory()) { + log.info("skipping data/config/{}", botDir); + } else { + log.info("will copy new data/config/{}", botDir); + try { + FileIO.copy(f.getAbsolutePath(), botDir); + } catch (Exception e) { + error(e); + } + } + } + runtime.invoke("publishConfigList"); + // FIXME - reduce the number of these + if (config.loadAppsScripts) { + loadAppsScripts(); + } + + if (config.loadInitScripts) { + loadInitScripts(); + } + + if (config.loadGestures) { + loadGestures(); + } + + execScript("InMoov2.py"); + log.info("here"); + + for (ServiceInterface si : services) { + if ("Servo".equals(si.getSimpleName())) { + send(si.getFullName(), "setAutoDisable", true); + } + } + + // FIXME - any interesting state changes or config stuff + // needs to be reported (once?) in onHeartbeat + + // FIXME - standardize multi-config examples should be available + // moved from startService to allow more simple control + // FIXME standard FileIO copyIfNotExists(src, dst) + // try { + // // copy config if it doesn't already exist + // String resourceBotDir = FileIO.gluePaths(getResourceDir(), "config"); + // List files = FileIO.getFileList(resourceBotDir); + // for (File f : files) { + // String botDir = "data/config/" + f.getName(); + // File bDir = new File(botDir); + // if (bDir.exists() || !f.isDirectory()) { + // log.info("skipping data/config/{}", botDir); + // } else { + // log.info("will copy new data/config/{}", botDir); + // try { + // FileIO.copy(f.getAbsolutePath(), botDir); + // } catch (Exception e) { + // error(e); + // } + // } + // } + // } catch (Exception e) { + // error(e); + // } + + // FIXME - find good way of running an animation "through" a state + if (config.neoPixelBootGreen && getPeer("neoPixel") != null) { + NeoPixel neoPixel = (NeoPixel) getPeer("neoPixel"); + if (neoPixel != null) { + // invoke("publishPlayAnimation", config.bootAnimation); + } + } + + if (config.startupSound && getPeer("audioPlayer") != null) { + ((AudioFile) getPeer("audioPlayer")) + .playBlocking(FileIO.gluePaths(getResourceDir(), "/system/sounds/startupsound.mp3")); + } + + if (config.systemEventsOnBoot) { + // reporting on all services and config started + if (bootedConfig != null) { + // configuration was processed before booting + systemEvent("CONFIG LOADED %s", bootedConfig); + } + } + + // FIXME - important to do invoke & fsm needs to be consistent order + + // if speaking then turn off animation + + // publish all the errors + + // switch off animations + + // start heartbeat + // say starting heartbeat + if (config.heartbeat) { + startHeartbeat(); + } else { + stopHeartbeat(); + } + + // say finished booting + fire("start"); + + // if (getPeer("mouth") != null) { + // AbstractSpeechSynthesis mouth = + // (AbstractSpeechSynthesis)getPeer("mouth"); + // mouth.setMute(wasMute); + // } + } catch (Exception e) { + hasBooted = false; + error(e); + } + + hasBooted = true; + + } + + public String publishConfigFinished(String configName) { info("config %s finished", configName); - invoke("publishEvent", "CONFIG LOADED " + configName); + systemEvent("CONFIG LOADED " + configName); return configName; } @@ -1208,6 +1784,28 @@ public List publishConfigList() { return configList; } + /** + * publishes a name of an animation, off/on control will be done through + * AudioListener interface + * + * @param name + * @return + */ + public String publishAnimation(String name) { + return name; + } + + /** + * publishes a name for NeoPixel.onFlash to consume, in a seperate channel to + * potentially be used by "speaking only" leds + * + * @param name + * @return + */ + public String publishSpeakingFlash(String name) { + return name; + } + /** * event publisher for the fsm - although other services potentially can * consume and filter this event channel @@ -1220,119 +1818,137 @@ public String publishEvent(String event) { } /** - * used to configure a flashing event - could use configuration to signal - * different colors and states + * publishes a name for NeoPixel.onFlash to consume * + * @param botType * @return */ public String publishFlash(String flashName) { return flashName; } + /** + * FSM is regularly driven by the heartbeat + * + * @return + */ public String publishHeartbeat() { - invoke("publishFlash", "heartbeat"); return getName(); } /** - * A more extensible interface point than publishEvent FIXME - create - * interface for this + *
+   * 
+   * Typically rebroadcast message from ProgramAB mrljson in aiml
    * 
+   * The oob syntax is:
+   *  <oob>
+   *    <mrljson>
+   *        [{method:on_new_user, data:[{"name":"<star/>"}]}]
+   *    </mrljson>
+   * </oob>
+   * 
+   * 
* @param msg * @return */ public Message publishMessage(Message msg) { + msg.sender = getName(); return msg; } - public HashMap publishMoveArm(String which, Double bicep, Double rotate, Double shoulder, Double omoplate) { - HashMap map = new HashMap<>(); - map.put("bicep", bicep); - map.put("rotate", rotate); - map.put("shoulder", shoulder); - map.put("omoplate", omoplate); - if ("left".equals(which)) { - invoke("publishMoveLeftArm", bicep, rotate, shoulder, omoplate); - } else { - invoke("publishMoveRightArm", bicep, rotate, shoulder, omoplate); - } + public Map publishMoveHead(Map map) { return map; } - public HashMap publishMoveHand(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { - HashMap map = new HashMap<>(); - map.put("which", which); - map.put("thumb", thumb); - map.put("index", index); - map.put("majeure", majeure); - map.put("ringFinger", ringFinger); - map.put("pinky", pinky); - map.put("wrist", wrist); - if ("left".equals(which)) { - invoke("publishMoveLeftHand", thumb, index, majeure, ringFinger, pinky, wrist); - } else { - invoke("publishMoveRightHand", thumb, index, majeure, ringFinger, pinky, wrist); - } + public Map publishMoveLeftArm(Map map) { return map; } - public HashMap publishMoveHead(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, Double rollNeck) { - HashMap map = new HashMap<>(); - map.put("neck", neck); - map.put("rothead", rothead); - map.put("eyeX", eyeX); - map.put("eyeY", eyeY); - map.put("jaw", jaw); - map.put("rollNeck", rollNeck); + public Map publishMoveLeftHand(Map map) { return map; } - public HashMap publishMoveLeftArm(Double bicep, Double rotate, Double shoulder, Double omoplate) { - HashMap map = new HashMap<>(); - map.put("bicep", bicep); - map.put("rotate", rotate); - map.put("shoulder", shoulder); - map.put("omoplate", omoplate); + public Map publishMoveRightArm(Map map) { return map; } - public HashMap publishMoveLeftHand(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { - HashMap map = new HashMap<>(); - map.put("thumb", thumb); - map.put("index", index); - map.put("majeure", majeure); - map.put("ringFinger", ringFinger); - map.put("pinky", pinky); - map.put("wrist", wrist); + public Map publishMoveRightHand(Map map) { return map; } - public HashMap publishMoveRightArm(Double bicep, Double rotate, Double shoulder, Double omoplate) { - HashMap map = new HashMap<>(); - map.put("bicep", bicep); - map.put("rotate", rotate); - map.put("shoulder", shoulder); - map.put("omoplate", omoplate); + public Map publishMoveTorso(Map map) { return map; } - public HashMap publishMoveRightHand(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { - HashMap map = new HashMap<>(); - map.put("thumb", thumb); - map.put("index", index); - map.put("majeure", majeure); - map.put("ringFinger", ringFinger); - map.put("pinky", pinky); - map.put("wrist", wrist); - return map; + public String publishPlayAudioFile(String filename) { + return filename; } - public HashMap publishMoveTorso(Double topStom, Double midStom, Double lowStom) { - HashMap map = new HashMap<>(); - map.put("topStom", topStom); - map.put("midStom", midStom); - map.put("lowStom", lowStom); - return map; + public String publishPlayAnimation(String animation) { + return animation; + } + + /** + * stop animation event + */ + public void publishStopAnimation() { + } + + /** + * initial state - updated on any state change + */ + String state = "boot"; + + public class InMoov2State { + public long ts = System.currentTimeMillis(); + public String src; + public String state; + public String event; + } + + /** + * The integration between the FiniteStateMachine (fsm) and the InMoov2 + * service and potentially other services (Python, ProgramAB) happens here. + * + * After boot all state changes get published here. + * + * Some InMoov2 service methods will be called here for "default + * implemenation" of states. If a user doesn't want to have that default + * implementation, they can change it by changing the definition of the state + * machine, and have a new state which will call a Python inmoov2 library + * callback. Overriding, appending, or completely transforming the behavior is + * all easily accomplished by managing the fsm and python inmoov2 library + * callbacks. + * + * Python inmoov2 callbacks ProgramAB topic switching + * + * Depending on config: + * + * + * @param stateChange + * @return + */ + public InMoov2State publishStateChange(FiniteStateMachine.StateChange stateChange) { + log.info("publishStateChange {}", stateChange); + InMoov2State state = new InMoov2State(); + state.src = getName(); + state.state = stateChange.state; + state.event = stateChange.event; + return state; + } + + /** + * event publisher for the fsm - although other services potentially can + * consume and filter this event channel + * + * @param event + * @return + */ + public String publishSystemEvent(String event) { + // well, it turned out underscore was a goofy selection, as underscore in + // aiml is wildcard ... duh + return String.format("SYSTEM_EVENT %s", event); } /** @@ -1343,11 +1959,21 @@ public String publishText(String text) { return text; } + /** + * default this will come from idle after some configurable time period + */ + public void random() { + Random random = (Random) getPeer("random"); + if (random != null) { + random.enable(); + } + } + @Override public void releasePeer(String peerKey) { super.releasePeer(peerKey); if (peerKey != null) { - invoke("publishEvent", "STOPPED " + peerKey); + systemEvent("STOPPED " + peerKey); } } @@ -1405,7 +2031,8 @@ public void setHandSpeed(String which, Double thumb, Double index, Double majeur setHandSpeed(which, thumb, index, majeure, ringFinger, pinky, null); } - public void setHandSpeed(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { + public void setHandSpeed(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, + Double wrist) { InMoov2Hand hand = getHand(which); if (hand == null) { warn("%s hand not started", which); @@ -1415,12 +2042,14 @@ public void setHandSpeed(String which, Double thumb, Double index, Double majeur } @Deprecated - public void setHandVelocity(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky) { + public void setHandVelocity(String which, Double thumb, Double index, Double majeure, Double ringFinger, + Double pinky) { setHandSpeed(which, thumb, index, majeure, ringFinger, pinky, null); } @Deprecated - public void setHandVelocity(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { + public void setHandVelocity(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, + Double wrist) { setHandSpeed(which, thumb, index, majeure, ringFinger, pinky, wrist); } @@ -1436,7 +2065,8 @@ public void setHeadSpeed(Double rothead, Double neck, Double eyeXSpeed, Double e setHeadSpeed(rothead, neck, eyeXSpeed, eyeYSpeed, jawSpeed, null); } - public void setHeadSpeed(Double rothead, Double neck, Double eyeXSpeed, Double eyeYSpeed, Double jawSpeed, Double rollNeckSpeed) { + public void setHeadSpeed(Double rothead, Double neck, Double eyeXSpeed, Double eyeYSpeed, Double jawSpeed, + Double rollNeckSpeed) { sendToPeer("head", "setSpeed", rothead, neck, eyeXSpeed, eyeYSpeed, jawSpeed, rollNeckSpeed); } @@ -1460,7 +2090,8 @@ public void setHeadVelocity(Double rothead, Double neck, Double eyeXSpeed, Doubl } @Deprecated - public void setHeadVelocity(Double rothead, Double neck, Double eyeXSpeed, Double eyeYSpeed, Double jawSpeed, Double rollNeckSpeed) { + public void setHeadVelocity(Double rothead, Double neck, Double eyeXSpeed, Double eyeYSpeed, Double jawSpeed, + Double rollNeckSpeed) { setHeadSpeed(rothead, neck, eyeXSpeed, eyeYSpeed, jawSpeed, rollNeckSpeed); } @@ -1472,12 +2103,15 @@ public void setLeftArmSpeed(Integer bicep, Integer rotate, Integer shoulder, Int setArmSpeed("left", (double) bicep, (double) rotate, (double) shoulder, (double) omoplate); } - public void setLeftHandSpeed(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { + public void setLeftHandSpeed(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, + Double wrist) { setHandSpeed("left", thumb, index, majeure, ringFinger, pinky, wrist); } - public void setLeftHandSpeed(Integer thumb, Integer index, Integer majeure, Integer ringFinger, Integer pinky, Integer wrist) { - setHandSpeed("left", (double) thumb, (double) index, (double) majeure, (double) ringFinger, (double) pinky, (double) wrist); + public void setLeftHandSpeed(Integer thumb, Integer index, Integer majeure, Integer ringFinger, Integer pinky, + Integer wrist) { + setHandSpeed("left", (double) thumb, (double) index, (double) majeure, (double) ringFinger, (double) pinky, + (double) wrist); } @Override @@ -1520,7 +2154,7 @@ public void setOpenCV(OpenCV opencv) { } public boolean setPirPlaySounds(boolean b) { - getTypedConfig().pirPlaySounds = b; + config.pirPlaySounds = b; return b; } @@ -1546,12 +2180,15 @@ public void setRightArmSpeed(Integer bicep, Integer rotate, Integer shoulder, In setArmSpeed("right", (double) bicep, (double) rotate, (double) shoulder, (double) omoplate); } - public void setRightHandSpeed(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { + public void setRightHandSpeed(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, + Double wrist) { setHandSpeed("right", thumb, index, majeure, ringFinger, pinky, wrist); } - public void setRightHandSpeed(Integer thumb, Integer index, Integer majeure, Integer ringFinger, Integer pinky, Integer wrist) { - setHandSpeed("right", (double) thumb, (double) index, (double) majeure, (double) ringFinger, (double) pinky, (double) wrist); + public void setRightHandSpeed(Integer thumb, Integer index, Integer majeure, Integer ringFinger, Integer pinky, + Integer wrist) { + setHandSpeed("right", (double) thumb, (double) index, (double) majeure, (double) ringFinger, (double) pinky, + (double) wrist); } public void setTorsoSpeed(Double topStom, Double midStom, Double lowStom) { @@ -1575,11 +2212,6 @@ public void setVoice(String name) { } } - // ----------------------------------------------------------------------------- - // These are methods added that were in InMoov1 that we no longer had in - // InMoov2. - // From original InMoov1 so we don't loose the - public void sleeping() { log.error("sleeping"); } @@ -1589,7 +2221,7 @@ public void speak(String toSpeak) { } public void speakAlert(String toSpeak) { - invoke("publishEvent", "ALERT"); + systemEvent("ALERT"); speakBlocking(toSpeak); } @@ -1626,91 +2258,7 @@ public void speakBlocking(String format, Object... args) { } } - public void startAll() throws Exception { - startAll(null, null); - } - - public void startAll(String leftPort, String rightPort) throws Exception { - startMouth(); - startChatBot(); - - // startHeadTracking(); - // startEyesTracking(); - // startOpenCV(); - startEar(); - - startServos(); - // startMouthControl(head.jaw, mouth); - - speakBlocking(get("STARTINGSEQUENCE")); - } - - public void startBrain() { - startChatBot(); - } - - public ProgramAB startChatBot() { - - try { - chatBot = (ProgramAB) startPeer("chatBot"); - - if (locale != null) { - chatBot.setCurrentBotName(locale.getTag()); - } - - // FIXME remove get en.properties stuff - speakBlocking(get("CHATBOTACTIVATED")); - - chatBot.attachTextPublisher(ear); - - // this.attach(chatBot); FIXME - attach as a TextPublisher - then - // re-publish - // FIXME - deal with language - // speakBlocking(get("CHATBOTACTIVATED")); - chatBot.repetitionCount(10); - // chatBot.setPath(getResourceDir() + fs + "chatbot"); - // chatBot.setPath(getDataDir() + "ProgramAB"); - chatBot.startSession("default", locale.getTag()); - // reset some parameters to default... - chatBot.setPredicate("topic", "default"); - chatBot.setPredicate("questionfirstinit", ""); - chatBot.setPredicate("tmpname", ""); - chatBot.setPredicate("null", ""); - // load last user session - if (!chatBot.getPredicate("name").isEmpty()) { - if (chatBot.getPredicate("lastUsername").isEmpty() || chatBot.getPredicate("lastUsername").equals("unknown") || chatBot.getPredicate("lastUsername").equals("default")) { - chatBot.setPredicate("lastUsername", chatBot.getPredicate("name")); - } - } - chatBot.setPredicate("parameterHowDoYouDo", ""); - chatBot.savePredicates(); - htmlFilter = (HtmlFilter) startPeer("htmlFilter");// Runtime.start("htmlFilter", - // "HtmlFilter"); - chatBot.attachTextListener(htmlFilter); - htmlFilter.attachTextListener((TextListener) getPeer("mouth")); - chatBot.attachTextListener(this); - // start session based on last recognized person - // if (!chatBot.getPredicate("default", "lastUsername").isEmpty() && - // !chatBot.getPredicate("default", "lastUsername").equals("unknown")) { - // chatBot.startSession(chatBot.getPredicate("lastUsername")); - // } - if (chatBot.getPredicate("default", "firstinit").isEmpty() || chatBot.getPredicate("default", "firstinit").equals("unknown") - || chatBot.getPredicate("default", "firstinit").equals("started")) { - chatBot.startSession(chatBot.getPredicate("default", "lastUsername")); - invoke("publishEvent", "FIRST INIT"); - } else { - chatBot.startSession(chatBot.getPredicate("default", "lastUsername")); - invoke("publishEvent", "WAKE UP"); - } - } catch (Exception e) { - speak("could not load chatBot"); - error(e.getMessage()); - speak(e.getMessage()); - } - broadcastState(); - return chatBot; - } - + @Deprecated /* this needs to be removed - runtime and config handle this */ public SpeechRecognizer startEar() { ear = (SpeechRecognizer) startPeer("ear"); @@ -1734,8 +2282,57 @@ public void startedGesture(String nameOfGesture) { } } + public class Heart implements Runnable { + private final ReentrantLock lock = new ReentrantLock(); + private Thread thread; + + @Override + public void run() { + if (lock.tryLock()) { + try { + while (!Thread.currentThread().isInterrupted()) { + invoke("publishHeartbeat"); + Thread.sleep(config.heartbeatInterval); + } + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } finally { + lock.unlock(); + log.info("heart stopping"); + thread = null; + } + } + } + + public void stop() { + if (thread != null) { + thread.interrupt(); + config.heartbeat = false; + } else { + log.info("heart already stopped"); + } + } + + public void start() { + if (thread == null) { + log.info("starting heart"); + thread = new Thread(this, String.format("%s-heart", getName())); + thread.start(); + config.heartbeat = true; + } else { + log.info("heart already started"); + } + } + } + + private transient final Heart heart = new Heart(); + + protected boolean heartBeating = false; + + protected boolean isSpeaking = false; + public void startHeartbeat() { - addTask(1000, "publishHeartbeat"); + heart.start(); } // TODO - general objective "might" be to reduce peers down to something @@ -1796,59 +2393,19 @@ public ServiceInterface startPeer(String peer) { @Override public void startService() { - super.startService(); - - InMoov2Config c = (InMoov2Config) config; - Runtime runtime = Runtime.getInstance(); - - // get service start and release life cycle events - runtime.attachServiceLifeCycleListener(getName()); - - List services = Runtime.getServices(); - for (ServiceInterface si : services) { - if ("Servo".equals(si.getSimpleName())) { - send(si.getFullName(), "setAutoDisable", true); - } - } - // get events of new services and shutdown - subscribe("runtime", "shutdown"); - // power up loopback subscription - addListener(getName(), "powerUp"); + try { - subscribe("runtime", "publishConfigList"); - if (runtime.isProcessingConfig()) { - invoke("publishEvent", "configStarted"); - } - subscribe("runtime", "publishStartConfig"); - subscribe("runtime", "publishFinishedConfig"); + super.startService(); - // chatbot getresponse attached to publishEvent - addListener("publishEvent", getPeerName("chatBot"), "getResponse"); + // core part of InMoov + fsm = (FiniteStateMachine) startPeer("fsm"); + fsm.init(); - try { - // copy config if it doesn't already exist - String resourceBotDir = FileIO.gluePaths(getResourceDir(), "config"); - List files = FileIO.getFileList(resourceBotDir); - for (File f : files) { - String botDir = "data/config/" + f.getName(); - File bDir = new File(botDir); - if (bDir.exists() || !f.isDirectory()) { - log.info("skipping data/config/{}", botDir); - } else { - log.info("will copy new data/config/{}", botDir); - try { - FileIO.copy(f.getAbsolutePath(), botDir); - } catch (Exception e) { - error(e); - } - } - } } catch (Exception e) { error(e); } - runtime.invoke("publishConfigList"); } public void startServos() { @@ -1876,12 +2433,13 @@ public void stop() { } public void stopGesture() { + // FIXME cannot be casting to Python Python p = (Python) Runtime.getService("python"); p.stop(); } public void stopHeartbeat() { - purgeTask("publishHeartbeat"); + heart.stop(); } public void stopNeopixelAnimation() { @@ -1908,7 +2466,21 @@ public void systemCheck() { Platform platform = Runtime.getPlatform(); setPredicate("system version", platform.getVersion()); // ERROR buffer !!! - invoke("publishEvent", "systemCheckFinished"); + systemEvent("systemCheckFinished"); + } + + public String systemEvent(String format, Object... args) { + if (format == null) { + return null; + } + String event = null; + if (args == null) { + event = format; + } else { + event = String.format(format, args); + } + invoke("publishEvent", event); + return event; } // FIXME - if this is really desired it will drive local references for all @@ -1922,44 +2494,121 @@ public void waitTargetPos() { sendToPeer("leftArm", "waitTargetPos"); sendToPeer("torso", "waitTargetPos"); } - + public boolean setSpeechType(String speechType) { - + if (speechType == null) { error("cannot change speech type to null"); return false; } - + if (!speechType.contains(".")) { speechType = "org.myrobotlab.service." + speechType; } - + Runtime runtime = Runtime.getInstance(); String peerName = getName() + ".mouth"; Plan plan = runtime.getDefault(peerName, speechType); try { - SpeechSynthesisConfig mouth = (SpeechSynthesisConfig)plan.get(peerName); + SpeechSynthesisConfig mouth = (SpeechSynthesisConfig) plan.get(peerName); mouth.speechRecognizers = new String[] { getName() + ".ear" }; savePeerConfig("mouth", plan.get(peerName)); - + if (isPeerStarted("mouth")) { - // restart + // restart releasePeer("mouth"); startPeer("mouth"); } - - } catch(Exception e) { + + } catch (Exception e) { error("could not create config for %s", speechType); return false; } - + return true; - + // updatePeerType("mouth" /* getPeerName("mouth") */, speechType); // return speechType; } + public void closeRightHand() { + + // if InMoov2Hand.close/open is used directly + // it prevents user's interception of the data + // and forces InMoov2Hand type to be used :( + // pub/sub is the way + + // hardcoded, but if necessary can be put in config + HashMap map = new HashMap<>(); + map.put("thumb", 130.0); + map.put("index", 180.0); + map.put("majeure", 180.0); + map.put("ringFinger", 180.0); + map.put("pinky", 180.0); + invoke("publishMoveRightHand", map); + + } + + public void openRightHand() { + // if InMoov2Hand.close/open is used directly + // it prevents user's interception of the data + // and forces InMoov2Hand type to be used :( + // pub/sub is the way + + // hardcoded, but if necessary can be put in config + HashMap map = new HashMap<>(); + map.put("thumb", 0.0); + map.put("index", 0.0); + map.put("majeure", 0.0); + map.put("ringFinger", 0.0); + map.put("pinky", 0.0); + invoke("publishMoveRightHand", map); + } + + public void closeLeftHand() { + + // if InMoov2Hand.close/open is used directly + // it prevents user's interception of the data + // and forces InMoov2Hand type to be used :( + // pub/sub is the way + + // hardcoded, but if necessary can be put in config + HashMap map = new HashMap<>(); + map.put("thumb", 130.0); + map.put("index", 180.0); + map.put("majeure", 180.0); + map.put("ringFinger", 180.0); + map.put("pinky", 180.0); + invoke("publishMoveLeftHand", map); + + } + + public void openLeftHand() { + // if InMoov2Hand.close/open is used directly + // it prevents user's interception of the data + // and forces InMoov2Hand type to be used :( + // pub/sub is the way + + // hardcoded, but if necessary can be put in config + HashMap map = new HashMap<>(); + map.put("thumb", 0.0); + map.put("index", 0.0); + map.put("majeure", 0.0); + map.put("ringFinger", 0.0); + map.put("pinky", 0.0); + invoke("publishMoveLeftHand", map); + } + + public void openHands() { + openLeftHand(); + openRightHand(); + } + + public void closeHands() { + closeLeftHand(); + closeRightHand(); + } public static void main(String[] args) { try { @@ -1968,32 +2617,32 @@ public static void main(String[] args) { // Platform.setVirtual(true); // Runtime.start("s01", "Servo"); // Runtime.start("intro", "Intro"); - + Runtime runtime = Runtime.getInstance(); + runtime.saveDefault("default-i01", "i01", "InMoov2", true); Runtime.startConfig("dev"); - + + boolean done = true; + if (done) { + return; + } + WebGui webgui = (WebGui) Runtime.create("webgui", "WebGui"); // webgui.setSsl(true); webgui.autoStartBrowser(false); // webgui.setPort(8888); webgui.startService(); - InMoov2 i01 = (InMoov2)Runtime.start("i01","InMoov2"); - - boolean done = true; - if (done) { - return; - } - + InMoov2 i01 = (InMoov2) Runtime.start("i01", "InMoov2"); - OpenCVConfig ocvConfig = i01.getPeerConfig("opencv", new StaticType<>() {}); + OpenCVConfig ocvConfig = i01.getPeerConfig("opencv", new StaticType<>() { + }); ocvConfig.flip = true; i01.setPeerConfigValue("opencv", "flip", true); // i01.savePeerConfig("", null); - - // Runtime.startConfig("default"); - - // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui", "intro", "Intro", "python", "Python" }); + // Runtime.startConfig("default"); + // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", + // "WebGui", "intro", "Intro", "python", "Python" }); Runtime.start("python", "Python"); // Runtime.start("ros", "Ros"); @@ -2031,11 +2680,15 @@ public static void main(String[] args) { random.addRandom(3000, 8000, "i01", "moveLeftArm", 0.0, 5.0, 85.0, 95.0, 25.0, 30.0, 10.0, 15.0); random.addRandom(3000, 8000, "i01", "moveRightArm", 0.0, 5.0, 85.0, 95.0, 25.0, 30.0, 10.0, 15.0); - random.addRandom(3000, 8000, "i01", "setLeftHandSpeed", 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0); - random.addRandom(3000, 8000, "i01", "setRightHandSpeed", 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0); + random.addRandom(3000, 8000, "i01", "setLeftHandSpeed", 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, + 8.0, 25.0); + random.addRandom(3000, 8000, "i01", "setRightHandSpeed", 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, + 8.0, 25.0); - random.addRandom(3000, 8000, "i01", "moveRightHand", 10.0, 160.0, 10.0, 60.0, 10.0, 60.0, 10.0, 60.0, 10.0, 60.0, 130.0, 175.0); - random.addRandom(3000, 8000, "i01", "moveLeftHand", 10.0, 160.0, 10.0, 60.0, 10.0, 60.0, 10.0, 60.0, 10.0, 60.0, 5.0, 40.0); + random.addRandom(3000, 8000, "i01", "moveRightHand", 10.0, 160.0, 10.0, 60.0, 10.0, 60.0, 10.0, 60.0, 10.0, 60.0, + 130.0, 175.0); + random.addRandom(3000, 8000, "i01", "moveLeftHand", 10.0, 160.0, 10.0, 60.0, 10.0, 60.0, 10.0, 60.0, 10.0, 60.0, + 5.0, 40.0); random.addRandom(200, 1000, "i01", "setHeadSpeed", 8.0, 20.0, 8.0, 20.0, 8.0, 20.0); random.addRandom(200, 1000, "i01", "moveHead", 70.0, 110.0, 65.0, 115.0, 70.0, 110.0); @@ -2055,5 +2708,35 @@ public static void main(String[] args) { } } + // FIXME - rebroadcast these + + @Override + public void onStartSpeaking(String utterance) { + isSpeaking = true; + } + + @Override + public void onEndSpeaking(String utterance) { + isSpeaking = false; + } + + /** + * Rebroadcasted topic change with source changed to this robot, + * python will consume it. + * + * @param topicChange + * @return the topic change + */ + public Event publishTopic(Event topicChange) { + topicChange.src = getName(); + return topicChange; + } + + // predicate change ? rebroadcasted ? + // FIXME if it went chatBot.publishSession -> InMoov2.onSession (if getState() != boot) InMoov2.publishSession + public Event publishSession(Event newSession) { + newSession.src = getName(); + return newSession; + } } diff --git a/src/main/java/org/myrobotlab/service/Lloyd.java b/src/main/java/org/myrobotlab/service/Lloyd.java index ca1dc561ff..4a5aeaf7b3 100755 --- a/src/main/java/org/myrobotlab/service/Lloyd.java +++ b/src/main/java/org/myrobotlab/service/Lloyd.java @@ -23,7 +23,7 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.opencv.OpenCVFilterDL4JTransfer; import org.myrobotlab.opencv.OpenCVFilterLloyd; -import org.myrobotlab.programab.OOBPayload; +import org.myrobotlab.programab.models.Oob; import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.interfaces.SpeechRecognizer; import org.myrobotlab.service.interfaces.SpeechSynthesis; @@ -143,7 +143,7 @@ public void startMouth() { public void startBrain() throws IOException { brain = (ProgramAB) Runtime.start("brain", "ProgramAB"); // TODO: setup the AIML / chat bot directory for all of this. - brain.startSession("ProgramAB", "person", "lloyd"); + brain.setSession("person", "lloyd"); } public void initializeBrain() { @@ -240,13 +240,15 @@ public void tellMeAboutLookup() { } public void createKnowledgeLookup(String pattern, String fieldName, String prefix, String suffix) { - OOBPayload oobTag = createSolrFieldSearchOOB(fieldName); + Oob oobTag = createSolrFieldSearchOOB(fieldName); // TODO: handle (in the template) a zero hit result ?) - String template = prefix + OOBPayload.asBlockingOOBTag(oobTag) + suffix; - brain.addCategory(pattern, template); + + // FIXME +// String template = prefix + OOBPayload.asBlockingOOBTag(oobTag) + suffix; +// brain.addCategory(pattern, template); } - private OOBPayload createSolrFieldSearchOOB(String fieldName) { + private Oob createSolrFieldSearchOOB(String fieldName) { String serviceName = "cloudMemory"; // TODO: make this something that's completely abstracted out from here. String methodName = "fetchFirstResultSentence"; @@ -257,8 +259,10 @@ private OOBPayload createSolrFieldSearchOOB(String fieldName) { // +has_infobox:true"); params.add("infobox_name_ss: title: text: +" + fieldName + ":* +has_infobox:true"); params.add(fieldName); - OOBPayload oobTag = new OOBPayload(serviceName, methodName, params); - return oobTag; +// OOBPayload oobTag = new OOBPayload(serviceName, methodName, params); +// return oobTag; + // FIXME + return null; } public void startMemory() { diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index af6c68c7bf..fbb433ac7a 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -1,97 +1,186 @@ package org.myrobotlab.service.config; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import org.myrobotlab.framework.Plan; import org.myrobotlab.jme3.UserDataConfig; import org.myrobotlab.math.MapperLinear; +import org.myrobotlab.math.MapperSimple; import org.myrobotlab.service.Pid.PidData; import org.myrobotlab.service.Runtime; import org.myrobotlab.service.config.FiniteStateMachineConfig.Transition; import org.myrobotlab.service.config.RandomConfig.RandomMessageConfig; +/** + * InMoov2Config - configuration for InMoov2 service + * - this is a "default" configuration + * If its configuration which will directly affect another service the naming + * pattern should be {peerName}{propertyName} + * e.g. neoPixelErrorRed + * + * FIXME make a color map that can be overridden + * + * @author GroG + * + */ public class InMoov2Config extends ServiceConfig { - public int analogPinFromSoundCard = 53; - - public int audioPollsBySeconds = 2; - - public boolean audioSignalProcessing=false; - + /** + * When the healthCheck is operating, it will check the battery level. + * If the battery level is < 5% it will publishFlash with red at regular interval + */ public boolean batteryInSystem = false; - - public boolean customSound=false; + + /** + * enable custom sound map for state changes + */ + public boolean customSound = false; + public boolean forceMicroOnIfSleeping = true; + + /** + * flashes if error has occurred - requires heartbeat + */ + public boolean healthCheckFlash = true; - public boolean healthCheckActivated = false; - - public int healthCheckTimerMs = 60000; - - public boolean heartbeat = false; - /** - * idle time measures the time the fsm is in an idle state + * flashes the neopixel every time a health check is preformed. + * green == good + * red == battery < 5% + */ + public boolean heartbeatFlash = false; + + /** + * Single heartbeat to drive InMoov2 .. it can check status, healthbeat, + * and fire events to the FSM. + * Checks battery level and sends a heartbeat flash on publishHeartbeat + * and onHeartbeat at a regular interval + */ + public boolean heartbeat = true; + + /** + * interval heath check processes in milliseconds */ - public boolean idleTimer = true; + public long heartbeatInterval = 3000; + /** + * loads all python gesture files in the gesture directory + */ public boolean loadGestures = true; + /** + * executes all scripts in the init directory on startup + */ + public boolean loadInitScripts = true; + /** * default to null - allow the OS to set it, unless explicilty set */ public String locale = null; // = "en-US"; - public boolean neoPixelBootGreen=true; + public boolean neoPixelBootGreen = true; public boolean neoPixelDownloadBlue = true; public boolean neoPixelErrorRed = true; - + public boolean neoPixelFlashWhenSpeaking = false; - - public boolean openCVFaceRecognizerActivated=true; - + + public boolean openCVFaceRecognizerActivated = true; + public boolean pirEnableTracking = false; - + + public boolean pirOnFlash = true; + /** - * play pir sounds when pir switching states - * sound located in data/InMoov2/sounds/pir-activated.mp3 - * sound located in data/InMoov2/sounds/pir-deactivated.mp3 + * play pir sounds when pir switching states sound located in + * data/InMoov2/sounds/pir-activated.mp3 sound located in + * data/InMoov2/sounds/pir-deactivated.mp3 */ public boolean pirPlaySounds = true; - + public boolean pirWakeUp = true; - + public boolean robotCanMoveHeadWhileSpeaking = true; - - + /** * startup and shutdown will pause inmoov - set the speed to this value then * attempt to move to rest */ public double shutdownStartupSpeed = 50; - + /** - * Sleep 5 minutes after last presence detected + * Sleep 5 minutes after last presence detected */ - public int sleepTimeoutMs=300000; - + public int sleepTimeoutMs = 300000; + public boolean startupSound = true; - public int trackingTimeoutMs=10000; + /** + * + */ + public boolean stateChangeIsMute = true; + + /** + * Interval in seconds for a idle state event to fire off. + * If the fsm is in a state which will allow transitioning, the InMoov2 + * state will transition to idle. Heartbeat will fire the event. + */ + public Integer stateIdleInterval = 120; - public String unlockInsult = "forgive me"; + /** + * Interval in seconds for a random state event to fire off. + * If the fsm is in a state which will allow transitioning, the InMoov2 + * state will transition to random. Heartbeat will fire the event. + */ + public Integer stateRandomInterval = 120; + + /** + * Determines if InMoov2 publish system events during boot state + */ + public boolean systemEventsOnBoot = false; + + /** + * The user id .. initially it would be the person starting the program + * but in a more general sense it would be the person "under focus" + * So, a newly discovered user may be the "user" - Python event hanlder + * on_new_user will set this when discovered + */ + public String user = null; + + /** + * Publish system event when state changes + */ + public boolean systemEventStateChange = true; + + public int trackingTimeoutMs = 10000; + + public String unlockInsult = "forgive me"; + public boolean virtual = false; + public String bootAnimation = "Theater Chase"; + + public boolean flashOnErrors = true; + + public boolean flashOnPir; + + public boolean loadAppsScripts = true; + public InMoov2Config() { } @Override public Plan getDefault(Plan plan, String name) { super.getDefault(plan, name); + + // FIXME define global peers named "python" "webgui" etc... + // peers FIXME global opencv addDefaultPeerConfig(plan, name, "audioPlayer", "AudioFile", true); @@ -101,6 +190,7 @@ public Plan getDefault(Plan plan, String name) { addDefaultPeerConfig(plan, name, "ear", "WebkitSpeechRecognition", false); addDefaultPeerConfig(plan, name, "eyeTracking", "Tracking", false); addDefaultPeerConfig(plan, name, "fsm", "FiniteStateMachine", false); + addDefaultPeerConfig(plan, name, "log", "Log", false); addDefaultPeerConfig(plan, name, "gpt3", "Gpt3", false); addDefaultPeerConfig(plan, name, "head", "InMoov2Head", false); addDefaultPeerConfig(plan, name, "headTracking", "Tracking", false); @@ -111,6 +201,7 @@ public Plan getDefault(Plan plan, String name) { addDefaultPeerConfig(plan, name, "leftArm", "InMoov2Arm", false); addDefaultPeerConfig(plan, name, "leftHand", "InMoov2Hand", false); addDefaultPeerConfig(plan, name, "mouth", "MarySpeech", false); + addDefaultPeerConfig(plan, name, "mouth.audioFile", "AudioFile", false); addDefaultPeerConfig(plan, name, "mouthControl", "MouthControl", false); addDefaultPeerConfig(plan, name, "neoPixel", "NeoPixel", false); addDefaultPeerConfig(plan, name, "opencv", "OpenCV", false); @@ -127,6 +218,25 @@ public Plan getDefault(Plan plan, String name) { addDefaultPeerConfig(plan, name, "torso", "InMoov2Torso", false); addDefaultPeerConfig(plan, name, "ultrasonicRight", "UltrasonicSensor", false); addDefaultPeerConfig(plan, name, "ultrasonicLeft", "UltrasonicSensor", false); + addDefaultPeerConfig(plan, name, "vertx", "Vertx", false); + addDefaultPeerConfig(plan, name, "webxr", "WebXR", false); + + WebXRConfig webxr = (WebXRConfig) plan.get(getPeerName("webxr")); + + Map map = new HashMap<>(); + MapperSimple mapper = new MapperSimple(-0.5, 0.5, 0, 180); + map.put("i01.head.neck", mapper); + webxr.controllerMappings.put("head.orientation.pitch", map); + + map = new HashMap<>(); + mapper = new MapperSimple(-0.5, 0.5, 0, 180); + map.put("i01.head.rothead", mapper); + webxr.controllerMappings.put("head.orientation.yaw", map); + + map = new HashMap<>(); + mapper = new MapperSimple(-0.5, 0.5, 0, 180); + map.put("i01.head.roll", mapper); + webxr.controllerMappings.put("head.orientation.roll", map); MouthControlConfig mouthControl = (MouthControlConfig) plan.get(getPeerName("mouthControl")); @@ -141,6 +251,8 @@ public Plan getDefault(Plan plan, String name) { mouthControl.mouth = i01Name + ".mouth"; ProgramABConfig chatBot = (ProgramABConfig) plan.get(getPeerName("chatBot")); + chatBot.listeners = new ArrayList<>(); + Runtime runtime = Runtime.getInstance(); String[] bots = new String[] { "cn-ZH", "en-US", "fi-FI", "hi-IN", "nl-NL", "ru-RU", "de-DE", "es-ES", "fr-FR", "it-IT", "pt-PT", "tr-TR" }; String tag = runtime.getLocaleTag(); @@ -149,19 +261,17 @@ public Plan getDefault(Plan plan, String name) { String lang = tagparts[0]; for (String b : bots) { if (b.startsWith(lang)) { - chatBot.currentBotName = b; + chatBot.botType = b; break; } } } - - chatBot.currentUserName = "human"; - - // chatBot.textListeners = new String[] { name + ".htmlFilter" }; - if (chatBot.listeners == null) { - chatBot.listeners = new ArrayList<>(); - } - chatBot.listeners.add(new Listener("publishText", name + ".htmlFilter", "onText")); + + // chatBot.currentUserName = "human"; is already default + + Gpt3Config gpt3 = (Gpt3Config) plan.get(getPeerName("gpt3")); + gpt3.listeners = new ArrayList<>(); + gpt3.listeners.add(new Listener("publishText", name + ".htmlFilter", "onText")); HtmlFilterConfig htmlFilter = (HtmlFilterConfig) plan.get(getPeerName("htmlFilter")); // htmlFilter.textListeners = new String[] { name + ".mouth" }; @@ -176,21 +286,15 @@ public Plan getDefault(Plan plan, String name) { mouth.voice = "Mark"; mouth.speechRecognizers = new String[] { name + ".ear" }; - // == Peer - servoMixer ============================= - // setup name references to different services - ServoMixerConfig servoMixer = (ServoMixerConfig) plan.get(getPeerName("servoMixer")); - servoMixer.listeners = new ArrayList<>(); - servoMixer.listeners.add(new Listener("publishText", name + ".mouth", "onText")); - //servoMixer.listeners.add(new Listener("publishText", name + ".chatBot", "onText")); // == Peer - ear ============================= // setup name references to different services WebkitSpeechRecognitionConfig ear = (WebkitSpeechRecognitionConfig) plan.get(getPeerName("ear")); - ear.listeners = new ArrayList<>(); + ear.listeners = new ArrayList<>(); ear.listeners.add(new Listener("publishText", name + ".chatBot", "onText")); ear.listening = true; // remove, should only need ServiceConfig.listeners - ear.textListeners = new String[]{name + ".chatBot"}; + ear.textListeners = new String[] { name + ".chatBot" }; JMonkeyEngineConfig simulator = (JMonkeyEngineConfig) plan.get(getPeerName("simulator")); @@ -259,63 +363,72 @@ public Plan getDefault(Plan plan, String name) { simulator.cameraLookAt = name + ".torso.lowStom"; FiniteStateMachineConfig fsm = (FiniteStateMachineConfig) plan.get(getPeerName("fsm")); - // TODO - events easily gotten from InMoov data ?? auto callbacks in python if exists ? + // TODO - events easily gotten from InMoov data ?? auto callbacks in python + // if exists ? + fsm.listeners = new ArrayList<>(); fsm.current = "boot"; - fsm.transitions.add(new Transition("boot", "configStarted", "applyingConfig")); - fsm.transitions.add(new Transition("applyingConfig", "getUserInfo", "getUserInfo")); - fsm.transitions.add(new Transition("applyingConfig", "systemCheck", "systemCheck")); - fsm.transitions.add(new Transition("applyingConfig", "wake", "awake")); - fsm.transitions.add(new Transition("getUserInfo", "systemCheck", "systemCheck")); - fsm.transitions.add(new Transition("systemCheck", "systemCheckFinished", "awake")); - fsm.transitions.add(new Transition("awake", "sleep", "sleeping")); + fsm.transitions.add(new Transition("boot", "start", "start")); + // fsm.transitions.add(new Transition("start", "idle", "idle")); + fsm.transitions.add(new Transition("start", "first_init", "first_init")); + fsm.transitions.add(new Transition("start", "wake", "wake")); + fsm.transitions.add(new Transition("idle", "random", "random")); + fsm.transitions.add(new Transition("random", "idle", "idle")); + fsm.transitions.add(new Transition("idle", "sleep", "sleep")); + fsm.transitions.add(new Transition("sleep", "wake", "wake")); + fsm.transitions.add(new Transition("idle", "power_down", "power_down")); + fsm.transitions.add(new Transition("wake", "idle", "idle")); + // fsm.transitions.add(new Transition("wake", "first_init", "first_init")); + // powerDown to shutdown + // fsm.transitions.add(new Transition("systemCheck", "systemCheckFinished", + // "awake")); + // fsm.transitions.add(new Transition("awake", "sleep", "sleeping")); - - PirConfig pir = (PirConfig) plan.get(getPeerName("pir")); - pir.pin = "23"; + pir.pin = "D23"; pir.controller = name + ".left"; pir.listeners = new ArrayList<>(); - pir.listeners.add(new Listener("publishPirOn", name, "onPirOn")); - + pir.listeners.add(new Listener("publishPirOn", name)); + pir.listeners.add(new Listener("publishPirOff", name)); + // == Peer - random ============================= RandomConfig random = (RandomConfig) plan.get(getPeerName("random")); random.enabled = false; // setup name references to different services - RandomMessageConfig rm = new RandomMessageConfig(name, "setLeftArmSpeed", 3000, 8000, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0); + RandomMessageConfig rm = new RandomMessageConfig(name, "setLeftArmSpeed", 3000, 8000, 8, 25, 8, 25, 8, 25, 8, 25); random.randomMessages.put(name + ".setLeftArmSpeed", rm); - rm = new RandomMessageConfig(name, "setRightArmSpeed", 3000, 8000, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0); + rm = new RandomMessageConfig(name, "setRightArmSpeed", 3000, 8000, 8, 25, 8, 25, 8, 25, 8, 25); random.randomMessages.put(name + ".setRightArmSpeed", rm); - rm = new RandomMessageConfig(name, "moveLeftArm", 000, 8000, 0.0, 5.0, 85.0, 95.0, 25.0, 30.0, 10.0, 15.0); + rm = new RandomMessageConfig(name, "moveLeftArm", 000, 8000, 0, 5, 85, 95, 25, 30, 10, 15); random.randomMessages.put(name + ".moveLeftArm", rm); - rm = new RandomMessageConfig(name, "moveRightArm", 3000, 8000, 0.0, 5.0, 85.0, 95.0, 25.0, 30.0, 10.0, 15.0); + rm = new RandomMessageConfig(name, "moveRightArm", 3000, 8000, 0, 5, 85, 95, 25, 30, 10, 15); random.randomMessages.put(name + ".moveRightArm", rm); - rm = new RandomMessageConfig(name, "setLeftHandSpeed", 3000, 8000, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0); + rm = new RandomMessageConfig(name, "setLeftHandSpeed", 3000, 8000, 8, 25, 8, 25, 8, 25, 8, 25, 8, 25, 8, 25); random.randomMessages.put(name + ".setLeftHandSpeed", rm); - rm = new RandomMessageConfig(name, "setRightHandSpeed", 3000, 8000, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0); + rm = new RandomMessageConfig(name, "setRightHandSpeed", 3000, 8000, 8, 25, 8, 25, 8, 25, 8, 25, 8, 25, 8, 25); random.randomMessages.put(name + ".setRightHandSpeed", rm); - rm = new RandomMessageConfig(name, "moveLeftHand", 3000, 8000, 10.0, 160.0, 10.0, 60.0, 10.0, 60.0, 10.0, 60.0, 10.0, 60.0, 130.0, 175.0); + rm = new RandomMessageConfig(name, "moveLeftHand", 3000, 8000, 10, 160, 10, 60, 10, 60, 10, 60, 10, 60, 130, 175); random.randomMessages.put(name + ".moveLeftHand", rm); - rm = new RandomMessageConfig(name, "moveRightHand", 3000, 8000, 10.0, 160.0, 10.0, 60.0, 10.0, 60.0, 10.0, 60.0, 10.0, 60.0, 130.0, 175.0); + rm = new RandomMessageConfig(name, "moveRightHand", 3000, 8000, 10, 160, 10, 60, 10, 60, 10, 60, 10, 60, 130, 175); random.randomMessages.put(name + ".moveRightHand", rm); - rm = new RandomMessageConfig(name, "setHeadSpeed",3000, 8000, 8.0, 20.0, 8.0, 20.0, 8.0, 20.0); + rm = new RandomMessageConfig(name, "setHeadSpeed", 3000, 8000, 8, 20, 8, 20, 8, 20); random.randomMessages.put(name + ".setHeadSpeed", rm); - rm = new RandomMessageConfig(name, "moveHead", 3000, 8000, 70.0, 110.0, 65.0, 115.0, 70.0, 110.0); + rm = new RandomMessageConfig(name, "moveHead", 3000, 8000, 70, 110, 65, 115, 70, 110); random.randomMessages.put(name + ".moveHead", rm); - rm = new RandomMessageConfig(name , "setTorsoSpeed", 3000, 8000, 2.0, 5.0, 2.0, 5.0, 2.0, 5.0); + rm = new RandomMessageConfig(name, "setTorsoSpeed", 3000, 8000, 2, 5, 2, 5, 2, 5); random.randomMessages.put(name + ".setTorsoSpeed", rm); - rm = new RandomMessageConfig(name, "moveTorso", 3000, 8000, 85.0, 95.0, 88.0, 93.0, 70.0, 110.0); + rm = new RandomMessageConfig(name, "moveTorso", 3000, 8000, 85, 95, 88, 93, 70, 110); random.randomMessages.put(name + ".moveTorso", rm); // == Peer - headTracking ============================= @@ -387,17 +500,114 @@ public Plan getDefault(Plan plan, String name) { plan.remove(name + ".eyeTracking.controller"); plan.remove(name + ".eyeTracking.controller.serial"); plan.remove(name + ".eyeTracking.cv"); - + + // LOOPBACK PUBLISHING - ITS A GREAT WAY TO SUPPORT + // EXTENSIBLE AND OVERRIDABLE BEHAVIORS + // inmoov2 default listeners listeners = new ArrayList<>(); // FIXME - should be getPeerName("neoPixel") - listeners.add(new Listener("publishFlash", name + ".neoPixel", "onLedDisplay")); - listeners.add(new Listener("publishEvent", name + ".fsm")); + // loopbacks allow user to override or extend with python +// listeners.add(new Listener("publishBoot", name)); + listeners.add(new Listener("publishHeartbeat", name)); +// listeners.add(new Listener("publishConfigFinished", name)); +// listeners.add(new Listener("publishStateChange", name)); + + // listeners.add(new Listener("publishPowerUp", name)); + // listeners.add(new Listener("publishPowerDown", name)); + // listeners.add(new Listener("publishError", name)); + + listeners.add(new Listener("publishMoveHead", name)); + listeners.add(new Listener("publishMoveRightArm", name)); + listeners.add(new Listener("publishMoveLeftArm", name)); + listeners.add(new Listener("publishMoveRightHand", name)); + listeners.add(new Listener("publishMoveLeftHand", name)); + listeners.add(new Listener("publishMoveTorso", name)); + + LogConfig log = (LogConfig) plan.get(getPeerName("log")); + log.listeners = new ArrayList<>(); + log.listeners.add(new Listener("publishLogEvents", name)); + + // mouth_audioFile.listeners.add(new Listener("publishAudioEnd", name)); + // mouth_audioFile.listeners.add(new Listener("publishAudioStart", name)); + + // InMoov2 --to--> service + listeners.add(new Listener("publishFlash", getPeerName("neoPixel"))); + listeners.add(new Listener("publishEvent", getPeerName("chatBot"), "getResponse")); + listeners.add(new Listener("publishPlayAudioFile", getPeerName("audioPlayer"))); + + listeners.add(new Listener("publishPlayAnimation", getPeerName("neoPixel"))); + listeners.add(new Listener("publishStopAnimation", getPeerName("neoPixel"))); + + // listeners.add(new Listener("publishPowerUp", name)); + // listeners.add(new Listener("publishPowerDown", name)); + // listeners.add(new Listener("publishError", name)); + + listeners.add(new Listener("publishMoveHead", name)); + listeners.add(new Listener("publishMoveRightArm", name)); + listeners.add(new Listener("publishMoveLeftArm", name)); + listeners.add(new Listener("publishMoveRightHand", name)); + listeners.add(new Listener("publishMoveLeftHand", name)); + listeners.add(new Listener("publishMoveTorso", name)); + + + // service --to--> InMoov2 + AudioFileConfig mouth_audioFile = (AudioFileConfig) plan.get(getPeerName("mouth.audioFile")); + mouth_audioFile.listeners = new ArrayList<>(); + mouth_audioFile.listeners.add(new Listener("publishPeak", name)); + + // service --> InMoov2 FIXME !!! - rebroadcast with event and event.src + mouth.listeners = new ArrayList<>(); + mouth.listeners.add(new Listener("publishStartSpeaking", name)); + mouth.listeners.add(new Listener("publishEndSpeaking", name)); + + // service --> hardcoded service name - NOT GOOD :( + mouth.listeners.add(new Listener("publishStartSpeaking", "python")); + mouth.listeners.add(new Listener("publishEndSpeaking", "python")); + + // mouth --> ear .. should it be audio file instead "closer to the actual audio"? + mouth.listeners.add(new Listener("publishStartSpeaking", getPeerName("ear"))); + mouth.listeners.add(new Listener("publishEndSpeaking", getPeerName("ear"))); + + + + webxr.listeners = new ArrayList<>(); + webxr.listeners.add(new Listener("publishJointAngles", name)); // TODO rebroadcast this + + // mouth_audioFile.listeners.add(new Listener("publishAudioEnd", name)); + // mouth_audioFile.listeners.add(new Listener("publishAudioStart", name)); + + // InMoov2 --to--> service + listeners.add(new Listener("publishEvent", getPeerName("chatBot"), "getResponse")); + listeners.add(new Listener("publishPlayAudioFile", getPeerName("audioPlayer"))); + listeners.add(new Listener("publishPython", "python")); + listeners.add(new Listener("publishSensorData", "python")); + listeners.add(new Listener("publishPredicate", "python")); + listeners.add(new Listener("publishStateChange", "python")); + listeners.add(new Listener("publishSession", "python")); + listeners.add(new Listener("publishMessage", "python")); + + + // service --to--> service + ServoMixerConfig servoMixer = (ServoMixerConfig) plan.get(getPeerName("servoMixer")); + servoMixer.listeners = new ArrayList<>(); + servoMixer.listeners.add(new Listener("publishText", getPeerName("mouth"), "onText")); + chatBot.listeners.add(new Listener("publishText", name + ".htmlFilter", "onText")); + + + // service --> InMoov2 --> rebroadcast correctly with src + chatBot.listeners.add(new Listener("publishTopic", name, "publishTopic")); + chatBot.listeners.add(new Listener("publishPredicate", name, "publishPredicate")); + chatBot.listeners.add(new Listener("publishSession", name, "publishSession")); + chatBot.listeners.add(new Listener("publishMessage", name, "publishMessage")); + fsm.listeners.add(new Listener("publishStateChange", name, "publishStateChange")); + + // remove the auto-added starts in the plan's runtime RuntimConfig.registry plan.removeStartsWith(name + "."); - + // rtConfig.add(name); // <-- adding i01 / not needed return plan; From 768575de3db460ffa6c17f4f666885e88455feea Mon Sep 17 00:00:00 2001 From: grog Date: Tue, 5 Dec 2023 11:09:59 -0800 Subject: [PATCH 005/106] more --- .../generics/SlidingWindowList.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/org/myrobotlab/generics/SlidingWindowList.java diff --git a/src/main/java/org/myrobotlab/generics/SlidingWindowList.java b/src/main/java/org/myrobotlab/generics/SlidingWindowList.java new file mode 100644 index 0000000000..2c048a532f --- /dev/null +++ b/src/main/java/org/myrobotlab/generics/SlidingWindowList.java @@ -0,0 +1,22 @@ +package org.myrobotlab.generics; + +import java.util.ArrayList; + +public class SlidingWindowList extends ArrayList { + private static final long serialVersionUID = 1L; + private final int maxSize; + + public SlidingWindowList(int maxSize) { + this.maxSize = maxSize; + } + + @Override + public boolean add(E element) { + boolean added = super.add(element); + if (size() > maxSize) { + removeRange(0, size() - maxSize); // Remove oldest elements if size exceeds maxSize + } + return added; + } + +} From 0dce02c68786a53818de146bbf2ceb51ccc20e73 Mon Sep 17 00:00:00 2001 From: grog Date: Tue, 5 Dec 2023 11:11:38 -0800 Subject: [PATCH 006/106] more --- .../java/org/myrobotlab/service/Shoutbox.java | 2 +- .../myrobotlab/service/data/SensorData.java | 42 ++++++++++++++++--- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Shoutbox.java b/src/main/java/org/myrobotlab/service/Shoutbox.java index a567ae073e..e063de93a1 100644 --- a/src/main/java/org/myrobotlab/service/Shoutbox.java +++ b/src/main/java/org/myrobotlab/service/Shoutbox.java @@ -453,7 +453,7 @@ public void startChatBot() throws IOException { return; } chatbot = (ProgramAB) Runtime.start("chatbot", "ProgramAB"); - chatbot.startSession("ProgramAB", "alice2"); + chatbot.setSession("ProgramAB", "alice2"); chatbot.addResponseListener(this); } diff --git a/src/main/java/org/myrobotlab/service/data/SensorData.java b/src/main/java/org/myrobotlab/service/data/SensorData.java index 363553b68a..7a922cebaa 100644 --- a/src/main/java/org/myrobotlab/service/data/SensorData.java +++ b/src/main/java/org/myrobotlab/service/data/SensorData.java @@ -2,17 +2,47 @@ import java.io.Serializable; +/** + * A generalized bucket for simplified channels to hold + * any type of sensor information and its source. + * + * @author GroG + * + */ public class SensorData implements Serializable { private static final long serialVersionUID = 1L; - Object data; + + /** + * timestamp + */ + public long ts = System.currentTimeMillis(); + + /** + * Type of string data so downstream receivers can + * interpret data field correctly, typically the + * name of the type of service which generated it. + */ + public String type; + + /** + * Service name where data came from. + */ + public String src; + + /** + * data of sensor + */ + public Object data; - public SensorData(Object data) { - this.data = data; + public SensorData() { } - - public Object getData() { - return data; + + public SensorData(String src, String type, Object data) { + this.src = src; + this.type = type; + this.data = data; } + } From 2f50853a23d938dc7c514e740aba7ba54ca8a0ca Mon Sep 17 00:00:00 2001 From: grog Date: Tue, 5 Dec 2023 11:14:28 -0800 Subject: [PATCH 007/106] hashToHex --- src/main/java/org/myrobotlab/codec/CodecUtils.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/myrobotlab/codec/CodecUtils.java b/src/main/java/org/myrobotlab/codec/CodecUtils.java index ba528b7a10..eb20b2191b 100644 --- a/src/main/java/org/myrobotlab/codec/CodecUtils.java +++ b/src/main/java/org/myrobotlab/codec/CodecUtils.java @@ -1685,4 +1685,10 @@ public static int[] hexToRGB(String hexValue) { } return rgb; } + + public static String hashcodeToHex(int hashCode) { + String hexString = Long.toHexString(hashCode).toUpperCase(); + return String.format("%6s", hexString).replace(' ', '0').substring(0, 6); + } + } From 10eb5e0707e34176b5bc665e76d4c8106a418652 Mon Sep 17 00:00:00 2001 From: grog Date: Tue, 5 Dec 2023 11:36:20 -0800 Subject: [PATCH 008/106] state change definition --- .../service/FiniteStateMachine.java | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/FiniteStateMachine.java b/src/main/java/org/myrobotlab/service/FiniteStateMachine.java index d04436f1e3..7a290bc459 100644 --- a/src/main/java/org/myrobotlab/service/FiniteStateMachine.java +++ b/src/main/java/org/myrobotlab/service/FiniteStateMachine.java @@ -12,6 +12,7 @@ import org.myrobotlab.framework.Service; import org.myrobotlab.framework.interfaces.MessageListener; import org.myrobotlab.framework.interfaces.ServiceInterface; +import org.myrobotlab.generics.SlidingWindowList; import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; @@ -51,7 +52,7 @@ public class FiniteStateMachine extends Service { /** * state history of fsm */ - protected List history = new ArrayList<>(); + protected List history = new SlidingWindowList<>(100); // TODO - .from("A").to("B").on(Messages.ANY) // TODO - .from("A").to("B").on(Messages.EMPTY) @@ -65,17 +66,27 @@ public class Tuple { } public class StateChange { - public String last; - public String current; + /** + * timestamp + */ + public long ts = System.currentTimeMillis(); + + /** + * current new state + */ + public String state; + + /** + * event which activated new state + */ public String event; - public StateChange(String last, String current, String event) { - this.last = last; - this.current = current; + public StateChange(String current, String event) { + this.state = current; this.event = event; } public String toString() { - return String.format("%s --%s--> %s", last, event, current); + return String.format("%s --%s--> %s", last, event, state); } } @@ -113,11 +124,8 @@ public String getNext(String key) { public void init() { stateMachine.init(); State state = stateMachine.getCurrent(); - if (history.size() > 100) { - history.remove(0); - } if (state != null) { - history.add(state.getName()); + history.add(new StateChange(state.getName(), String.format("%s.setCurrent", getName()))); } } @@ -194,8 +202,9 @@ public void fire(String event) { log.info("fired event ({}) -> ({}) moves to ({})", event, last == null ? null : last.getName(), current == null ? null : current.getName()); if (last != null && !last.equals(current)) { - invoke("publishStateChange", new StateChange(last.getName(), current.getName(), event)); - history.add(current.getName()); + StateChange stateChange = new StateChange(current.getName(), event); + invoke("publishStateChange", stateChange); + history.add(stateChange); } } catch (Exception e) { log.error("fire threw", e); @@ -247,7 +256,7 @@ public StateChange publishStateChange(StateChange stateChange) { for (String listener : messageListeners) { ServiceInterface service = Runtime.getService(listener); if (service != null) { - org.myrobotlab.framework.Message msg = org.myrobotlab.framework.Message.createMessage(getName(), listener, CodecUtils.getCallbackTopicName(stateChange.current), null); + org.myrobotlab.framework.Message msg = org.myrobotlab.framework.Message.createMessage(getName(), listener, CodecUtils.getCallbackTopicName(stateChange.state), null); service.in(msg); } } @@ -419,7 +428,7 @@ public void setCurrent(String state) { stateMachine.setCurrent(state); current = stateMachine.getCurrent(); if (last != null && !last.equals(current)) { - invoke("publishStateChange", new StateChange(last.getName(), current.getName(), null)); + invoke("publishStateChange", new StateChange(current.getName(), String.format("%s.setCurrent", getName()))); } } catch (Exception e) { log.error("setCurrent threw", e); @@ -427,4 +436,12 @@ public void setCurrent(String state) { } } + public String getPreviousState() { + if (history.size() == 0) { + return null; + } else { + return history.get(history.size() - 2).state; + } + } + } \ No newline at end of file From 0473e76b152582757d5ee48f018b55e0492e75f3 Mon Sep 17 00:00:00 2001 From: grog Date: Tue, 5 Dec 2023 11:45:05 -0800 Subject: [PATCH 009/106] no more oob payload --- .../myrobotlab/programab/OOBPayloadTest.java | 32 ------------------- .../org/myrobotlab/service/HarryTest.java | 6 ++-- 2 files changed, 4 insertions(+), 34 deletions(-) delete mode 100644 src/test/java/org/myrobotlab/programab/OOBPayloadTest.java diff --git a/src/test/java/org/myrobotlab/programab/OOBPayloadTest.java b/src/test/java/org/myrobotlab/programab/OOBPayloadTest.java deleted file mode 100644 index 3761bc5c29..0000000000 --- a/src/test/java/org/myrobotlab/programab/OOBPayloadTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.myrobotlab.programab; - -import static org.junit.Assert.assertEquals; - -import java.util.ArrayList; - -import org.junit.Test; -import org.myrobotlab.test.AbstractTest; - -public class OOBPayloadTest extends AbstractTest { - - @Test - public void basicTestOOBPayload() { - // test the getters/setters of oob object. - OOBPayload payload = new OOBPayload(); - payload.setMethodName("foo"); - ArrayList params = new ArrayList(); - payload.setParams(params); - payload.setServiceName("fooservice"); - - assertEquals(payload.getMethodName(), "foo"); - assertEquals(payload.getParams(), params); - assertEquals(payload.getServiceName(), "fooservice"); - - OOBPayload pl2 = new OOBPayload("boo2", "fooo2", params); - assertEquals(pl2.getMethodName(), "fooo2"); - assertEquals(pl2.getParams(), params); - assertEquals(pl2.getServiceName(), "boo2"); - - } - -} diff --git a/src/test/java/org/myrobotlab/service/HarryTest.java b/src/test/java/org/myrobotlab/service/HarryTest.java index 29527a0845..b635ea2a6b 100755 --- a/src/test/java/org/myrobotlab/service/HarryTest.java +++ b/src/test/java/org/myrobotlab/service/HarryTest.java @@ -144,7 +144,8 @@ public void testDynamic() throws SolrServerException, IOException, InterruptedEx solr.startEmbedded(); createAIML(); ProgramAB harry = (ProgramAB) Runtime.start("harry", "ProgramAB"); - harry.startSession("testbots", "username", "test"); + harry.setBotType("testbots"); + harry.startSession("username", "test", true); goLearnStuff(solr, harry); @@ -166,7 +167,8 @@ public void testHarry() throws Exception { solr.startEmbedded(); createAIML(); ProgramAB harry = (ProgramAB) Runtime.start("harry", "ProgramAB"); - harry.startSession("testbots", "username", "test"); + harry.setSession("username", "test"); + // harry.startSession("username", "test"); // start the opencv service with the yolo filter. OpenCV cv = (OpenCV) Runtime.start("cv", "OpenCV"); From a0e79dadfad6a6022dda5b4169c4173221466052 Mon Sep 17 00:00:00 2001 From: grog Date: Tue, 5 Dec 2023 11:50:08 -0800 Subject: [PATCH 010/106] formatted xml and fixed ui --- .../WebGui/app/service/js/ProgramABGui.js | 432 +++++++++--------- .../app/service/views/ProgramABGui.html | 12 +- .../service/ArduinoMotorPotTest.java | 2 +- .../ProgramAB/bots/lloyd/aiml/lloyd.aiml | 394 ++++++++++------ 4 files changed, 473 insertions(+), 367 deletions(-) diff --git a/src/main/resources/resource/WebGui/app/service/js/ProgramABGui.js b/src/main/resources/resource/WebGui/app/service/js/ProgramABGui.js index 38211728ff..0e34121c1c 100644 --- a/src/main/resources/resource/WebGui/app/service/js/ProgramABGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/ProgramABGui.js @@ -1,6 +1,12 @@ -angular.module('mrlapp.service.ProgramABGui', []).controller('ProgramABGuiCtrl', ['$scope', '$compile', 'mrl', '$uibModal', '$sce', function($scope, $compile, mrl, $uibModal, $sce) { +angular.module("mrlapp.service.ProgramABGui", []).controller("ProgramABGuiCtrl", [ + "$scope", + "$compile", + "mrl", + "$uibModal", + "$sce", + function ($scope, $compile, mrl, $uibModal, $sce) { // $modal ???? - console.info('ProgramABGuiCtrl') + console.info("ProgramABGuiCtrl") // grab the self and message var _self = this var startDialog = null @@ -8,8 +14,8 @@ angular.module('mrlapp.service.ProgramABGui', []).controller('ProgramABGuiCtrl', // use $scope only when the variable // needs to interract with the display - $scope.currentUserName = '' - $scope.utterance = '' + $scope.currentUserName = "" + $scope.utterance = "" $scope.currentSessionKey = null $scope.status = null $scope.predicates = [] @@ -18,10 +24,10 @@ angular.module('mrlapp.service.ProgramABGui', []).controller('ProgramABGuiCtrl', $scope.aimlEditor = null $scope.tabs = { - "selected": 1 + selected: 1, } $scope.tabsRight = { - "selected": 1 + selected: 1, } // active tab index @@ -29,7 +35,7 @@ angular.module('mrlapp.service.ProgramABGui', []).controller('ProgramABGuiCtrl', $scope.aimlFile = "blah \n blah " $scope.aimlFileData = { - "data": "HELLO THERE !!!" + data: "HELLO THERE !!!", } $scope.lastResponse @@ -38,7 +44,7 @@ angular.module('mrlapp.service.ProgramABGui', []).controller('ProgramABGuiCtrl', // be put in an object to be effectively modified // when $sope is used $scope.edit = { - properties: false + properties: false, } $scope.chatLog = [] @@ -47,269 +53,263 @@ angular.module('mrlapp.service.ProgramABGui', []).controller('ProgramABGuiCtrl', $scope.log = [] // following the template. - this.updateState = function(service) { - // use another scope var to transfer/merge selection - // from user - service.currentSession is always read-only - // all service data should never be written to, only read from - $scope.currentUserName = service.config.currentUserName - $scope.service = service - $scope.currentSessionKey = $scope.getCurrentSessionKey() - - /* + this.updateState = function (service) { + // use another scope var to transfer/merge selection + // from user - service.currentSession is always read-only + // all service data should never be written to, only read from + $scope.currentUserName = service.config.username + $scope.service = service + $scope.currentSessionKey = $scope.getCurrentSessionKey() + + /* for (let bot in $scope.service.sessions){ for (let username in $scope.service.sessions[bot]){ console.info(username) } } */ - } - this.onMsg = function(inMsg) { - // console.info("ProgramABGui.onMsg(" + inMsg.method + ')') - let data = inMsg.data[0] - - switch (inMsg.method) { - - case 'onStatus': - $scope.status = data; - $scope.$apply() - break - - case 'onBotImage': - $scope.currentBotImage = data - $scope.$apply() - break - - case 'onState': - _self.updateState(data) - $scope.$apply() - break - - case 'onTopic': - $scope.service.currentTopic = data - $scope.$apply() - break - - case 'onAimlFile': - $scope.aimlFileData.data = data - $scope.$apply() - break - - case 'onPredicates': - $scope.predicates = data - $scope.$apply() - break - - case 'onPredicate': - $scope.predicates[data.name] = data.value - $scope.$apply() - break - - - case 'onRequest': - var textData = data - $scope.chatLog.unshift({ - type: 'User', - name: $scope.currentUserName, - text: $sce.trustAsHtml(textData) - }) - console.info('onRequest', textData) - $scope.$apply() - break - case 'onResponse': - var textData = data - $scope.chatLog.unshift({ - type: 'Bot', - name: $scope.service.config.currentBotName, - text: $sce.trustAsHtml(data.msg) - }) - $scope.lastResponse = textData - $scope.$apply() - break - case 'onLog': - var textData = data - let filename = null - parts = textData.split(" ") - if (parts.length > 2 && parts[1] == "Matched:") { - filename = parts[parts.length - 1] - textData = textData.substr(0, textData.lastIndexOf(' ') + 1) - // pos0 = textData.lastIndexOf(' ') + 1 - // url = textData.substr(0,pos0) + '' - // url = textData.substr(0,pos0) + '' - // textData = url - } - - $scope.log.unshift({ - 'name': '', - 'text': textData, - 'filename': filename - }) - - $scope.$apply() - break - case 'onOOBText': - var textData = data - $scope.chatLog.unshift({ - name: " > oob <", - text: $sce.trustAsHtml(textData) - }) - console.info('currResponse', textData) - $scope.$apply() - break + this.onMsg = function (inMsg) { + // console.info("ProgramABGui.onMsg(" + inMsg.method + ')') + let data = inMsg.data[0] + + switch (inMsg.method) { + case "onStatus": + $scope.status = data + $scope.$apply() + break + + case "onBotImage": + $scope.currentBotImage = data + $scope.$apply() + break + + case "onState": + _self.updateState(data) + $scope.$apply() + break + + case "onTopic": + $scope.service.currentTopic = data + $scope.$apply() + break + + case "onAimlFile": + $scope.aimlFileData.data = data + $scope.$apply() + break + + case "onPredicates": + $scope.predicates = data + $scope.$apply() + break + + case "onPredicate": + $scope.predicates[data.name] = data.value + $scope.$apply() + break + + case "onRequest": + var textData = data + $scope.chatLog.unshift({ + type: "User", + name: $scope.currentUserName, + text: $sce.trustAsHtml(textData), + }) + console.info("onRequest", textData) + $scope.$apply() + break + case "onResponse": + var textData = data + $scope.chatLog.unshift({ + type: "Bot", + name: $scope.service.config.botType, + text: $sce.trustAsHtml(data.msg), + }) + $scope.lastResponse = textData + $scope.$apply() + break + case "onLog": + var textData = data + let filename = null + parts = textData.split(" ") + if (parts.length > 2 && parts[1] == "Matched:") { + filename = parts[parts.length - 1] + textData = textData.substr(0, textData.lastIndexOf(" ") + 1) + // pos0 = textData.lastIndexOf(' ') + 1 + // url = textData.substr(0,pos0) + '' + // url = textData.substr(0,pos0) + '' + // textData = url + } + + $scope.log.unshift({ + name: "", + text: textData, + filename: filename, + }) + + $scope.$apply() + break + case "onOOBText": + var textData = data + $scope.chatLog.unshift({ + name: " > oob <", + text: $sce.trustAsHtml(textData), + }) + console.info("currResponse", textData) + $scope.$apply() + break default: - console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) - break - } + console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) + break + } } - $scope.getAimlFile = function(filename) { - $scope.aimlFile = filename - console.log('getting aiml file ' + filename) - msg.send('getAimlFile', $scope.service.config.currentBotName, filename) - $scope.tabs.selected = 2 + $scope.getAimlFile = function (filename) { + $scope.aimlFile = filename + console.log("getting aiml file " + filename) + msg.send("getAimlFile", $scope.service.config.botType, filename) + $scope.tabs.selected = 2 } - $scope.saveAimlFile = function() { - msg.send("saveAimlFile", $scope.service.config.currentBotName, $scope.aimlFile, $scope.aimlFileData.data) + $scope.saveAimlFile = function () { + msg.send("saveAimlFile", $scope.service.config.botType, $scope.aimlFile, $scope.aimlFileData.data) } - $scope.setSessionKey = function() { - msg.send("setCurrentUserName", $scope.service.config.currentUserName) - msg.send("setCurrentBotName", $scope.service.config.currentBotName) + $scope.setSessionKey = function () { + msg.send("setCurrentUserName", $scope.service.config.username) + msg.send("setCurrentBotName", $scope.service.config.botType) } - $scope.getBotInfo = function() { - if ($scope.service && $scope.service.bots){ - return $scope.service.bots[$scope.service.config.currentBotName] - } - return null + $scope.getBotInfo = function () { + if ($scope.service && $scope.service.bots) { + return $scope.service.bots[$scope.service.config.botType] + } + return null } - $scope.getCurrentSession = function() { - if (!$scope.service.sessions){ - return null - } - if ($scope.getCurrentSessionKey()in $scope.service.sessions) { - return $scope.service.sessions[$scope.getCurrentSessionKey()] - } + $scope.getCurrentSession = function () { + if (!$scope.service.sessions) { return null + } + if ($scope.getCurrentSessionKey() in $scope.service.sessions) { + return $scope.service.sessions[$scope.getCurrentSessionKey()] + } + return null } - $scope.getCurrentSessionKey = function() { - return $scope.service.config.currentUserName + ' <-> ' + $scope.service.config.currentBotName + $scope.getCurrentSessionKey = function () { + return $scope.service.config.username + " <-> " + $scope.service.config.botType } - $scope.test = function(session, utterance) { - msg.send("getCategories", "hello") + $scope.test = function (session, utterance) { + msg.send("getCategories", "hello") } - $scope.getSessionResponse = function(utterance) { - console.info("SESSION GET RESPONSE (" + $scope.currentUserName + " " + $scope.service.config.currentBotName + ")") - $scope.getResponse($scope.currentUserName, $scope.service.config.currentBotName, utterance) + $scope.getSessionResponse = function (utterance) { + console.info("SESSION GET RESPONSE (" + $scope.currentUserName + " " + $scope.service.config.botType + ")") + $scope.getResponse($scope.currentUserName, $scope.service.config.botType, utterance) } - $scope.getResponse = function(username, botname, utterance) { - console.info("USER BOT RESPONSE (" + username + " " + botname + ")") - msg.send("getResponse", username, botname, utterance) - $scope.utterance = "" + $scope.getResponse = function (username, botname, utterance) { + console.info("USER BOT RESPONSE (" + username + " " + botname + ")") + msg.send("getResponse", username, botname, utterance) + $scope.utterance = "" } - $scope.startDialog = function() { - startDialog = $uibModal.open({ - templateUrl: "startDialog.html", - scope: $scope, - controller: function($scope) { - $scope.cancel = function() { - startDialog.dismiss() - } - - } - }) + $scope.startDialog = function () { + startDialog = $uibModal.open({ + templateUrl: "startDialog.html", + scope: $scope, + controller: function ($scope) { + $scope.cancel = function () { + startDialog.dismiss() + } + }, + }) } - $scope.startSession = function(username, botname) { - $scope.currentUserName = username - $scope.chatLog.unshift("Reload Session for Bot " + botname) - $scope.startSessionLabel = 'Reload Session' - msg.send("startSession", username, botname) - startDialog.dismiss() + $scope.startSession = function (username, botname) { + $scope.currentUserName = username + $scope.chatLog.unshift("Reload Session for Bot " + botname) + $scope.startSessionLabel = "Reload Session" + msg.send("startSession", username, botname) + startDialog.dismiss() } - $scope.savePredicates = function() { - $scope.service = mrl.getService($scope.service.name) - mrl.sendTo($scope.service.name, "savePredicates") - // FIXME !!!! lame + $scope.savePredicates = function () { + $scope.service = mrl.getService($scope.service.name) + mrl.sendTo($scope.service.name, "savePredicates") + // FIXME !!!! lame } - $scope.getProperties = function() { - if (!$scope.getBotInfo()){ - return null - } - return $scope.getBotInfo()['properties'] + $scope.getProperties = function () { + if (!$scope.getBotInfo()) { + return null + } + return $scope.getBotInfo()["properties"] } - $scope.getProperty = function(propName) { - try { - if ($scope.getBotInfo() && $scope.getBotInfo()['properties']){ - return $scope.getBotInfo()['properties'][propName] - } - } catch (error){ - console.warn('getProperty(' + propName + ') not found') - return null + $scope.getProperty = function (propName) { + try { + if ($scope.getBotInfo() && $scope.getBotInfo()["properties"]) { + return $scope.getBotInfo()["properties"][propName] } + } catch (error) { + console.warn("getProperty(" + propName + ") not found") + return null + } } - $scope.removeBotProperty = function(propName) { - delete $scope.getBotInfo()['properties'][propName] - msg.send("removeBotProperty", propName) + $scope.removeBotProperty = function (propName) { + delete $scope.getBotInfo()["properties"][propName] + msg.send("removeBotProperty", propName) } - $scope.aceLoaded = function(_editor) { - // _editor.setReadOnly(true); - $scope.aimlEditor = _editor - console.log('aceLoaded') + $scope.aceLoaded = function (_editor) { + // _editor.setReadOnly(true); + $scope.aimlEditor = _editor + console.log("aceLoaded") } - $scope.aceChanged = function(e) { - // - console.log('aceChanged') + $scope.aceChanged = function (e) { + // + console.log("aceChanged") } - $scope.getBotPath = function(e) { - if ($scope.service?.bots && $scope.service?.bots[$scope.service?.config?.currentBotName]?.path){ - return $scope.service?.bots[$scope.service?.config.currentBotName].path - } - return null + $scope.getBotPath = function (e) { + if ($scope.service?.bots && $scope.service?.bots[$scope.service?.config?.currentBotName]?.path) { + return $scope.service?.bots[$scope.service?.config.botType].path + } + return null } - - - $scope.getStatusLabel = function(level) { - if (level == 'error') { - return 'row label col-md-12 label-danger' - } - if (level == 'warn') { - return 'row label col-md-12 label-warning' - } - return 'row label col-md-12 label-info' + $scope.getStatusLabel = function (level) { + if (level == "error") { + return "row label col-md-12 label-danger" + } + if (level == "warn") { + return "row label col-md-12 label-warning" + } + + return "row label col-md-12 label-info" } // subscribe to the response from programab. - msg.subscribe('publishTopic') - msg.subscribe('publishRequest') - msg.subscribe('publishResponse') - msg.subscribe('publishLog') - msg.subscribe('publishOOBText') - msg.subscribe('getPredicates') - msg.subscribe('publishPredicate') - msg.subscribe('getAimlFile') + msg.subscribe("publishTopic") + msg.subscribe("publishRequest") + msg.subscribe("publishResponse") + msg.subscribe("publishLog") + msg.subscribe("publishOOBText") + msg.subscribe("getPredicates") + msg.subscribe("publishPredicate") + msg.subscribe("getAimlFile") + msg.send("getPredicates") - msg.send('getPredicates') - msg.subscribe(this) -} + }, ]) /* .filter('orderObjectBy', function() { diff --git a/src/main/resources/resource/WebGui/app/service/views/ProgramABGui.html b/src/main/resources/resource/WebGui/app/service/views/ProgramABGui.html index 8a9d2876c0..c8863b5fa5 100644 --- a/src/main/resources/resource/WebGui/app/service/views/ProgramABGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/ProgramABGui.html @@ -19,17 +19,17 @@ - +
- + - - + + - + @@ -41,7 +41,7 @@
- +
diff --git a/src/test/java/org/myrobotlab/service/ArduinoMotorPotTest.java b/src/test/java/org/myrobotlab/service/ArduinoMotorPotTest.java index 3493779590..ffe514e72c 100644 --- a/src/test/java/org/myrobotlab/service/ArduinoMotorPotTest.java +++ b/src/test/java/org/myrobotlab/service/ArduinoMotorPotTest.java @@ -74,7 +74,7 @@ private String join(ArrayList list, String joinChar) { public void onSensorData(SensorData event) { // about we downsample this call? - int[] data = (int[]) event.getData(); + int[] data = (int[]) event.data; count++; int value = data[0]; log.info("Data: {}", data); diff --git a/src/test/resources/ProgramAB/bots/lloyd/aiml/lloyd.aiml b/src/test/resources/ProgramAB/bots/lloyd/aiml/lloyd.aiml index 257d55e668..9a40847137 100644 --- a/src/test/resources/ProgramAB/bots/lloyd/aiml/lloyd.aiml +++ b/src/test/resources/ProgramAB/bots/lloyd/aiml/lloyd.aiml @@ -1,167 +1,273 @@ - - -* -