diff --git a/.github/build.yml b/.github/workflows/build.yml similarity index 94% rename from .github/build.yml rename to .github/workflows/build.yml index b510c17054..06d98292ca 100644 --- a/.github/build.yml +++ b/.github/workflows/build.yml @@ -3,8 +3,8 @@ name: Java CI on: - # push: - pull_request: + push: + # pull_request: jobs: build: @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up java uses: actions/setup-java@v3 with: @@ -23,12 +23,12 @@ jobs: cache: "maven" - name: Install Missing Dependencies - run: sudo apt-get install -y libv4l-0 libopencv-dev python3-opencv + run: sudo apt-get install -y libv4l-0 libopencv-dev python3-opencv libgtk2.0-0 - name: Dependency Test # installs all dependencies run: mvn test -Dtest=org.myrobotlab.framework.DependencyTest -q - name: Build with Maven # currently cannot test opencv - run: mvn clean verify -q + run: mvn clean verify -q -Dtest=!org.myrobotlab.opencv.* - name: Get next version uses: reecetech/version-increment@2023.9.3 @@ -84,4 +84,3 @@ jobs: tag_name: ${{ steps.version.outputs.version }} generate_release_notes: true body_path: ./release-template.md - diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000000..ae9e523f54 --- /dev/null +++ b/TODO.md @@ -0,0 +1,22 @@ +## TODO + +- neopixel when attached doesn't show the attached controller +- Implement robotCanMoveHeadWhileSpeaking with guards in a state (idle ?) +- Make list of Input yolo, camera cv filters, pir, finger sensors ... into a context - the context construct a prompt -> perception +- Convert all JukeBox or AudioFile.py to InMoov2 +- Make LanguageModel +- Make ContextManager +- prompt - gets generated with system info, therby creating "perception" e.g. proximity sensor 2, classification human, position 30 + , if a human is nearby assume its - can answer the question ... where are you ... ("you are just to the left of me") +- runtime should say what version java is running and warn if not valid +- lower volume or change boot up sound +- current config name doesn't show up in runtime +- initCheckUp.py isn't getting run +- peak is not working or implemented in the UI +- peak isn't default +- multiple sets of process id's on stale ui - fix by stablizing new randome one ? + +## DONE + +- a delete config button - should do a move to trash directory with a datetimestamp +- dot dot on Runtime... platform info is late, or doesn't get published should be x86 64 diff --git a/src/main/java/org/myrobotlab/io/FileIO.java b/src/main/java/org/myrobotlab/io/FileIO.java index 6e061e8c33..95c5134a3c 100644 --- a/src/main/java/org/myrobotlab/io/FileIO.java +++ b/src/main/java/org/myrobotlab/io/FileIO.java @@ -31,6 +31,7 @@ package org.myrobotlab.io; import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -38,6 +39,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URI; import java.net.URL; import java.nio.charset.Charset; @@ -50,6 +52,7 @@ import java.util.Properties; import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.TimeUnit; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -1424,5 +1427,62 @@ public static String normalize(String dirPath) { return dirPath.replace("\\", "/"); } } + + /** + * Checks if a specific executable command is available in the system. + * This function attempts to execute the command with a timeout of 3 seconds to ensure + * that the check does not hang indefinitely. The process is considered available + * if it can be started without throwing an exception and completes successfully + * within the specified timeout. + * + * @param command the command to check for availability. + * @return true if the command is available and completes successfully within the timeout, false otherwise. + */ + + public static boolean isExecutableAvailable(String command) { + return isExecutableAvailable(command, 3); + } + + /** + * Checks if a specific executable command is available in the system. + * This function attempts to execute the command with a timeout to ensure + * that the check does not hang indefinitely. The process is considered available + * if it can be started without throwing an exception and completes successfully + * within the specified timeout. + * + * @param command the command to check for availability. + * @param timeoutSeconds the maximum time in seconds to wait for the command to complete. + * @return true if the command is available and completes successfully within the timeout, false otherwise. + */ + public static boolean isExecutableAvailable(String command, int timeoutSeconds) { + try { + // Attempt to execute the command + Process process = java.lang.Runtime.getRuntime().exec(command); + + // Wait for the process to complete with a timeout + if (process.waitFor(timeoutSeconds, TimeUnit.SECONDS) && process.exitValue() == 0) { + return true; + } + + // Read and log any errors from the attempted command + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { + String line; + while ((line = reader.readLine()) != null) { + log.info(line); + } + } + + return false; + } catch (IOException e) { + log.info("IOException: " + e.getMessage()); + return false; + } catch (InterruptedException e) { + log.info("InterruptedException: " + e.getMessage()); + // Restore interrupted state + Thread.currentThread().interrupt(); + return false; + } + } + } diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 4f3fac9546..884b64f6db 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -5,20 +5,23 @@ import java.io.IOException; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeSet; import org.apache.commons.io.FilenameUtils; import org.myrobotlab.framework.Message; +import org.myrobotlab.framework.Peer; import org.myrobotlab.framework.Plan; import org.myrobotlab.framework.Platform; import org.myrobotlab.framework.Registration; import org.myrobotlab.framework.Service; -import org.myrobotlab.framework.StaticType; import org.myrobotlab.framework.Status; import org.myrobotlab.framework.interfaces.ServiceInterface; import org.myrobotlab.io.FileIO; @@ -31,11 +34,9 @@ import org.myrobotlab.programab.Session; import org.myrobotlab.service.FiniteStateMachine.StateChange; 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.AudioData; import org.myrobotlab.service.data.JoystickData; import org.myrobotlab.service.data.Locale; import org.myrobotlab.service.interfaces.IKJointAngleListener; @@ -46,7 +47,6 @@ 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; @@ -54,13 +54,57 @@ public class InMoov2 extends Service implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, IKJointAngleListener { - public final static Logger log = LoggerFactory.getLogger(InMoov2.class); + public class Heart implements Runnable { + private Thread thread; + + @Override + public void run() { + try { + while (!Thread.currentThread().isInterrupted()) { + invoke("publishHeartbeat"); + Thread.sleep(config.heartbeatInterval); + } + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } finally { + log.info("heart stopping"); + thread = null; + } + } - public static LinkedHashMap lpVars = new LinkedHashMap(); + synchronized 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 static final long serialVersionUID = 1L; + synchronized public void stop() { + if (thread != null) { + thread.interrupt(); + config.heartbeat = false; + } else { + log.info("heart already stopped"); + } + } + } + + public static class Heartbeat { + public long count = 0; + public long ts = System.currentTimeMillis(); - static String speechRecognizer = "WebkitSpeechRecognition"; + public Heartbeat(InMoov2 inmoov) { + this.count = inmoov.heartbeatCount; + } + } + + public final static Logger log = LoggerFactory.getLogger(InMoov2.class); + + private static final long serialVersionUID = 1L; /** * This method will load a python file into the python interpreter. @@ -72,6 +116,7 @@ public class InMoov2 extends Service @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 ! Py4j would break Python p = (Python) Runtime.getService("python"); log.info("Loading Python file {}", f.getAbsolutePath()); if (p == null) { @@ -123,102 +168,22 @@ public static void main(String[] args) { return; } - 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.start("python", "Python"); - // Runtime.start("ros", "Ros"); - Runtime.start("intro", "Intro"); - // InMoov2 i01 = (InMoov2) Runtime.start("i01", "InMoov2"); - // i01.startPeer("simulator"); - // Runtime.startConfig("i01-05"); - // Runtime.startConfig("pir-01"); - - // Polly polly = (Polly)Runtime.start("i01.mouth", "Polly"); - // i01 = (InMoov2) Runtime.start("i01", "InMoov2"); - - // polly.speakBlocking("Hi, to be or not to be that is the question, - // wheather to take arms against a see of trouble, and by aposing them end - // them, to sleep, to die"); - // i01.startPeer("mouth"); - // i01.speakBlocking("Hi, to be or not to be that is the question, - // wheather to take arms against a see of trouble, and by aposing them end - // them, to sleep, to die"); - - Runtime.start("python", "Python"); - - // i01.startSimulator(); - Plan plan = Runtime.load("webgui", "WebGui"); - // WebGuiConfig webgui = (WebGuiConfig) plan.get("webgui"); - // webgui.autoStartBrowser = false; - Runtime.startConfig("webgui"); - Runtime.start("webgui", "WebGui"); - - Random random = (Random) Runtime.start("random", "Random"); - - random.addRandom(3000, 8000, "i01", "setLeftArmSpeed", 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0); - random.addRandom(3000, 8000, "i01", "setRightArmSpeed", 8.0, 25.0, 8.0, 25.0, 8.0, 25.0, 8.0, 25.0); - - 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", "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); - - random.addRandom(200, 1000, "i01", "setTorsoSpeed", 2.0, 5.0, 2.0, 5.0, 2.0, 5.0); - random.addRandom(200, 1000, "i01", "moveTorso", 85.0, 95.0, 88.0, 93.0, 70.0, 110.0); - - random.save(); - - // i01.startChatBot(); - // - // i01.startAll("COM3", "COM4"); - Runtime.start("python", "Python"); - } catch (Exception e) { log.error("main threw", e); } } - protected transient ProgramAB chatBot; - - protected List configList; - /** - * Configuration from runtime has started. This is when runtime starts - * processing a configuration set for the first time since inmoov was started + * number of times waited in boot state */ - protected boolean configStarted = false; + protected int bootCount = 0; - String currentConfigurationName = "default"; + protected List configList; 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; @@ -229,9 +194,9 @@ public static void main(String[] args) { */ protected boolean hasBooted = false; - protected long heartbeatCount = 0; + private transient final Heart heart = new Heart(); - protected transient HtmlFilter htmlFilter; + protected long heartbeatCount = 0; protected transient ImageDisplay imageDisplay; @@ -239,8 +204,6 @@ public static void main(String[] args) { protected String lastGestureExecuted; - protected Long lastPirActivityTime; - protected String lastState = null; /** @@ -250,8 +213,6 @@ public static void main(String[] args) { protected int maxInactivityTimeSeconds = 120; - protected transient SpeechSynthesis mouth; - protected boolean mute = false; protected transient OpenCV opencv; @@ -263,10 +224,17 @@ public static void main(String[] args) { */ protected String state = "boot"; + protected long stateLastIdleTime = System.currentTimeMillis(); + + protected long stateLastRandomTime = System.currentTimeMillis(); + protected String voiceSelected; + protected boolean pirActive = false; + 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 @@ -280,35 +248,12 @@ public InMoov2Config apply(InMoov2Config c) { super.apply(c); try { - locales = Locale.getLocaleMap("en-US", "fr-FR", "es-ES", "de-DE", "nl-NL", "pl-PL", "ru-RU", "hi-IN", "it-IT", "fi-FI", "pt-PT", "tr-TR"); - if (c.locale != null) { setLocale(c.locale); } else { setLocale(getSupportedLocale(Runtime.getInstance().getLocale().toString())); } - if (c.execScript) { - execScript(); - } - - loadAppsScripts(); - - loadInitScripts(); - - if (c.loadGestures) { - loadGestures(); - } - - if (c.heartbeat) { - startHeartbeat(); - } else { - stopHeartbeat(); - } - - // one way sync configuration into predicates - configToPredicates(); - } catch (Exception e) { error(e); } @@ -347,6 +292,174 @@ public void beginCheckingOnInactivity(int maxInactivityTimeSeconds) { addTask("checkInactivity", 5 * 1000, 0, "checkInactivity"); } + /** + * 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. + * + * This method gets called multiple times by the heart beat, it walks through + * required processing and effectively waits and tries again if not finished. + * While in "boot", nothing else should be allowed to process until this + * process is completed. + */ + synchronized public void boot() { + + Runtime runtime = Runtime.getInstance(); + + try { + + if (hasBooted) { + log.warn("will not boot again"); + return; + } + + if (bootCount == 0) { + info("%s BOOTING ....", getName()); + } + + bootCount++; + log.info("BOOT count {}", bootCount); + + if (runtime.isProcessingConfig()) { + info("BOOT runtime still processing config set %s, waiting ....", runtime.getConfigName()); + return; + } + + if (!isReady()) { + info("BOOT %s is not yet ready, waiting ....", getName()); + return; + } + + info("BOOT starting mandatory services"); + + try { + // This is required the core of InMoov is + // a FSM ProgramAB and some form of Python/Jython + startPeer("fsm"); + + // Chatbot is a required part of InMoov2 + ProgramAB chatBot = (ProgramAB) startPeer("chatBot"); + chatBot = (ProgramAB) startPeer("chatBot"); + chatBot.startSession(); + chatBot.setPredicate("robot", getName()); + } catch (IOException e) { + error(e); + } + + // InMoov2 is now "ready" for mandatory synchronous processing + info("BOOT starting scripts"); + + // check all required services are completely started - or + // wait/return until they are + + // there is not much point in running InMoov2 without its + // core dependencies - those dependencies are ProgramAB, + // FiniteStatemachine and Py4j/Python - so boot will not + // finish unless these services have loaded + + // Although this exposes type, it does use startPeer + // which allows the potential of the user switching types of processors + // if the processor + + // TODO - make Py4j without zombies and more robust + /** + * Py4j is not ready for primetime yet + * + *
+       * 
+       * Py4j py4j = (Py4j) startPeer("py4j");
+       * if (!py4j.isReady()) {
+       *   log.warn("{} not ready....", getPeerName("py4j"));
+       *   return;
+       * }
+       * String code = FileIO.toString(getResourceDir() + fs + "InMoov2.py");
+       * py4j.exec(code);
+       * 
+       * 
+ */ + + // load the InMoov2.py and publish it for Python/Jython or Py4j to consume + if (config.execScript) { + execScript(); + } + + // TODO - MAKE BOOT REPORT !!!! deliver it on a heartbeat + runtime.invoke("publishConfigList"); + // FIXME - reduce the number of these + if (config.loadAppsScripts) { + loadAppsScripts(); + } + + if (config.loadInitScripts) { + loadInitScripts(); + } + + if (config.loadGestures) { + loadGestures(); + } + + if (config.startupSound) { + String startupsound = FileIO.gluePaths(getResourceDir(), "/system/sounds/startupsound.mp3"); + playAudioFile(startupsound); + } + + List services = Runtime.getServices(); + for (ServiceInterface si : services) { + if ("Servo".equals(si.getSimpleName())) { + send(si.getFullName(), "setAutoDisable", true); + } + } + hasBooted = true; + } catch (Exception e) { + hasBooted = false; + error(e); + } + + /** + * TODO reporting on errors found in boot process TODO make a report on all + * peers that have started, of the config processed if there was a config + * set + */ + if (config.reportOnBoot) { + systemEvent("CONFIG STARTED %s", runtime.getConfigName()); + + // TODO spin through all services in the order they were started + // send all system events + Collection local = Runtime.getLocalServices().values(); + List ordered = new ArrayList<>(local); + ordered.removeIf(Objects::isNull); + Collections.sort(ordered); + + Map peers = getPeers(); + Set peerNames = new HashSet<>(); + for (String peerKey : peers.keySet()) { + Peer peer = peers.get(peerKey); + if (peer.name == null) { + peerNames.add(String.format("%s.%s", getName(), peerKey)); + } else { + peerNames.add(peer.name); + } + } + + for (ServiceInterface si : ordered) { + if (peerNames.contains(si.getName())) { + systemEvent("STARTED %s", getPeerKey(si.getName()).replace(".", " ")); + } + } + + // reporting on all services and config started + systemEvent("CONFIG LOADED %s", runtime.getConfigName()); + } + + // say finished booting + fire("wake"); + } + public void cameraOff() { if (opencv != null) { opencv.stopCapture(); @@ -465,6 +578,7 @@ public void closeAllImages() { */ public void configToPredicates() { log.info("configToPredicates"); + ProgramAB chatBot = (ProgramAB) getPeer("chatBot"); if (chatBot != null) { Class pojoClass = config.getClass(); Field[] fields = pojoClass.getDeclaredFields(); @@ -543,15 +657,6 @@ public void displayFullScreen(String src) { } } - public void enable() { - sendToPeer("head", "enable"); - sendToPeer("rightHand", "enable"); - sendToPeer("leftHand", "enable"); - sendToPeer("rightArm", "enable"); - sendToPeer("leftArm", "enable"); - sendToPeer("torso", "enable"); - } - public void enableRandomHead() { Random random = (Random) getPeer("random"); if (random != null) { @@ -562,6 +667,15 @@ public void enableRandomHead() { } } + public void enable() { + sendToPeer("head", "enable"); + sendToPeer("rightHand", "enable"); + sendToPeer("leftHand", "enable"); + sendToPeer("rightArm", "enable"); + sendToPeer("leftArm", "enable"); + sendToPeer("torso", "enable"); + } + /** * Single place for InMoov2 service to execute arbitrary code - needed * initially to set "global" vars in python @@ -569,10 +683,16 @@ public void enableRandomHead() { * @param pythonCode * @return */ + @Deprecated /* should publishProcessing or publishPython - no direct handle */ public boolean exec(String pythonCode) { try { - Python p = (Python) Runtime.start("python", "Python"); - return p.exec(pythonCode, true); + Python p = (Python) Runtime.getService("python"); + if (p != null) { + return p.exec(pythonCode, true); + } else { + warn("python not available"); + return false; + } } catch (Exception e) { error("unable to execute script %s", pythonCode); return false; @@ -621,15 +741,9 @@ public void execScript() { * 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); + invoke("publishPython", script); } public void finishedGesture() { @@ -646,9 +760,18 @@ public void finishedGesture(String nameOfGesture) { } } - // FIXME - this isn't the callback for fsm - why is it needed here ? + /** + * Fire an event to the FSM, potentially this can cause a state change + * + * @param event + */ public void fire(String event) { - invoke("publishEvent", event); + FiniteStateMachine fsm = (FiniteStateMachine) getPeer("fsm"); + if (fsm != null) { + fsm.fire(event); + } else { + log.warn("cannot fire event %s fsm not ready", event); + } } public void fullSpeed() { @@ -695,30 +818,32 @@ 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() { @@ -738,24 +863,25 @@ public OpenCV getOpenCV() { return opencv; } - public Object getPredicate(String key) { + public String getPredicate(String key) { ProgramAB chatBot = (ProgramAB) getPeer("chatBot"); if (chatBot != null) { - return chatBot.getPredicate(key); + return getPredicate(chatBot.getConfig().currentUserName, key); } else { - error("no chatBot available"); + log.info("chatBot not ready"); + return null; } - return null; } public String getPredicate(String user, String key) { ProgramAB chatBot = (ProgramAB) getPeer("chatBot"); if (chatBot != null) { + return chatBot.getPredicate(user, key); } else { - error("no chatBot available"); + log.info("chatBot not ready"); + return null; } - return null; } /** @@ -767,12 +893,13 @@ public String getPredicate(String user, String key) { public Response getResponse(String text) { ProgramAB chatBot = (ProgramAB) getPeer("chatBot"); if (chatBot != null) { + Response response = chatBot.getResponse(text); return response; } else { - log.info("chatbot not ready"); + log.info("chatBot not ready"); + return null; } - return null; } public InMoov2Arm getRightArm() { @@ -855,6 +982,11 @@ public boolean isMute() { public boolean isSpeaking() { return isSpeaking; } + + public boolean isPirActive() { + return pirActive; + } + /** * execute python scripts in the app directory on startup of the service @@ -950,7 +1082,8 @@ public boolean accept(File dir, String name) { if (files != null) { for (File file : files) { - Python p = (Python) Runtime.start("python", "Python"); + + Python p = (Python) Runtime.getService("python"); if (p != null) { p.execFile(file.getAbsolutePath()); } @@ -1064,6 +1197,23 @@ public PredicateEvent onChangePredicate(PredicateEvent event) { // do defaults ? return event; } + + /** + * Subscription for audioPlayer starting to play a file. + * @param data + */ + public void onAudioStart(AudioData data) { + processMessage("onAudioStart", data); + } + + /** + * Subscription for audioPlayer stopping an audio file. + * @param data + */ + public void onAudioEnd(AudioData data) { + processMessage("onAudioEnd", data); + } + /** * comes in from runtime which owns the config list @@ -1207,15 +1357,8 @@ public void onPirOff() { */ public void onPirOn() { log.info("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"); - } - } + setPredicate(String.format("%s.pir_on", getName()), System.currentTimeMillis()); + processMessage("onPirOn"); } // GOOD GOOD GOOD - LOOPBACK - flexible and replacable by python @@ -1253,6 +1396,12 @@ public boolean onSense(boolean b) { } else { invoke("publishEvent", "PIR OFF"); } + + // Better - processed through a potentially configured "processor" + // "named" message since sender is this service + processMessage("onSense", b); + pirActive = b; + return b; } @@ -1284,60 +1433,10 @@ public void onStartConfig(String configName) { */ @Override public void onStarted(String name) { - - log.info("onStarted {}", name); try { - Runtime runtime = Runtime.getInstance(); log.info("onStarted {}", name); - - // BAD IDEA - better to ask for a system report or an error report - // if (runtime.isProcessingConfig()) { - // invoke("publishEvent", "CONFIG STARTED"); - // } - - String peerKey = getPeerKey(name); - if (peerKey == null) { - // service not a peer - return; - } - - if (runtime.isProcessingConfig() && !configStarted) { - invoke("publishEvent", "CONFIG STARTED " + runtime.getConfigName()); - configStarted = true; - } - - invoke("publishEvent", "STARTED " + peerKey.replace(".", " ")); - - switch (peerKey) { - case "audioPlayer": - break; - case "chatBot": - ProgramAB chatBot = (ProgramAB) Runtime.getService(name); - chatBot.attachTextListener(getPeerName("htmlFilter")); - startPeer("htmlFilter"); - break; - case "ear": - AbstractSpeechRecognizer ear = (AbstractSpeechRecognizer) Runtime.getService(name); - ear.attachTextListener(getPeerName("chatBot")); - break; - case "htmlFilter": - TextPublisher htmlFilter = (TextPublisher) Runtime.getService(name); - htmlFilter.attachTextListener(getPeerName("mouth")); - break; - case "mouth": - mouth = (AbstractSpeechSynthesis) Runtime.getService(name); - mouth.attachSpeechListener(getPeerName("ear")); - break; - case "opencv": - subscribeTo(name, "publishOpenCVData"); - break; - default: - log.info("unknown peer {} not handled in onStarted", peerKey); - break; - } - - // type processing for Servo + // new servo ServiceInterface si = Runtime.getService(name); if ("Servo".equals(si.getSimpleName())) { log.info("sending setAutoDisable true to {}", name); @@ -1372,8 +1471,17 @@ public void onText(String text) { log.info("onText - {}", text); invoke("publishText", text); } + + public void playAudioFile(String filename) { + log.info("playAudioFile {}", filename); + invoke("publishPlayAudioFile", filename); + } // TODO FIX/CHECK this, migrate from python land + @Deprecated /* + * these are fsm states and should be implemented in python + * callbacks + */ public void powerDown() { rest(); @@ -1392,6 +1500,10 @@ public void powerDown() { // TODO FIX/CHECK this, migrate from python land // FIXME - defaultPowerUp switchable + override + @Deprecated /* + * these are fsm states and should be implemented in python + * callbacks + */ public void powerUp() { enable(); rest(); @@ -1408,6 +1520,34 @@ public void powerUp() { public void processMessage(String method) { processMessage(method, (Object[]) null); } + + public void playMusic() { + AudioFile af = (AudioFile) getPeer("audioPlayer"); + if (af != null) { + af.startPlaylist(); + } + } + + public void nextPlay() { + AudioFile af = (AudioFile) getPeer("audioPlayer"); + if (af != null) { + af.skip(); + } + } + + public void searchPlay(String requestedSong) { + AudioFile af = (AudioFile) getPeer("audioPlayer"); + if (af != null) { + Map> pls = af.getPlaylists(); + for(String playlist: pls.keySet()) { + for (String song : pls.get(playlist)) { + if (song.contains(requestedSong)) { + af.play(song); + } + } + } + } + } /** * Will publish processing messages to the processor(s) currently subscribed. @@ -1486,11 +1626,84 @@ public String publishFlash(String flashName) { return flashName; } - public String publishHeartbeat() { - if (config.heartbeatFlash) { - invoke("publishFlash", "heartbeat"); + /** + * FIXME - get rid of all functionality here - should all be controlled by + * behaviors + * + * A heartbeat that continues to check status, and fire events to the FSM. + * Checks battery, flashes leds and processes all the configured checks in + * onHeartbeat at a regular interval + */ + public Heartbeat publishHeartbeat() { + log.debug("publishHeartbeat"); + heartbeatCount++; + Heartbeat heartbeat = new Heartbeat(this); + try { + + if ("boot".equals(state)) { + // continue booting - we don't put heartbeats in user/python space + // until java-land is done booting + log.info("boot hasn't completed, will not process heartbeat - trying boot"); + boot(); + return heartbeat; + } + + 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; + } + + if (System.currentTimeMillis() > stateLastIdleTime + (config.stateIdleInterval * 1000)) { + // idle event to be handled with the processor + // processMessage("onIdle"); + fire("idle"); + stateLastIdleTime = System.currentTimeMillis(); + } + + // interval event firing + if (config.stateRandomInterval != null && System.currentTimeMillis() > stateLastRandomTime + (config.stateRandomInterval * 1000)) { + // fsm.fire("random"); + stateLastRandomTime = System.currentTimeMillis(); + } + + } catch (Exception e) { + error(e); } - return getName(); + + // 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"); + } + } + + // FIXME - add errors to heartbeat + processMessage("onHeartbeat", heartbeat); + return heartbeat; } /** @@ -1709,6 +1922,7 @@ public void releasePeer(String peerKey) { public void releaseService() { try { disable(); + heart.stop(); super.releaseService(); } catch (Exception e) { error(e); @@ -1859,6 +2073,18 @@ public void setLocale(String code) { // super.setLocale(code); for (ServiceInterface si : Runtime.getLocalServices().values()) { if (!si.equals(this)) { + // by default, InMoov2 tries to set all Locales on all services + // from its configured Locale, or a default Locale set on the OS. + // This works ok when ProgramAB is providing translations, however, + // in the case of "brain" translation will be provided at a different + // layer, therefore resetting chatBot to en-US bot from configured "brain" bot + // is prevented + if (si instanceof ProgramAB) { + ProgramAB chatbot = (ProgramAB)si; + if ("brain".equals(chatbot.getCurrentBotName())){ + continue; + } + } si.setLocale(code); } } @@ -1882,14 +2108,10 @@ public boolean setPirPlaySounds(boolean b) { public Object setPredicate(String key, Object data) { ProgramAB chatBot = (ProgramAB) getPeer("chatBot"); - if (chatBot != null) { - if (data == null) { - chatBot.setPredicate(key, null); // "unknown" "null" other sillyness ? - } else { - chatBot.setPredicate(key, data.toString()); - } + if (data == null) { + chatBot.setPredicate(key, null); // "unknown" "null" other sillyness ? } else { - error("no chatBot available"); + chatBot.setPredicate(key, data.toString()); } return data; } @@ -1925,8 +2147,10 @@ public boolean setSpeechType(String speechType) { String peerName = getName() + ".mouth"; Plan plan = runtime.getDefault(peerName, speechType); try { - SpeechSynthesisConfig mouth = (SpeechSynthesisConfig) plan.get(peerName); - mouth.speechRecognizers = new String[] { getName() + ".ear" }; + // this should be handled in config.listeners + // SpeechSynthesisConfig mouth = (SpeechSynthesisConfig) + // plan.get(peerName); + // mouth.speechRecognizers = new String[] { getName() + ".ear" }; savePeerConfig("mouth", plan.get(peerName)); @@ -1948,7 +2172,12 @@ public boolean setSpeechType(String speechType) { } public void setTopic(String topic) { - chatBot.setTopic(topic); + ProgramAB chatBot = (ProgramAB) getPeer("chatBot"); + if (chatBot != null) { + chatBot.setTopic(topic); + } else { + log.info("chatBot not ready"); + } } public void setTorsoSpeed(Double topStom, Double midStom, Double lowStom) { @@ -1959,24 +2188,6 @@ public void setTorsoSpeed(Integer topStom, Integer midStom, Integer lowStom) { setTorsoSpeed((double) topStom, (double) midStom, (double) lowStom); } - // ----------------------------------------------------------------------------- - // These are methods added that were in InMoov1 that we no longer had in - // InMoov2. - // From original InMoov1 so we don't loose the - - @Deprecated /* use setTorsoSpeed */ - public void setTorsoVelocity(Double topStom, Double midStom, Double lowStom) { - setTorsoSpeed(topStom, midStom, lowStom); - } - - public void setVoice(String name) { - if (mouth != null) { - mouth.setVoice(name); - voiceSelected = name; - speakBlocking(String.format("%s %s", get("SETLANG"), name)); - } - } - public void sleeping() { log.error("sleeping"); } @@ -2018,105 +2229,6 @@ public void speakBlocking(String format, Object... args) { } } - @Deprecated /* use startPeers */ - public void startAll() throws Exception { - startAll(null, null); - } - - @Deprecated /* use startPeers */ - public void startAll(String leftPort, String rightPort) throws Exception { - startMouth(); - startChatBot(); - - // startHeadTracking(); - // startEyesTracking(); - // startOpenCV(); - startEar(); - - startServos(); - // startMouthControl(head.jaw, mouth); - - speakBlocking(get("STARTINGSEQUENCE")); - } - - @Deprecated /* i01.startPeer("chatBot") - all details should be in config */ - public void startBrain() { - startChatBot(); - } - - @Deprecated /* i01.startPeer("chatBot") - all details should be in config */ - 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 /* use startPeer */ - public SpeechRecognizer startEar() { - - ear = (SpeechRecognizer) startPeer("ear"); - ear.attachSpeechSynthesis((SpeechSynthesis) getPeer("mouth")); - ear.attachTextListener(chatBot); - broadcastState(); - return ear; - } - public void startedGesture() { startedGesture("unknown"); } @@ -2132,48 +2244,7 @@ public void startedGesture(String nameOfGesture) { } public void startHeartbeat() { - addTask(1000, "publishHeartbeat"); - } - - // TODO - general objective "might" be to reduce peers down to something - // that does not need a reference - where type can be switched before creation - // and the only thing needed is pubs/subs that are not handled in abstracts - @Deprecated /* use startPeer */ - public SpeechSynthesis startMouth() { - - // FIXME - set type ??? - maybe a good product of InMoov - // if "new" type cannot necessarily grab yml file - // setMouthType - - // FIXME - bad to have a reference, should only need the "name" of the - // service !!! - mouth = (SpeechSynthesis) startPeer("mouth"); - - // voices = mouth.getVoices(); - // Voice voice = mouth.getVoice(); - // if (voice != null) { - // voiceSelected = voice.getName(); - // } - - if (mute) { - mouth.setMute(true); - } - - mouth.attachSpeechRecognizer(ear); - // mouth.attach(htmlFilter); // same as chatBot not needed - - // this.attach((Attachable) mouth); - // if (ear != null) .... - - broadcastState(); - - speakBlocking(get("STARTINGMOUTH")); - if (isVirtual()) { - speakBlocking(get("STARTINGVIRTUALHARD")); - } - speakBlocking(get("WHATISTHISLANGUAGE")); - - return mouth; + heart.start(); } @Deprecated /* use startPeer */ @@ -2194,34 +2265,30 @@ public ServiceInterface startPeer(String peer) { public void startService() { super.startService(); + // just for comparing config with current "default" + // debugging only Runtime runtime = Runtime.getInstance(); + // if you hardcode subscriptions here - they should + // be controlled/branched by config + // 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); - } - } - + // FIXME all subscriptions should be in InMoov2Config // get events of new services and shutdown + // we can't add listener's in config, perhaps there should be + // "subscriptions" in config too ? subscribe("runtime", "shutdown"); - // power up loopback subscription - addListener(getName(), "powerUp"); - subscribe("runtime", "publishConfigList"); - if (runtime.isProcessingConfig()) { - invoke("publishEvent", "configStarted"); - } - subscribe("runtime", "publishConfigStarted"); - subscribe("runtime", "publishConfigFinished"); + runtime.invoke("publishConfigList"); - // chatbot getresponse attached to publishEvent - addListener("publishEvent", getPeerName("chatBot"), "getResponse"); + if (config.heartbeat) { + startHeartbeat(); + } else { + stopHeartbeat(); + } - runtime.invoke("publishConfigList"); } public void startServos() { @@ -2249,12 +2316,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() { @@ -2262,25 +2330,39 @@ public void stopNeopixelAnimation() { } public void systemCheck() { + Platform platform = Runtime.getPlatform(); log.info("systemCheck()"); int servoCount = 0; - int servoAttachedCount = 0; for (ServiceInterface si : Runtime.getServices()) { if (si.getClass().getSimpleName().equals("Servo")) { servoCount++; - if (((Servo) si).getController() != null) { - servoAttachedCount++; - } } } - setPredicate("systemServoCount", servoCount); - setPredicate("systemAttachedServoCount", servoAttachedCount); - setPredicate("systemFreeMemory", Runtime.getFreeMemory()); - Platform platform = Runtime.getPlatform(); - setPredicate("system version", platform.getVersion()); - // ERROR buffer !!! - // invoke("publishEvent", "systemCheckFinished"); + // TODO check for latest version if not experimental + // TODO change to experimental :) + String version = ("unknownVersion".equals(platform.getVersion())) ? "experimental" : platform.getVersion(); + + setPredicate("system.version", version); + setPredicate("system.uptime", Runtime.getUptime()); + setPredicate("system.servoCount", servoCount); + setPredicate("system.serviceCount", Runtime.getServices().size()); + setPredicate("system.freeMemory", Runtime.getFreeMemory() / 1000000); + setPredicate("system.errorsExist", errors.size() > 0); + setPredicate("system.errorCount", errors.size()); + setPredicate("state", getState()); + setPredicate("system.batteryLevel", Runtime.getBatteryLevel().intValue()); + + } + + public String systemEvent(String eventMsg) { + invoke("publishSystemEvent", eventMsg); + return eventMsg; + } + + public String systemEvent(String format, Object... ags) { + String eventMsg = String.format(format, ags); + return systemEvent(eventMsg); } // FIXME - if this is really desired it will drive local references for all @@ -2295,4 +2377,22 @@ public void waitTargetPos() { sendToPeer("torso", "waitTargetPos"); } + public void foundPerson(String name) { + foundPerson(name, 1.0); + } + + public void foundPerson(String name, Double confidence) { + if (confidence == null) { + confidence = 1.0; + } + Map data = new HashMap<>(); + data.put("name", name); + data.put("confidence", confidence); + invoke("publishFoundPerson", data); + } + + public Map publishFoundPerson(Map data) { + return data; + } + } diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 2185727eb5..faf505a2c4 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -3684,24 +3684,26 @@ public static Double getBatteryLevel() { } } else if (platform.isLinux()) { - // TODO This is incorrect, will not work when unplugged - // and acpitool output is different than expected, - // at least on Ubuntu 22.04 - consider oshi library - String ret = Runtime.execute("acpi"); - int pos0 = ret.indexOf("%"); - - if (pos0 != -1) { - int pos1 = ret.lastIndexOf(" ", pos0); - // int pos1 = ret.indexOf("%", pos0); - String dble = ret.substring(pos1, pos0).trim(); - try { - r = Double.parseDouble(dble); - } catch (Exception e) { - log.error("no Battery detected by system"); + // TODO This is incorrect, will not work when unplugged + // and acpitool output is different than expected, + // at least on Ubuntu 22.04 - consider oshi library + if (FileIO.isExecutableAvailable("acpi")) { + String ret = Runtime.execute("acpi"); + int pos0 = ret.indexOf("%"); + + if (pos0 != -1) { + int pos1 = ret.lastIndexOf(" ", pos0); + // int pos1 = ret.indexOf("%", pos0); + String dble = ret.substring(pos1, pos0).trim(); + try { + r = Double.parseDouble(dble); + } catch (Exception e) { + log.error("no Battery detected by system"); + } + return r; } - return r; + log.info(ret); } - log.info(ret); } else if (platform.isMac()) { String ret = Runtime.execute("pmset -g batt"); int pos0 = ret.indexOf("Battery-0"); diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 8052dea636..4618103310 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -538,8 +538,7 @@ public Plan getDefault(Plan plan, String name) { listeners.add(new Listener("publishStopAnimation", getPeerName("neoPixel"))); // listeners.add(new Listener("publishProcessMessage", // getPeerName("python"), "onPythonMessage")); - listeners.add(new Listener("publishProcessMessage", getPeerName("python"), "onPythonMessage")); - + listeners.add(new Listener("publishProcessMessage", getPeerName("python"), "onPythonMessage")); listeners.add(new Listener("publishPython", getPeerName("python"))); // InMoov2 --to--> InMoov2 @@ -551,6 +550,10 @@ public Plan getDefault(Plan plan, String name) { listeners.add(new Listener("publishMoveTorso", getPeerName("torso"), "onMove")); // service --to--> InMoov2 + AudioFileConfig audioPlayer = (AudioFileConfig) plan.get(getPeerName("audioPlayer")); + audioPlayer.listeners.add(new Listener("publishAudioStart", name)); + audioPlayer.listeners.add(new Listener("publishAudioEnd", name)); + htmlFilter.listeners.add(new Listener("publishText", name)); OakDConfig oakd = (OakDConfig) plan.get(getPeerName("oakd")); diff --git a/src/main/resources/resource/WebGui/app/service/js/PollyGui.js b/src/main/resources/resource/WebGui/app/service/js/PollyGui.js index f9e42cdb40..fcc6634646 100644 --- a/src/main/resources/resource/WebGui/app/service/js/PollyGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/PollyGui.js @@ -33,6 +33,10 @@ angular.module('mrlapp.service.PollyGui', []).controller('PollyGuiCtrl', ['peer' $scope.spoken = data $scope.$apply() break + case 'onStatus': + $scope.status = data + $scope.$apply() + break default: console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) break diff --git a/src/main/resources/resource/WebGui/app/views/tabsViewCtrl.js b/src/main/resources/resource/WebGui/app/views/tabsViewCtrl.js index 6dbdf20fbc..c4e6f5edb7 100644 --- a/src/main/resources/resource/WebGui/app/views/tabsViewCtrl.js +++ b/src/main/resources/resource/WebGui/app/views/tabsViewCtrl.js @@ -1,5 +1,13 @@ -angular.module('mrlapp.mrl').controller('tabsViewCtrl', ['$location', '$scope', '$filter', '$timeout', 'mrl', '$state', '$stateParams', function($location, $scope, $filter, $timeout, mrl, $state, $stateParams) { - console.info('tabsViewCtrl $scope.$id - ' + $scope.$id) +angular.module("mrlapp.mrl").controller("tabsViewCtrl", [ + "$location", + "$scope", + "$filter", + "$timeout", + "mrl", + "$state", + "$stateParams", + function ($location, $scope, $filter, $timeout, mrl, $state, $stateParams) { + console.info("tabsViewCtrl $scope.$id - " + $scope.$id) _self = this $scope.history = [] @@ -10,65 +18,71 @@ angular.module('mrlapp.mrl').controller('tabsViewCtrl', ['$location', '$scope', // setting callback method in service so other controllers // can set searchText - $scope.setSearchText = function(text) { - $scope.searchText.displayName = text + $scope.setSearchText = function (text) { + $scope.searchText.displayName = text } - $scope.noworky = function() { - noWorkySvc.openNoWorkyModal($scope.panel.name) + $scope.noworky = function () { + noWorkySvc.openNoWorkyModal($scope.panel.name) } - $scope.updateServiceData = function() { - //get an updated / fresh servicedata & convert it to json - var servicedata = mrl.getService($scope.view_tab) - $scope.servicedatajson = JSON.stringify(servicedata, null, 2) + $scope.updateServiceData = function () { + //get an updated / fresh servicedata & convert it to json + var servicedata = mrl.getService($scope.view_tab) + $scope.servicedatajson = JSON.stringify(servicedata, null, 2) } - $scope.getName = function(panel) { - return panel.name + $scope.getName = function (panel) { + return panel.name } //service-panels & update-routine - var panelsUpdated = function(panels) { - console.debug('tabsViewCtrl.panelsUpdated ' + panels.length) - $scope.panels = panels - - if (!$scope.view_tab && panels.length > 0 && $scope.panels[$scope.panels.length - 1].name.startsWith('intro')) {// $scope.changeTab($scope.panels[0].name) - } - - // if /#/service/{servicename} - change the tab - if ($scope.servicename) {// $scope.changeTab($scope.servicename) - } + var panelsUpdated = function (panels) { + console.debug("tabsViewCtrl.panelsUpdated " + panels.length) + $scope.panels = panels + + if (!$scope.view_tab && panels.length > 0 && $scope.panels[$scope.panels.length - 1].name.startsWith("intro")) { + // $scope.changeTab($scope.panels[0].name) + } + + // if /#/service/{servicename} - change the tab + if ($scope.servicename) { + // $scope.changeTab($scope.servicename) + } } /** * go to a service tab * direction - reverse or null (forward) */ - $scope.changeTab = function(tab) { - tab = mrl.getFullName(tab) - $scope.view_tab = tab - $scope.history.push(tab) - - // $location.path('service/' + tab, false) - // $state.go('tabs2','/service/' + tab) - - // $state.transitionTo('tabs2', {id: tab}, { - // location: true, - // inherit: true, - // relative: $state.$current, - // notify: false - // }) - - $state.go('tabs2', { - servicename: tab - }, { - notify: false, - reload: false - }) - // $state.go('tabs2', { servicename: tab }, {notify:false, reload:true}) - // $state.go($state.current, {}, {reload: true}) - /* + $scope.changeTab = function (tab) { + tab = mrl.getFullName(tab) + $scope.view_tab = tab + $scope.history.push(tab) + + // $location.path('service/' + tab, false) + // $state.go('tabs2','/service/' + tab) + + // $state.transitionTo('tabs2', {id: tab}, { + // location: true, + // inherit: true, + // relative: $state.$current, + // notify: false + // }) + + $state.go( + "tabs2", + { + servicename: tab, + }, + { + notify: false, + reload: false, + } + ) + // $state.go('tabs2', { servicename: tab }, {notify:false, reload:true}) + // $state.go($state.current, {}, {reload: true}) + /* $state.transitionTo('tabs2', { id: newId }, { @@ -79,23 +93,27 @@ angular.module('mrlapp.mrl').controller('tabsViewCtrl', ['$location', '$scope', })*/ } - $scope.goBack = function(){ - // pop self - $scope.history.pop() - // go back one - tab = $scope.history[$scope.history.length - 1] - $scope.view_tab = tab - - $state.go('tabs2', { - servicename: tab - }, { - notify: false, - reload: false - }) - + $scope.goBack = function () { + // pop self + $scope.history.pop() + // go back one + tab = $scope.history[$scope.history.length - 1] + $scope.view_tab = tab + + $state.go( + "tabs2", + { + servicename: tab, + }, + { + notify: false, + reload: false, + } + ) } - $scope.searchText = {// displayName: "" + $scope.searchText = { + // displayName: "" } $scope.hasNewStatus = true @@ -105,5 +123,5 @@ angular.module('mrlapp.mrl').controller('tabsViewCtrl', ['$location', '$scope', mrl.setSearchFunction($scope.setSearchText) mrl.setTabsViewCtrl(this) mrl.subscribeToUpdates(panelsUpdated) -} + }, ]) diff --git a/src/test/java/org/myrobotlab/service/HarryTest.java b/src/test/java/org/myrobotlab/service/HarryTest.java index 29527a0845..1d1995fc5f 100755 --- a/src/test/java/org/myrobotlab/service/HarryTest.java +++ b/src/test/java/org/myrobotlab/service/HarryTest.java @@ -228,7 +228,7 @@ public void testHarry() throws Exception { // if startInMoov: // i01.startAll(leftPort, rightPort) // else: - i01.mouth = mouth; + i01.startPeer("mouth"); solr.attachAllInboxes(); solr.attachAllOutboxes(); diff --git a/src/test/java/org/myrobotlab/service/RandomTest.java b/src/test/java/org/myrobotlab/service/RandomTest.java index f089f2e453..1950a4476e 100644 --- a/src/test/java/org/myrobotlab/service/RandomTest.java +++ b/src/test/java/org/myrobotlab/service/RandomTest.java @@ -40,8 +40,10 @@ public void testService() throws Exception { assertTrue("should have method", random.getKeySet().contains("clock.setInterval")); - assertTrue(String.format("random method 1 should be %d => 5000 values", clock.getInterval()), 5000 <= clock.getInterval()); - assertTrue(String.format("random method 1 should be %d <= 10000 values", clock.getInterval()), clock.getInterval() <= 10000); + assertTrue(String.format("random method 1 should be %d => 5000 values", clock.getInterval()), + 5000 <= clock.getInterval()); + assertTrue(String.format("random method 1 should be %d <= 10000 values", clock.getInterval()), + clock.getInterval() <= 10000); random.remove("clock.setInterval"); @@ -70,8 +72,10 @@ public void testService() throws Exception { sleep(200); clock.setInterval(9999); assertTrue("clock should not be started 3", !clock.isClockRunning()); - assertTrue(String.format("random method 2 should be %d => 5000 values", clock.getInterval()), 5000 <= clock.getInterval()); - assertTrue(String.format("random method 2 should be %d <= 10000 values", clock.getInterval()), clock.getInterval() <= 10000); + assertTrue(String.format("random method 2 should be %d => 5000 values", clock.getInterval()), + 5000 <= clock.getInterval()); + assertTrue(String.format("random method 2 should be %d <= 10000 values", clock.getInterval()), + clock.getInterval() <= 10000); // disable all random.disable(); @@ -85,8 +89,10 @@ public void testService() throws Exception { random.enable(); sleep(1000); assertTrue("clock should not be started 5", !clock.isClockRunning()); - assertTrue(String.format("random method 3 should be %d => 5000 values", clock.getInterval()), 5000 <= clock.getInterval()); - assertTrue(String.format("random method 3 should be %d <= 10000 values", clock.getInterval()), clock.getInterval() <= 10000); + assertTrue(String.format("random method 3 should be %d => 5000 values", clock.getInterval()), + 5000 <= clock.getInterval()); + assertTrue(String.format("random method 3 should be %d <= 10000 values", clock.getInterval()), + clock.getInterval() <= 10000); clock.stopClock(); random.purge(); diff --git a/src/test/java/org/myrobotlab/service/ServiceInterfaceTest.java b/src/test/java/org/myrobotlab/service/ServiceInterfaceTest.java index c2ec409817..6f5c117b03 100644 --- a/src/test/java/org/myrobotlab/service/ServiceInterfaceTest.java +++ b/src/test/java/org/myrobotlab/service/ServiceInterfaceTest.java @@ -56,7 +56,7 @@ private boolean serviceHasWebPage(String service) { private boolean serviceInterfaceTest(String service) throws IOException { // see if we can start/stop and release the service. - + // set a configuration path Runtime.setConfig("serviceInterfaceTest"); @@ -65,9 +65,9 @@ private boolean serviceInterfaceTest(String service) throws IOException { log.warn("Runtime Create returned a null service for {}", service); return false; } - System.out.println("Service Test:" + service); + System.out.println("ServiceInterface Test:" + service); - if (service.equals("As5048AEncoder")){ + if (service.equals("As5048AEncoder")) { log.info("here"); } @@ -85,7 +85,7 @@ private boolean serviceInterfaceTest(String service) throws IOException { foo.startService(); foo.save(); // foo.load(); SHOULD NOT BE USED ! - // foo.apply(); <- THIS SHOULD BE IMPLEMENTED + // foo.apply(); <- THIS SHOULD BE IMPLEMENTED foo.stopService(); foo.releaseService(); @@ -103,9 +103,9 @@ public final void testAllServices() throws ClassNotFoundException, IOException { ArrayList servicesNotInServiceDataJson = new ArrayList(); HashSet blacklist = new HashSet(); - blacklist.add("OpenNi"); - blacklist.add("As5048AEncoder"); - blacklist.add("IntegratedMovement"); + blacklist.add("OpenNi"); + blacklist.add("As5048AEncoder"); + blacklist.add("IntegratedMovement"); blacklist.add("VirtualDevice"); blacklist.add("Joystick"); blacklist.add("GoogleAssistant"); @@ -145,8 +145,9 @@ public final void testAllServices() throws ClassNotFoundException, IOException { // FIXME - must have different thread (prefix script) which runs a timer - // script REQUIRED to complete in 4 minutes ... or BOOM it fails - // sts.clear(); - // sts.add(sd.getServiceType("org.myrobotlab.service.InMoov")); + // USEFUL FOR DEBUGGING SINGLE SERVICE +// sts.clear(); +// sts.add(ServiceData.getMetaData("org.myrobotlab.service.NeoPixel")); for (MetaData serviceType : sts) { // test single service @@ -164,7 +165,7 @@ public final void testAllServices() throws ClassNotFoundException, IOException { continue; } // log.info("Testing Service: {}", service); - + System.out.println("testing " + service); MetaData st = ServiceData.getMetaData("org.myrobotlab.service." + service);