From 829ccd1178ba69e5d034bf40e1558d164c2503ec Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 20 Jan 2024 05:25:57 -0800 Subject: [PATCH 001/131] model for aiml xml --- .../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 +++++++++++++++ 5 files changed, 154 insertions(+) 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 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<String> 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> 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> oob; +// +// public List<Oob> getOob() { +// return oob; +// } +// +// public void setOob(List<Oob> oob) { +// this.oob = oob; +// } + + public static void main(String[] args) { + + try { + + // String xml = "<template>XXX<oob><mrl><service>blah</service><method>method</method></mrl></oob></template>"; + // String xml = "<template>XXXX<oob><mrl><service>blah1</service><method>method1</method></mrl><mrl><service>blah2</service><method>method2</method></mrl></oob></template>"; + String xml = "<template>XXXX<oob><mrl><service>blah1</service><method>method1</method><param>p1</param><param>p2</param><param>p3</param></mrl><mrl><service>blah2</service><method>method2</method></mrl><mrljson>[\"method\":\"doIt\",\"data\":[\"p1\"]]</mrljson></oob></template>"; + + XmlMapper xmlMapper = new XmlMapper(); + Template template = xmlMapper.readValue(xml, Template.class); + + System.out.println(template); + + } catch(Exception e) { + e.printStackTrace(); + } + } + +} From 67bd304087adcb0f3e6fc2f88a86bcc2f31311f5 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 20 Jan 2024 07:48:16 -0800 Subject: [PATCH 002/131] InMoov gets a heart --- .../java/org/myrobotlab/service/InMoov2.java | 622 +++++++++++++----- .../service/config/InMoov2Config.java | 2 +- 2 files changed, 441 insertions(+), 183 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 339aeaf107..462258a2ac 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -4,15 +4,23 @@ import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +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.framework.Message; +import org.myrobotlab.framework.Peer; import org.myrobotlab.framework.Plan; import org.myrobotlab.framework.Platform; import org.myrobotlab.framework.Registration; @@ -41,14 +49,73 @@ 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<InMoov2Config> implements ServiceLifeCycleListener, TextListener, TextPublisher, - JoystickListener, LocaleProvider, IKJointAngleListener { +public class InMoov2 extends Service<InMoov2Config> implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, IKJointAngleListener { + + public class Heartbeat { + public long count = 0; + public long ts = System.currentTimeMillis(); + public String state; + public List<LogEntry> errors; + double batteryLevel = 100; + public boolean isPirOn = false; + + public Heartbeat(InMoov2 inmoov) { + this.state = inmoov.state; + this.errors = inmoov.errors; + this.count = inmoov.heartbeatCount; + this.isPirOn = inmoov.isPirOn; + } + } + + 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 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"); + } + } + + public void stop() { + if (thread != null) { + thread.interrupt(); + config.heartbeat = false; + } else { + log.info("heart already stopped"); + } + } + } public final static Logger log = LoggerFactory.getLogger(InMoov2.class); @@ -58,6 +125,7 @@ public class InMoov2 extends Service<InMoov2Config> implements ServiceLifeCycleL static String speechRecognizer = "WebkitSpeechRecognition"; + /** * This method will load a python file into the python interpreter. * @@ -68,6 +136,7 @@ public class InMoov2 extends Service<InMoov2Config> implements ServiceLifeCycleL @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) { @@ -97,17 +166,19 @@ public static boolean loadFile(String file) { return true; } + /** + * number of times waited in boot state + */ + protected int bootCount = 0; + protected transient ProgramAB chatBot; protected List<String> configList; /** - * Configuration from runtime has started. This is when runtime starts - * processing a configuration set for the first time since inmoov was started + * map of events or states to sounds */ - protected boolean configStarted = false; - - String currentConfigurationName = "default"; + protected Map<String, String> customSoundMap = new TreeMap<>(); protected transient SpeechRecognizer ear; @@ -126,14 +197,31 @@ public static boolean loadFile(String file) { protected Set<String> gestures = new TreeSet<String>(); + /** + * Prevents actions or events from happening when InMoov2 is first booted + */ + private boolean hasBooted = false; + + private transient final Heart heart = new Heart(); + + protected long heartbeatCount = 0; + + protected boolean heartBeating = false; + protected transient HtmlFilter htmlFilter; protected transient ImageDisplay imageDisplay; + protected boolean isPirOn = false; + + protected boolean isSpeaking = false; + protected String lastGestureExecuted; protected Long lastPirActivityTime; + protected String lastState = null; + /** * supported locales */ @@ -149,10 +237,22 @@ public static boolean loadFile(String file) { protected transient Python python; + /** + * initial state - updated on any state change + */ + String state = "boot"; + + protected long stateLastIdleTime = System.currentTimeMillis(); + + protected long stateLastRandomTime = System.currentTimeMillis(); + protected String voiceSelected; + protected Double batteryLevel = 100.0; + 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 @@ -166,29 +266,12 @@ 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"); - if (c.locale != null) { setLocale(c.locale); } else { setLocale(getSupportedLocale(Runtime.getInstance().getLocale().toString())); } - loadAppsScripts(); - - loadInitScripts(); - - if (c.loadGestures) { - loadGestures(); - } - - if (c.heartbeat) { - startHeartbeat(); - } else { - stopHeartbeat(); - } - } catch (Exception e) { error(e); } @@ -215,6 +298,141 @@ public void attachTextPublisher(TextPublisher service) { subscribe(service.getName(), "publishText"); } + /** + * 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; + } + + bootCount++; + log.info("boot count {}", bootCount); + + // config has not finished processing yet.. + if (runtime.isProcessingConfig()) { + log.warn("runtime still processing config set {}, waiting ....", runtime.getConfigName()); + return; + } + + // 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 + * <pre> + + 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); + + </pre> + */ + + // 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"); + invoke("publishPlayAudioFile", startupsound); + } + + List<ServiceInterface> 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<ServiceInterface> local = Runtime.getLocalServices().values(); + List<ServiceInterface> ordered = new ArrayList<>(local); + ordered.removeIf(Objects::isNull); + Collections.sort(ordered); + + Map<String, Peer> peers = getPeers(); + Set<String> 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 beginCheckingOnInactivity() { beginCheckingOnInactivity(maxInactivityTimeSeconds); } @@ -584,6 +802,14 @@ public InMoov2Hand getRightHand() { return (InMoov2Hand) getPeer("rightHand"); } + public String getState() { + FiniteStateMachine fsm = (FiniteStateMachine) getPeer("fsm"); + if (fsm == null) { + return null; + } + return fsm.getCurrent(); + } + /** * matches on language only not variant expands language match to full InMoov2 * bot locale @@ -870,6 +1096,11 @@ public void onCreated(String fullname) { log.info("{} created", fullname); } + @Override + public void onEndSpeaking(String utterance) { + isSpeaking = false; + } + public void onFinishedConfig(String configName) { log.info("onFinishedConfig"); // invoke("publishEvent", "configFinished"); @@ -885,6 +1116,10 @@ public void onGestureStatus(Status status) { unsubscribe("python", "publishStatus", this.getName(), "onGestureStatus"); } + /** + * 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<String, Double> angleMap) { log.debug("onJointAngles {}", angleMap); @@ -951,7 +1186,7 @@ public OpenCVData onOpenCVData(OpenCVData data) { * @param volume */ public void onPeak(double volume) { - if (config.neoPixelFlashWhenSpeaking && !configStarted) { + if (config.neoPixelFlashWhenSpeaking && !"boot".equals(getState())) { if (volume > 0.5) { invoke("publishSpeakingFlash", "speaking"); } @@ -1031,121 +1266,10 @@ public void onStartConfig(String configName) { */ @Override public void onStarted(String name) { - InMoov2Config c = (InMoov2Config) config; - - 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); - - switch (peerKey) { - case "audioPlayer": - break; - case "chatBot": - ProgramAB chatBot = (ProgramAB) Runtime.getService(name); - chatBot.attachTextListener(getPeerName("htmlFilter")); - startPeer("htmlFilter"); - break; - case "controller3": - break; - case "controller4": - break; - case "ear": - AbstractSpeechRecognizer ear = (AbstractSpeechRecognizer) Runtime.getService(name); - ear.attachTextListener(getPeerName("chatBot")); - break; - case "eyeTracking": - break; - case "fsm": - break; - case "gpt3": - break; - case "head": - addListener("publishMoveHead", name); - break; - case "headTracking": - break; - case "htmlFilter": - TextPublisher htmlFilter = (TextPublisher) Runtime.getService(name); - htmlFilter.attachTextListener(getPeerName("mouth")); - break; - case "imageDisplay": - break; - case "leap": - break; - case "left": - break; - case "leftArm": - addListener("publishMoveLeftArm", name, "onMoveArm"); - break; - case "leftHand": - addListener("publishMoveLeftHand", name, "onMoveHand"); - break; - case "mouth": - mouth = (AbstractSpeechSynthesis) Runtime.getService(name); - mouth.attachSpeechListener(getPeerName("ear")); - break; - case "mouthControl": - break; - case "neoPixel": - break; - case "opencv": - subscribeTo(name, "publishOpenCVData"); - break; - case "openni": - break; - case "openWeatherMap": - break; - case "pid": - break; - case "pir": - break; - case "random": - break; - case "right": - break; - case "rightArm": - addListener("publishMoveRightArm", name, "onMoveArm"); - break; - case "rightHand": - addListener("publishMoveRightHand", name, "onMoveHand"); - break; - case "servoMixer": - break; - case "simulator": - break; - case "torso": - addListener("publishMoveTorso", name); - break; - case "ultrasonicRight": - break; - case "ultrasonicLeft": - break; - default: - log.warn("unknown peer %s not hanled 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); @@ -1158,8 +1282,52 @@ public void onStarted(String name) { } } + // FIXME - rebroadcast these + @Override + public void onStartSpeaking(String utterance) { + isSpeaking = true; + } + + /** + * 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 FiniteStateMachine.StateChange onStateChange(FiniteStateMachine.StateChange stateChange) { + try { + log.info("onStateChange {}", stateChange); + + lastState = state; + state = stateChange.state; + + processMessage("onStateChange", stateChange); + + } catch (Exception e) { + error(e); + } + return stateChange; + } + @Override public void onStopped(String name) { + log.info("service {} has stopped"); // using release peer for peer releasing // FIXME - auto remove subscriptions of peers? } @@ -1262,9 +1430,102 @@ public String publishFlash(String flashName) { return flashName; } - public String publishHeartbeat() { - invoke("publishFlash", "heartbeat"); - return getName(); + /** + * 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"); + 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)) { + fsm.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); + } + + // 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; + } + + public void processMessage(String method) { + processMessage(method, null); + } + + /** + * Will publish processing messages to the processor(s) currently subscribed. + * + * @param method + * @param data + */ + public void processMessage(String method, Object data) { + // User processing should not occur until after boot has completed + if (!state.equals("boot")) { + // FIXME - this needs to be in config + // FIXME - change peer name to "processor" + String processor = getPeerName("py4j"); + Message msg = Message.createMessage(getName(), processor, method, data); + // FIXME - is this too much abstraction .. to publish as well as + // configurable send ? + invoke("publishProcessMessage", msg); + } } /** @@ -1393,6 +1654,7 @@ public void releasePeer(String peerKey) { public void releaseService() { try { disable(); + heart.stop(); super.releaseService(); } catch (Exception e) { error(e); @@ -1791,7 +2053,7 @@ public void startedGesture(String nameOfGesture) { } public void startHeartbeat() { - addTask(1000, "publishHeartbeat"); + heart.start(); } // TODO - general objective "might" be to reduce peers down to something @@ -1852,57 +2114,42 @@ public ServiceInterface startPeer(String peer) { @Override public void startService() { super.startService(); - + // FIXME - hardcoded peer no choice of type + fsm = (FiniteStateMachine) startPeer("fsm"); + + // a python processor is + // necessary for InMoov2 to properly + // function but this is not the place to start it + // it should be a peer definition too, it can be "python" + // it doesn't need to be i01.python to be a peer + // also should determine type Py4j or Python + // Runtime.start("python"); + + // 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<ServiceInterface> 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"); - // chatbot getresponse attached to publishEvent - addListener("publishEvent", getPeerName("chatBot"), "getResponse"); + runtime.invoke("publishConfigList"); - try { - // copy config if it doesn't already exist - String resourceBotDir = FileIO.gluePaths(getResourceDir(), "config"); - List<File> 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); + if (config.heartbeat) { + startHeartbeat(); + } else { + stopHeartbeat(); } - runtime.invoke("publishConfigList"); } public void startServos() { @@ -1930,12 +2177,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() { @@ -1962,7 +2210,17 @@ public void systemCheck() { Platform platform = Runtime.getPlatform(); setPredicate("system version", platform.getVersion()); // ERROR buffer !!! - invoke("publishEvent", "systemCheckFinished"); + systemEvent("SYSTEMCHECKFINISHED"); // wtf is this? + } + + 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 diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 564fe516e4..3c9571e435 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -55,7 +55,7 @@ public class InMoov2Config extends ServiceConfig { * fire events to the FSM. Checks battery level and sends a heartbeat flash on * publishHeartbeat and onHeartbeat at a regular interval */ - public boolean heartbeat = false; + public boolean heartbeat = true; /** * flashes the neopixel every time a health check is preformed. green == good From 754bed4f764ac802d8643ea841184573e4be0105 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 20 Jan 2024 13:38:40 -0800 Subject: [PATCH 003/131] jython worky with InMoov2.py --- .../java/org/myrobotlab/service/InMoov2.java | 104 ++++++++++++++---- .../service/config/InMoov2Config.java | 8 +- 2 files changed, 90 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 462258a2ac..cba05f550f 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -58,7 +58,7 @@ public class InMoov2 extends Service<InMoov2Config> implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, IKJointAngleListener { - public class Heartbeat { + public static class Heartbeat { public long count = 0; public long ts = System.currentTimeMillis(); public String state; @@ -360,6 +360,10 @@ synchronized public void boot() { </pre> */ + + // load the InMoov2.py and publish it for Python/Jython or Py4j to consume + String script = getResourceAsString("InMoov2.py"); + invoke("publishPython", script); // TODO - MAKE BOOT REPORT !!!! deliver it on a heartbeat runtime.invoke("publishConfigList"); @@ -730,30 +734,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); + 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; - - } catch (Exception e) { - error(e); - return null; } + return lastActivityTime; } public InMoov2Arm getLeftArm() { @@ -1520,7 +1526,9 @@ public void processMessage(String method, Object data) { if (!state.equals("boot")) { // FIXME - this needs to be in config // FIXME - change peer name to "processor" - String processor = getPeerName("py4j"); + // String processor = getPeerName("py4j"); + String processor = "python"; + Message msg = Message.createMessage(getName(), processor, method, data); // FIXME - is this too much abstraction .. to publish as well as // configurable send ? @@ -1634,6 +1642,62 @@ public HashMap<String, Double> publishMoveTorso(Double topStom, Double midStom, return map; } + public String publishPlayAudioFile(String filename) { + return filename; + } + + /** + * Processing publishing point, where everything InMoov2 wants to be processed + * is turned into a message and published. + * + * @param msg + * @return + */ + public Message publishProcessMessage(Message msg) { + return msg; + } + + /** + * Possible pub/sub way to interface with python - no blocking though + * + * @param code + * @return + */ + public String publishPython(String code) { + return code; + } + + + /** + * 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; + } + + /** + * stop animation event + */ + public void publishStopAnimation() { + } + + /** + * 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); + } + /** * all published text from InMoov2 - including ProgramAB */ diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 3c9571e435..014cfcf0e9 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -188,7 +188,7 @@ public Plan getDefault(Plan plan, String name) { addDefaultPeerConfig(plan, name, "openWeatherMap", "OpenWeatherMap", false); addDefaultPeerConfig(plan, name, "pid", "Pid", false); addDefaultPeerConfig(plan, name, "pir", "Pir", false); - addDefaultPeerConfig(plan, name, "py4j", "Py4j", true); + addDefaultPeerConfig(plan, name, "py4j", "Py4j", false); addDefaultPeerConfig(plan, name, "random", "Random", false); addDefaultPeerConfig(plan, name, "right", "Arduino", false); addDefaultPeerConfig(plan, name, "rightArm", "InMoov2Arm", false); @@ -519,7 +519,11 @@ public Plan getDefault(Plan plan, String name) { 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("publishProcessMessage", getPeerName("py4j"), "onPythonMessage")); + // listeners.add(new Listener("publishProcessMessage", getPeerName("python"), "onPythonMessage")); + listeners.add(new Listener("publishProcessMessage", "python", "onPythonMessage")); + listeners.add(new Listener("publishPython", "python")); + + // InMoov2 --to--> InMoov2 listeners.add(new Listener("publishMoveHead", name)); From ed459dafbc7da4867a86e7867e99f3c97c6b3487 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 20 Jan 2024 20:56:11 -0800 Subject: [PATCH 004/131] reset --- .../org/myrobotlab/service/RandomTest.java | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/test/java/org/myrobotlab/service/RandomTest.java b/src/test/java/org/myrobotlab/service/RandomTest.java index 9c3739f510..1e42ab9e3a 100644 --- a/src/test/java/org/myrobotlab/service/RandomTest.java +++ b/src/test/java/org/myrobotlab/service/RandomTest.java @@ -17,24 +17,23 @@ public class RandomTest extends AbstractServiceTest { * rarely happens - seems not useful and silly */ public Service createService() throws Exception { - return (Service) Runtime.start("randomTest", "Random"); + return (Service) Runtime.start("random", "Random"); } - + @Before /* before each test */ public void setUp() throws IOException { // remove all services - also resets config name to DEFAULT effectively Runtime.releaseAll(true, true); - // clean our config directory - // Runtime.removeConfig("RandomTest"); + // clean our config directory + Runtime.removeConfig("RandomTest"); // set our config Runtime.setConfig("RandomTest"); } - @Override public void testService() throws Exception { Clock clock = (Clock) Runtime.start("clock", "Clock"); - Random random = (Random) Runtime.start("randomTest", "Random"); + Random random = (Random) Runtime.start("random", "Random"); clock.stopClock(); clock.setInterval(1000); @@ -43,65 +42,71 @@ public void testService() throws Exception { random.addRandom(0, 200, "clock", "setInterval", 5000, 10000); random.enable(); - sleep(1000); + sleep(500); 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"); - + assertTrue("should not have method", !random.getKeySet().contains("clock.setInterval")); random.addRandom(0, 200, "clock", "setInterval", 5000, 10000); random.addRandom(0, 200, "clock", "startClock"); - + sleep(500); assertTrue("clock should be started 1", clock.isClockRunning()); - + // disable all of a services random events random.disable("clock.startClock"); clock.stopClock(); sleep(250); assertTrue("clock should not be started 1", !clock.isClockRunning()); - + // enable all of a service's random events random.enable("clock.startClock"); sleep(250); assertTrue("clock should be started 2", clock.isClockRunning()); - + // disable one method - leave other enabled random.disable("clock.startClock"); clock.stopClock(); clock.setInterval(999999); sleep(200); 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(); sleep(200); clock.setInterval(999999); - assertTrue("clock should not be started 4", !clock.isClockRunning()); - assertEquals(999999, (long)clock.getInterval()); + assertTrue("clock should not be started 4", !clock.isClockRunning()); + assertEquals(999999, (long) clock.getInterval()); // re-enable all that were previously enabled but not explicitly disabled ones 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(); - + Map<String, RandomMessage> events = random.getRandomEvents(); assertTrue(events.size() == 0); - + random.addRandom("named task", 200, 500, "clock", "setInterval", 100, 1000, 10); - + clock.releaseService(); random.releaseService(); From e0d1687d9aa144fc3bbab7c68f1e3ad649edf67c Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 21 Jan 2024 06:47:48 -0800 Subject: [PATCH 005/131] ordered and formatted --- .../java/org/myrobotlab/service/InMoov2.java | 567 +++++++++--------- .../service/config/InMoov2Config.java | 8 +- 2 files changed, 272 insertions(+), 303 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index cba05f550f..caa06027f3 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -12,7 +12,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; @@ -36,7 +35,6 @@ import org.myrobotlab.programab.PredicateEvent; import org.myrobotlab.programab.Response; 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; @@ -56,23 +54,8 @@ import org.myrobotlab.service.interfaces.TextPublisher; import org.slf4j.Logger; -public class InMoov2 extends Service<InMoov2Config> implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, IKJointAngleListener { - - public static class Heartbeat { - public long count = 0; - public long ts = System.currentTimeMillis(); - public String state; - public List<LogEntry> errors; - double batteryLevel = 100; - public boolean isPirOn = false; - - public Heartbeat(InMoov2 inmoov) { - this.state = inmoov.state; - this.errors = inmoov.errors; - this.count = inmoov.heartbeatCount; - this.isPirOn = inmoov.isPirOn; - } - } +public class InMoov2 extends Service<InMoov2Config> + implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, IKJointAngleListener { public class Heart implements Runnable { private final ReentrantLock lock = new ReentrantLock(); @@ -117,6 +100,22 @@ public void stop() { } } + public static class Heartbeat { + double batteryLevel = 100; + public long count = 0; + public List<LogEntry> errors; + public boolean isPirOn = false; + public String state; + public long ts = System.currentTimeMillis(); + + public Heartbeat(InMoov2 inmoov) { + this.state = inmoov.state; + this.errors = inmoov.errors; + this.count = inmoov.heartbeatCount; + this.isPirOn = inmoov.isPirOn; + } + } + public final static Logger log = LoggerFactory.getLogger(InMoov2.class); public static LinkedHashMap<String, String> lpVars = new LinkedHashMap<String, String>(); @@ -125,12 +124,11 @@ public void stop() { static String speechRecognizer = "WebkitSpeechRecognition"; - /** * 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 */ @@ -166,6 +164,102 @@ public static boolean loadFile(String file) { return true; } + public static void main(String[] args) { + try { + + LoggingFactory.init(Level.ERROR); + // Platform.setVirtual(true); + // Runtime.start("s01", "Servo"); + // Runtime.start("intro", "Intro"); + + Runtime.startConfig("dev"); + + 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; + } + + 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 Double batteryLevel = 100.0; + /** * number of times waited in boot state */ @@ -248,8 +342,6 @@ public static boolean loadFile(String file) { protected String voiceSelected; - protected Double batteryLevel = 100.0; - 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"); @@ -298,6 +390,18 @@ public void attachTextPublisher(TextPublisher service) { subscribe(service.getName(), "publishText"); } + public void beginCheckingOnInactivity() { + beginCheckingOnInactivity(maxInactivityTimeSeconds); + } + + public void beginCheckingOnInactivity(int maxInactivityTimeSeconds) { + this.maxInactivityTimeSeconds = maxInactivityTimeSeconds; + // speakBlocking("power down after %s seconds inactivity is on", + // this.maxInactivityTimeSeconds); + log.info("power down after %s seconds inactivity is on", this.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 @@ -348,19 +452,20 @@ synchronized public void boot() { // TODO - make Py4j without zombies and more robust /** * Py4j is not ready for primetime yet + * * <pre> - - 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); + * + * 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); + * + * </pre> + */ - </pre> - */ - // load the InMoov2.py and publish it for Python/Jython or Py4j to consume String script = getResourceAsString("InMoov2.py"); invoke("publishPython", script); @@ -437,18 +542,6 @@ synchronized public void boot() { fire("wake"); } - public void beginCheckingOnInactivity() { - beginCheckingOnInactivity(maxInactivityTimeSeconds); - } - - public void beginCheckingOnInactivity(int maxInactivityTimeSeconds) { - this.maxInactivityTimeSeconds = maxInactivityTimeSeconds; - // speakBlocking("power down after %s seconds inactivity is on", - // this.maxInactivityTimeSeconds); - log.info("power down after %s seconds inactivity is on", this.maxInactivityTimeSeconds); - addTask("checkInactivity", 5 * 1000, 0, "checkInactivity"); - } - public void cameraOff() { if (opencv != null) { opencv.stopCapture(); @@ -629,7 +722,7 @@ public boolean exec(String 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) { @@ -657,7 +750,7 @@ public String execGesture(String gesture) { * a filesystem file :P * * @param someScriptName - * execute a resource script + * execute a resource script * @return success or failure */ public boolean execScript(String someScriptName) { @@ -750,16 +843,16 @@ public Long getLastActivityTime() { if (leftArm != null) lastActivityTime = Math.max(lastActivityTime, leftArm); if (rightArm != null) - lastActivityTime = Math.max(lastActivityTime, rightArm); + lastActivityTime = Math.max(lastActivityTime, rightArm); if (leftHand != null) - lastActivityTime = Math.max(lastActivityTime, leftHand); + lastActivityTime = Math.max(lastActivityTime, leftHand); if (rightHand != null) - lastActivityTime = Math.max(lastActivityTime, rightHand); + lastActivityTime = Math.max(lastActivityTime, rightHand); if (torso != null) - lastActivityTime = Math.max(lastActivityTime, torso); + lastActivityTime = Math.max(lastActivityTime, torso); } - return lastActivityTime; + return lastActivityTime; } public InMoov2Arm getLeftArm() { @@ -859,15 +952,6 @@ public void halfSpeed() { sendToPeer("torso", "setSpeed", 20.0, 20.0, 20.0); } - /** - * execute python scripts in the init directory on startup of the service - * - * @throws IOException - */ - public void loadInitScripts() throws IOException { - loadScripts(getResourceDir() + fs + "init"); - } - public boolean isCameraOn() { if (opencv != null) { if (opencv.isCapturing()) { @@ -901,7 +985,7 @@ 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) { @@ -941,6 +1025,15 @@ public boolean loadGestures(String directory) { return true; } + /** + * execute python scripts in the init directory on startup of the service + * + * @throws IOException + */ + public void loadInitScripts() throws IOException { + loadScripts(getResourceDir() + fs + "init"); + } + /** * Generalized directory python script loading method * @@ -991,8 +1084,7 @@ 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) { + 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); } @@ -1044,10 +1136,8 @@ 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) { @@ -1058,10 +1148,8 @@ 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) { @@ -1090,7 +1178,7 @@ public PredicateEvent onChangePredicate(PredicateEvent event) { * comes in from runtime which owns the config list * * @param configList - * list of configs + * list of configs */ public void onConfigList(List<String> configList) { this.configList = configList; @@ -1146,23 +1234,6 @@ public void onJoystickInput(JoystickData input) throws Exception { invoke("publishEvent", "joystick"); } - public String onNewState(String state) { - log.error("onNewState {}", state); - - // put configurable filter here ! - - // state substitutions ? - // let python subscribe directly to fsm.publishNewState - - // if - invoke(state); - // depending on configuration .... - // call python ? - // fire fsm events ? - // do defaults ? - return state; - } - /** * Centralized logging system will have all logging from all services, * including lower level logs that do not propegate as statuses @@ -1180,6 +1251,22 @@ public void onLogEvents(List<LogEntry> log) { } } + public String onNewState(String state) { + log.error("onNewState {}", state); + + // put configurable filter here ! + + // state substitutions ? + // let python subscribe directly to fsm.publishNewState + + // if + invoke(state); + // depending on configuration .... + // call python ? + // fire fsm events ? + // do defaults ? + return state; + } public OpenCVData onOpenCVData(OpenCVData data) { // FIXME - publish event with or without data ? String file reference @@ -1380,6 +1467,31 @@ public void powerUp() { python.execMethod("power_up"); } + public void processMessage(String method) { + processMessage(method, null); + } + + /** + * Will publish processing messages to the processor(s) currently subscribed. + * + * @param method + * @param data + */ + public void processMessage(String method, Object data) { + // User processing should not occur until after boot has completed + if (!state.equals("boot")) { + // FIXME - this needs to be in config + // FIXME - change peer name to "processor" + // String processor = getPeerName("py4j"); + String processor = "python"; + + Message msg = Message.createMessage(getName(), processor, method, data); + // FIXME - is this too much abstraction .. to publish as well as + // configurable send ? + invoke("publishProcessMessage", msg); + } + } + /** * easy utility to publishMessage * @@ -1392,12 +1504,6 @@ public void publish(String name, String method, Object... data) { invoke("publishMessage", msg); } - public String publishConfigStarted(String configName) { - info("config %s started", configName); - invoke("publishEvent", "CONFIG STARTED " + configName); - return configName; - } - public String publishConfigFinished(String configName) { info("config %s finished", configName); invoke("publishEvent", "CONFIG LOADED " + configName); @@ -1415,6 +1521,12 @@ public List<String> publishConfigList() { return configList; } + public String publishConfigStarted(String configName) { + info("config %s started", configName); + invoke("publishEvent", "CONFIG STARTED " + configName); + return configName; + } + /** * event publisher for the fsm - although other services potentially can * consume and filter this event channel @@ -1511,31 +1623,6 @@ public Heartbeat publishHeartbeat() { return heartbeat; } - public void processMessage(String method) { - processMessage(method, null); - } - - /** - * Will publish processing messages to the processor(s) currently subscribed. - * - * @param method - * @param data - */ - public void processMessage(String method, Object data) { - // User processing should not occur until after boot has completed - if (!state.equals("boot")) { - // FIXME - this needs to be in config - // FIXME - change peer name to "processor" - // String processor = getPeerName("py4j"); - String processor = "python"; - - Message msg = Message.createMessage(getName(), processor, method, data); - // FIXME - is this too much abstraction .. to publish as well as - // configurable send ? - invoke("publishProcessMessage", msg); - } - } - /** * A more extensible interface point than publishEvent FIXME - create * interface for this @@ -1547,8 +1634,7 @@ public Message publishMessage(Message msg) { return msg; } - public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double rotate, Double shoulder, - Double omoplate) { + public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double rotate, Double shoulder, Double omoplate) { HashMap<String, Double> map = new HashMap<>(); map.put("bicep", bicep); map.put("rotate", rotate); @@ -1562,8 +1648,7 @@ public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double return map; } - public HashMap<String, Object> publishMoveHand(String which, Double thumb, Double index, Double majeure, - Double ringFinger, Double pinky, Double wrist) { + public HashMap<String, Object> publishMoveHand(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { HashMap<String, Object> map = new HashMap<>(); map.put("which", which); map.put("thumb", thumb); @@ -1580,8 +1665,7 @@ public HashMap<String, Object> publishMoveHand(String which, Double thumb, Doubl return map; } - public HashMap<String, Double> publishMoveHead(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, - Double rollNeck) { + public HashMap<String, Double> publishMoveHead(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, Double rollNeck) { HashMap<String, Double> map = new HashMap<>(); map.put("neck", neck); map.put("rothead", rothead); @@ -1601,8 +1685,7 @@ public HashMap<String, Double> publishMoveLeftArm(Double bicep, Double rotate, D return map; } - public HashMap<String, Double> publishMoveLeftHand(Double thumb, Double index, Double majeure, Double ringFinger, - Double pinky, Double wrist) { + public HashMap<String, Double> publishMoveLeftHand(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { HashMap<String, Double> map = new HashMap<>(); map.put("thumb", thumb); map.put("index", index); @@ -1622,8 +1705,7 @@ public HashMap<String, Double> publishMoveRightArm(Double bicep, Double rotate, return map; } - public HashMap<String, Double> publishMoveRightHand(Double thumb, Double index, Double majeure, Double ringFinger, - Double pinky, Double wrist) { + public HashMap<String, Double> publishMoveRightHand(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { HashMap<String, Double> map = new HashMap<>(); map.put("thumb", thumb); map.put("index", index); @@ -1667,7 +1749,6 @@ public String publishPython(String code) { return code; } - /** * publishes a name for NeoPixel.onFlash to consume, in a seperate channel to * potentially be used by "speaking only" leds @@ -1769,8 +1850,7 @@ 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); @@ -1780,14 +1860,12 @@ 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); } @@ -1803,8 +1881,7 @@ 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); } @@ -1828,8 +1905,7 @@ 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); } @@ -1841,15 +1917,12 @@ 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 @@ -1918,15 +1991,49 @@ 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 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); + mouth.speechRecognizers = new String[] { getName() + ".ear" }; + + savePeerConfig("mouth", plan.get(peerName)); + + if (isPeerStarted("mouth")) { + // restart + releasePeer("mouth"); + startPeer("mouth"); + } + + } catch (Exception e) { + error("could not create config for %s", speechType); + return false; + } + + return true; + + // updatePeerType("mouth" /* getPeerName("mouth") */, speechType); + // return speechType; } public void setTorsoSpeed(Double topStom, Double midStom, Double lowStom) { @@ -1937,6 +2044,11 @@ 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); @@ -1950,11 +2062,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"); } @@ -2057,8 +2164,7 @@ public ProgramAB startChatBot() { 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")) { + if (chatBot.getPredicate("lastUsername").isEmpty() || chatBot.getPredicate("lastUsername").equals("unknown") || chatBot.getPredicate("lastUsername").equals("default")) { chatBot.setPredicate("lastUsername", chatBot.getPredicate("name")); } } @@ -2074,8 +2180,7 @@ public ProgramAB startChatBot() { // !chatBot.getPredicate("default", "lastUsername").equals("unknown")) { // chatBot.startSession(chatBot.getPredicate("lastUsername")); // } - if (chatBot.getPredicate("default", "firstinit").isEmpty() - || chatBot.getPredicate("default", "firstinit").equals("unknown") + 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"); @@ -2299,138 +2404,4 @@ public void 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); - mouth.speechRecognizers = new String[] { getName() + ".ear" }; - - savePeerConfig("mouth", plan.get(peerName)); - - if (isPeerStarted("mouth")) { - // restart - releasePeer("mouth"); - startPeer("mouth"); - } - - } catch (Exception e) { - error("could not create config for %s", speechType); - return false; - } - - return true; - - // updatePeerType("mouth" /* getPeerName("mouth") */, speechType); - // return speechType; - } - - public static void main(String[] args) { - try { - - LoggingFactory.init(Level.ERROR); - // Platform.setVirtual(true); - // Runtime.start("s01", "Servo"); - // Runtime.start("intro", "Intro"); - - Runtime.startConfig("dev"); - - 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; - } - - 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); - } - } - } diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 014cfcf0e9..c599a5a1ad 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -519,11 +519,10 @@ public Plan getDefault(Plan plan, String name) { 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("publishProcessMessage", getPeerName("python"), "onPythonMessage")); + // listeners.add(new Listener("publishProcessMessage", + // getPeerName("python"), "onPythonMessage")); listeners.add(new Listener("publishProcessMessage", "python", "onPythonMessage")); listeners.add(new Listener("publishPython", "python")); - - // InMoov2 --to--> InMoov2 listeners.add(new Listener("publishMoveHead", name)); @@ -536,12 +535,11 @@ public Plan getDefault(Plan plan, String name) { // service --to--> InMoov2 AudioFileConfig mouth_audioFile = (AudioFileConfig) plan.get(getPeerName("mouth.audioFile")); mouth_audioFile.listeners.add(new Listener("publishPeak", name)); - + OakDConfig oakd = (OakDConfig) plan.get(getPeerName("oakd")); oakd.listeners.add(new Listener("publishClassification", name)); oakd.getPeer("py4j").name = getPeerName("py4j"); - webxr.listeners.add(new Listener("publishJointAngles", name)); // mouth_audioFile.listeners.add(new Listener("publishAudioEnd", name)); From 683808410395130c743fc102628b34999b6c7803 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 21 Jan 2024 06:58:19 -0800 Subject: [PATCH 006/131] recovering from bad merge --- .../java/org/myrobotlab/service/InMoov2.java | 122 ++++++------------ 1 file changed, 41 insertions(+), 81 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 568edece8d..caa06027f3 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -55,8 +55,7 @@ import org.slf4j.Logger; public class InMoov2 extends Service<InMoov2Config> - implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, - IKJointAngleListener { + implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, IKJointAngleListener { public class Heart implements Runnable { private final ReentrantLock lock = new ReentrantLock(); @@ -129,7 +128,7 @@ public Heartbeat(InMoov2 inmoov) { * 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 */ @@ -235,15 +234,11 @@ 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); @@ -263,8 +258,6 @@ public static void main(String[] args) { } } - <<<<<<<HEAD - protected Double batteryLevel = 100.0; /** @@ -272,7 +265,6 @@ public static void main(String[] args) { */ protected int bootCount = 0; - =======>>>>>>>60e56597d 29d 490d 553855 aff73d5930130c063d protected transient ProgramAB chatBot; protected List<String> configList; @@ -352,8 +344,7 @@ public static void main(String[] args) { 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"); + 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 @@ -731,7 +722,7 @@ public boolean exec(String 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) { @@ -759,7 +750,7 @@ public String execGesture(String gesture) { * a filesystem file :P * * @param someScriptName - * execute a resource script + * execute a resource script * @return success or failure */ public boolean execScript(String someScriptName) { @@ -837,18 +828,11 @@ public InMoov2Head getHead() { */ public Long getLastActivityTime() { 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 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; @@ -1001,7 +985,7 @@ 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) { @@ -1100,8 +1084,7 @@ 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) { + 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); } @@ -1153,10 +1136,8 @@ 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) { @@ -1167,10 +1148,8 @@ 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) { @@ -1199,7 +1178,7 @@ public PredicateEvent onChangePredicate(PredicateEvent event) { * comes in from runtime which owns the config list * * @param configList - * list of configs + * list of configs */ public void onConfigList(List<String> configList) { this.configList = configList; @@ -1260,7 +1239,7 @@ public void onJoystickInput(JoystickData input) throws Exception { * including lower level logs that do not propegate as statuses * * @param log - * - flushed log from Log service + * - flushed log from Log service */ public void onLogEvents(List<LogEntry> log) { // scan for warn or errors @@ -1603,8 +1582,7 @@ public Heartbeat publishHeartbeat() { } // interval event firing - if (config.stateRandomInterval != null - && System.currentTimeMillis() > stateLastRandomTime + (config.stateRandomInterval * 1000)) { + if (config.stateRandomInterval != null && System.currentTimeMillis() > stateLastRandomTime + (config.stateRandomInterval * 1000)) { // fsm.fire("random"); stateLastRandomTime = System.currentTimeMillis(); } @@ -1656,8 +1634,7 @@ public Message publishMessage(Message msg) { return msg; } - public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double rotate, Double shoulder, - Double omoplate) { + public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double rotate, Double shoulder, Double omoplate) { HashMap<String, Double> map = new HashMap<>(); map.put("bicep", bicep); map.put("rotate", rotate); @@ -1671,8 +1648,7 @@ public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double return map; } - public HashMap<String, Object> publishMoveHand(String which, Double thumb, Double index, Double majeure, - Double ringFinger, Double pinky, Double wrist) { + public HashMap<String, Object> publishMoveHand(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { HashMap<String, Object> map = new HashMap<>(); map.put("which", which); map.put("thumb", thumb); @@ -1689,8 +1665,7 @@ public HashMap<String, Object> publishMoveHand(String which, Double thumb, Doubl return map; } - public HashMap<String, Double> publishMoveHead(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, - Double rollNeck) { + public HashMap<String, Double> publishMoveHead(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, Double rollNeck) { HashMap<String, Double> map = new HashMap<>(); map.put("neck", neck); map.put("rothead", rothead); @@ -1710,8 +1685,7 @@ public HashMap<String, Double> publishMoveLeftArm(Double bicep, Double rotate, D return map; } - public HashMap<String, Double> publishMoveLeftHand(Double thumb, Double index, Double majeure, Double ringFinger, - Double pinky, Double wrist) { + public HashMap<String, Double> publishMoveLeftHand(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { HashMap<String, Double> map = new HashMap<>(); map.put("thumb", thumb); map.put("index", index); @@ -1731,8 +1705,7 @@ public HashMap<String, Double> publishMoveRightArm(Double bicep, Double rotate, return map; } - public HashMap<String, Double> publishMoveRightHand(Double thumb, Double index, Double majeure, Double ringFinger, - Double pinky, Double wrist) { + public HashMap<String, Double> publishMoveRightHand(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { HashMap<String, Double> map = new HashMap<>(); map.put("thumb", thumb); map.put("index", index); @@ -1877,8 +1850,7 @@ 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); @@ -1888,14 +1860,12 @@ 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); } @@ -1911,8 +1881,7 @@ 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); } @@ -1936,8 +1905,7 @@ 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); } @@ -1949,15 +1917,12 @@ 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 @@ -2026,15 +1991,12 @@ 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 boolean setSpeechType(String speechType) { @@ -2202,8 +2164,7 @@ public ProgramAB startChatBot() { 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")) { + if (chatBot.getPredicate("lastUsername").isEmpty() || chatBot.getPredicate("lastUsername").equals("unknown") || chatBot.getPredicate("lastUsername").equals("default")) { chatBot.setPredicate("lastUsername", chatBot.getPredicate("name")); } } @@ -2219,8 +2180,7 @@ public ProgramAB startChatBot() { // !chatBot.getPredicate("default", "lastUsername").equals("unknown")) { // chatBot.startSession(chatBot.getPredicate("lastUsername")); // } - if (chatBot.getPredicate("default", "firstinit").isEmpty() - || chatBot.getPredicate("default", "firstinit").equals("unknown") + 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"); From b5794f5f33c46b96be1f2797fd95a13045219f8a Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 21 Jan 2024 07:01:45 -0800 Subject: [PATCH 007/131] reset randomtest --- .../org/myrobotlab/service/RandomTest.java | 55 +++++++++---------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/src/test/java/org/myrobotlab/service/RandomTest.java b/src/test/java/org/myrobotlab/service/RandomTest.java index 1e42ab9e3a..9c3739f510 100644 --- a/src/test/java/org/myrobotlab/service/RandomTest.java +++ b/src/test/java/org/myrobotlab/service/RandomTest.java @@ -17,23 +17,24 @@ public class RandomTest extends AbstractServiceTest { * rarely happens - seems not useful and silly */ public Service createService() throws Exception { - return (Service) Runtime.start("random", "Random"); + return (Service) Runtime.start("randomTest", "Random"); } - + @Before /* before each test */ public void setUp() throws IOException { // remove all services - also resets config name to DEFAULT effectively Runtime.releaseAll(true, true); - // clean our config directory - Runtime.removeConfig("RandomTest"); + // clean our config directory + // Runtime.removeConfig("RandomTest"); // set our config Runtime.setConfig("RandomTest"); } + @Override public void testService() throws Exception { Clock clock = (Clock) Runtime.start("clock", "Clock"); - Random random = (Random) Runtime.start("random", "Random"); + Random random = (Random) Runtime.start("randomTest", "Random"); clock.stopClock(); clock.setInterval(1000); @@ -42,71 +43,65 @@ public void testService() throws Exception { random.addRandom(0, 200, "clock", "setInterval", 5000, 10000); random.enable(); - sleep(500); + sleep(1000); 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"); - + assertTrue("should not have method", !random.getKeySet().contains("clock.setInterval")); random.addRandom(0, 200, "clock", "setInterval", 5000, 10000); random.addRandom(0, 200, "clock", "startClock"); - + sleep(500); assertTrue("clock should be started 1", clock.isClockRunning()); - + // disable all of a services random events random.disable("clock.startClock"); clock.stopClock(); sleep(250); assertTrue("clock should not be started 1", !clock.isClockRunning()); - + // enable all of a service's random events random.enable("clock.startClock"); sleep(250); assertTrue("clock should be started 2", clock.isClockRunning()); - + // disable one method - leave other enabled random.disable("clock.startClock"); clock.stopClock(); clock.setInterval(999999); sleep(200); 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(); sleep(200); clock.setInterval(999999); - assertTrue("clock should not be started 4", !clock.isClockRunning()); - assertEquals(999999, (long) clock.getInterval()); + assertTrue("clock should not be started 4", !clock.isClockRunning()); + assertEquals(999999, (long)clock.getInterval()); // re-enable all that were previously enabled but not explicitly disabled ones 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(); - + Map<String, RandomMessage> events = random.getRandomEvents(); assertTrue(events.size() == 0); - + random.addRandom("named task", 200, 500, "clock", "setInterval", 100, 1000, 10); - + clock.releaseService(); random.releaseService(); From 37a379590f162c92a62358c35c5605aaf75d98c7 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 21 Jan 2024 08:13:15 -0800 Subject: [PATCH 008/131] finite state history --- .../service/views/FiniteStateMachineGui.html | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/resources/resource/WebGui/app/service/views/FiniteStateMachineGui.html b/src/main/resources/resource/WebGui/app/service/views/FiniteStateMachineGui.html index 067ee7624c..b61f5af303 100644 --- a/src/main/resources/resource/WebGui/app/service/views/FiniteStateMachineGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/FiniteStateMachineGui.html @@ -54,5 +54,22 @@ <h3>Last Event {{event}} Current State: {{current}}</h3> <td></td> </tr> </table> +<table class="table-condensed table-striped table-bordered"> + <thead> + <tr> + <th>Timestamp</th> + <th>State</th> + <th>Event</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="item in service.history"> + <td>{{ item.ts }}</td> + <td>{{ item.state }}</td> + <td>{{ item.event }}</td> + </tr> + </tbody> +</table> + </div> </div> From a7c1b662f74b9cbd73101ee3faff739cc6f80ff9 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 21 Jan 2024 13:56:02 -0800 Subject: [PATCH 009/131] fixed fire fsm event --- .../java/org/myrobotlab/service/InMoov2.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index caa06027f3..719025ce8c 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -778,9 +778,15 @@ 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); + // Should this be sent to chatbot too ? + // invoke("publishEvent", event); + fsm.fire(event); } public void fullSpeed() { @@ -2283,16 +2289,13 @@ public ServiceInterface startPeer(String peer) { @Override public void startService() { super.startService(); - // FIXME - hardcoded peer no choice of type + + // This is required the core of InMoov is + // a FSM ProgramAB and some form of Python/Jython fsm = (FiniteStateMachine) startPeer("fsm"); - // a python processor is - // necessary for InMoov2 to properly - // function but this is not the place to start it - // it should be a peer definition too, it can be "python" - // it doesn't need to be i01.python to be a peer - // also should determine type Py4j or Python - // Runtime.start("python"); + // A python process is required - should be defined as a peer + // of Type Python or Py4j // just for comparing config with current "default" // debugging only From b90bd80573f4266edb384bf282505bed424eb0f9 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 21 Jan 2024 14:19:19 -0800 Subject: [PATCH 010/131] requested updates --- .../org/myrobotlab/programab/models/Oob.java | 12 +++-- .../myrobotlab/programab/models/Sraix.java | 16 +++++- .../myrobotlab/programab/models/Template.java | 45 ++++++---------- .../myrobotlab/programab/TemplateTest.java | 52 +++++++++++++++++++ 4 files changed, 91 insertions(+), 34 deletions(-) create mode 100644 src/test/java/org/myrobotlab/programab/TemplateTest.java diff --git a/src/main/java/org/myrobotlab/programab/models/Oob.java b/src/main/java/org/myrobotlab/programab/models/Oob.java index 833bab5a0f..5e0d99c4cf 100644 --- a/src/main/java/org/myrobotlab/programab/models/Oob.java +++ b/src/main/java/org/myrobotlab/programab/models/Oob.java @@ -4,11 +4,17 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +/** + * AIML 2.0 Oob Out Of Band xml defined with mrl - legacy and mrljson - json + * typed message + * + * @author GroG + * + */ public class Oob { - + public String mrljson; - + @JacksonXmlElementWrapper(useWrapping = false) public List<Mrl> mrl; } - diff --git a/src/main/java/org/myrobotlab/programab/models/Sraix.java b/src/main/java/org/myrobotlab/programab/models/Sraix.java index 99b0639cb6..1b130ad805 100644 --- a/src/main/java/org/myrobotlab/programab/models/Sraix.java +++ b/src/main/java/org/myrobotlab/programab/models/Sraix.java @@ -1,10 +1,22 @@ package org.myrobotlab.programab.models; -// FIXME add attributes and internal tags +/** + * Basic Sraix model, AIML 2.0 has more elements but these seemed like the most + * relevant and ar actually used. + * + * @author GroG + * + */ public class Sraix { + /** + * Search text when a query is sent to a remote system + */ public String search; + /** + * Oob is Out Of Band text which can be handled by internal processing + */ 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 index 91f8e5de51..d657973005 100644 --- a/src/main/java/org/myrobotlab/programab/models/Template.java +++ b/src/main/java/org/myrobotlab/programab/models/Template.java @@ -5,47 +5,34 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; -//@JacksonXmlRootElement(localName = "template") -//@JsonIgnoreProperties(ignoreUnknown = true) +/** + * General aiml template used for future parsing + * + * @author GroG + */ @JsonIgnoreProperties(ignoreUnknown = true) public class Template { - // @JacksonXmlElementWrapper(useWrapping = false) - + @JacksonXmlProperty(localName = "template") - @JacksonXmlText - public String text; - - -public Oob oob; - -// @JsonProperty("ignorable") -// public List<Oob> oob; -// -// public List<Oob> getOob() { -// return oob; -// } -// -// public void setOob(List<Oob> oob) { -// this.oob = oob; -// } - + public String text; + + public Oob oob; + public static void main(String[] args) { try { - - // String xml = "<template>XXX<oob><mrl><service>blah</service><method>method</method></mrl></oob></template>"; - // String xml = "<template>XXXX<oob><mrl><service>blah1</service><method>method1</method></mrl><mrl><service>blah2</service><method>method2</method></mrl></oob></template>"; + String xml = "<template>XXXX<oob><mrl><service>blah1</service><method>method1</method><param>p1</param><param>p2</param><param>p3</param></mrl><mrl><service>blah2</service><method>method2</method></mrl><mrljson>[\"method\":\"doIt\",\"data\":[\"p1\"]]</mrljson></oob></template>"; - + XmlMapper xmlMapper = new XmlMapper(); Template template = xmlMapper.readValue(xml, Template.class); - + System.out.println(template); - - } catch(Exception e) { + + } catch (Exception e) { e.printStackTrace(); } - } + } } diff --git a/src/test/java/org/myrobotlab/programab/TemplateTest.java b/src/test/java/org/myrobotlab/programab/TemplateTest.java new file mode 100644 index 0000000000..8d2beb245d --- /dev/null +++ b/src/test/java/org/myrobotlab/programab/TemplateTest.java @@ -0,0 +1,52 @@ +package org.myrobotlab.programab; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.programab.models.Mrl; +import org.myrobotlab.programab.models.Template; +import org.slf4j.Logger; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + +public class TemplateTest { + + public final static Logger log = LoggerFactory.getLogger(TemplateTest.class); + + @Test + public void testXmlParsing() { + try { + + String xml = "<template>XXXX<oob><mrl><service>blah1</service><method>method1</method><param>p1</param><param>p2</param><param>p3</param></mrl><mrl><service>blah2</service><method>method2</method></mrl><mrljson>[\"method\":\"doIt\",\"data\":[\"p1\"]]</mrljson></oob></template>"; + + XmlMapper xmlMapper = new XmlMapper(); + Template template = xmlMapper.readValue(xml, Template.class); + + assertNotNull(template); + assertEquals("XXXX", template.text); + + // Verify Oob parsing + assertNotNull(template.oob); + assertEquals(2, template.oob.mrl.size()); + + // Verify the first Mrl + Mrl mrl1 = template.oob.mrl.get(0); + assertEquals("blah1", mrl1.service); + assertEquals("method1", mrl1.method); + assertEquals(3, mrl1.params.size()); + + // Verify the second Mrl + Mrl mrl2 = template.oob.mrl.get(1); + assertEquals("blah2", mrl2.service); + assertEquals("method2", mrl2.method); + assertNull(mrl2.params); + + } catch (Exception e) { + fail("Exception occurred: " + e.getMessage()); + } + } +} From 8d4fdb992b9bc75e81993e48fb87f73f8644fa65 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 23 Jan 2024 22:34:44 -0800 Subject: [PATCH 011/131] init scripts turned off --- src/main/java/org/myrobotlab/service/Python.java | 2 +- .../java/org/myrobotlab/service/config/InMoov2Config.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Python.java b/src/main/java/org/myrobotlab/service/Python.java index c17a905f23..44f38f4bac 100644 --- a/src/main/java/org/myrobotlab/service/Python.java +++ b/src/main/java/org/myrobotlab/service/Python.java @@ -677,7 +677,7 @@ public void onStarted(String serviceName) { @Override public void onReleased(String serviceName) { - String registerScript = String.format("%s = None\n", CodecUtils.getSafeReferenceName(serviceName)); + String registerScript = String.format("%s = None\n", CodecUtils.getSafeReferenceName(CodecUtils.getShortName(serviceName))); exec(registerScript, false); } diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 1a4cbf51fe..0a4937210a 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -68,17 +68,17 @@ public class InMoov2Config extends ServiceConfig { */ public long heartbeatInterval = 3000; - public boolean loadAppsScripts = true; + public boolean loadAppsScripts = false; /** * loads all python gesture files in the gesture directory */ - public boolean loadGestures = true; + public boolean loadGestures = false; /** * executes all scripts in the init directory on startup */ - public boolean loadInitScripts = true; + public boolean loadInitScripts = false; /** * default to null - allow the OS to set it, unless explicilty set @@ -546,7 +546,7 @@ public Plan getDefault(Plan plan, String name) { // mouth_audioFile.listeners.add(new Listener("publishAudioStart", name)); // Needs upcoming pr - // fsm.listeners.add(new Listener("publishStateChange", name)); + fsm.listeners.add(new Listener("publishStateChange", name)); return plan; } From 9aacb0b81e6401e0ebfc0444f8994d76cd19a800 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Fri, 26 Jan 2024 08:35:09 -0800 Subject: [PATCH 012/131] publishStateChange vs onStateChange --- .../org/myrobotlab/service/HtmlFilter.java | 28 ++++----- .../java/org/myrobotlab/service/InMoov2.java | 27 ++++---- .../org/myrobotlab/service/ProgramAB.java | 62 +++++++++++++------ .../service/config/InMoov2Config.java | 5 +- .../app/service/js/FiniteStateMachineGui.js | 1 + 5 files changed, 72 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/HtmlFilter.java b/src/main/java/org/myrobotlab/service/HtmlFilter.java index ff0a4b4fb1..66134acd34 100644 --- a/src/main/java/org/myrobotlab/service/HtmlFilter.java +++ b/src/main/java/org/myrobotlab/service/HtmlFilter.java @@ -36,8 +36,7 @@ public HtmlFilter(String n, String id) { // helper function to add html tags public String addHtml(String text) { - HtmlFilterConfig c = (HtmlFilterConfig) config; - return c.preHtmlTag + text + c.postHtmlTag; + return config.preHtmlTag + text + config.postHtmlTag; } public void addTextListener(TextListener service) { @@ -45,18 +44,15 @@ public void addTextListener(TextListener service) { } public String getPostHtmlTag() { - HtmlFilterConfig c = (HtmlFilterConfig) config; - return c.postHtmlTag; + return config.postHtmlTag; } public String getPreHtmlTag() { - HtmlFilterConfig c = (HtmlFilterConfig) config; - return c.preHtmlTag; + return config.preHtmlTag; } public boolean isStripHtml() { - HtmlFilterConfig c = (HtmlFilterConfig) config; - return c.stripHtml; + return config.stripHtml; } @Override @@ -67,20 +63,20 @@ public void onText(String text) { @Override public String processText(String text) { - HtmlFilterConfig c = (HtmlFilterConfig) config; + invoke("publishRawText", text); String processedText = text; - if (c.stripHtml) { + if (config.stripHtml) { // clean text processedText = stripHtml(text); } else { processedText = addHtml(text); } - if (c.stripUrls) { + if (config.stripUrls) { processedText = stripUrls(processedText); } @@ -104,8 +100,8 @@ public String publishText(String text) { * - a string to append to the text */ public void setPostHtmlTag(String postHtmlTag) { - HtmlFilterConfig c = (HtmlFilterConfig) config; - c.postHtmlTag = postHtmlTag; + + config.postHtmlTag = postHtmlTag; } /** @@ -115,8 +111,7 @@ public void setPostHtmlTag(String postHtmlTag) { * - a string to prepend to the text. */ public void setPreHtmlTag(String preHtmlTag) { - HtmlFilterConfig c = (HtmlFilterConfig) config; - c.preHtmlTag = preHtmlTag; + config.preHtmlTag = preHtmlTag; } /** @@ -127,8 +122,7 @@ public void setPreHtmlTag(String preHtmlTag) { * - if true, all content between <and > will be removed. */ public void setStripHtml(boolean stripHtml) { - HtmlFilterConfig c = (HtmlFilterConfig) config; - c.stripHtml = stripHtml; + config.stripHtml = stripHtml; } // helper function to strip html tags. diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 719025ce8c..ab6bb08b94 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -34,6 +34,7 @@ import org.myrobotlab.opencv.OpenCVData; import org.myrobotlab.programab.PredicateEvent; import org.myrobotlab.programab.Response; +import org.myrobotlab.service.FiniteStateMachine.StateChange; import org.myrobotlab.service.Log.LogEntry; import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; import org.myrobotlab.service.config.InMoov2Config; @@ -1388,6 +1389,8 @@ public void onStartSpeaking(String utterance) { } /** + * publishStateChange + * * The integration between the FiniteStateMachine (fsm) and the InMoov2 * service and potentially other services (Python, ProgramAB) happens here. * @@ -1405,22 +1408,19 @@ public void onStartSpeaking(String utterance) { * * Depending on config: * - * * @param stateChange * @return */ - public FiniteStateMachine.StateChange onStateChange(FiniteStateMachine.StateChange stateChange) { - try { - log.info("onStateChange {}", stateChange); - - lastState = state; - state = stateChange.state; + public StateChange publishStateChange(StateChange stateChange) { + log.info("publishStateChange {}", stateChange); + + log.info("onStateChange {}", stateChange); - processMessage("onStateChange", stateChange); + lastState = state; + state = stateChange.state; - } catch (Exception e) { - error(e); - } + processMessage("onStateChange", stateChange); + return stateChange; } @@ -1583,7 +1583,8 @@ public Heartbeat publishHeartbeat() { } if (System.currentTimeMillis() > stateLastIdleTime + (config.stateIdleInterval * 1000)) { - fsm.fire("idle"); + // idle event to be handled with the processor + processMessage("onIdle"); stateLastIdleTime = System.currentTimeMillis(); } @@ -1628,7 +1629,7 @@ public Heartbeat publishHeartbeat() { processMessage("onHeartbeat", heartbeat); return heartbeat; } - + /** * A more extensible interface point than publishEvent FIXME - create * interface for this diff --git a/src/main/java/org/myrobotlab/service/ProgramAB.java b/src/main/java/org/myrobotlab/service/ProgramAB.java index 332d546df9..804115b67e 100644 --- a/src/main/java/org/myrobotlab/service/ProgramAB.java +++ b/src/main/java/org/myrobotlab/service/ProgramAB.java @@ -97,7 +97,7 @@ public class ProgramAB extends Service<ProgramABConfig> boolean peerSearch = true; transient SimpleLogPublisher logPublisher = null; - + final transient private OobProcessor oobProcessor; /** @@ -762,7 +762,7 @@ 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 * @@ -1103,7 +1103,7 @@ public ProgramABConfig apply(ProgramABConfig c) { addBotPath(botPath); } } - + if (c.botDir == null) { c.botDir = getResourceDir(); } @@ -1116,19 +1116,40 @@ public ProgramABConfig apply(ProgramABConfig c) { if (c.currentUserName != null) { setCurrentUserName(c.currentUserName); } - + if (c.currentBotName != null) { setCurrentBotName(c.currentBotName); - } - + } + if (c.startTopic != null) { - setTopic(c.startTopic); + setTopic(c.startTopic); } - return c; } + /** + * Set the current locale for this service. In ProgramAB's case if a bot + * matches the local then set the bot + * + */ + @Override + public void setLocale(String code) { + if (code == null) { + error("locale cannot be null"); + return; + } + locale = new Locale(code); + log.info("{} new locale is {}", getName(), code); + + for (String bot : bots.keySet()) { + if (code.equals(bot)) { + setCurrentBotName(bot); + } + } + broadcastState(); + } + public static void main(String args[]) { try { LoggingFactory.init("INFO"); @@ -1306,7 +1327,7 @@ public void sleep() { @Override public void onUtterance(Utterance utterance) throws Exception { - + log.info("Utterance Received " + utterance); boolean talkToBots = false; @@ -1371,17 +1392,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<String, Object> cfgMap = yaml.load(yml); - + for (Map.Entry<String, Object> entry : cfgMap.entrySet()) { if (entry.getValue() == null) { setPredicate("cfg_" + entry.getKey(), null); @@ -1389,7 +1411,7 @@ public void onConfig(ServiceConfig cfg) { setPredicate("cfg_" + entry.getKey(), entry.getValue().toString()); } } - + invoke("getPredicates"); } @@ -1402,19 +1424,19 @@ public TopicChange publishTopic(TopicChange topicChange) { return topicChange; } - public String getTopic() { + public String getTopic() { return getPredicate(getCurrentUserName(), "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) { + + public void setTopic(String topic) { setPredicate(getCurrentUserName(), "topic", topic); } diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 0a4937210a..1ec4694f0e 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -522,6 +522,7 @@ public Plan getDefault(Plan plan, String name) { // listeners.add(new Listener("publishProcessMessage", // getPeerName("python"), "onPythonMessage")); listeners.add(new Listener("publishProcessMessage", "python", "onPythonMessage")); + listeners.add(new Listener("publishPython", "python")); // InMoov2 --to--> InMoov2 @@ -535,6 +536,8 @@ public Plan getDefault(Plan plan, String name) { // service --to--> InMoov2 AudioFileConfig mouth_audioFile = (AudioFileConfig) plan.get(getPeerName("mouth.audioFile")); mouth_audioFile.listeners.add(new Listener("publishPeak", name)); + + htmlFilter.listeners.add(new Listener("publishText", name)); OakDConfig oakd = (OakDConfig) plan.get(getPeerName("oakd")); oakd.listeners.add(new Listener("publishClassification", name)); @@ -546,7 +549,7 @@ public Plan getDefault(Plan plan, String name) { // mouth_audioFile.listeners.add(new Listener("publishAudioStart", name)); // Needs upcoming pr - fsm.listeners.add(new Listener("publishStateChange", name)); + fsm.listeners.add(new Listener("publishStateChange", name, "publishStateChange")); return plan; } diff --git a/src/main/resources/resource/WebGui/app/service/js/FiniteStateMachineGui.js b/src/main/resources/resource/WebGui/app/service/js/FiniteStateMachineGui.js index 42c559c999..d08f8f2fc8 100644 --- a/src/main/resources/resource/WebGui/app/service/js/FiniteStateMachineGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/FiniteStateMachineGui.js @@ -51,6 +51,7 @@ angular.module('mrlapp.service.FiniteStateMachineGui', []).controller('FiniteSta break case 'onStateChange': $scope.current = data.current + $scope.service.history.push(data) $scope.$apply() break default: From 4c9e5e0fd976f4d349bdd1b7d5f3338e20a79532 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Fri, 26 Jan 2024 15:04:48 -0800 Subject: [PATCH 013/131] execScript() --- .../java/org/myrobotlab/service/InMoov2.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index ab6bb08b94..146b72e68d 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -468,8 +468,7 @@ synchronized public void boot() { */ // load the InMoov2.py and publish it for Python/Jython or Py4j to consume - String script = getResourceAsString("InMoov2.py"); - invoke("publishPython", script); + execScript(); // TODO - MAKE BOOT REPORT !!!! deliver it on a heartbeat runtime.invoke("publishConfigList"); @@ -740,6 +739,14 @@ public String execGesture(String gesture) { } return python.evalAndWait(gesture); } + + /** + * Reload the InMoov2.py script + */ + public void execScript() { + execScript("InMoov2.py"); + } + /** * FIXME - I think there was lots of confusion of executing resources or just @@ -754,15 +761,9 @@ public String execGesture(String gesture) { * execute a resource script * @return success or failure */ - public boolean execScript(String someScriptName) { - try { - Python p = (Python) Runtime.start("python", "Python"); + public void execScript(String someScriptName) { String script = getResourceAsString(someScriptName); - return p.exec(script, true); - } catch (Exception e) { - error("unable to execute script %s", someScriptName); - return false; - } + invoke("publishPython", script); } public void finishedGesture() { @@ -1801,6 +1802,7 @@ public void releasePeer(String peerKey) { invoke("publishEvent", "STOPPED " + peerKey); } } + @Override public void releaseService() { From 5bbdfe0e997a73b89ecb448e90820149da64baee Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 28 Jan 2024 09:54:22 -0800 Subject: [PATCH 014/131] config utils --- .../org/myrobotlab/config/ConfigUtils.java | 71 +++++++++++++++++++ .../org/myrobotlab/framework/Service.java | 16 ++--- src/main/java/org/myrobotlab/io/FileIO.java | 36 +++------- .../java/org/myrobotlab/service/InMoov2.java | 14 +++- .../java/org/myrobotlab/service/Runtime.java | 14 +++- .../service/config/InMoov2Config.java | 2 - .../java/org/myrobotlab/io/FileIOTest.java | 5 -- 7 files changed, 113 insertions(+), 45 deletions(-) create mode 100644 src/main/java/org/myrobotlab/config/ConfigUtils.java diff --git a/src/main/java/org/myrobotlab/config/ConfigUtils.java b/src/main/java/org/myrobotlab/config/ConfigUtils.java new file mode 100644 index 0000000000..35c8a776a8 --- /dev/null +++ b/src/main/java/org/myrobotlab/config/ConfigUtils.java @@ -0,0 +1,71 @@ +package org.myrobotlab.config; + +import java.io.File; +import java.io.IOException; + +import org.myrobotlab.codec.CodecUtils; +import org.myrobotlab.framework.StartYml; +import org.myrobotlab.io.FileIO; +import org.myrobotlab.service.Runtime; +import org.myrobotlab.service.config.RuntimeConfig; + +public class ConfigUtils { + + /** + * This gets the current resource root without starting a Runtime instance if + * not already started. The resource root depends on config, if Runtime is + * running the logic and current config name is already available. If Runtime + * is not running, we need to go through a series of steps to deterime where + * the resource root is configured. + * + * @return + */ + public static String getResourceRoot() { + + String resource = "resource"; + + // check if runtime is running + if (!Runtime.isAvailable()) { + // check for start.yml + + File checkStartYml = new File("start.yml"); + StartYml startYml = new StartYml(); + if (checkStartYml.exists()) { + String yml; + try { + yml = FileIO.toString("start.yml"); + startYml = CodecUtils.fromYaml(yml, StartYml.class); + + // see if autostart is on with a config + if (startYml.enable) { + // use that config to find runtime.yml + + File runtimeYml = new File(Runtime.ROOT_CONFIG_DIR + File.separator + startYml.config + File.separator + "runtime.yml"); + if (runtimeYml.exists()) { + // parse that file look for resource: entry in file + RuntimeConfig config = (RuntimeConfig) CodecUtils.readServiceConfig(runtimeYml.getAbsolutePath()); + resource = config.resource; + } + + } else { + // start.yml enable = false / so we'll use default config + File runtimeYml = new File(Runtime.ROOT_CONFIG_DIR + File.separator + "default" + File.separator + "runtime.yml"); + if (runtimeYml.exists()) { + // parse that file look for resource: entry in file + RuntimeConfig config = (RuntimeConfig) CodecUtils.readServiceConfig(runtimeYml.getAbsolutePath()); + resource = config.resource; + } + } + + } catch (IOException e) { + // problem getting or parsing + // going to assume default "resource" + } + } // no startYml + return resource; + } else { + // Runtime is available - ask it + return Runtime.getInstance().getConfig().resource; + } + } +} diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java index 8f0d4ad2bf..95e23a5616 100644 --- a/src/main/java/org/myrobotlab/framework/Service.java +++ b/src/main/java/org/myrobotlab/framework/Service.java @@ -57,6 +57,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import org.myrobotlab.codec.CodecUtils; +import org.myrobotlab.config.ConfigUtils; import org.myrobotlab.framework.interfaces.Attachable; import org.myrobotlab.framework.interfaces.Broadcaster; import org.myrobotlab.framework.interfaces.ConfigurableService; @@ -474,22 +475,15 @@ static public String getResourceDir(Class<?> clazz, String additionalPath) { * then it needs an instance of Runtime which is not available. * */ - @Deprecated /* this should not be static - remove it */ static public String getResourceDir(String serviceType, String additionalPath) { // setting resource directory - String resourceDir = null; + String resource = ConfigUtils.getResourceRoot() + fs + serviceType; - // stupid solution to get past static problem - if (!"Runtime".equals(serviceType)) { - resourceDir = Runtime.getInstance().getConfig().resource + fs + serviceType; - } else { - resourceDir = "resource"; - } if (additionalPath != null) { - resourceDir = FileIO.gluePaths(resourceDir, additionalPath); + resource = FileIO.gluePaths(resource, additionalPath); } - return resourceDir; + return resource; } /** @@ -516,7 +510,7 @@ public String getResourcePath(String additionalPath) { */ static public String getResourceRoot() { - return Runtime.getInstance().getConfig().resource; + return ConfigUtils.getResourceRoot();//Runtime.getInstance().getConfig().resource; } /** diff --git a/src/main/java/org/myrobotlab/io/FileIO.java b/src/main/java/org/myrobotlab/io/FileIO.java index 2cdad66af2..8b3fe189c4 100644 --- a/src/main/java/org/myrobotlab/io/FileIO.java +++ b/src/main/java/org/myrobotlab/io/FileIO.java @@ -58,8 +58,8 @@ import java.util.zip.ZipException; import org.apache.commons.io.Charsets; +import org.myrobotlab.config.ConfigUtils; import org.myrobotlab.framework.Platform; -import org.myrobotlab.framework.Service; import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; @@ -854,8 +854,6 @@ public static void main(String[] args) throws ZipException, IOException { f = new File(uri); log.info("{} exists {}", uri, f.exists()); - log.info("isJar : {}", isJar()); - } catch (Exception e) { Logging.logError(e); } @@ -870,33 +868,22 @@ public static void main(String[] args) throws ZipException, IOException { * Python/examples/someFile.py * @return byte array */ - @Deprecated /* user Service.getResource(src) */ static public final byte[] resourceToByteArray(String src) { - // this path assumes in a jar ? - // String filename = "/resource/" + src; - log.info("looking for Resource {}", src); + log.info("looking for resource {}", src); InputStream isr = null; - if (isJar()) { - // this path assumes in a jar ? ensure it's forward slashes - String filename = "/resource/" + src.replace("\\", "/"); - isr = FileIO.class.getResourceAsStream(filename); - } else { - String localFilename = Service.getResourceRoot() + File.separator + src; - try { - isr = new FileInputStream(localFilename); - } catch (Exception e) { - Logging.logError(e); - log.error("File not found. {}", localFilename, e); - return null; - } + String resource = ConfigUtils.getResourceRoot(); + String localFilename = resource + File.separator + src; + try { + isr = new FileInputStream(localFilename); + } catch (Exception e) { + Logging.logError(e); + log.error("file not found. {}", localFilename, e); + return null; } + byte[] data = null; try { - if (isr == null) { - log.error("can not find resource [{}]", src); - return null; - } data = toByteArray(isr); } finally { try { @@ -918,7 +905,6 @@ static public final byte[] resourceToByteArray(String src) { * Python/examples/someFile.py * @return string */ - @Deprecated /* use Service.getResourceAsString(src) */ static public final String resourceToString(String src) { byte[] bytes = resourceToByteArray(src); if (bytes == null) { diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 146b72e68d..0248333656 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -890,13 +890,18 @@ public Object getPredicate(String key) { return null; } + /** + * getResponse from ProgramAB + * @param text + * @return + */ 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.warn("chatbot not ready"); } return null; } @@ -2044,6 +2049,13 @@ public boolean setSpeechType(String speechType) { // updatePeerType("mouth" /* getPeerName("mouth") */, speechType); // return speechType; } + + public void setTopic(String topic) { + ProgramAB chatBot = (ProgramAB)getPeer("chatBot"); + if (chatBot != null) { + chatBot.setTopic(topic); + } + } public void setTorsoSpeed(Double topStom, Double midStom, Double lowStom) { sendToPeer("torso", "setSpeed", topStom, midStom, lowStom); diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 2b4b40e59f..a989e7046a 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -229,7 +229,7 @@ public class Runtime extends Service<RuntimeConfig> implements MessageListener, /** * default parent path of configPath static ! */ - final static protected String ROOT_CONFIG_DIR = DATA_DIR + fs + "config"; + public final static String ROOT_CONFIG_DIR = DATA_DIR + fs + "config"; /** * number of services created by this runtime @@ -926,6 +926,10 @@ public static Runtime getInstance() { Runtime.startConfig(options.config); } else if (startYml != null && startYml.config != null && startYml.enable) { Runtime.startConfig(startYml.config); + } else { + RuntimeConfig rtConfig = runtime.readServiceConfig(runtime.getConfigName(), "runtime", new StaticType<>() { + }); + runtime.apply(rtConfig); } } catch (Exception e) { log.info("runtime will not be loading config"); @@ -5408,4 +5412,12 @@ public static void removeConfig(String configName) { } } + /** + * Method used to determine is runtime is running without starting it + * @return true if available + */ + static public boolean isAvailable() { + return runtime != null && runtime.isRunning(); + } + } diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 1ec4694f0e..13eefd7b56 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -266,8 +266,6 @@ public Plan getDefault(Plan plan, String name) { } } - chatBot.currentUserName = "human"; - chatBot.listeners.add(new Listener("publishText", name + ".htmlFilter", "onText")); Gpt3Config gpt3 = (Gpt3Config) plan.get(getPeerName("gpt3")); diff --git a/src/test/java/org/myrobotlab/io/FileIOTest.java b/src/test/java/org/myrobotlab/io/FileIOTest.java index a8700219a8..c554835175 100644 --- a/src/test/java/org/myrobotlab/io/FileIOTest.java +++ b/src/test/java/org/myrobotlab/io/FileIOTest.java @@ -134,11 +134,6 @@ public void testGluePaths() { assertEquals("/abc/def/", ret); } - @Test - public void testIsJar() { - assertFalse(FileIO.isJar()); - } - @Test public void testGetFileListString() throws IOException { String dir = FileIO.gluePaths(tempDir, "testGetFileListString"); From 565f9ddf6288e59c1b742ba5f58cb8030f408eea Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 30 Jan 2024 05:18:11 -0800 Subject: [PATCH 015/131] set chatBot reference on startService added setup to fsm --- .../java/org/myrobotlab/service/InMoov2.java | 95 ++++++++++--------- .../service/config/InMoov2Config.java | 8 +- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 0248333656..3db43b416e 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -739,7 +739,7 @@ public String execGesture(String gesture) { } return python.evalAndWait(gesture); } - + /** * Reload the InMoov2.py script */ @@ -747,7 +747,6 @@ public void execScript() { execScript("InMoov2.py"); } - /** * 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 @@ -762,8 +761,8 @@ public void execScript() { * @return success or failure */ public void execScript(String someScriptName) { - String script = getResourceAsString(someScriptName); - invoke("publishPython", script); + String script = getResourceAsString(someScriptName); + invoke("publishPython", script); } public void finishedGesture() { @@ -880,30 +879,23 @@ public OpenCV getOpenCV() { return opencv; } - public Object getPredicate(String key) { - ProgramAB chatBot = (ProgramAB) getPeer("chatBot"); - if (chatBot != null) { - return chatBot.getPredicate(key); - } else { - error("no chatBot available"); - } - return null; + public String getPredicate(String key) { + return getPredicate(chatBot.getConfig().currentUserName, key); + } + + public String getPredicate(String user, String key) { + return chatBot.getPredicate(user, key); } /** * getResponse from ProgramAB + * * @param text * @return */ public Response getResponse(String text) { - ProgramAB chatBot = (ProgramAB) getPeer("chatBot"); - if (chatBot != null) { - Response response = chatBot.getResponse(text); - return response; - } else { - log.warn("chatbot not ready"); - } - return null; + Response response = chatBot.getResponse(text); + return response; } public InMoov2Arm getRightArm() { @@ -965,6 +957,22 @@ public void halfSpeed() { sendToPeer("torso", "setSpeed", 20.0, 20.0, 20.0); } + /** + * If there have been any errors + * + * @return + */ + public boolean hasErrors() { + return errors.size() > 0; + } + + /** + * clear all errors + */ + public void clearErrors() { + errors.clear(); + } + public boolean isCameraOn() { if (opencv != null) { if (opencv.isCapturing()) { @@ -1306,12 +1314,9 @@ public void onPeak(double volume) { 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"); - } + String botState = chatBot.getPredicate("botState"); + if ("sleeping".equals(botState)) { + invoke("publishEvent", "WAKE"); } } @@ -1419,14 +1424,14 @@ public void onStartSpeaking(String utterance) { */ public StateChange publishStateChange(StateChange stateChange) { log.info("publishStateChange {}", stateChange); - + log.info("onStateChange {}", stateChange); lastState = state; state = stateChange.state; processMessage("onStateChange", stateChange); - + return stateChange; } @@ -1561,6 +1566,9 @@ public String publishFlash(String flashName) { } /** + * 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 @@ -1635,7 +1643,7 @@ public Heartbeat publishHeartbeat() { processMessage("onHeartbeat", heartbeat); return heartbeat; } - + /** * A more extensible interface point than publishEvent FIXME - create * interface for this @@ -1807,7 +1815,6 @@ public void releasePeer(String peerKey) { invoke("publishEvent", "STOPPED " + peerKey); } } - @Override public void releaseService() { @@ -1984,15 +1991,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; } @@ -2049,12 +2051,9 @@ public boolean setSpeechType(String speechType) { // updatePeerType("mouth" /* getPeerName("mouth") */, speechType); // return speechType; } - + public void setTopic(String topic) { - ProgramAB chatBot = (ProgramAB)getPeer("chatBot"); - if (chatBot != null) { - chatBot.setTopic(topic); - } + chatBot.setTopic(topic); } public void setTorsoSpeed(Double topStom, Double midStom, Double lowStom) { @@ -2159,7 +2158,6 @@ public void startBrain() { public ProgramAB startChatBot() { try { - chatBot = (ProgramAB) startPeer("chatBot"); if (locale != null) { chatBot.setCurrentBotName(locale.getTag()); @@ -2309,6 +2307,15 @@ public void startService() { // a FSM ProgramAB and some form of Python/Jython fsm = (FiniteStateMachine) startPeer("fsm"); + // Chatbot is a required part of InMoov2 + chatBot = (ProgramAB) startPeer("chatBot"); + try { + chatBot.startSession(); + chatBot.setPredicate("robot", getName()); + } catch (IOException e) { + error(e); + } + // A python process is required - should be defined as a peer // of Type Python or Py4j diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 13eefd7b56..e567016cdc 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -362,16 +362,16 @@ public Plan getDefault(Plan plan, String name) { // exists ? fsm.current = "boot"; fsm.transitions.add(new Transition("boot", "wake", "wake")); - fsm.transitions.add(new Transition("wake", "idle", "idle")); - fsm.transitions.add(new Transition("first_init", "idle", "idle")); + // fsm.transitions.add(new Transition("wake", "idle", "idle")); wake, setup, nor sleep should be affected by idle + fsm.transitions.add(new Transition("setup", "setup_done", "idle")); 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("sleep", "power_down", "power_down")); fsm.transitions.add(new Transition("idle", "power_down", "power_down")); - fsm.transitions.add(new Transition("wake", "first_init", "first_init")); - fsm.transitions.add(new Transition("idle", "first_init", "first_init")); + fsm.transitions.add(new Transition("wake", "setup", "setup")); + fsm.transitions.add(new Transition("idle", "setup", "setup")); // power_down to shutdown // fsm.transitions.add(new Transition("systemCheck", "systemCheckFinished", // "awake")); From d2510bc16a2b4e995e4258a39c4d4a7a7217695b Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Thu, 1 Feb 2024 08:28:33 -0800 Subject: [PATCH 016/131] working processor action in servomixer --- .../org/myrobotlab/kinematics/Action.java | 7 + .../java/org/myrobotlab/service/Py4j.java | 7 +- .../java/org/myrobotlab/service/Python.java | 3 +- .../org/myrobotlab/service/ServoMixer.java | 681 +++++++++--------- .../service/config/ServoMixerConfig.java | 5 + .../service/interfaces/Processor.java | 18 + .../WebGui/app/service/js/ServoMixerGui.js | 8 + .../app/service/views/ServoMixerGui.html | 8 +- 8 files changed, 392 insertions(+), 345 deletions(-) create mode 100644 src/main/java/org/myrobotlab/service/interfaces/Processor.java diff --git a/src/main/java/org/myrobotlab/kinematics/Action.java b/src/main/java/org/myrobotlab/kinematics/Action.java index afb159ffcb..6eb4108fda 100644 --- a/src/main/java/org/myrobotlab/kinematics/Action.java +++ b/src/main/java/org/myrobotlab/kinematics/Action.java @@ -57,4 +57,11 @@ public static Action createGestureToAction(String gestureName) { action.value = gestureName; return action; } + + public static Action createProcessingAction(String methodName) { + Action action = new Action(); + action.type = "process"; + action.value = methodName; + return action; + } } diff --git a/src/main/java/org/myrobotlab/service/Py4j.java b/src/main/java/org/myrobotlab/service/Py4j.java index 16b335f455..d5530fc50d 100644 --- a/src/main/java/org/myrobotlab/service/Py4j.java +++ b/src/main/java/org/myrobotlab/service/Py4j.java @@ -25,6 +25,7 @@ import org.myrobotlab.service.data.Script; import org.myrobotlab.service.interfaces.Executor; import org.myrobotlab.service.interfaces.Gateway; +import org.myrobotlab.service.interfaces.Processor; import org.slf4j.Logger; import py4j.GatewayServer; @@ -54,7 +55,7 @@ * * @author GroG */ -public class Py4j extends Service<Py4jConfig> implements GatewayServerListener, Gateway { +public class Py4j extends Service<Py4jConfig> implements GatewayServerListener, Gateway, Processor { /** * POJO class to tie all the data elements of a external python process @@ -235,17 +236,19 @@ public void connectionStopped(Py4JServerConnection gatewayConnection) { * * @param code The Python code to execute in the interpreter. */ - public void exec(String code) { + public boolean exec(String code) { log.info(String.format("exec %s", code)); try { if (handler != null) { handler.exec(code); + return true; } else { error("handler is null"); } } catch (Exception e) { error(e); } + return false; } private String getClientKey(Py4JServerConnection gatewayConnection) { diff --git a/src/main/java/org/myrobotlab/service/Python.java b/src/main/java/org/myrobotlab/service/Python.java index 90502146f2..77fd9fd529 100644 --- a/src/main/java/org/myrobotlab/service/Python.java +++ b/src/main/java/org/myrobotlab/service/Python.java @@ -25,6 +25,7 @@ import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.config.PythonConfig; import org.myrobotlab.service.data.Script; +import org.myrobotlab.service.interfaces.Processor; import org.myrobotlab.service.interfaces.ServiceLifeCycleListener; import org.myrobotlab.service.meta.abstracts.MetaData; import org.python.core.Py; @@ -49,7 +50,7 @@ * @author GroG * */ -public class Python extends Service<PythonConfig> implements ServiceLifeCycleListener, MessageListener { +public class Python extends Service<PythonConfig> implements ServiceLifeCycleListener, MessageListener, Processor { /** * this thread handles all callbacks to Python process all input and sets msg diff --git a/src/main/java/org/myrobotlab/service/ServoMixer.java b/src/main/java/org/myrobotlab/service/ServoMixer.java index 644557185b..5e20888104 100755 --- a/src/main/java/org/myrobotlab/service/ServoMixer.java +++ b/src/main/java/org/myrobotlab/service/ServoMixer.java @@ -15,7 +15,6 @@ import java.util.concurrent.Executors; import org.myrobotlab.codec.CodecUtils; -import org.myrobotlab.framework.Message; import org.myrobotlab.framework.Registration; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.interfaces.Attachable; @@ -28,6 +27,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.config.ServoMixerConfig; +import org.myrobotlab.service.interfaces.Processor; import org.myrobotlab.service.interfaces.SelectListener; import org.myrobotlab.service.interfaces.ServiceLifeCycleListener; import org.myrobotlab.service.interfaces.ServoControl; @@ -44,31 +44,6 @@ */ public class ServoMixer extends Service<ServoMixerConfig> implements ServiceLifeCycleListener, SelectListener { - public class PlayingGesture { - public String name; - public Gesture gesture; - public int startIndex = 0; - - public PlayingGesture(String name, Gesture gesture) { - this(name, gesture, 0); - } - - public PlayingGesture(String name, Gesture gesture, int index) { - this.name = name; - this.gesture = gesture; - this.startIndex = index; - } - - public String toString() { - int actionCnt = 0; - if (gesture != null && gesture.actions != null) { - actionCnt = gesture.actions.size(); - } - return String.format("name:%s actionCnt:%d index:%d", name, actionCnt, startIndex); - } - - } - /** * The Player plays a requested gesture, which is a sequence of Poses. Poses * can be positions, delays, or speech. It publishes when it starts a gesture @@ -78,10 +53,10 @@ public String toString() { * */ public class Player implements Runnable { - protected String playingGesture = null; - protected boolean running = false; transient private ExecutorService executor; + protected String playingGesture = null; transient Deque<PlayingGesture> playStack = new ArrayDeque<>(); + protected boolean running = false; // FIXME - add optional start index to start playing at a midpoint private void play() { @@ -188,8 +163,17 @@ public void processAction(PlayingGesture current, int i) { case "speak": speak((Map) action.value); break; - case "msg": - invoke("publishProcessMessage", (Message) action.value); + case "process": + + if (config.processor != null) { + Processor processor = (Processor) Runtime.getService(config.processor); + String code = (String) action.value; + if (!processor.exec(code)) { + error("could not execute %s", code); + } + } else { + error("no processor"); + } break; default: { error("do not know how to handle gesture part of type %s", action.type); @@ -232,16 +216,102 @@ public void stop() { } } + public class PlayingGesture { + public Gesture gesture; + public String name; + public int startIndex = 0; + + public PlayingGesture(String name, Gesture gesture) { + this(name, gesture, 0); + } + + public PlayingGesture(String name, Gesture gesture, int index) { + this.name = name; + this.gesture = gesture; + this.startIndex = index; + } + + public String toString() { + int actionCnt = 0; + if (gesture != null && gesture.actions != null) { + actionCnt = gesture.actions.size(); + } + return String.format("name:%s actionCnt:%d index:%d", name, actionCnt, startIndex); + } + + } + public final static Logger log = LoggerFactory.getLogger(InMoov2.class); private static final long serialVersionUID = 1L; + public static void main(String[] args) throws Exception { + + try { + Runtime.main(new String[] { "--id", "admin" }); + LoggingFactory.init("INFO"); + + Runtime.start("i01.head.rothead", "Servo"); + Runtime.start("i01.head.neck", "Servo"); + WebGui webgui = (WebGui) Runtime.create("webgui", "WebGui"); + webgui.autoStartBrowser(false); + webgui.startService(); + Python python = (Python) Runtime.start("python", "Python"); + ServoMixer mixer = (ServoMixer) Runtime.start("mixer", "ServoMixer"); + // mixer.playGesture(""); + + boolean done = true; + if (done) { + return; + } + + mixer.addNewGestureFile("test"); + Gesture gesture = mixer.getGesture("test"); + String gestureName = "aaa"; + String servoName = "i01.head.rothead"; + Double position = 90.0; + Double speed = null; + + Map<String, Map<String, Object>> moves = new TreeMap<>(); + + Map<String, Object> poseMove1 = new TreeMap<>(); + poseMove1.put("position", 90.0); + + Map<String, Object> poseMove2 = new TreeMap<>(); + poseMove2.put("position", 90); + poseMove2.put("speed", 35.0); + + moves.put("i01.head.rothead", poseMove1); + moves.put("i01.head.neck", poseMove2); + + mixer.openGesture(gestureName); + mixer.addMoveToAction(moves); // autofill delay from keyframe ? + mixer.saveGesture(); + + mixer.addNewGestureFile("test2"); + + // mixer.save(gestureName); + mixer.addGestureToAction("test"); + + mixer.saveGesture(); + + // mixer.setPose("test", ) + } catch (Exception e) { + log.error("main threw", e); + } + } + + // TODO selected servos + /** * Set of servo names kept in sync with current registry */ protected Set<String> allServos = new TreeSet<>(); - // TODO selected servos + /** + * gesture name of the currentGesture + */ + protected String currentEditGestureName = null; /** * current gesture being edited @@ -253,13 +323,67 @@ public void stop() { */ final protected transient Player player = new Player(); + public ServoMixer(String n, String id) { + super(n, id); + } + + public void addAction(Action action, Integer index) { + if (currentGesture == null) { + error("current gesture not set"); + return; + } + if (index != null) { + if (currentGesture.actions.size() == 0) { + currentGesture.actions.add(action); + } else { + currentGesture.actions.add(index, action); + } + } else { + currentGesture.actions.add(action); + } + } + + public void addGestureToAction(String gestureName) { + addGestureToAction(gestureName, null); + }; + + public void addGestureToAction(String gestureName, Integer index) { + addAction(Action.createGestureToAction(gestureName), index); + } + + public void addMoveToAction(List<String> servos) { + addMoveToAction(servos, null); + } + /** - * gesture name of the currentGesture + * list of servos to add to an action - they're current speed and input + * positions will be added at index + * + * @param servos + * @param index */ - protected String currentEditGestureName = null; + public void addMoveToAction(List<String> servos, Integer index) { + Map<String, Map<String, Object>> moves = new TreeMap<>(); + for (String servoName : servos) { + ServoControl sc = (ServoControl) Runtime.getService(servoName); + Map<String, Object> posAndSpeed = new TreeMap<>(); + if (sc == null) { + error("%s not a valid service name", servoName); + continue; + } + posAndSpeed.put("position", sc.getCurrentInputPos()); + posAndSpeed.put("speed", sc.getSpeed()); + moves.put(servoName, posAndSpeed); + } + addAction(Action.createMoveToAction(moves), index); + } - public ServoMixer(String n, String id) { - super(n, id); + public void addMoveToAction(Map<String, Map<String, Object>> moves) { + addMoveToAction(moves, null); + } + + public void addMoveToAction(Map<String, Map<String, Object>> moves, Integer index) { + addAction(Action.createMoveToAction(moves), index); } /** @@ -294,10 +418,28 @@ public String addNewGestureFile(String name) { return filename; } - public void saveGesture(String name) { - // FIXME - warn if overwrite - // FIXME - don't warn if opened - saveGesture(name, currentGesture); + public void addProcessingAction(String methodName) { + addProcessingAction(methodName, null); + } + + public void addProcessingAction(String methodName, Integer index) { + addAction(Action.createProcessingAction(methodName), index); + } + + public void addSleepAction(double sleep) { + addSleepAction(sleep, null); + } + + public void addSleepAction(double sleep, Integer index) { + addAction(Action.createSleepAction(sleep), index); + } + + public void addSpeakAction(Map<String, Object> speechCommand) { + addSpeakAction(speechCommand, null); + } + + public void addSpeakAction(Map<String, Object> speechCommand, Integer index) { + addAction(Action.createSpeakAction(speechCommand), index); } /** @@ -308,24 +450,14 @@ public void attach(Attachable attachable) { if (attachable instanceof Servo) { attachServo((Servo) attachable); } - }; + } /** * attach(String) should always have the implementation */ @Override public void attach(String name) { - allServos.add(name); - // refresh subscribers - invoke("getServos"); - } - - /** - * detach(String) should always have the implementation - */ - @Override - public void detach(String name) { - allServos.remove(name); + allServos.add(name); // refresh subscribers invoke("getServos"); } @@ -341,6 +473,20 @@ public void attachServo(Servo servo) { attach(servo.getName()); } + /** + * detach(String) should always have the implementation + */ + @Override + public void detach(String name) { + allServos.remove(name); + // refresh subscribers + invoke("getServos"); + } + + public Gesture getGesture() { + return currentGesture; + } + public Gesture getGesture(String name) { Gesture gesture = null; @@ -404,9 +550,10 @@ public List<String> getGestureFiles() { public String getPosesDirectory() { return config.posesDir; } - + /** * get a refreshed ordered list of servos + * * @return */ public Set<String> getServos() { @@ -431,34 +578,61 @@ public List<ServoControl> listAllServos() { return servos; } - public void step(int index) { - step(currentEditGestureName, index); - } - - public void step(String gestureName, int index) { - - if (gestureName == null) { - error("gesture name cannot be null"); + public void moveActionDown(int index) { + if (currentGesture == null) { + error("cannot move: gesture not set"); return; } - if (!gestureName.equals(currentEditGestureName)) { - // load gesture - getGesture(gestureName); + List<Action> list = currentGesture.actions; + + if (index >= 0 && index < list.size() - 1) { + Action action = list.remove(index); + list.add(index + 1, action); + } else { + error("index out of range or at the end of the list."); } + invoke("getGesture"); + } + public void moveActionUp(int index) { if (currentGesture == null) { - error("gesture cannot be nulle"); + error("cannot move gesture not set"); return; } - player.processAction(new PlayingGesture(gestureName, currentGesture), index); - // step to next action - index++; - if (index < currentGesture.actions.size()) { - Action action = currentGesture.actions.get(index); - invoke("publishPlayingAction", action); - invoke("publishPlayingActionIndex", index); + List<Action> list = currentGesture.actions; + + if (index > 0 && index < list.size()) { + Action action = list.remove(index); + list.add(index - 1, action); + } else { + error("index out of range or at the beginning of the list."); + } + invoke("getGesture"); + } + + private void moveTo(String servoName, Map<String, Object> move) { + ServoControl servo = (ServoControl) Runtime.getService(servoName); + if (servo == null) { + warn("servo (%s) cannot move to pose because it does not exist", servoName); + return; + } + + Object speed = move.get("speed"); + if (speed != null) { + if (speed instanceof Integer) { + servo.setSpeed((Integer) speed); + } else if (speed instanceof Double) { + servo.setSpeed((Double) speed); + } + } + + Object position = move.get("position"); + if (position instanceof Integer) { + servo.moveTo((Integer) position); + } else if (position instanceof Double) { + servo.moveTo((Double) position); } } @@ -534,6 +708,24 @@ public void onStarted(String name) { public void onStopped(String name) { } + public Gesture openGesture(String name) { + if (currentGesture != null) { + warn("replacing current gesture"); + // prompt user return null etc. + } + + Gesture gesture = getGesture(name); + if (gesture == null) { + // gesture not found make new + gesture = new Gesture(); + } + + currentEditGestureName = name; + currentGesture = gesture; + + return currentGesture; + } + public void playGesture(String name) { // Gesture gesture = (Gesture) broadcast("getGesture", name); invoke("getGesture", name); @@ -585,17 +777,6 @@ public String publishPlayingPose(String name) { return name; } - /** - * Processing publishing point, where everything InMoov2 wants to be processed - * is turned into a message and published. - * - * @param msg - * @return - */ - public Message publishProcessMessage(Message msg) { - return msg; - } - public String publishStopPose(String name) { return name; } @@ -611,6 +792,23 @@ public String publishText(String text) { return text; } + public void removeActionFromGesture(int index) { + try { + + Gesture gesture = getGesture(); + if (gesture == null) { + error("gesture not set"); + return; + } + if (gesture.actions.size() != 0 && index < gesture.actions.size()) { + gesture.actions.remove(index); + } + + } catch (Exception e) { + error(e); + } + } + public void removeGesture(String name) { try { @@ -632,57 +830,6 @@ public void removeGesture(String name) { } } - public void moveActionUp(int index) { - if (currentGesture == null) { - error("cannot move gesture not set"); - return; - } - - List<Action> list = currentGesture.actions; - - if (index > 0 && index < list.size()) { - Action action = list.remove(index); - list.add(index - 1, action); - } else { - error("index out of range or at the beginning of the list."); - } - invoke("getGesture"); - } - - public void moveActionDown(int index) { - if (currentGesture == null) { - error("cannot move: gesture not set"); - return; - } - - List<Action> list = currentGesture.actions; - - if (index >= 0 && index < list.size() - 1) { - Action action = list.remove(index); - list.add(index + 1, action); - } else { - error("index out of range or at the end of the list."); - } - invoke("getGesture"); - } - - public void removeActionFromGesture(int index) { - try { - - Gesture gesture = getGesture(); - if (gesture == null) { - error("gesture not set"); - return; - } - if (gesture.actions.size() != 0 && index < gesture.actions.size()) { - gesture.actions.remove(index); - } - - } catch (Exception e) { - error(e); - } - } - public void rest() { for (String servo : allServos) { Servo s = (Servo) Runtime.getService(servo); @@ -690,6 +837,17 @@ public void rest() { } } + public void saveGesture() { + // FIXME check if exists - ask overwrite + saveGesture(currentEditGestureName, currentGesture); + } + + public void saveGesture(String name) { + // FIXME - warn if overwrite + // FIXME - don't warn if opened + saveGesture(name, currentGesture); + } + /** * Takes name of a file and a json encoded string of a gesture, saves it to * file and sets the "current" gesture to the data @@ -767,6 +925,32 @@ public void setPosesDirectory(String posesDirectory) { broadcastState(); } + private void speak(Map<String, Object> speechPart) { + if (config.mouth == null) { + warn("mouth configuration not set"); + return; + } + SpeechSynthesis mouth = (SpeechSynthesis) Runtime.getService(config.mouth); + if (mouth == null) { + error("%s speech synthesis service missing", config.mouth); + return; + } + try { + // makes it harder to block + // FIXME if blocking send(mouthName, "speak") + // TODO - show multiple SpeechSynthesis select like Servos + Boolean blocking = (Boolean) speechPart.get("blocking"); + // if (blocking != null && blocking) { + mouth.speakBlocking((String) speechPart.get("text")); // default blocking + // } else { + // mouth.speak((String) speechPart.get("text")); + // } + } catch (Exception e) { + error(e); + } + + } + @Override public void startService() { try { @@ -790,225 +974,48 @@ public void startService() { } } - /** - * stop the current running gesture - */ - public void stop() { - player.stop(); - } - - @Override - public void stopService() { - super.stopService(); - player.stop(); - } - - public static void main(String[] args) throws Exception { - - try { - Runtime.main(new String[] { "--id", "admin" }); - LoggingFactory.init("INFO"); - - Runtime.start("i01.head.rothead", "Servo"); - Runtime.start("i01.head.neck", "Servo"); - WebGui webgui = (WebGui) Runtime.create("webgui", "WebGui"); - webgui.autoStartBrowser(false); - webgui.startService(); - Python python = (Python) Runtime.start("python", "Python"); - ServoMixer mixer = (ServoMixer) Runtime.start("mixer", "ServoMixer"); - // mixer.playGesture(""); - - boolean done = true; - if (done) { - return; - } - - mixer.addNewGestureFile("test"); - Gesture gesture = mixer.getGesture("test"); - String gestureName = "aaa"; - String servoName = "i01.head.rothead"; - Double position = 90.0; - Double speed = null; - - Map<String, Map<String, Object>> moves = new TreeMap<>(); - - Map<String, Object> poseMove1 = new TreeMap<>(); - poseMove1.put("position", 90.0); - - Map<String, Object> poseMove2 = new TreeMap<>(); - poseMove2.put("position", 90); - poseMove2.put("speed", 35.0); - - moves.put("i01.head.rothead", poseMove1); - moves.put("i01.head.neck", poseMove2); - - mixer.openGesture(gestureName); - mixer.addMoveToAction(moves); // autofill delay from keyframe ? - mixer.saveGesture(); - - mixer.addNewGestureFile("test2"); - - // mixer.save(gestureName); - mixer.addGestureToAction("test"); - - mixer.saveGesture(); - - // mixer.setPose("test", ) - } catch (Exception e) { - log.error("main threw", e); - } - } - - public void addGestureToAction(String gestureName) { - addGestureToAction(gestureName, null); - } - - public void addGestureToAction(String gestureName, Integer index) { - addAction(Action.createGestureToAction(gestureName), index); - } - - public void saveGesture() { - // FIXME check if exists - ask overwrite - saveGesture(currentEditGestureName, currentGesture); + public void step(int index) { + step(currentEditGestureName, index); } - public Gesture openGesture(String name) { - if (currentGesture != null) { - warn("replacing current gesture"); - // prompt user return null etc. - } + public void step(String gestureName, int index) { - Gesture gesture = getGesture(name); - if (gesture == null) { - // gesture not found make new - gesture = new Gesture(); + if (gestureName == null) { + error("gesture name cannot be null"); + return; } - currentEditGestureName = name; - currentGesture = gesture; - - return currentGesture; - } - - public void addSpeakAction(Map<String, Object> speechCommand) { - addSpeakAction(speechCommand, null); - } - - public void addSpeakAction(Map<String, Object> speechCommand, Integer index) { - addAction(Action.createSpeakAction(speechCommand), index); - } - - public void addMoveToAction(List<String> servos) { - addMoveToAction(servos, null); - } - - /** - * list of servos to add to an action - they're current speed and input - * positions will be added at index - * - * @param servos - * @param index - */ - public void addMoveToAction(List<String> servos, Integer index) { - Map<String, Map<String, Object>> moves = new TreeMap<>(); - for (String servoName : servos) { - ServoControl sc = (ServoControl) Runtime.getService(servoName); - Map<String, Object> posAndSpeed = new TreeMap<>(); - if (sc == null) { - error("%s not a valid service name", servoName); - continue; - } - posAndSpeed.put("position", sc.getCurrentInputPos()); - posAndSpeed.put("speed", sc.getSpeed()); - moves.put(servoName, posAndSpeed); + if (!gestureName.equals(currentEditGestureName)) { + // load gesture + getGesture(gestureName); } - addAction(Action.createMoveToAction(moves), index); - } - - public void addMoveToAction(Map<String, Map<String, Object>> moves) { - addMoveToAction(moves, null); - } - - public void addMoveToAction(Map<String, Map<String, Object>> moves, Integer index) { - addAction(Action.createMoveToAction(moves), index); - } - public void addAction(Action action, Integer index) { if (currentGesture == null) { - error("current gesture not set"); - return; - } - if (index != null) { - if (currentGesture.actions.size() == 0) { - currentGesture.actions.add(action); - } else { - currentGesture.actions.add(index, action); - } - } else { - currentGesture.actions.add(action); - } - } - - public void addSleepAction(double sleep) { - addSleepAction(sleep, null); - } - - public void addSleepAction(double sleep, Integer index) { - addAction(Action.createSleepAction(sleep), index); - } - - public Gesture getGesture() { - return currentGesture; - } - - private void moveTo(String servoName, Map<String, Object> move) { - ServoControl servo = (ServoControl) Runtime.getService(servoName); - if (servo == null) { - warn("servo (%s) cannot move to pose because it does not exist", servoName); + error("gesture cannot be nulle"); return; } - Object speed = move.get("speed"); - if (speed != null) { - if (speed instanceof Integer) { - servo.setSpeed((Integer) speed); - } else if (speed instanceof Double) { - servo.setSpeed((Double) speed); - } - } - - Object position = move.get("position"); - if (position instanceof Integer) { - servo.moveTo((Integer) position); - } else if (position instanceof Double) { - servo.moveTo((Double) position); + player.processAction(new PlayingGesture(gestureName, currentGesture), index); + // step to next action + index++; + if (index < currentGesture.actions.size()) { + Action action = currentGesture.actions.get(index); + invoke("publishPlayingAction", action); + invoke("publishPlayingActionIndex", index); } } - private void speak(Map<String, Object> speechPart) { - if (config.mouth == null) { - warn("mouth configuration not set"); - return; - } - SpeechSynthesis mouth = (SpeechSynthesis) Runtime.getService(config.mouth); - if (mouth == null) { - error("%s speech synthesis service missing", config.mouth); - return; - } - try { - // makes it harder to block - // FIXME if blocking send(mouthName, "speak") - // TODO - show multiple SpeechSynthesis select like Servos - Boolean blocking = (Boolean) speechPart.get("blocking"); - // if (blocking != null && blocking) { - mouth.speakBlocking((String) speechPart.get("text")); // default blocking - // } else { - // mouth.speak((String) speechPart.get("text")); - // } - } catch (Exception e) { - error(e); - } + /** + * stop the current running gesture + */ + public void stop() { + player.stop(); + } + @Override + public void stopService() { + super.stopService(); + player.stop(); } } diff --git a/src/main/java/org/myrobotlab/service/config/ServoMixerConfig.java b/src/main/java/org/myrobotlab/service/config/ServoMixerConfig.java index b62c1200a5..813b32ae8a 100644 --- a/src/main/java/org/myrobotlab/service/config/ServoMixerConfig.java +++ b/src/main/java/org/myrobotlab/service/config/ServoMixerConfig.java @@ -22,5 +22,10 @@ public class ServoMixerConfig extends ServiceConfig { * speech service name */ public String mouth; + + /** + * name of the default processor + */ + public String processor = "python"; } diff --git a/src/main/java/org/myrobotlab/service/interfaces/Processor.java b/src/main/java/org/myrobotlab/service/interfaces/Processor.java new file mode 100644 index 0000000000..8a32c2622a --- /dev/null +++ b/src/main/java/org/myrobotlab/service/interfaces/Processor.java @@ -0,0 +1,18 @@ +package org.myrobotlab.service.interfaces; + +public interface Processor { + + /** + * + * FIXME - this should be refactored to return an Object + * but I don't want to break anything for now + * FIXME - if there is an error this should throw and return + * and object or null if successful + * + * A processor can exec code + * @param code + * @return success or not + */ + public boolean exec(String code); + +} diff --git a/src/main/resources/resource/WebGui/app/service/js/ServoMixerGui.js b/src/main/resources/resource/WebGui/app/service/js/ServoMixerGui.js index 85dc0cbcf3..6c231adacb 100644 --- a/src/main/resources/resource/WebGui/app/service/js/ServoMixerGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/ServoMixerGui.js @@ -241,6 +241,14 @@ angular.module("mrlapp.service.ServoMixerGui", []).controller("ServoMixerGuiCtrl msg.send("getGesture") } + $scope.addPython = function(methodName){ + let index = parseInt($scope.state.gestureIndex) + 1 + $scope.state.gestureIndex = index + "" + msg.send("addProcessingAction", methodName, index) + msg.send("getGesture") + + } + $scope.playGesture = function (gesture) { if (gesture) { msg.send("playGesture", gesture) diff --git a/src/main/resources/resource/WebGui/app/service/views/ServoMixerGui.html b/src/main/resources/resource/WebGui/app/service/views/ServoMixerGui.html index 4e7b48072a..4e38b899b4 100644 --- a/src/main/resources/resource/WebGui/app/service/views/ServoMixerGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/ServoMixerGui.html @@ -78,16 +78,14 @@ <h3>{{state.selectedGestureFile}} {{state.playingPose.name}} {{state.gestureInde </td> </tr> -<!-- <tr><td> - <button class="btn btn-default" ng-click="addSleep(sleep)" title="Add sleep in seconds to gesture" style="width: 100%;text-align: left;"> + <button class="btn btn-default" ng-click="addPython(methodName)" title="Add a python method" style="width: 100%;text-align: left;"> <span class="glyphicon glyphicon-plus"></span> - message + python </button></td><td> - <input class="form-control servo-mixer-pose" type="textarea" ng-model="text" placeholder='{"method":"playFile","data"["laugh.mp3"]}' /> + <input class="form-control servo-mixer-pose" type="text" ng-model="methodName" placeholder='method_name' /> </td> </tr> ---> </table> From 3398f8a7744e001476da68385ea38e1309ff355a Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Fri, 2 Feb 2024 06:18:31 -0800 Subject: [PATCH 017/131] fix runtime.resource for tests --- .../java/org/myrobotlab/service/Runtime.java | 3 +-- .../java/org/myrobotlab/test/AbstractTest.java | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 54f9462425..66cf15b322 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -2786,10 +2786,9 @@ public Runtime(String n, String id) { if (deps.size() == 0) { metaData.installed = true; } else { - warn("{} not installed", metaData.getSimpleName()); + log.info("{} not installed", metaData.getSimpleName()); } } - } } diff --git a/src/test/java/org/myrobotlab/test/AbstractTest.java b/src/test/java/org/myrobotlab/test/AbstractTest.java index c4fc33b83d..00cc6a202d 100644 --- a/src/test/java/org/myrobotlab/test/AbstractTest.java +++ b/src/test/java/org/myrobotlab/test/AbstractTest.java @@ -1,5 +1,7 @@ package org.myrobotlab.test; +import java.io.File; +import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -13,10 +15,12 @@ import org.junit.BeforeClass; import org.junit.Rule; import org.junit.rules.TestName; +import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.Platform; import org.myrobotlab.framework.interfaces.Attachable; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.Runtime; +import org.myrobotlab.service.config.RuntimeConfig; import org.slf4j.Logger; public class AbstractTest { @@ -83,6 +87,20 @@ public static void main(String[] args) { @BeforeClass public static void setUpAbstractTest() throws Exception { + + // setup runtime resource = src/main/resources/resource + File runtimeYml = new File("data/conf/default/runtime.yml"); + if (!runtimeYml.exists()) { + RuntimeConfig rc = new RuntimeConfig(); + rc.resource = "src/main/resources/resource"; + String yml = CodecUtils.toYaml(rc); + + FileOutputStream fos = null; + fos = new FileOutputStream(runtimeYml); + fos.write(yml.getBytes()); + fos.close(); + + } Platform.setVirtual(true); From 557cd80986e66b871c19c47263ecedae18e7ae4d Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Fri, 2 Feb 2024 06:22:13 -0800 Subject: [PATCH 018/131] again --- src/test/java/org/myrobotlab/test/AbstractTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/myrobotlab/test/AbstractTest.java b/src/test/java/org/myrobotlab/test/AbstractTest.java index 00cc6a202d..3b3351c674 100644 --- a/src/test/java/org/myrobotlab/test/AbstractTest.java +++ b/src/test/java/org/myrobotlab/test/AbstractTest.java @@ -91,6 +91,7 @@ public static void setUpAbstractTest() throws Exception { // setup runtime resource = src/main/resources/resource File runtimeYml = new File("data/conf/default/runtime.yml"); if (!runtimeYml.exists()) { + runtimeYml.getParentFile().mkdirs(); RuntimeConfig rc = new RuntimeConfig(); rc.resource = "src/main/resources/resource"; String yml = CodecUtils.toYaml(rc); From 0a3c81cf50dea1eb50576e4d5d2d585bd85005b4 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Fri, 2 Feb 2024 06:26:55 -0800 Subject: [PATCH 019/131] again --- src/test/java/org/myrobotlab/test/AbstractTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/myrobotlab/test/AbstractTest.java b/src/test/java/org/myrobotlab/test/AbstractTest.java index 3b3351c674..3ea2b7f589 100644 --- a/src/test/java/org/myrobotlab/test/AbstractTest.java +++ b/src/test/java/org/myrobotlab/test/AbstractTest.java @@ -90,7 +90,7 @@ public static void setUpAbstractTest() throws Exception { // setup runtime resource = src/main/resources/resource File runtimeYml = new File("data/conf/default/runtime.yml"); - if (!runtimeYml.exists()) { +// if (!runtimeYml.exists()) { runtimeYml.getParentFile().mkdirs(); RuntimeConfig rc = new RuntimeConfig(); rc.resource = "src/main/resources/resource"; @@ -101,7 +101,7 @@ public static void setUpAbstractTest() throws Exception { fos.write(yml.getBytes()); fos.close(); - } +// } Platform.setVirtual(true); From ce82eb5d6087b6b92391e3beea6eb05b147cd986 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Fri, 2 Feb 2024 06:30:31 -0800 Subject: [PATCH 020/131] corrected wrong path --- src/test/java/org/myrobotlab/test/AbstractTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/myrobotlab/test/AbstractTest.java b/src/test/java/org/myrobotlab/test/AbstractTest.java index 3ea2b7f589..6b7ec2b432 100644 --- a/src/test/java/org/myrobotlab/test/AbstractTest.java +++ b/src/test/java/org/myrobotlab/test/AbstractTest.java @@ -89,7 +89,7 @@ public static void main(String[] args) { public static void setUpAbstractTest() throws Exception { // setup runtime resource = src/main/resources/resource - File runtimeYml = new File("data/conf/default/runtime.yml"); + File runtimeYml = new File("data/config/default/runtime.yml"); // if (!runtimeYml.exists()) { runtimeYml.getParentFile().mkdirs(); RuntimeConfig rc = new RuntimeConfig(); From 0483fb827c3da507f8fa355fe32fa0887eca4de9 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 4 Feb 2024 07:13:12 -0800 Subject: [PATCH 021/131] Subscription fixes in config - removal of hardcoded subscriptions --- .../org/myrobotlab/config/ConfigUtils.java | 71 ++++++++++++++++ .../java/org/myrobotlab/service/InMoov2.java | 83 ------------------- .../org/myrobotlab/service/InMoov2Arm.java | 2 +- .../org/myrobotlab/service/InMoov2Hand.java | 2 +- .../org/myrobotlab/service/InMoov2Head.java | 2 +- .../org/myrobotlab/service/InMoov2Torso.java | 2 +- .../service/config/InMoov2Config.java | 12 +-- 7 files changed, 81 insertions(+), 93 deletions(-) create mode 100644 src/main/java/org/myrobotlab/config/ConfigUtils.java diff --git a/src/main/java/org/myrobotlab/config/ConfigUtils.java b/src/main/java/org/myrobotlab/config/ConfigUtils.java new file mode 100644 index 0000000000..35c8a776a8 --- /dev/null +++ b/src/main/java/org/myrobotlab/config/ConfigUtils.java @@ -0,0 +1,71 @@ +package org.myrobotlab.config; + +import java.io.File; +import java.io.IOException; + +import org.myrobotlab.codec.CodecUtils; +import org.myrobotlab.framework.StartYml; +import org.myrobotlab.io.FileIO; +import org.myrobotlab.service.Runtime; +import org.myrobotlab.service.config.RuntimeConfig; + +public class ConfigUtils { + + /** + * This gets the current resource root without starting a Runtime instance if + * not already started. The resource root depends on config, if Runtime is + * running the logic and current config name is already available. If Runtime + * is not running, we need to go through a series of steps to deterime where + * the resource root is configured. + * + * @return + */ + public static String getResourceRoot() { + + String resource = "resource"; + + // check if runtime is running + if (!Runtime.isAvailable()) { + // check for start.yml + + File checkStartYml = new File("start.yml"); + StartYml startYml = new StartYml(); + if (checkStartYml.exists()) { + String yml; + try { + yml = FileIO.toString("start.yml"); + startYml = CodecUtils.fromYaml(yml, StartYml.class); + + // see if autostart is on with a config + if (startYml.enable) { + // use that config to find runtime.yml + + File runtimeYml = new File(Runtime.ROOT_CONFIG_DIR + File.separator + startYml.config + File.separator + "runtime.yml"); + if (runtimeYml.exists()) { + // parse that file look for resource: entry in file + RuntimeConfig config = (RuntimeConfig) CodecUtils.readServiceConfig(runtimeYml.getAbsolutePath()); + resource = config.resource; + } + + } else { + // start.yml enable = false / so we'll use default config + File runtimeYml = new File(Runtime.ROOT_CONFIG_DIR + File.separator + "default" + File.separator + "runtime.yml"); + if (runtimeYml.exists()) { + // parse that file look for resource: entry in file + RuntimeConfig config = (RuntimeConfig) CodecUtils.readServiceConfig(runtimeYml.getAbsolutePath()); + resource = config.resource; + } + } + + } catch (IOException e) { + // problem getting or parsing + // going to assume default "resource" + } + } // no startYml + return resource; + } else { + // Runtime is available - ask it + return Runtime.getInstance().getConfig().resource; + } + } +} diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index d9b93659cd..53d2019ba7 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -1123,7 +1123,6 @@ public void onStartConfig(String configName) { */ @Override public void onStarted(String name) { - InMoov2Config c = (InMoov2Config) config; log.info("onStarted {}", name); try { @@ -1157,81 +1156,21 @@ public void onStarted(String name) { chatBot.attachTextListener(getPeerName("htmlFilter")); startPeer("htmlFilter"); break; - case "controller3": - break; - case "controller4": - break; case "ear": AbstractSpeechRecognizer ear = (AbstractSpeechRecognizer) Runtime.getService(name); ear.attachTextListener(getPeerName("chatBot")); break; - case "eyeTracking": - break; - case "fsm": - break; - case "gpt3": - break; - case "head": - addListener("publishMoveHead", name); - break; - case "headTracking": - break; case "htmlFilter": TextPublisher htmlFilter = (TextPublisher) Runtime.getService(name); htmlFilter.attachTextListener(getPeerName("mouth")); break; - case "imageDisplay": - break; - case "leap": - break; - case "left": - break; - case "leftArm": - addListener("publishMoveLeftArm", name, "onMoveArm"); - break; - case "leftHand": - addListener("publishMoveLeftHand", name, "onMoveHand"); - break; case "mouth": mouth = (AbstractSpeechSynthesis) Runtime.getService(name); mouth.attachSpeechListener(getPeerName("ear")); break; - case "mouthControl": - break; - case "neoPixel": - break; case "opencv": subscribeTo(name, "publishOpenCVData"); break; - case "openni": - break; - case "openWeatherMap": - break; - case "pid": - break; - case "pir": - break; - case "random": - break; - case "right": - break; - case "rightArm": - addListener("publishMoveRightArm", name, "onMoveArm"); - break; - case "rightHand": - addListener("publishMoveRightHand", name, "onMoveHand"); - break; - case "servoMixer": - break; - case "simulator": - break; - case "torso": - addListener("publishMoveTorso", name); - break; - case "ultrasonicRight": - break; - case "ultrasonicLeft": - break; default: log.warn("unknown peer %s not hanled in onStarted", peerKey); break; @@ -1991,28 +1930,6 @@ public void startService() { // chatbot getresponse attached to publishEvent addListener("publishEvent", getPeerName("chatBot"), "getResponse"); - try { - // copy config if it doesn't already exist - String resourceBotDir = FileIO.gluePaths(getResourceDir(), "config"); - List<File> 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"); } diff --git a/src/main/java/org/myrobotlab/service/InMoov2Arm.java b/src/main/java/org/myrobotlab/service/InMoov2Arm.java index d190af46e8..3b74a3bf20 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Arm.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Arm.java @@ -89,7 +89,7 @@ public static DHRobotArm getDHRobotArm(String name, String side) { return arm; } - @Deprecated /* use onMove */ + @Deprecated /* use onMove(map) */ public void onMoveArm(HashMap<String, Double> map) { onMove(map); } diff --git a/src/main/java/org/myrobotlab/service/InMoov2Hand.java b/src/main/java/org/myrobotlab/service/InMoov2Hand.java index 876bfcf5ca..b30c2bd792 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Hand.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Hand.java @@ -490,7 +490,7 @@ public LeapData onLeapData(LeapData data) { return data; } - @Deprecated /* use onMove */ + @Deprecated /* use onMove(map) */ public void onMoveHand(HashMap<String, Double> map) { onMove(map); } diff --git a/src/main/java/org/myrobotlab/service/InMoov2Head.java b/src/main/java/org/myrobotlab/service/InMoov2Head.java index f77ef9c823..f3f5edf366 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Head.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Head.java @@ -221,7 +221,7 @@ public void lookAt(Double x, Double y, Double z) { log.info("object distance is {},rothead servo {},neck servo {} ", distance, rotation, colatitude); } - @Deprecated /* use onMoov */ + @Deprecated /* use onMove(map) */ public void onMoveHead(HashMap<String, Double> map) { onMove(map); } diff --git a/src/main/java/org/myrobotlab/service/InMoov2Torso.java b/src/main/java/org/myrobotlab/service/InMoov2Torso.java index f3953699c5..75fa410ca2 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Torso.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Torso.java @@ -94,7 +94,7 @@ public void disable() { lowStom.disable(); } - @Deprecated /* use onMove */ + @Deprecated /* use onMove(map) */ public void onMoveTorso(HashMap<String, Double> map) { onMove(map); } diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 55630c9f1b..c0d624d3e6 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -522,12 +522,12 @@ public Plan getDefault(Plan plan, String name) { listeners.add(new Listener("publishProcessMessage", getPeerName("py4j"), "onPythonMessage")); // InMoov2 --to--> InMoov2 - 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)); + listeners.add(new Listener("publishMoveHead", getPeerName("head"), "onMove")); + listeners.add(new Listener("publishMoveRightArm", getPeerName("rightArm"), "onMove")); + listeners.add(new Listener("publishMoveLeftArm", getPeerName("leftArm"), "onMove")); + listeners.add(new Listener("publishMoveRightHand", getPeerName("rightHand"), "onMove")); + listeners.add(new Listener("publishMoveLeftHand", getPeerName("leftHand"), "onMove")); + listeners.add(new Listener("publishMoveTorso", getPeerName("torso"), "onMove")); // service --to--> InMoov2 AudioFileConfig mouth_audioFile = (AudioFileConfig) plan.get(getPeerName("mouth.audioFile")); From 135f12aa671f3ede45619cb406c58d7e67f9381f Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 4 Feb 2024 07:20:18 -0800 Subject: [PATCH 022/131] small runtime updates --- src/main/java/org/myrobotlab/service/Runtime.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 3a66750f54..e5da31305f 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -229,7 +229,7 @@ public class Runtime extends Service<RuntimeConfig> implements MessageListener, /** * default parent path of configPath static ! */ - final static protected String ROOT_CONFIG_DIR = DATA_DIR + fs + "config"; + public final static String ROOT_CONFIG_DIR = DATA_DIR + fs + "config"; /** * number of services created by this runtime @@ -5408,4 +5408,12 @@ public static void removeConfig(String configName) { } } + /** + * Method used to determine is runtime is running without starting it + * @return true if available + */ + static public boolean isAvailable() { + return runtime != null && runtime.isRunning(); + } + } From 13323366c5ef4e1304545fab57945c1af83951e5 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 4 Feb 2024 07:23:58 -0800 Subject: [PATCH 023/131] added test --- src/test/java/org/myrobotlab/service/RuntimeTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/org/myrobotlab/service/RuntimeTest.java b/src/test/java/org/myrobotlab/service/RuntimeTest.java index 50c6c03268..2a5db617ad 100644 --- a/src/test/java/org/myrobotlab/service/RuntimeTest.java +++ b/src/test/java/org/myrobotlab/service/RuntimeTest.java @@ -1,6 +1,7 @@ package org.myrobotlab.service; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.Date; @@ -103,6 +104,15 @@ public void testRuntimeLocale() { assertEquals("fr-FR", l.toString()); } + + @Test + public void testRuntimeIsAvailable() { + Runtime runtime = Runtime.getInstance(); + assertTrue(Runtime.isAvailable()); + Runtime.releaseAll(true, true); + assertFalse(Runtime.isAvailable()); + } + @Test public void testGetDescribeMessage() { From 2ea567e0e60b21b3ad343fdc2ee2464e73b408f1 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 4 Feb 2024 07:47:06 -0800 Subject: [PATCH 024/131] trying to make idempotent test --- src/test/java/org/myrobotlab/service/RuntimeTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/myrobotlab/service/RuntimeTest.java b/src/test/java/org/myrobotlab/service/RuntimeTest.java index 2a5db617ad..a20a13db38 100644 --- a/src/test/java/org/myrobotlab/service/RuntimeTest.java +++ b/src/test/java/org/myrobotlab/service/RuntimeTest.java @@ -107,10 +107,12 @@ public void testRuntimeLocale() { @Test public void testRuntimeIsAvailable() { - Runtime runtime = Runtime.getInstance(); + Runtime.getInstance(); assertTrue(Runtime.isAvailable()); Runtime.releaseAll(true, true); assertFalse(Runtime.isAvailable()); + Runtime.getInstance(); + assertTrue(Runtime.isAvailable()); } From 6a0507172abfe51c686c1399ad2626c33b45f408 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 4 Feb 2024 08:32:27 -0800 Subject: [PATCH 025/131] npe check --- src/main/java/org/myrobotlab/service/Runtime.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 66cf15b322..9def8ddf30 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -2023,7 +2023,9 @@ synchronized public static void unregister(String inName) { // and config RuntimeConfig c = (RuntimeConfig) Runtime.getInstance().config; - c.remove(CodecUtils.getShortName(name)); + if (c != null) { + c.remove(CodecUtils.getShortName(name)); + } log.info("released {}", name); } From dcdab9bdc4d2e793a148982a21fb4ae8afd9fc41 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 4 Feb 2024 10:57:01 -0800 Subject: [PATCH 026/131] finally ! fixed randomTest issue --- src/test/java/org/myrobotlab/service/RandomTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/myrobotlab/service/RandomTest.java b/src/test/java/org/myrobotlab/service/RandomTest.java index 9c3739f510..7c8add5923 100644 --- a/src/test/java/org/myrobotlab/service/RandomTest.java +++ b/src/test/java/org/myrobotlab/service/RandomTest.java @@ -74,7 +74,7 @@ public void testService() throws Exception { // disable one method - leave other enabled random.disable("clock.startClock"); clock.stopClock(); - clock.setInterval(999999); + clock.setInterval(9999); sleep(200); 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()); @@ -83,9 +83,9 @@ public void testService() throws Exception { // disable all random.disable(); sleep(200); - clock.setInterval(999999); + clock.setInterval(9999); assertTrue("clock should not be started 4", !clock.isClockRunning()); - assertEquals(999999, (long)clock.getInterval()); + assertEquals(9999, (long)clock.getInterval()); // re-enable all that were previously enabled but not explicitly disabled ones random.enable(); From 2e8fdb566d14bb4804a36662ca1fa21ee15d6d8a Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Mon, 5 Feb 2024 11:14:52 -0800 Subject: [PATCH 027/131] javadoc updates to runtime config --- .../java/org/myrobotlab/service/Runtime.java | 1 - .../service/config/RuntimeConfig.java | 25 +++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 3f5641ba90..8e96ec71b5 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -938,7 +938,6 @@ public static Runtime getInstance() { } catch (Exception e) { log.error("runtime will not be loading config", e); } - } // synchronized lock } diff --git a/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java b/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java index 3e989aaea3..6296f4d536 100644 --- a/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java +++ b/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java @@ -17,12 +17,30 @@ public class RuntimeConfig extends ServiceConfig { * virtual hardware if enabled all services created will enable virtualization if applicable */ public Boolean virtual = false; + + /** + * Determines if stdin can be used for commands + */ public boolean enableCli = true; + + /** + * Log level debug, info, warning, error + */ public String logLevel = "info"; + + /** + * Locale setting for the instance, initial default will be set by the default jvm/os + * through java.util.Locale.getDefault() + */ public String locale; - // NEED THIS PRIVATE BUT CANNOT BE - public List<String> registry = new ArrayList<>(); + + /** + * Although this should be a set of unique services, it cannot be a LinkedHashSet + * because SnakeYml's interpretation would be a map with null values. Instead + * its a protected member with accessors that prevent duplicates. + */ + protected List<String> registry = new ArrayList<>(); /** * Root of resource location @@ -30,6 +48,9 @@ public class RuntimeConfig extends ServiceConfig { public String resource = "resource"; + /** + * Constructor sets the default locale if not already set. + */ public RuntimeConfig() { if (locale == null) { locale = Locale.getDefault().getTag(); From 0c4ba80581c9d8bbc0c535c674023a10b9a75507 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Mon, 5 Feb 2024 11:36:37 -0800 Subject: [PATCH 028/131] guard against no runtime.xml --- src/main/java/org/myrobotlab/service/Runtime.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 8e96ec71b5..61f66cd4ef 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -910,7 +910,6 @@ public static Runtime getInstance() { Runtime.setAllVirtual(Platform.isVirtual()); // setting the singleton security - Security.getInstance(); runtime.getRepo().addStatusPublisher(runtime); FileIO.extractResources(); // protected services we don't want to remove when releasing a config @@ -929,7 +928,9 @@ public static Runtime getInstance() { } else { RuntimeConfig rtConfig = runtime.readServiceConfig(runtime.getConfigName(), "runtime", new StaticType<>() { }); - runtime.apply(rtConfig); + if (rtConfig != null) { + runtime.apply(rtConfig); + } } } catch (Exception e) { log.info("runtime will not be loading config"); From beafe7208cd84d8b0c0bcb26a8ed857fd8241a9b Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Mon, 5 Feb 2024 11:59:15 -0800 Subject: [PATCH 029/131] removed dependency test --- Jenkinsfile | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 8e7ccc45f2..63a28dcadb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -64,18 +64,18 @@ pipeline { } // stage build - stage('dependencies') { - when { - expression { params.verify == 'true' } - } - steps { - script { - sh ''' - mvn test -Dtest=org.myrobotlab.framework.DependencyTest -q - ''' - } - } - } // stage dependencies + // stage('dependencies') { + // when { + // expression { params.verify == 'true' } + // } + // steps { + // script { + // sh ''' + // mvn test -Dtest=org.myrobotlab.framework.DependencyTest -q + // ''' + // } + // } + // } // stage dependencies // --fail-fast // -DargLine="-Xmx1024m" From dbe67cf25dfd86b0a2970bdf567795fd46756a07 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Mon, 5 Feb 2024 12:15:48 -0800 Subject: [PATCH 030/131] fixing unit tests --- src/main/java/org/myrobotlab/service/Runtime.java | 10 +++++++++- src/test/java/org/myrobotlab/framework/ConfigTest.java | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 61f66cd4ef..4976d8c349 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -583,6 +583,8 @@ public final static void createAndStartServices(List<String> services) { @Override public boolean setVirtual(boolean b) { boolean changed = config.virtual != b; + config.virtual = b; + isVirtual = b; setAllVirtual(b); if (changed) { broadcastState(); @@ -909,7 +911,6 @@ public static Runtime getInstance() { // platform virtual is higher priority than service virtual Runtime.setAllVirtual(Platform.isVirtual()); - // setting the singleton security runtime.getRepo().addStatusPublisher(runtime); FileIO.extractResources(); // protected services we don't want to remove when releasing a config @@ -932,6 +933,13 @@ public static Runtime getInstance() { runtime.apply(rtConfig); } } + + + // FIXME - should simply set default RuntimeConfig services and include security + // setting the singleton security + Security.getInstance(); + + } catch (Exception e) { log.info("runtime will not be loading config"); } diff --git a/src/test/java/org/myrobotlab/framework/ConfigTest.java b/src/test/java/org/myrobotlab/framework/ConfigTest.java index 4f8f1a9567..95b3da920b 100644 --- a/src/test/java/org/myrobotlab/framework/ConfigTest.java +++ b/src/test/java/org/myrobotlab/framework/ConfigTest.java @@ -109,7 +109,7 @@ public void testStartNoConfig() throws Exception { // starting an empty config automatically needs a runtime, and runtime // by default starts the singleton security service names = Runtime.getServiceNames(); - assertEquals("complete teardown should be 2 after trying to start a config runtime and security", 2, names.length); + assertEquals("complete teardown should be 2 after trying to start a config runtime and security", 1, names.length); } From 62aa00728e57660ae19e73ea806d78098f3f5d1b Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 6 Feb 2024 06:35:08 -0800 Subject: [PATCH 031/131] config update --- .../org/myrobotlab/service/config/OpenWeatherMapConfig.java | 2 +- .../myrobotlab/service/config/YahooFinanceStockQuoteConfig.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/config/OpenWeatherMapConfig.java b/src/main/java/org/myrobotlab/service/config/OpenWeatherMapConfig.java index 37cdd82a0b..9a6a51baab 100644 --- a/src/main/java/org/myrobotlab/service/config/OpenWeatherMapConfig.java +++ b/src/main/java/org/myrobotlab/service/config/OpenWeatherMapConfig.java @@ -3,7 +3,7 @@ import org.myrobotlab.framework.Peer; import org.myrobotlab.framework.Plan; -public class OpenWeatherMapConfig extends ServiceConfig { +public class OpenWeatherMapConfig extends HttpClientConfig { public String currentUnits; public String currentTown; diff --git a/src/main/java/org/myrobotlab/service/config/YahooFinanceStockQuoteConfig.java b/src/main/java/org/myrobotlab/service/config/YahooFinanceStockQuoteConfig.java index d351154c44..32ae5984d8 100644 --- a/src/main/java/org/myrobotlab/service/config/YahooFinanceStockQuoteConfig.java +++ b/src/main/java/org/myrobotlab/service/config/YahooFinanceStockQuoteConfig.java @@ -1,5 +1,5 @@ package org.myrobotlab.service.config; -public class YahooFinanceStockQuoteConfig extends ServiceConfig { +public class YahooFinanceStockQuoteConfig extends HttpClientConfig { } From 139d01f7372467a404a361f57cdd22668923845b Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 6 Feb 2024 06:35:39 -0800 Subject: [PATCH 032/131] http client config --- src/main/java/org/myrobotlab/service/HttpClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/HttpClient.java b/src/main/java/org/myrobotlab/service/HttpClient.java index c7caaa2982..bb43043861 100644 --- a/src/main/java/org/myrobotlab/service/HttpClient.java +++ b/src/main/java/org/myrobotlab/service/HttpClient.java @@ -54,7 +54,7 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.net.InstallCert; -import org.myrobotlab.service.config.ServiceConfig; +import org.myrobotlab.service.config.HttpClientConfig; import org.myrobotlab.service.data.HttpData; import org.myrobotlab.service.interfaces.HttpDataListener; import org.myrobotlab.service.interfaces.HttpResponseListener; @@ -74,7 +74,7 @@ * - Proxies proxies proxies ! - * https://memorynotfound.com/configure-http-proxy-settings-java/ */ -public class HttpClient<C extends ServiceConfig> extends Service<C> implements TextPublisher { +public class HttpClient<C extends HttpClientConfig> extends Service<C> implements TextPublisher { public final static Logger log = LoggerFactory.getLogger(HttpClient.class); From 743226653f5f5f34773837953a9f39b510366bc7 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Fri, 9 Feb 2024 10:26:48 -0800 Subject: [PATCH 033/131] synchronized --- README.md | 36 + .../java/org/myrobotlab/codec/CodecUtils.java | 4 +- .../org/myrobotlab/config/ConfigUtils.java | 142 +- .../org/myrobotlab/framework/CmdOptions.java | 62 +- .../org/myrobotlab/framework/Platform.java | 51 +- .../org/myrobotlab/framework/Service.java | 6 +- .../org/myrobotlab/framework/StartYml.java | 4 - .../framework/repo/MavenWrapper.java | 6 +- .../java/org/myrobotlab/process/Launcher.java | 43 +- .../java/org/myrobotlab/service/Hd44780.java | 2 +- .../java/org/myrobotlab/service/InMoov2.java | 42 - .../org/myrobotlab/service/JMonkeyEngine.java | 2 +- .../org/myrobotlab/service/MotorDualPwm.java | 2 +- .../java/org/myrobotlab/service/Mqtt.java | 2 +- .../java/org/myrobotlab/service/Runtime.java | 1513 ++++++++--------- .../java/org/myrobotlab/service/Serial.java | 2 +- .../java/org/myrobotlab/service/WebGui.java | 46 +- .../service/config/RuntimeConfig.java | 26 +- .../service/interfaces/Gateway.java | 2 +- .../myrobotlab/service/meta/JoystickMeta.java | 6 +- .../myrobotlab/vertx/WebSocketHandler.java | 2 +- .../org/myrobotlab/codec/CodecUtilsTest.java | 19 - .../myrobotlab/config/ConfigUtilsTest.java | 43 + .../myrobotlab/framework/CmdOptionsTest.java | 32 +- .../org/myrobotlab/framework/ConfigTest.java | 132 +- .../org/myrobotlab/framework/ServiceTest.java | 46 - .../org/myrobotlab/service/RuntimeTest.java | 13 - .../org/myrobotlab/service/SerialTest.java | 2 +- .../org/myrobotlab/test/AbstractTest.java | 8 +- 29 files changed, 1079 insertions(+), 1217 deletions(-) create mode 100644 src/test/java/org/myrobotlab/config/ConfigUtilsTest.java delete mode 100644 src/test/java/org/myrobotlab/framework/ServiceTest.java diff --git a/README.md b/README.md index 69e1433cbf..f22e3b94f8 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,42 @@ type: Runtime virtual: false ``` +# Starting Flowchart +```mermaid +flowchart LR + CommandLine[CommandLine] + Runtime.main([Runtime.main]) + install{install} + shutdown([shutdown]) + checkForStartYml{start.yml + exists?} + startYmlEnabled{start.yml + enabled?} + + CommandLine --> Runtime.main + Runtime.main --> checkForStartYml + checkForStartYml --> |yes| loadStartYml[load start.yml] + checkForStartYml --> |no| createDefaultStartYml[create default start.yml] + createDefaultStartYml --> loadStartYml + loadStartYml --> startYmlEnabled + startYmlEnabled --> |yes| Runtime.startConfig[config = start.yml config] + startYmlEnabled --> |no| default[config = default] + Runtime.startConfig --> loadRuntimeConfig[load runtime config] + default --> loadRuntimeConfig + loadRuntimeConfig --> startRuntime[start runtime] + startRuntime --> applyRuntimeConfig[apply runtime config + does not process registry] + applyRuntimeConfig --> install{install?} + + install -->|yes| loadServiceData[loadServiceData] + install -->|no| Runtime.startConf[get runtime.startConfig config] + + loadServiceData --> findUninstalledDependencies[find uninstallled dependencies] + findUninstalledDependencies -->installDependencies[install dependencies] + installDependencies --> shutdown +``` + + # Network Distributed Architecture ## Websockets - Default Response for New Connection diff --git a/src/main/java/org/myrobotlab/codec/CodecUtils.java b/src/main/java/org/myrobotlab/codec/CodecUtils.java index 372caa474b..09d2086c9e 100644 --- a/src/main/java/org/myrobotlab/codec/CodecUtils.java +++ b/src/main/java/org/myrobotlab/codec/CodecUtils.java @@ -495,7 +495,7 @@ public static String getFullName(String name) { } if (getId(name) == null) { - return name + '@' + Platform.getLocalInstance().getId(); + return name + '@' + Runtime.getInstance().getId(); } else { return name; } @@ -1466,7 +1466,7 @@ public static boolean isLocal(String name) { if (!name.contains("@")) { return true; } - return name.substring(name.indexOf("@") + 1).equals(Platform.getLocalInstance().getId()); + return name.substring(name.indexOf("@") + 1).equals(Runtime.getInstance().getId()); } /** diff --git a/src/main/java/org/myrobotlab/config/ConfigUtils.java b/src/main/java/org/myrobotlab/config/ConfigUtils.java index 35c8a776a8..19c256a8cf 100644 --- a/src/main/java/org/myrobotlab/config/ConfigUtils.java +++ b/src/main/java/org/myrobotlab/config/ConfigUtils.java @@ -4,13 +4,26 @@ import java.io.IOException; import org.myrobotlab.codec.CodecUtils; +import org.myrobotlab.framework.CmdOptions; import org.myrobotlab.framework.StartYml; import org.myrobotlab.io.FileIO; +import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.Runtime; import org.myrobotlab.service.config.RuntimeConfig; +import org.slf4j.Logger; +/** + * Class to process basic configuration functions and processing. + * + * @author GroG + * + */ public class ConfigUtils { + public final static Logger log = LoggerFactory.getLogger(Runtime.class); + + private static RuntimeConfig config; + /** * This gets the current resource root without starting a Runtime instance if * not already started. The resource root depends on config, if Runtime is @@ -21,51 +34,100 @@ public class ConfigUtils { * @return */ public static String getResourceRoot() { + if (config == null) { + loadRuntimeConfig(null); + } + return config.resource; + + } + + /** + * Loads a runtime config based on the configName. config = + * data/config/{configName}/runtime.yml If one does exits, it is returned, if + * one does not exist a default one is created and saved. + * + * @param configName + * @return + */ + static public RuntimeConfig loadRuntimeConfig(CmdOptions options) { + + if (config != null) { + return config; + } - String resource = "resource"; + StartYml startYml = loadStartYml(); + String configName = null; - // check if runtime is running - if (!Runtime.isAvailable()) { - // check for start.yml + if (startYml.enable) { + configName = startYml.config; + } + + // start with default + config = new RuntimeConfig(); + try { - File checkStartYml = new File("start.yml"); - StartYml startYml = new StartYml(); - if (checkStartYml.exists()) { - String yml; + File runtimeYml = new File(Runtime.ROOT_CONFIG_DIR + File.separator + configName + File.separator + "runtime.yml"); + if (runtimeYml.exists()) { + // parse that file look for resource: entry in file + config = (RuntimeConfig) CodecUtils.readServiceConfig(runtimeYml.getAbsolutePath()); + } else { + FileIO.toFile(runtimeYml, CodecUtils.toYaml(config).getBytes()); + } + + } catch (IOException e) { + log.error("loadRuntimeConfig threw", e); + } + + if (options != null && options.id != null) { + config.id = options.id; + } + + return config; + } + + public static StartYml loadStartYml() { + StartYml startYml = new StartYml(); + String defaultStartFile = CodecUtils.toYaml(startYml); + File checkStartYml = new File("start.yml"); + if (!checkStartYml.exists()) { + // save default start.yml + startYml = new StartYml(); + try { + FileIO.toFile("start.yml", defaultStartFile); + } catch (IOException e) { + log.error("could not save start.yml"); + } + } else { + // load start.yml + try { + String yml = FileIO.toString("start.yml"); + startYml = CodecUtils.fromYaml(yml, StartYml.class); + } catch (Exception e) { + log.error("could not load start.yml replacing with new start.yml", e); + startYml = new StartYml(); try { - yml = FileIO.toString("start.yml"); - startYml = CodecUtils.fromYaml(yml, StartYml.class); - - // see if autostart is on with a config - if (startYml.enable) { - // use that config to find runtime.yml - - File runtimeYml = new File(Runtime.ROOT_CONFIG_DIR + File.separator + startYml.config + File.separator + "runtime.yml"); - if (runtimeYml.exists()) { - // parse that file look for resource: entry in file - RuntimeConfig config = (RuntimeConfig) CodecUtils.readServiceConfig(runtimeYml.getAbsolutePath()); - resource = config.resource; - } - - } else { - // start.yml enable = false / so we'll use default config - File runtimeYml = new File(Runtime.ROOT_CONFIG_DIR + File.separator + "default" + File.separator + "runtime.yml"); - if (runtimeYml.exists()) { - // parse that file look for resource: entry in file - RuntimeConfig config = (RuntimeConfig) CodecUtils.readServiceConfig(runtimeYml.getAbsolutePath()); - resource = config.resource; - } - } - - } catch (IOException e) { - // problem getting or parsing - // going to assume default "resource" + FileIO.toFile("start.yml", defaultStartFile); + } catch (IOException ex) { + log.error("could not save start.yml", ex); } - } // no startYml - return resource; - } else { - // Runtime is available - ask it - return Runtime.getInstance().getConfig().resource; + } } + log.info("start.yml exists {} {}", checkStartYml.exists(), CodecUtils.toJson(startYml)); + return startYml; } + + public static String getId() { + if (config == null) { + loadRuntimeConfig(null); + } + return config.id; + } + + /** + * If Runtime.releaseAll is called the statics here should be reset + */ + public static void reset() { + config = null; + } + } diff --git a/src/main/java/org/myrobotlab/framework/CmdOptions.java b/src/main/java/org/myrobotlab/framework/CmdOptions.java index f0eb00c0e7..2c357e8db6 100644 --- a/src/main/java/org/myrobotlab/framework/CmdOptions.java +++ b/src/main/java/org/myrobotlab/framework/CmdOptions.java @@ -26,9 +26,7 @@ * </pre> */ @Command(name = "java -jar myrobotlab.jar ") -public class CmdOptions { - - public final String DEFAULT_CONNECT = "http://localhost:8888"; +public class CmdOptions { static boolean contains(List<String> l, String flag) { for (String f : l) { @@ -39,51 +37,28 @@ static boolean contains(List<String> l, String flag) { return false; } - // launcher ?? - @Option(names = { "-a", "--auto-update" }, description = "auto updating - this feature allows mrl instances to be automatically updated when a new version is available") - public boolean autoUpdate = false; - // launcher @Option(names = { "-c", - "--config" }, fallbackValue="default", description = "Specify a configuration set to start. The config set is a directory which has all the necessary configuration files. It loads runtime.yml first, and subsequent service configuration files will then load. \n example: --config data/config/my-config-dir") + "--config" }, fallbackValue = "default", description = "Specify a configuration set to start. The config set is a directory which has all the necessary configuration files. It loads runtime.yml first, and subsequent service configuration files will then load. \n example: --config data/config/my-config-dir") public String config = null; - @Option(names = { - "--connect" }, arity = "0..*", /* - * defaultValue = DEFAULT_CONNECT, - */ fallbackValue = DEFAULT_CONNECT, description = "connects this mrl instance to another mrl instance - default is " + DEFAULT_CONNECT) - public String connect = null; - @Option(names = { "-h", "-?", "--help" }, description = "shows help") public boolean help = false; - - @Option(names = { "-r", "--config-root" }, description = "sets configuration root, the root for which all config directories are in") - public String configRoot = null; - - - @Option(names = { "--id" }, description = "process identifier to be mdns or network overlay name for this instance - one is created at random if not assigned") + @Option(names = { + "--id" }, description = "process identifier to be mdns or network overlay name for this instance - one is created at random if not assigned") public String id; @Option(names = { "-i", "--install" }, arity = "0..*", description = "installs all dependencies for all services, --install {serviceType} installs dependencies for a specific service, if no type is specified then all services are installed") public String install[]; - @Option(names = { "-I", - "--invoke" }, arity = "0..*", description = "invokes a method on a service --invoke {serviceName} {method} {param0} {param1} ... : --invoke python execFile myFile.py") - public String invoke[]; - - // for launcher @Option(names = { "-j", "--jvm" }, arity = "0..*", description = "jvm parameters for the instance of mrl") public String jvm; - @Option(names = { "-l", "--log-level" }, description = "log level - helpful for troubleshooting [debug info warn error]") + @Option(names = { "-l", + "--log-level" }, description = "log level - helpful for troubleshooting [debug info warn error]") public String logLevel = "info"; - @Option(names = { "--log-file" }, description = "log file name [myrobotlab.log]") - public String logFile = "myrobotlab.log"; - - // FIXME - highlight or italics for examples !! - // launcher @Option(names = { "-m", "--memory" }, description = "adjust memory can e.g. -m 2g \n -m 128m") public String memory = null; @@ -91,9 +66,6 @@ static boolean contains(List<String> l, String flag) { "--services" }, arity = "0..*", description = "services requested on startup, the services must be {name} {Type} paired, e.g. gui SwingGui webgui WebGui servo Servo ...") public List<String> services = new ArrayList<>(); - @Option(names = { "-V", "--virtual" }, description = "sets global environment as virtual - all services which support virtual hardware will create virtual hardware") - public boolean virtual = false; - public CmdOptions() { } @@ -133,34 +105,18 @@ public static String toString(String[] cmdLine) { * * @return the list of output command * @throws IOException - * boom + * boom * */ public List<String> getOutputCmd() throws IOException { List<String> cmd = new ArrayList<>(); - if (autoUpdate) { - cmd.add("-a"); - } - if (config != null) { cmd.add("--config"); cmd.add(config); } - if (connect != null) { - cmd.add("-c"); - cmd.add(connect); - } - - if (invoke != null) { - cmd.add("-I"); - for (int i = 0; i < invoke.length; ++i) { - cmd.add(invoke[i]); - } - } - if (help) { cmd.add("-h"); } @@ -206,10 +162,6 @@ public List<String> getOutputCmd() throws IOException { cmd.add(s); } - if (virtual) { - cmd.add("-v"); - } - return cmd; } diff --git a/src/main/java/org/myrobotlab/framework/Platform.java b/src/main/java/org/myrobotlab/framework/Platform.java index 1b1ed4f2d5..5742bf365e 100644 --- a/src/main/java/org/myrobotlab/framework/Platform.java +++ b/src/main/java/org/myrobotlab/framework/Platform.java @@ -13,6 +13,7 @@ import java.util.TreeMap; import java.util.zip.ZipFile; +import org.myrobotlab.config.ConfigUtils; // Do not pull in deps to this class ! import org.myrobotlab.io.FileIO; import org.myrobotlab.logging.Level; @@ -64,13 +65,7 @@ public class Platform implements Serializable { String vmName; String vmVersion; String mrlVersion; - boolean isVirtual = false; - /** - * Static identifier to identify the "instance" of myrobotlab - similar to - * network ip of a device and used in a similar way - */ - String id; String branch; String pid; @@ -95,7 +90,7 @@ public class Platform implements Serializable { * All data should be accessed through public functions on the local instance. * If the local instance is desired. If its from a serialized instance, the * "getters" will be retrieving appropriate info for that serialized instance. - * + * * @return - return the local instance of the current platform */ public static Platform getLocalInstance() { @@ -121,7 +116,8 @@ public static Platform getLocalInstance() { // === ARCH === String arch = System.getProperty("os.arch").toLowerCase(); - if ("i386".equals(arch) || "i486".equals(arch) || "i586".equals(arch) || "i686".equals(arch) || "amd64".equals(arch) || arch.startsWith("x86")) { + if ("i386".equals(arch) || "i486".equals(arch) || "i586".equals(arch) || "i686".equals(arch) + || "amd64".equals(arch) || arch.startsWith("x86")) { platform.arch = "x86"; // don't care at the moment } @@ -159,7 +155,8 @@ public static Platform getLocalInstance() { // tries very hard to hide this from running programs String procArch = System.getenv("PROCESSOR_ARCHITECTURE"); String procArchWow64 = System.getenv("PROCESSOR_ARCHITEW6432"); - platform.osBitness = (procArch != null && procArch.endsWith("64") || procArchWow64 != null && procArchWow64.endsWith("64")) ? 64 : 32; + platform.osBitness = (procArch != null && procArch.endsWith("64") + || procArchWow64 != null && procArchWow64.endsWith("64")) ? 64 : 32; switch (arch) { case "x86": case "i386": @@ -460,19 +457,6 @@ public String toString() { return String.format("%s.%d.%s", arch, jvmBitness, os); } - /** - * @return The instance identifier of the current running myrobotlab. Used for - * connecting multiple myrobotlabs together - * - */ - public String getId() { - // null ids are not allowed - if (id == null) { - id = NameGenerator.getName(); - } - return id; - } - /** * @return The Computer's hostname */ @@ -480,15 +464,6 @@ public String getHostname() { return hostname; } - /** - * @param newId - * Set your own instance identifier - * - */ - public void setId(String newId) { - id = newId; - } - /** * @return the time when this instance was started * @@ -497,20 +472,6 @@ public Date getStartTime() { return startTime; } - /** - * @return true if running in virtual mode - * - */ - public static boolean isVirtual() { - Platform p = getLocalInstance(); - return p.isVirtual; - } - - public static void setVirtual(boolean b) { - Platform p = getLocalInstance(); - p.isVirtual = b; - } - public static void main(String[] args) { try { LoggingFactory.init(Level.DEBUG); diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java index 95e23a5616..0449c05e16 100644 --- a/src/main/java/org/myrobotlab/framework/Service.java +++ b/src/main/java/org/myrobotlab/framework/Service.java @@ -619,7 +619,7 @@ public Service(String reservedKey, String inId) { // necessary for serialized transport\ if (inId == null) { - id = Platform.getLocalInstance().getId(); + id = ConfigUtils.getId(); log.debug("creating local service for id {}", id); } else { id = inId; @@ -670,7 +670,7 @@ public Service(String reservedKey, String inId) { // register this service if local - if we are a foreign service, we probably // are being created in a // registration already - if (id.equals(Platform.getLocalInstance().getId())) { + if (id.equals(ConfigUtils.getId())) { Registration registration = new Registration(this); Runtime.register(registration); } @@ -1504,7 +1504,7 @@ public ServiceConfig getFilteredConfig() { // The StringUtils.removeEnd() call is a no-op when the ID is not our // local ID, // so doesn't conflict with remote routes - Listener newConfigListener = new Listener(listener.topicMethod, StringUtil.removeEnd(listener.callbackName, '@' + Platform.getLocalInstance().getId()), + Listener newConfigListener = new Listener(listener.topicMethod, StringUtil.removeEnd(listener.callbackName, '@' + Runtime.getInstance().getId()), listener.callbackMethod); newListeners.add(newConfigListener); } diff --git a/src/main/java/org/myrobotlab/framework/StartYml.java b/src/main/java/org/myrobotlab/framework/StartYml.java index c8bfb25a44..b1806d203c 100644 --- a/src/main/java/org/myrobotlab/framework/StartYml.java +++ b/src/main/java/org/myrobotlab/framework/StartYml.java @@ -9,10 +9,6 @@ * */ public class StartYml { - /** - * instance id of myrobotlab, default will be dynamically generated - */ - public String id; /** * configuration set to start under /data/config/{configName} diff --git a/src/main/java/org/myrobotlab/framework/repo/MavenWrapper.java b/src/main/java/org/myrobotlab/framework/repo/MavenWrapper.java index 8374f92cdf..62c0027cbd 100644 --- a/src/main/java/org/myrobotlab/framework/repo/MavenWrapper.java +++ b/src/main/java/org/myrobotlab/framework/repo/MavenWrapper.java @@ -1,5 +1,5 @@ package org.myrobotlab.framework.repo; - +import org.myrobotlab.service.Runtime; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -281,6 +281,8 @@ public static void main(String[] args) { LoggingFactory.init(Level.INFO); + Runtime.getInstance(); + File libraries = new File(ServiceData.LIBRARIES); libraries.mkdir(); File cache = new File(ServiceData.LIBRARIES + File.separator + "serviceData.json"); @@ -309,7 +311,7 @@ public static void main(String[] args) { // repo.installTo(dir); // repo.install(); // repo.installEach(); <-- TODO - test - + Runtime.shutdown(); log.info("done"); } catch (Exception e) { diff --git a/src/main/java/org/myrobotlab/process/Launcher.java b/src/main/java/org/myrobotlab/process/Launcher.java index 3e819f3c59..7954fd7d5d 100644 --- a/src/main/java/org/myrobotlab/process/Launcher.java +++ b/src/main/java/org/myrobotlab/process/Launcher.java @@ -2,9 +2,6 @@ import java.io.File; import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -241,41 +238,15 @@ public static void main(String[] args) { return; } - boolean instanceAlreadyRunning = false; - - try { - URI uri = new URI(options.connect); - Socket socket = new Socket(); - socket.connect(new InetSocketAddress(uri.getHost(), uri.getPort()), 1000); - socket.close(); - instanceAlreadyRunning = true; - } catch (Exception e) { - log.info("could not connect to {}", options.connect); + log.info("spawning new instance"); + ProcessBuilder builder = createBuilder(options); + process = builder.start(); + if (process.isAlive()) { + log.info("process is alive"); + } else { + log.error("process died"); } - if (instanceAlreadyRunning && options.connect.equals(options.DEFAULT_CONNECT)) { - log.error("zombie instance already running at {}", options.DEFAULT_CONNECT); - return; - } - - if (!instanceAlreadyRunning || !options.connect.equals(options.DEFAULT_CONNECT)) { - log.info("spawning new instance"); - ProcessBuilder builder = createBuilder(options); - process = builder.start(); - if (process.isAlive()) { - log.info("process is alive"); - } else { - log.error("process died"); - } - } - - /* - * // FIXME - use wsclient for remote access if (options.client != null) { - * // FIXME - delay & auto connect Client.main(new String[] { "-c", - * options.client }); } else { // terminating - "if" runtime exists - if - * not no biggy Runtime.shutdown(); } - */ - } catch (Exception e) { log.error("main threw", e); } diff --git a/src/main/java/org/myrobotlab/service/Hd44780.java b/src/main/java/org/myrobotlab/service/Hd44780.java index 9294fee992..72a4039585 100644 --- a/src/main/java/org/myrobotlab/service/Hd44780.java +++ b/src/main/java/org/myrobotlab/service/Hd44780.java @@ -687,7 +687,7 @@ public void preShutdown() { public static void main(String[] args) { try { LoggingFactory.init(Level.INFO); - Platform.setVirtual(false); + Runtime.getInstance().setVirtual(false); Runtime.start("webgui", "WebGui"); Pcf8574 pcf = (Pcf8574) Runtime.start("pcf8574t", "Pcf8574"); diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 0c4486c40c..e6ad6326ab 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -2178,7 +2178,6 @@ public void startAll() throws Exception { @Deprecated /* use startPeers */ public void startAll(String leftPort, String rightPort) throws Exception { - startMouth(); startChatBot(); // startHeadTracking(); @@ -2289,47 +2288,6 @@ public void startHeartbeat() { heart.start(); } - // 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 (Platform.isVirtual()) { - speakBlocking(get("STARTINGVIRTUALHARD")); - } - speakBlocking(get("WHATISTHISLANGUAGE")); - - return mouth; - } - @Deprecated /* use startPeer */ public OpenCV startOpenCV() { speakBlocking(get("STARTINGOPENCV")); diff --git a/src/main/java/org/myrobotlab/service/JMonkeyEngine.java b/src/main/java/org/myrobotlab/service/JMonkeyEngine.java index 2d19048c84..af2d354f43 100644 --- a/src/main/java/org/myrobotlab/service/JMonkeyEngine.java +++ b/src/main/java/org/myrobotlab/service/JMonkeyEngine.java @@ -2489,7 +2489,7 @@ public static void main(String[] args) { i01.startPeer("simulator"); } - Platform.setVirtual(true); + Runtime.getInstance().setVirtual(true); // Runtime.main(new String[] { "--interactive", "--id", "admin" }); JMonkeyEngine jme = (JMonkeyEngine) Runtime.start("simulator", "JMonkeyEngine"); diff --git a/src/main/java/org/myrobotlab/service/MotorDualPwm.java b/src/main/java/org/myrobotlab/service/MotorDualPwm.java index e1746ca071..3187e52ee0 100644 --- a/src/main/java/org/myrobotlab/service/MotorDualPwm.java +++ b/src/main/java/org/myrobotlab/service/MotorDualPwm.java @@ -96,7 +96,7 @@ public static void main(String[] args) { LoggingFactory.init(Level.INFO); String arduinoPort = "COM5"; - Platform.setVirtual(true); + Runtime.getInstance().setVirtual(true); Runtime.startConfig("dev"); Runtime.start("webgui", "WebGui"); MotorDualPwm motor = (MotorDualPwm) Runtime.start("motor", "MotorDualPwm"); diff --git a/src/main/java/org/myrobotlab/service/Mqtt.java b/src/main/java/org/myrobotlab/service/Mqtt.java index 8466016039..b8e27bcc38 100644 --- a/src/main/java/org/myrobotlab/service/Mqtt.java +++ b/src/main/java/org/myrobotlab/service/Mqtt.java @@ -570,7 +570,7 @@ public void messageArrived(String topic, MqttMessage message) throws MqttExcepti // 4. describe new instance for me // FIXME why isn't this using Gateway.getDescribeMessage()? Message describe = Message.createMessage(String.format("%s@%s", getName(), getId()), "runtime@" + remoteId, "describe", - new Object[] { Gateway.FILL_UUID_MAGIC_VAL, new DescribeQuery(Platform.getLocalInstance().getId(), uuid) }); + new Object[] { Gateway.FILL_UUID_MAGIC_VAL, new DescribeQuery(Runtime.getInstance().getId(), uuid) }); describe.sendingMethod = "onConnect"; sendRemote(describe); diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 4976d8c349..36fe72a3f8 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -47,6 +47,7 @@ import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.codec.CodecUtils.ApiDescription; import org.myrobotlab.codec.ForeignProcessUtils; +import org.myrobotlab.config.ConfigUtils; import org.myrobotlab.framework.CmdOptions; import org.myrobotlab.framework.DescribeQuery; import org.myrobotlab.framework.DescribeResults; @@ -129,7 +130,7 @@ * */ public class Runtime extends Service<RuntimeConfig> implements MessageListener, ServiceLifeCyclePublisher, RemoteMessageHandler, ConnectionManager, Gateway, LocaleProvider { - + final static private long serialVersionUID = 1L; // FIXME - AVOID STATIC FIELDS !!! use .getInstance() to get the singleton @@ -167,6 +168,8 @@ public class Runtime extends Service<RuntimeConfig> implements MessageListener, protected final Map<String, Set<String>> typeToInterface = new HashMap<>(); + private static final Object processLock = new Object(); + /** * FILTERED_INTERFACES are the set of low level interfaces which we are * interested in filtering out if we want to maintain a data structure which @@ -188,7 +191,13 @@ public class Runtime extends Service<RuntimeConfig> implements MessageListener, * name. It cannot be null, it cannot have "/" or "\" in the name - it has to * be a valid file name for the OS. It's defaulted to "default". Changed often */ - protected String configName = "default"; + protected static String configName = "default"; + + /** + * The runtime config which Runtime was started with. This is the config which + * will be applied to Runtime when its created on startup. + */ + // protected static RuntimeConfig startConfig = null; /** * State variable reporting if runtime is currently starting services from @@ -378,30 +387,32 @@ static public ServiceInterface create(String name) { * - Can be null if a service file exists for named service * @return the service */ - static public synchronized ServiceInterface create(String name, String type) { + static public ServiceInterface create(String name, String type) { - try { - ServiceInterface si = Runtime.getService(name); - if (si != null) { - return si; - } + synchronized (processLock) { - // FIXME remove configName from loadService - Plan plan = Runtime.load(name, type); - Runtime.check(name, type); - // at this point - the plan should be loaded, now its time to create the - // children peers - // and parent service - createServicesFromPlan(plan, null, name); - si = Runtime.getService(name); - if (si == null) { - Runtime.getInstance().error("coult not create %s of type %s", name, type); + try { + ServiceInterface si = Runtime.getService(name); + if (si != null) { + return si; + } + + Plan plan = Runtime.load(name, type); + Runtime.check(name, type); + // at this point - the plan should be loaded, now its time to create the + // children peers + // and parent service + createServicesFromPlan(plan, null, name); + si = Runtime.getService(name); + if (si == null) { + Runtime.getInstance().error("coult not create %s of type %s", name, type); + } + return si; + } catch (Exception e) { + runtime.error(e); } - return si; - } catch (Exception e) { - runtime.error(e); + return null; } - return null; } /** @@ -414,43 +425,46 @@ static public synchronized ServiceInterface create(String name, String type) { * @param name * @return */ - synchronized private static Map<String, ServiceInterface> createServicesFromPlan(Plan plan, Map<String, ServiceInterface> createdServices, String name) { - - if (createdServices == null) { - createdServices = new LinkedHashMap<>(); - } + private static Map<String, ServiceInterface> createServicesFromPlan(Plan plan, Map<String, ServiceInterface> createdServices, String name) { - // Plan's config - RuntimeConfig plansRtConfig = (RuntimeConfig) plan.get("runtime"); - // current Runtime config - RuntimeConfig currentConfig = Runtime.getInstance().config; + synchronized (processLock) { - for (String service : plansRtConfig.getRegistry()) { - // FIXME - determine if you want to return a complete merge of activated - // or just "recent" - if (Runtime.getService(service) != null) { - continue; + if (createdServices == null) { + createdServices = new LinkedHashMap<>(); } - ServiceConfig sc = plan.get(service); - if (sc == null) { - runtime.error("could not get %s from plan", service); - continue; - } - ServiceInterface si = createService(service, sc.type, null); - // process the base listeners/subscription of ServiceConfig - si.addConfigListeners(sc); - if (si instanceof ConfigurableService) { - try { - ((ConfigurableService) si).apply(sc); - } catch (Exception e) { - Runtime.getInstance().error("could not apply config of type %s to service %s, using default config", sc.type, si.getName(), sc.type); + + // Plan's config + RuntimeConfig plansRtConfig = (RuntimeConfig) plan.get("runtime"); + // current Runtime config + RuntimeConfig currentConfig = Runtime.getInstance().config; + + for (String service : plansRtConfig.getRegistry()) { + // FIXME - determine if you want to return a complete merge of activated + // or just "recent" + if (Runtime.getService(service) != null) { + continue; + } + ServiceConfig sc = plan.get(service); + if (sc == null) { + runtime.error("could not get %s from plan", service); + continue; + } + ServiceInterface si = createService(service, sc.type, null); + // process the base listeners/subscription of ServiceConfig + si.addConfigListeners(sc); + if (si instanceof ConfigurableService) { + try { + ((ConfigurableService) si).apply(sc); + } catch (Exception e) { + Runtime.getInstance().error("could not apply config of type %s to service %s, using default config", sc.type, si.getName(), sc.type); + } } + createdServices.put(service, si); + currentConfig.add(service); } - createdServices.put(service, si); - currentConfig.add(service); - } - return createdServices; + return createdServices; + } } public String getServiceExample(String serviceType) { @@ -601,7 +615,6 @@ public boolean setVirtual(boolean b) { * @return b */ static public boolean setAllVirtual(boolean b) { - Platform.setVirtual(b); for (ServiceInterface si : getServices()) { if (!si.isRuntime()) { si.setVirtual(b); @@ -624,7 +637,6 @@ static public boolean setAllVirtual(boolean b) { */ public void setAutoStart(boolean autoStart) throws IOException { log.debug("setAutoStart {}", autoStart); - startYml.id = getId(); startYml.enable = autoStart; startYml.config = configName; FileIO.toFile("start.yml", CodecUtils.toYaml(startYml)); @@ -659,126 +671,128 @@ public void setAutoStart(boolean autoStart) throws IOException { * '/', or a service with the same name exists but has a different * type, will return null instead. */ - static private synchronized ServiceInterface createService(String name, String type, String inId) { - log.info("Runtime.createService {}", name); + static private ServiceInterface createService(String name, String type, String inId) { + synchronized (processLock) { + log.info("Runtime.createService {}", name); - if (name == null) { - runtime.error("service name cannot be null"); + if (name == null) { + runtime.error("service name cannot be null"); - return null; - } + return null; + } - if (name.contains("@") || name.contains("/")) { - runtime.error("service name cannot contain '@' or '/': {}", name); + if (name.contains("@") || name.contains("/")) { + runtime.error("service name cannot contain '@' or '/': {}", name); - return null; - } + return null; + } - String fullName; - if (inId == null || inId.equals("")) - fullName = getFullName(name); - else - fullName = String.format("%s@%s", name, inId); + String fullName; + if (inId == null || inId.equals("")) + fullName = getFullName(name); + else + fullName = String.format("%s@%s", name, inId); - if (type == null) { - ServiceConfig sc; - try { - sc = CodecUtils.readServiceConfig(runtime.getConfigName() + fs + name + ".yml"); - } catch (IOException e) { - runtime.error("could not find type for service %s", name); - return null; + if (type == null) { + ServiceConfig sc; + try { + sc = CodecUtils.readServiceConfig(runtime.getConfigName() + fs + name + ".yml"); + } catch (IOException e) { + runtime.error("could not find type for service %s", name); + return null; + } + if (sc != null) { + log.info("found type for {} in plan", name); + type = sc.type; + } else { + runtime.error("createService type not specified and could not get type for {} from plan", name); + return null; + } } - if (sc != null) { - log.info("found type for {} in plan", name); - type = sc.type; - } else { - runtime.error("createService type not specified and could not get type for {} from plan", name); + + if (type == null) { + runtime.error("cannot create service {} no type in plan or yml file", name); return null; } - } - if (type == null) { - runtime.error("cannot create service {} no type in plan or yml file", name); - return null; - } + String fullTypeName = CodecUtils.makeFullTypeName(type); + + ServiceInterface si = Runtime.getService(fullName); + if (si != null) { + if (!si.getTypeKey().equals(fullTypeName)) { + runtime.error("Service with name {} already exists but is of type {} while requested type is ", name, si.getTypeKey(), type); + return null; + } + return si; + } - String fullTypeName = CodecUtils.makeFullTypeName(type); + // DO NOT LOAD HERE !!! - doing so would violate the service life cycle ! + // only try to resolve type by the plan - if not then error out - ServiceInterface si = Runtime.getService(fullName); - if (si != null) { - if (!si.getTypeKey().equals(fullTypeName)) { - runtime.error("Service with name {} already exists but is of type {} while requested type is ", name, si.getTypeKey(), type); + String id = (inId == null) ? Runtime.getInstance().getId() : inId; + if (name.length() == 0 || fullTypeName == null || fullTypeName.length() == 0) { + log.error("{} not a type or {} not defined ", fullTypeName, name); return null; } - return si; - } - - // DO NOT LOAD HERE !!! - doing so would violate the service life cycle ! - // only try to resolve type by the plan - if not then error out - String id = (inId == null) ? Platform.getLocalInstance().getId() : inId; - if (name.length() == 0 || fullTypeName == null || fullTypeName.length() == 0) { - log.error("{} not a type or {} not defined ", fullTypeName, name); - return null; - } + // TODO - test new create of existing service + ServiceInterface sw = Runtime.getService(String.format("%s@%s", name, id)); + if (sw != null) { + log.info("service {} already exists", name); + return sw; + } - // TODO - test new create of existing service - ServiceInterface sw = Runtime.getService(String.format("%s@%s", name, id)); - if (sw != null) { - log.info("service {} already exists", name); - return sw; - } + try { - try { + if (log.isDebugEnabled()) { + // TODO - determine if there have been new classes added from + // ivy --> Boot Classloader --> Ext ClassLoader --> System + // ClassLoader + // http://blog.jamesdbloom.com/JVMInternals.html + log.debug("ABOUT TO LOAD CLASS"); + log.debug("loader for this class " + Runtime.class.getClassLoader().getClass().getCanonicalName()); + log.debug("parent " + Runtime.class.getClassLoader().getParent().getClass().getCanonicalName()); + log.debug("system class loader " + ClassLoader.getSystemClassLoader()); + log.debug("parent should be null" + ClassLoader.getSystemClassLoader().getParent().getClass().getCanonicalName()); + log.debug("thread context " + Thread.currentThread().getContextClassLoader().getClass().getCanonicalName()); + log.debug("thread context parent " + Thread.currentThread().getContextClassLoader().getParent().getClass().getCanonicalName()); + } - if (log.isDebugEnabled()) { - // TODO - determine if there have been new classes added from - // ivy --> Boot Classloader --> Ext ClassLoader --> System - // ClassLoader - // http://blog.jamesdbloom.com/JVMInternals.html - log.debug("ABOUT TO LOAD CLASS"); - log.debug("loader for this class " + Runtime.class.getClassLoader().getClass().getCanonicalName()); - log.debug("parent " + Runtime.class.getClassLoader().getParent().getClass().getCanonicalName()); - log.debug("system class loader " + ClassLoader.getSystemClassLoader()); - log.debug("parent should be null" + ClassLoader.getSystemClassLoader().getParent().getClass().getCanonicalName()); - log.debug("thread context " + Thread.currentThread().getContextClassLoader().getClass().getCanonicalName()); - log.debug("thread context parent " + Thread.currentThread().getContextClassLoader().getParent().getClass().getCanonicalName()); - } + // FIXME - error if deps are missing - prompt license + // require restart ! + // FIXME - this should happen after inspecting the "loaded" "plan" not + // during the create/start/apply ! + + // create an instance + Object newService = Instantiator.getThrowableNewInstance(null, fullTypeName, name, id); + log.debug("returning {}", fullTypeName); + si = (ServiceInterface) newService; + + // si.setId(id); + if (Runtime.getInstance().getId().equals(id)) { + si.setVirtual(Runtime.getInstance().isVirtual()); + Runtime.getInstance().creationCount++; + si.setOrder(Runtime.getInstance().creationCount); + } - // FIXME - error if deps are missing - prompt license - // require restart ! - // FIXME - this should happen after inspecting the "loaded" "plan" not - // during the create/start/apply ! - - // create an instance - Object newService = Instantiator.getThrowableNewInstance(null, fullTypeName, name, id); - log.debug("returning {}", fullTypeName); - si = (ServiceInterface) newService; - - // si.setId(id); - if (Platform.getLocalInstance().getId().equals(id)) { - si.setVirtual(Platform.isVirtual()); - Runtime.getInstance().creationCount++; - si.setOrder(Runtime.getInstance().creationCount); - } + if (runtime != null) { - if (runtime != null) { + runtime.invoke("created", getFullName(name)); - runtime.invoke("created", getFullName(name)); + // add all the service life cycle subscriptions + // runtime.addListener("registered", name); + // runtime.addListener("created", name); + // runtime.addListener("started", name); + // runtime.addListener("stopped", name); + // runtime.addListener("released", name); + } - // add all the service life cycle subscriptions - // runtime.addListener("registered", name); - // runtime.addListener("created", name); - // runtime.addListener("started", name); - // runtime.addListener("stopped", name); - // runtime.addListener("released", name); + return (Service) newService; + } catch (Exception e) { + log.error("createService failed for {}@{} of type {}", name, id, fullTypeName, e); } - - return (Service) newService; - } catch (Exception e) { - log.error("createService failed for {}@{} of type {}", name, id, fullTypeName, e); + return null; } - return null; } static public Map<String, Map<String, List<MRLListener>>> getNotifyEntries() { @@ -885,67 +899,47 @@ public static final long getFreeMemory() { public static Runtime getInstance() { if (runtime == null) { synchronized (INSTANCE_LOCK) { - if (runtime == null) { + try { - // all though this is appropriate it cannot be done - // because you need runtime to correctly load/start/etc the plan - // so it needs to be bootstrapped - // load("runtime", "Runtime"); + RuntimeConfig c = null; + if (runtime == null) { + c = ConfigUtils.loadRuntimeConfig(options); - // just create Runtime - runtime = (Runtime) createService(RUNTIME_NAME, "Runtime", Platform.getLocalInstance().getId()); - } - try { - // a bit backwards - it loads after it been created - // but its necessary because you need an runtime instance before you - // load - - File cfgRoot = new File(ROOT_CONFIG_DIR); - cfgRoot.mkdirs(); - if (startYml.enable) { - Runtime.load("runtime", "Runtime"); - } - runtime.config.add("runtime"); + runtime = (Runtime) createService(RUNTIME_NAME, "Runtime", c.id); + runtime.startService(); + // klunky + Runtime.register(new Registration(runtime)); - runtime.startService(); - // platform virtual is higher priority than service virtual - Runtime.setAllVirtual(Platform.isVirtual()); + // assign, do not apply otherwise there will be + // a chicken-egg problem + runtime.config = c; + } runtime.getRepo().addStatusPublisher(runtime); + runtime.startService(); + // extract resources "if a jar" FileIO.extractResources(); - // protected services we don't want to remove when releasing a config - runtime.startingServices.add("runtime"); - runtime.startingServices.add("security"); - runtime.startingServices.add("webgui"); - runtime.startingServices.add("python"); - runtime.startInteractiveMode(); - try { - if (options.config != null) { - Runtime.startConfig(options.config); - } else if (startYml != null && startYml.config != null && startYml.enable) { - Runtime.startConfig(startYml.config); - } else { - RuntimeConfig rtConfig = runtime.readServiceConfig(runtime.getConfigName(), "runtime", new StaticType<>() { - }); - if (rtConfig != null) { - runtime.apply(rtConfig); - } - } - + if (Runtime.options.install != null) { + // minimal processed runtime - return it + return runtime; + } - // FIXME - should simply set default RuntimeConfig services and include security - // setting the singleton security - Security.getInstance(); + runtime.apply(c); - - } catch (Exception e) { - log.info("runtime will not be loading config"); + if (options.services != null) { + log.info("command line override for services created"); + createAndStartServices(options.services); + } else { + log.info("processing config.registry"); + if (startYml.enable) { + Runtime.startConfig(startYml.config); + } } } catch (Exception e) { - log.error("runtime will not be loading config", e); + log.error("runtime getInstance threw", e); } } // synchronized lock } @@ -1111,7 +1105,7 @@ public static Map<String, ServiceInterface> getLocalServices() { Map<String, ServiceInterface> local = new HashMap<>(); for (String serviceName : registry.keySet()) { // FIXME @ should be a requirement of "all" entries for consistency - if (!serviceName.contains("@") || serviceName.endsWith(String.format("@%s", Platform.getLocalInstance().getId()))) { + if (!serviceName.contains("@") || serviceName.endsWith(String.format("@%s", Runtime.getInstance().getId()))) { local.put(serviceName, registry.get(serviceName)); } } @@ -1161,8 +1155,10 @@ public static Map<String, MethodEntry> getMethodMap(String inName) { * * @return list of registrations */ - synchronized public List<Registration> getServiceList() { - return registry.values().stream().map(si -> new Registration(si.getId(), si.getName(), si.getTypeKey())).collect(Collectors.toList()); + public List<Registration> getServiceList() { + synchronized (processLock) { + return registry.values().stream().map(si -> new Registration(si.getId(), si.getName(), si.getTypeKey())).collect(Collectors.toList()); + } } // FIXME - scary function - returns private data @@ -1208,10 +1204,16 @@ public static <S extends ServiceInterface> S getService(String inName, StaticTyp * */ static public String[] getServiceNames() { - Set<String> ret = registry.keySet(); + Set<String> ret = registry.keySet(); String[] services = new String[ret.size()]; - - String localId = Platform.getLocalInstance().getId(); + if (ret.size() == 0) { + return services; + } + + // if there are more than 0 services we need runtime + // to filter to make sure they are "local" + // and this requires a runtime service + String localId = Runtime.getInstance().getId(); int cnt = 0; for (String fullname : ret) { if (fullname.endsWith(String.format("@%s", localId))) { @@ -1356,22 +1358,24 @@ public ServiceTypeNameResults getServiceTypeNamesFromInterface(String interfaze) * no longer used or needed - change events are pushed no longer * pulled <-- Over complicated solution */ - public static synchronized List<ServiceInterface> getServicesFromInterface(Class<?> interfaze) { - List<ServiceInterface> ret = new ArrayList<ServiceInterface>(); - - for (String service : getServiceNames()) { - Class<?> clazz = getService(service).getClass(); - while (clazz != null) { - for (Class<?> inter : clazz.getInterfaces()) { - if (inter.getName().equals(interfaze.getName())) { - ret.add(getService(service)); - continue; + public static List<ServiceInterface> getServicesFromInterface(Class<?> interfaze) { + synchronized (processLock) { + List<ServiceInterface> ret = new ArrayList<ServiceInterface>(); + + for (String service : getServiceNames()) { + Class<?> clazz = getService(service).getClass(); + while (clazz != null) { + for (Class<?> inter : clazz.getInterfaces()) { + if (inter.getName().equals(interfaze.getName())) { + ret.add(getService(service)); + continue; + } } + clazz = clazz.getSuperclass(); } - clazz = clazz.getSuperclass(); } + return ret; } - return ret; } /** @@ -1551,35 +1555,36 @@ static public void install(String serviceType) { * if this should block until done. * */ - synchronized static public void install(String serviceType, Boolean blocking) { - Runtime r = getInstance(); + static public void install(String serviceType, Boolean blocking) { + synchronized (processLock) { + Runtime r = getInstance(); - if (blocking == null) { - blocking = false; - } + if (blocking == null) { + blocking = false; + } - installerThread = new Thread() { - @Override - public void run() { - try { - if (serviceType == null) { - r.getRepo().install(); - } else { - r.getRepo().install(serviceType); + installerThread = new Thread() { + @Override + public void run() { + try { + if (serviceType == null) { + r.getRepo().install(); + } else { + r.getRepo().install(serviceType); + } + } catch (Exception e) { + r.error("dependencies failed - install error", e); + throw new RuntimeException(String.format("dependencies failed - install error %s", e.getMessage())); } - } catch (Exception e) { - r.error("dependencies failed - install error", e); - throw new RuntimeException(String.format("dependencies failed - install error %s", e.getMessage())); } - } - }; + }; - if (blocking) { - installerThread.run(); - } else { - installerThread.start(); + if (blocking) { + installerThread.run(); + } else { + installerThread.start(); + } } - } /** @@ -1620,7 +1625,7 @@ static public void invokeCommands(String[] invoke) { */ public static boolean isLocal(String serviceName) { ServiceInterface sw = getService(serviceName); - return Objects.equals(sw.getId(), Platform.getLocalInstance().getId()); + return Objects.equals(sw.getId(), Runtime.getInstance().getId()); } /* @@ -1717,10 +1722,12 @@ public void onState(ServiceInterface updatedService) { registry.put(String.format("%s@%s", updatedService.getName(), updatedService.getId()), updatedService); } - public static synchronized Registration register(String id, String name, String typeKey, ArrayList<String> interfaces) { - Registration proxy = new Registration(id, name, typeKey, interfaces); - register(proxy); - return proxy; + public static Registration register(String id, String name, String typeKey, ArrayList<String> interfaces) { + synchronized (processLock) { + Registration proxy = new Registration(id, name, typeKey, interfaces); + register(proxy); + return proxy; + } } /** @@ -1744,167 +1751,174 @@ public static synchronized Registration register(String id, String name, String * @return registration * */ - public static synchronized Registration register(Registration registration) { + public static Registration register(Registration registration) { + synchronized (processLock) { + try { - try { + // TODO - have rules on what registrations to accept - dependent on + // security, desire, re-broadcasting configuration etc. - // TODO - have rules on what registrations to accept - dependent on - // security, desire, re-broadcasting configuration etc. + String fullname = String.format("%s@%s", registration.getName(), registration.getId()); + if (registry.containsKey(fullname)) { + log.info("{} already registered", fullname); + return registration; + } - String fullname = String.format("%s@%s", registration.getName(), registration.getId()); - if (registry.containsKey(fullname)) { - log.info("{} already registered", fullname); - return registration; - } + // if (!ForeignProcessUtils.isValidTypeKey(registration.getTypeKey())) { + // log.error("Invalid type key being registered: " + + // registration.getTypeKey()); + // return null; + // } - // if (!ForeignProcessUtils.isValidTypeKey(registration.getTypeKey())) { - // log.error("Invalid type key being registered: " + - // registration.getTypeKey()); - // return null; - // } + log.info("{}@{} registering at {} of type {}", registration.getName(), registration.getId(), ConfigUtils.getId(), registration.getTypeKey()); - log.info("{}@{} registering at {} of type {}", registration.getName(), registration.getId(), Platform.getLocalInstance().getId(), registration.getTypeKey()); + if (!registration.isLocal(ConfigUtils.getId())) { - if (!registration.isLocal(Platform.getLocalInstance().getId())) { + // Check if we're registering a java service + if (ForeignProcessUtils.isValidJavaClassName(registration.getTypeKey())) { - // Check if we're registering a java service - if (ForeignProcessUtils.isValidJavaClassName(registration.getTypeKey())) { + String fullTypeName; + if (registration.getTypeKey().contains(".")) { + fullTypeName = registration.getTypeKey(); + } else { + fullTypeName = String.format("org.myrobotlab.service.%s", registration.getTypeKey()); + } - String fullTypeName; - if (registration.getTypeKey().contains(".")) { - fullTypeName = registration.getTypeKey(); + try { + // de-serialize, class exists + registration.service = Runtime.createService(registration.getName(), fullTypeName, registration.getId()); + if (registration.getState() != null) { + copyShallowFrom(registration.service, CodecUtils.fromJson(registration.getState(), Class.forName(fullTypeName))); + } + } catch (ClassNotFoundException classNotFoundException) { + log.error(String.format("Unknown service class for %s@%s: %s", registration.getName(), registration.getId(), registration.getTypeKey()), classNotFoundException); + return null; + } } else { - fullTypeName = String.format("org.myrobotlab.service.%s", registration.getTypeKey()); - } - - try { - // de-serialize, class exists - registration.service = Runtime.createService(registration.getName(), fullTypeName, registration.getId()); - if (registration.getState() != null) { - copyShallowFrom(registration.service, CodecUtils.fromJson(registration.getState(), Class.forName(fullTypeName))); + // We're registering a foreign process service. We don't need to + // check + // ForeignProcessUtils.isForeignTypeKey() because the type key is + // valid + // but is not a java class name + + // Class does not exist, check if registration has empty interfaces + // Interfaces should always include ServiceInterface if coming from + // remote client + if (registration.interfaces == null || registration.interfaces.isEmpty()) { + log.error("Unknown service type being registered, registration does not contain any " + "interfaces for proxy generation: " + registration.getTypeKey()); + return null; } - } catch (ClassNotFoundException classNotFoundException) { - log.error(String.format("Unknown service class for %s@%s: %s", registration.getName(), registration.getId(), registration.getTypeKey()), classNotFoundException); - return null; - } - } else { - // We're registering a foreign process service. We don't need to check - // ForeignProcessUtils.isForeignTypeKey() because the type key is - // valid - // but is not a java class name - - // Class does not exist, check if registration has empty interfaces - // Interfaces should always include ServiceInterface if coming from - // remote client - if (registration.interfaces == null || registration.interfaces.isEmpty()) { - log.error("Unknown service type being registered, registration does not contain any " + "interfaces for proxy generation: " + registration.getTypeKey()); - return null; - } - // FIXME - probably some more clear definition about the requirements - // of remote - // service registration - // In general, there should be very few requirements if any, besides - // providing a - // name, and the proxy - // interface should be responsible for creating a minimal - // interpretation - // (ServiceInterface) for the remote - // service - - // Class<?>[] interfaces = registration.interfaces.stream().map(i -> { - // try { - // return Class.forName(i); - // } catch (ClassNotFoundException e) { - // throw new RuntimeException("Unable to load interface " + i + " - // defined in remote registration " + registration, e); - // } - // }).toArray(Class<?>[]::new); - - // registration.service = (ServiceInterface) - // Proxy.newProxyInstance(Runtime.class.getClassLoader(), interfaces, - // new ProxyServiceInvocationHandler(registration.getName(), - // registration.getId())); - try { - registration.service = ProxyFactory.createProxyService(registration); - log.info("Created proxy: " + registration.service); - } catch (Exception e) { - // at the moment preventing throw - Runtime.getInstance().error(e); + // FIXME - probably some more clear definition about the + // requirements + // of remote + // service registration + // In general, there should be very few requirements if any, besides + // providing a + // name, and the proxy + // interface should be responsible for creating a minimal + // interpretation + // (ServiceInterface) for the remote + // service + + // Class<?>[] interfaces = registration.interfaces.stream().map(i -> + // { + // try { + // return Class.forName(i); + // } catch (ClassNotFoundException e) { + // throw new RuntimeException("Unable to load interface " + i + " + // defined in remote registration " + registration, e); + // } + // }).toArray(Class<?>[]::new); + + // registration.service = (ServiceInterface) + // Proxy.newProxyInstance(Runtime.class.getClassLoader(), + // interfaces, + // new ProxyServiceInvocationHandler(registration.getName(), + // registration.getId())); + try { + registration.service = ProxyFactory.createProxyService(registration); + log.info("Created proxy: " + registration.service); + } catch (Exception e) { + // at the moment preventing throw + Runtime.getInstance().error(e); + } } } - } - - registry.put(fullname, registration.service); - - if (runtime != null) { - - String type = registration.getTypeKey(); - - // If type does not exist in typeToNames, make it an empty hash set and - // return it - Set<String> names = runtime.typeToNames.computeIfAbsent(type, k -> new HashSet<>()); - names.add(fullname); - // FIXME - most of this could be static as it represents meta data of - // class and interfaces + registry.put(fullname, registration.service); + + if (runtime != null) { + + String type = registration.getTypeKey(); + + // If type does not exist in typeToNames, make it an empty hash set + // and + // return it + Set<String> names = runtime.typeToNames.computeIfAbsent(type, k -> new HashSet<>()); + names.add(fullname); + + // FIXME - most of this could be static as it represents meta data of + // class and interfaces + + // FIXME - was false - setting now to true .. because + // 1 edge case - "can something fulfill my need of an interface - is + // not + // currently + // switching to true + boolean updatedServiceLists = false; + + // maintaining interface type relations + // see if this service type is new + // PROCESS INDEXES ! - FIXME - will need this in unregister + // ALL CLASS/TYPE PROCESSING only needs to happen once per type + if (!runtime.serviceTypes.contains(type)) { + // CHECK IF "CAN FULFILL" + // add the interfaces of the new service type + Set<String> interfaces = ClassUtil.getInterfaces(registration.service.getClass(), FILTERED_INTERFACES); + for (String interfaze : interfaces) { + Set<String> types = runtime.interfaceToType.get(interfaze); + if (types == null) { + types = new HashSet<>(); + } + types.add(registration.getTypeKey()); + runtime.interfaceToType.put(interfaze, types); + } - // FIXME - was false - setting now to true .. because - // 1 edge case - "can something fulfill my need of an interface - is not - // currently - // switching to true - boolean updatedServiceLists = false; + runtime.typeToInterface.put(type, interfaces); + runtime.serviceTypes.add(registration.getTypeKey()); + updatedServiceLists = true; + } - // maintaining interface type relations - // see if this service type is new - // PROCESS INDEXES ! - FIXME - will need this in unregister - // ALL CLASS/TYPE PROCESSING only needs to happen once per type - if (!runtime.serviceTypes.contains(type)) { - // CHECK IF "CAN FULFILL" - // add the interfaces of the new service type - Set<String> interfaces = ClassUtil.getInterfaces(registration.service.getClass(), FILTERED_INTERFACES); - for (String interfaze : interfaces) { - Set<String> types = runtime.interfaceToType.get(interfaze); - if (types == null) { - types = new HashSet<>(); + // check to see if any of our interfaces can fulfill requested ones + Set<String> myInterfaces = runtime.typeToInterface.get(type); + for (String inter : myInterfaces) { + if (runtime.interfaceToNames.containsKey(inter)) { + runtime.interfaceToNames.get(inter).add(fullname); + updatedServiceLists = true; } - types.add(registration.getTypeKey()); - runtime.interfaceToType.put(interfaze, types); } - runtime.typeToInterface.put(type, interfaces); - runtime.serviceTypes.add(registration.getTypeKey()); - updatedServiceLists = true; - } - - // check to see if any of our interfaces can fulfill requested ones - Set<String> myInterfaces = runtime.typeToInterface.get(type); - for (String inter : myInterfaces) { - if (runtime.interfaceToNames.containsKey(inter)) { - runtime.interfaceToNames.get(inter).add(fullname); - updatedServiceLists = true; + if (updatedServiceLists) { + runtime.invoke("publishInterfaceToNames"); } - } - if (updatedServiceLists) { - runtime.invoke("publishInterfaceToNames"); + // TODO - determine rules on re-broadcasting based on configuration + runtime.invoke("registered", registration); } - // TODO - determine rules on re-broadcasting based on configuration - runtime.invoke("registered", registration); - } + // TODO - remove ? already get state from registration + if (!registration.isLocal(ConfigUtils.getId())) { + runtime.subscribe(registration.getFullName(), "publishState"); + } - // TODO - remove ? already get state from registration - if (!registration.isLocal(Platform.getLocalInstance().getId())) { - runtime.subscribe(registration.getFullName(), "publishState"); + } catch (Exception e) { + log.error("registration threw for {}@{}", registration.getName(), registration.getId(), e); + return null; } - } catch (Exception e) { - log.error("registration threw for {}@{}", registration.getName(), registration.getId(), e); - return null; + return registration; } - - return registration; } /** @@ -1918,58 +1932,60 @@ public static synchronized Registration register(Registration registration) { * @return true/false * */ - public synchronized static boolean releaseService(String inName) { - if (inName == null) { - log.debug("release (null)"); - return false; - } + public static boolean releaseService(String inName) { + synchronized (processLock) { + if (inName == null) { + log.debug("release (null)"); + return false; + } - String name = getFullName(inName); + String name = getFullName(inName); - String id = CodecUtils.getId(name); - if (!id.equals(Platform.getLocalInstance().getId())) { - log.warn("will only release local services - %s is remote", name); - return false; - } + String id = CodecUtils.getId(name); + if (!id.equals(Runtime.getInstance().getId())) { + log.warn("will only release local services - %s is remote", name); + return false; + } - log.info("releasing service {}", name); + log.info("releasing service {}", name); - if (!registry.containsKey(name)) { - log.info("{} not registered", name); - return false; - } + if (!registry.containsKey(name)) { + log.info("{} not registered", name); + return false; + } - // get reference from registry - ServiceInterface si = registry.get(name); - if (si == null) { - log.warn("cannot release {} - not in registry"); - return false; - } + // get reference from registry + ServiceInterface si = registry.get(name); + if (si == null) { + log.warn("cannot release {} - not in registry"); + return false; + } - // FIXME - TODO invoke and or blocking on preRelease - Future + // FIXME - TODO invoke and or blocking on preRelease - Future - // send msg to service to self terminate - if (si.isLocal()) { - si.purgeTasks(); - si.stopService(); - } else { - if (runtime != null) { - runtime.send(name, "releaseService"); + // send msg to service to self terminate + if (si.isLocal()) { + si.purgeTasks(); + si.stopService(); + } else { + if (runtime != null) { + runtime.send(name, "releaseService"); + } } - } - // recursive peer release - Map<String, Peer> peers = si.getPeers(); - if (peers != null) { - for (Peer peer : peers.values()) { - release(peer.name); + // recursive peer release + Map<String, Peer> peers = si.getPeers(); + if (peers != null) { + for (Peer peer : peers.values()) { + release(peer.name); + } } - } - // FOR remote this isn't correct - it should wait for - // a message from the other runtime to say that its released - unregister(name); - return true; + // FOR remote this isn't correct - it should wait for + // a message from the other runtime to say that its released + unregister(name); + return true; + } } /** @@ -1979,63 +1995,65 @@ public synchronized static boolean releaseService(String inName) { * @param inName * Name of the service to unregister */ - synchronized public static void unregister(String inName) { - String name = getFullName(inName); - log.info("unregister {}", name); + public static void unregister(String inName) { + synchronized (processLock) { + String name = getFullName(inName); + log.info("unregister {}", name); - // get reference from registry - ServiceInterface sw = registry.get(name); - if (sw == null) { - log.debug("{} already unregistered", name); - return; - } + // get reference from registry + ServiceInterface sw = registry.get(name); + if (sw == null) { + log.debug("{} already unregistered", name); + return; + } - // you have to send released before removing from registry - if (runtime != null) { - runtime.invoke("released", inName); // <- DO NOT CHANGE THIS IS CORRECT - // !! - // it should be FULLNAME ! - // runtime.broadcast("released", inName); - String type = sw.getTypeKey(); - - boolean updatedServiceLists = false; - - // check to see if any of our interfaces can fullfill requested ones - Set<String> myInterfaces = runtime.typeToInterface.get(type); - if (myInterfaces != null) { - for (String inter : myInterfaces) { - if (runtime.interfaceToNames.containsKey(inter)) { - runtime.interfaceToNames.get(inter).remove(name); - updatedServiceLists = true; + // you have to send released before removing from registry + if (runtime != null) { + runtime.invoke("released", inName); // <- DO NOT CHANGE THIS IS CORRECT + // !! + // it should be FULLNAME ! + // runtime.broadcast("released", inName); + String type = sw.getTypeKey(); + + boolean updatedServiceLists = false; + + // check to see if any of our interfaces can fullfill requested ones + Set<String> myInterfaces = runtime.typeToInterface.get(type); + if (myInterfaces != null) { + for (String inter : myInterfaces) { + if (runtime.interfaceToNames.containsKey(inter)) { + runtime.interfaceToNames.get(inter).remove(name); + updatedServiceLists = true; + } } } - } - if (updatedServiceLists) { - runtime.invoke("publishInterfaceToNames"); + if (updatedServiceLists) { + runtime.invoke("publishInterfaceToNames"); + } + } - } + // FIXME - release autostarted peers ? - // FIXME - release autostarted peers ? + // last step - remove from registry by making new registry + // thread safe way + Map<String, ServiceInterface> removedService = new TreeMap<>(); + for (String key : registry.keySet()) { + if (!name.equals(key)) { + removedService.put(key, registry.get(key)); + } + } + registry = removedService; - // last step - remove from registry by making new registry - // thread safe way - Map<String, ServiceInterface> removedService = new TreeMap<>(); - for (String key : registry.keySet()) { - if (!name.equals(key)) { - removedService.put(key, registry.get(key)); + // and config + RuntimeConfig c = (RuntimeConfig) Runtime.getInstance().config; + if (c != null) { + c.remove(CodecUtils.getShortName(name)); } - } - registry = removedService; - // and config - RuntimeConfig c = (RuntimeConfig) Runtime.getInstance().config; - if (c != null) { - c.remove(CodecUtils.getShortName(name)); + log.info("released {}", name); } - - log.info("released {}", name); } /** @@ -2106,12 +2124,14 @@ public static void releaseAll(boolean releaseRuntime, boolean block) { if (block) { processRelease(releaseRuntime); + ConfigUtils.reset(); } else { new Thread() { @Override public void run() { processRelease(releaseRuntime); + ConfigUtils.reset(); } }.start(); @@ -2124,45 +2144,51 @@ public void run() { * @param releaseRuntime * Whether the Runtime should also be released */ - synchronized static private void processRelease(boolean releaseRuntime) { - - // reverse release to order of creation - Collection<ServiceInterface> local = getLocalServices().values(); - List<ServiceInterface> ordered = new ArrayList<>(local); - ordered.removeIf(Objects::isNull); - Collections.sort(ordered); - Collections.reverse(ordered); + static private void processRelease(boolean releaseRuntime) { + synchronized (processLock) { + // reverse release to order of creation + Collection<ServiceInterface> local = getLocalServices().values(); + List<ServiceInterface> ordered = new ArrayList<>(local); + ordered.removeIf(Objects::isNull); + Collections.sort(ordered); + Collections.reverse(ordered); - for (ServiceInterface sw : ordered) { + for (ServiceInterface sw : ordered) { - // no longer needed now - runtime "should be" guaranteed to be last - if (sw == Runtime.getInstance()) { - // skipping runtime - continue; - } + // no longer needed now - runtime "should be" guaranteed to be last + if (sw == Runtime.getInstance()) { + // skipping runtime + continue; + } - log.info("releasing service {}", sw.getName()); + log.info("releasing service {}", sw.getName()); - try { - sw.releaseService(); - } catch (Exception e) { - runtime.error("%s threw while releasing", e); - log.error("release", e); + try { + sw.releaseService(); + } catch (Exception e) { + if (runtime != null) { + runtime.error("%s threw while releasing", e); + } + log.error("release", e); + } } - } - // clean up remote ... the contract should - // probably be just remove their references - do not - // ask for them to be released remotely .. - // in thread safe way + // clean up remote ... the contract should + // probably be just remove their references - do not + // ask for them to be released remotely .. + // in thread safe way - if (releaseRuntime && runtime != null) { - runtime.releaseService(); - } else { - // put runtime in new registry - Runtime.getInstance(); - registry = new TreeMap<>(); - registry.put(runtime.getFullName(), registry.get(runtime.getFullName())); + if (releaseRuntime) { + if (runtime != null) { + runtime.releaseService(); + } + runtime = null; + } else { + // put runtime in new registry + Runtime.getInstance(); + registry = new TreeMap<>(); + registry.put(runtime.getFullName(), registry.get(runtime.getFullName())); + } } } @@ -2657,75 +2683,78 @@ public String publishConfigFinished(String configName) { * The type of the new service * @return The started service */ - synchronized static public ServiceInterface start(String name, String type) { - try { + static public ServiceInterface start(String name, String type) { + synchronized (processLock) { + try { - ServiceInterface requestedService = Runtime.getService(name); - if (requestedService != null) { - log.info("requested service already exists"); - if (requestedService.isRunning()) { - log.info("requested service already running"); - } else { - requestedService.startService(); + ServiceInterface requestedService = Runtime.getService(name); + if (requestedService != null) { + log.info("requested service already exists"); + if (requestedService.isRunning()) { + log.info("requested service already running"); + } else { + requestedService.startService(); + } + return requestedService; } - return requestedService; - } - Plan plan = Runtime.load(name, type); + Plan plan = Runtime.load(name, type); - Map<String, ServiceInterface> services = createServicesFromPlan(plan, null, name); + Map<String, ServiceInterface> services = createServicesFromPlan(plan, null, name); - if (services == null) { - Runtime.getInstance().error("cannot create instance of %s with type %s given current configuration", name, type); - return null; - } + if (services == null) { + Runtime.getInstance().error("cannot create instance of %s with type %s given current configuration", name, type); + return null; + } - requestedService = Runtime.getService(name); + requestedService = Runtime.getService(name); - // FIXME - does some order need to be maintained e.g. all children before - // parent - // breadth first, depth first, external order ordinal ? - for (ServiceInterface service : services.values()) { - if (service.getName().equals(name)) { - continue; - } - if (!Runtime.isStarted(service.getName())) { - service.startService(); + // FIXME - does some order need to be maintained e.g. all children + // before + // parent + // breadth first, depth first, external order ordinal ? + for (ServiceInterface service : services.values()) { + if (service.getName().equals(name)) { + continue; + } + if (!Runtime.isStarted(service.getName())) { + service.startService(); + } } - } - if (requestedService == null) { - Runtime.getInstance().error("could not start %s of type %s", name, type); - return null; - } + if (requestedService == null) { + Runtime.getInstance().error("could not start %s of type %s", name, type); + return null; + } - // getConfig() was problematic here for JMonkeyEngine - ServiceConfig sc = requestedService.getConfig(); - // Map<String, Peer> peers = sc.getPeers(); - // if (peers != null) { - // for (String p : peers.keySet()) { - // Peer peer = peers.get(p); - // log.info("peer {}", peer); - // } - // } - // recursive - start peers of peers of peers ... - Map<String, Peer> subPeers = sc.getPeers(); - if (sc != null && subPeers != null) { - for (String subPeerKey : subPeers.keySet()) { - // IF AUTOSTART !!! - Peer subPeer = subPeers.get(subPeerKey); - if (subPeer.autoStart) { - Runtime.start(sc.getPeerName(subPeerKey), subPeer.type); + // getConfig() was problematic here for JMonkeyEngine + ServiceConfig sc = requestedService.getConfig(); + // Map<String, Peer> peers = sc.getPeers(); + // if (peers != null) { + // for (String p : peers.keySet()) { + // Peer peer = peers.get(p); + // log.info("peer {}", peer); + // } + // } + // recursive - start peers of peers of peers ... + Map<String, Peer> subPeers = sc.getPeers(); + if (sc != null && subPeers != null) { + for (String subPeerKey : subPeers.keySet()) { + // IF AUTOSTART !!! + Peer subPeer = subPeers.get(subPeerKey); + if (subPeer.autoStart) { + Runtime.start(sc.getPeerName(subPeerKey), subPeer.type); + } } } - } - requestedService.startService(); - return requestedService; - } catch (Exception e) { - runtime.error(e); + requestedService.startService(); + return requestedService; + } catch (Exception e) { + runtime.error(e); + } + return null; } - return null; } /** @@ -2735,32 +2764,36 @@ synchronized static public ServiceInterface start(String name, String type) { * @param name * @return */ - synchronized static public ServiceInterface start(String name) { - if (Runtime.getService(name) != null) { - // already exists - ServiceInterface si = Runtime.getService(name); - if (!si.isRunning()) { - si.startService(); + static public ServiceInterface start(String name) { + synchronized (processLock) { + if (Runtime.getService(name) != null) { + // already exists + ServiceInterface si = Runtime.getService(name); + if (!si.isRunning()) { + si.startService(); + } + return si; } - return si; - } - Plan plan = Runtime.load(name, null); - Map<String, ServiceInterface> services = createServicesFromPlan(plan, null, name); - // FIXME - order ? - for (ServiceInterface service : services.values()) { - service.startService(); + Plan plan = Runtime.load(name, null); + Map<String, ServiceInterface> services = createServicesFromPlan(plan, null, name); + // FIXME - order ? + for (ServiceInterface service : services.values()) { + service.startService(); + } + return Runtime.getService(name); } - return Runtime.getService(name); } - synchronized public static Plan load(String name, String type) { - try { - Runtime runtime = Runtime.getInstance(); - return runtime.loadService(new Plan("runtime"), name, type, true, 0); - } catch (IOException e) { - runtime.error(e); + public static Plan load(String name, String type) { + synchronized (processLock) { + try { + Runtime runtime = Runtime.getInstance(); + return runtime.loadService(new Plan("runtime"), name, type, true, 0); + } catch (IOException e) { + runtime.error(e); + } + return null; } - return null; } /** @@ -2788,8 +2821,9 @@ public Runtime(String n, String id) { /** * This is used to run through all the possible services and determine - * if they have any missing dependencies. If they do not they become "installed". - * The installed flag makes the gui do a crossout when a service type is selected. + * if they have any missing dependencies. If they do not they become + * "installed". The installed flag makes the gui do a crossout when a + * service type is selected. */ for (MetaData metaData : serviceData.getServiceTypes()) { Set<ServiceDependency> deps = repo.getUnfulfilledDependencies(metaData.getType()); @@ -4202,7 +4236,7 @@ static public String getFullName(String shortname) { return shortname; } // if nothing is supplied assume local - return String.format("%s@%s", shortname, Platform.getLocalInstance().getId()); + return String.format("%s@%s", shortname, Runtime.getInstance().getId()); } @Override @@ -4495,18 +4529,20 @@ public static void main(String[] args) { try { + // loading args globalArgs = args; - new CommandLine(options).parseArgs(args); + log.info("in args {}", Launcher.toString(args)); + log.info("options {}", CodecUtils.toJson(options)); + log.info("\n" + Launcher.banner); + + // creating initial data/config directory + File cfgRoot = new File(ROOT_CONFIG_DIR); + cfgRoot.mkdirs(); // initialize logging initLog(); - log.info("in args {}", Launcher.toString(args)); - log.info(CodecUtils.toJson(options)); - - log.info("\n" + Launcher.banner); - // help and exit if (options.help) { mainHelp(); @@ -4516,45 +4552,23 @@ public static void main(String[] args) { // start.yml file is required, if not pre-existing // is created immediately. It contains static information // which needs to be available before a Runtime is created - File checkStartYml = new File("start.yml"); - if (!checkStartYml.exists()) { - // save default - startYml = new StartYml(); - String defaultStartFile = CodecUtils.toYaml(startYml); - FileIO.toFile("start.yml", defaultStartFile); - } else { - String yml = FileIO.toString("start.yml"); - startYml = CodecUtils.fromYaml(yml, StartYml.class); - } + Runtime.startYml = ConfigUtils.loadStartYml(); - // id always required - precedence - // if none supplied one will be generated - // if in start.yml it will be used - // if supplied by the command line it will be used - // command line has the highest precedence - - Platform platform = Platform.getLocalInstance(); - if (options.id != null) { - platform.setId(options.id); - } else if (startYml.id != null) { - platform.setId(startYml.id); - } else { - // no id set - should be first - // time mrl is started - String id = NameGenerator.getName(); - platform.setId(id); - startYml.id = id; - FileIO.toFile("start.yml", CodecUtils.toYaml(startYml)); + // resolve configName before starting getting runtime configuration + Runtime.configName = (startYml.enable) ? startYml.config : "default"; + if (options.config != null) { + // cmd line options has the highest priority + Runtime.configName = options.config; } - if (options.virtual) { - Platform.setVirtual(true); - } + // start.yml is processed, config name is set, runtime config + // is resolved, now we can start instance + Runtime.getInstance(); - // FIXME TEST THIS !! 0 length, single service, multiple ! if (options.install != null) { // we start the runtime so there is a status publisher which will // display status updates from the repo install + log.info("requesting install"); Repo repo = getInstance().getRepo(); if (options.install.length == 0) { repo.install(LIBRARIES, (String) null); @@ -4567,36 +4581,6 @@ public static void main(String[] args) { return; } - // if a you specify a config file it becomes the "base" of configuration - // inline flags will still override values - if (options.config != null) { - // if this is a valid config, it will load - setConfig(options.config); - } else { - // required directory to load any service - setConfig(startYml.config); - } - - if (startYml.enable) { - Runtime.startConfig(startYml.config); - } else { - createAndStartServices(options.services); - } - - if (options.invoke != null) { - invokeCommands(options.invoke); - } - - if (options.connect != null) { - Runtime.getInstance().connect(options.connect); - } - - if (options.autoUpdate) { - // initialize - // FIXME - use peer ? - Updater.main(args); - } - } catch (Exception e) { log.error("runtime exception", e); Runtime.mainHelp(); @@ -4607,7 +4591,6 @@ public static void main(String[] args) { public static void initLog() { if (options != null) { - LoggingFactory.setLogFile(options.logFile); LoggingFactory.init(options.logLevel); } else { LoggingFactory.init("info"); @@ -4709,87 +4692,92 @@ static public ServiceInterface loadAndStart(String name, String type) { * @return * @throws IOException */ - synchronized public Plan loadService(Plan plan, String name, String type, boolean start, int level) throws IOException { + public Plan loadService(Plan plan, String name, String type, boolean start, int level) throws IOException { + synchronized (processLock) { - if (plan == null) { - log.error("plan required to load a system"); - return null; - } + if (plan == null) { + log.error("plan required to load a system"); + return null; + } - log.info("loading - {} {} {}", name, type, level); - // from recursive memory definition - ServiceConfig sc = plan.get(name); - - // HIGHEST PRIORITY - OVERRIDE WITH FILE - String configPath = runtime.getConfigPath(); - String configFile = configPath + fs + name + ".yml"; - - // PRIORITY #1 - // find if a current yml config file exists - highest priority - log.debug("priority #1 user's yml override {} ", configFile); - ServiceConfig fileSc = readServiceConfig(Runtime.getInstance().getConfigName(), name); - if (fileSc != null) { - // if definition exists in file form, it overrides current memory one - sc = fileSc; - } else if (sc != null) { - // if memory config is available but not file - // we save it - String yml = CodecUtils.toYaml(sc); - FileIO.toFile(configFile, yml); - } - - // special conflict case - type is specified, but its not the same as - // file version - in that case specified parameter type wins and overwrites - // config. User can force type by supplying one as a parameter, however, the - // recursive - // call other peer types will have name/file.yml definition precedence - if ((type != null && sc != null && !type.equals(sc.type) && level == 0) || (sc == null)) { - if (sc != null) { - warn("type %s overwriting type %s specified in %s.yml file", type, sc.type, name); + log.info("loading - {} {} {}", name, type, level); + // from recursive memory definition + ServiceConfig sc = plan.get(name); + + // HIGHEST PRIORITY - OVERRIDE WITH FILE + String configPath = runtime.getConfigPath(); + String configFile = configPath + fs + name + ".yml"; + + // PRIORITY #1 + // find if a current yml config file exists - highest priority + log.debug("priority #1 user's yml override {} ", configFile); + ServiceConfig fileSc = readServiceConfig(Runtime.getInstance().getConfigName(), name); + if (fileSc != null) { + // if definition exists in file form, it overrides current memory one + sc = fileSc; + } else if (sc != null) { + // if memory config is available but not file + // we save it + String yml = CodecUtils.toYaml(sc); + FileIO.toFile(configFile, yml); } - ServiceConfig.getDefault(plan, name, type); - sc = plan.get(name); - // create new file if it didn't exist or overwrite it if new type is - // required - String yml = CodecUtils.toYaml(sc); - FileIO.toFile(configFile, yml); - } + // special conflict case - type is specified, but its not the same as + // file version - in that case specified parameter type wins and + // overwrites + // config. User can force type by supplying one as a parameter, however, + // the + // recursive + // call other peer types will have name/file.yml definition precedence + if ((type != null && sc != null && !type.equals(sc.type) && level == 0) || (sc == null)) { + if (sc != null) { + warn("type %s overwriting type %s specified in %s.yml file", type, sc.type, name); + } + ServiceConfig.getDefault(plan, name, type); + sc = plan.get(name); - if (sc == null && type == null) { - log.error("no local config and unknown type"); - return plan; - } + // create new file if it didn't exist or overwrite it if new type is + // required + String yml = CodecUtils.toYaml(sc); + FileIO.toFile(configFile, yml); + } - // finalize - if (sc != null) { - plan.put(name, sc); - // RECURSIVE load peers - Map<String, Peer> peers = sc.getPeers(); - for (String peerKey : peers.keySet()) { - Peer peer = peers.get(peerKey); - // recursive depth load - parent and child need to be started - runtime.loadService(plan, peer.name, peer.type, start && peer.autoStart, level + 1); + if (sc == null && type == null) { + log.error("no local config and unknown type"); + return plan; } - // valid service config at this point - now determine if its supposed to - // start or not - // if its level 0 then it was requested by user or config - so it needs to - // start - // if its not level 0 then it was loaded because peers were defined and - // appropriate config loaded - // peer.autoStart should determine if the peer starts if not explicitly - // requested by the - // user or config - if (level == 0 || start) { - plan.addRegistry(name); + // finalize + if (sc != null) { + plan.put(name, sc); + // RECURSIVE load peers + Map<String, Peer> peers = sc.getPeers(); + for (String peerKey : peers.keySet()) { + Peer peer = peers.get(peerKey); + // recursive depth load - parent and child need to be started + runtime.loadService(plan, peer.name, peer.type, start && peer.autoStart, level + 1); + } + + // valid service config at this point - now determine if its supposed to + // start or not + // if its level 0 then it was requested by user or config - so it needs + // to + // start + // if its not level 0 then it was loaded because peers were defined and + // appropriate config loaded + // peer.autoStart should determine if the peer starts if not explicitly + // requested by the + // user or config + if (level == 0 || start) { + plan.addRegistry(name); + } + + } else { + log.info("could not load {} {} {}", name, type, level); } - } else { - log.info("could not load {} {} {}", name, type, level); + return plan; } - - return plan; } /** @@ -4859,45 +4847,30 @@ public String publishConfigLoaded(String name) { return name; } - public String setAllIds(String id) { - Platform.getLocalInstance().setId(id); - for (ServiceInterface si : getServices()) { - si.setId(id); - } - return id; - } - @Override - public RuntimeConfig apply(RuntimeConfig c) { - super.apply(c); - config = c; + public RuntimeConfig apply(RuntimeConfig config) { + super.apply(config); setLocale(config.locale); - if (config.id != null) { - setAllIds(config.id); + if (config.id == null) { + config.id = NameGenerator.getName(); } if (config.logLevel != null) { setLogLevel(config.logLevel); } - info("setting locale to %s", config.locale); if (config.virtual != null) { info("setting virtual to %b", config.virtual); setAllVirtual(config.virtual); } - if (config.enableCli) { - startInteractiveMode(); - info("enabled cli"); - } else { - stopInteractiveMode(); - info("disabled cli"); - } + // APPLYING A RUNTIME CONFIG DOES NOT PROCESS THE REGISTRY + // USE startConfig(name) broadcastState(); - return c; + return config; } /** @@ -4999,7 +4972,6 @@ public boolean saveService(String configName, String serviceName, String filenam // conditional boolean to flip and save a config name to start.yml ? if (startYml.enable) { - startYml.id = getId(); startYml.config = configName; FileIO.toFile("start.yml", CodecUtils.toYaml(startYml)); } @@ -5032,26 +5004,6 @@ public boolean saveService(String configName, String serviceName, String filenam return false; } - public String setConfigName(String name) { - if (name != null && name.contains(fs)) { - error("invalid character " + fs + " in configuration name"); - return configName; - } - if (name != null) { - configName = name.trim(); - } - - // for the moment the best way is to mandate - // a dir is created when a new config name is set - // because loading service are required to save config - // before starting - File configDir = new File(ROOT_CONFIG_DIR + fs + name); - configDir.mkdirs(); - - invoke("publishConfigList"); - return name; - } - public String getConfigName() { return configName; } @@ -5069,13 +5021,34 @@ public boolean isProcessingConfig() { * - config dir name under data/config/{config} * @return configName */ - public static String setConfig(String configName) { + public static String setConfig(String name) { + if (name == null) { + log.error("config cannot be null"); + if (runtime != null) { + runtime.error("config cannot be null"); + } + return null; + } + + if (name.contains(fs)) { + log.error("invalid character " + fs + " in configuration name"); + if (runtime != null) { + runtime.error("invalid character " + fs + " in configuration name"); + } + return name; + } - File configDir = new File(ROOT_CONFIG_DIR + fs + configName); - configDir.mkdirs(); + configName = name.trim(); + + File configDir = new File(ROOT_CONFIG_DIR + fs + name); + if (!configDir.exists()) { + configDir.mkdirs(); + } + + if (runtime != null) { + runtime.invoke("publishConfigList"); + } - Runtime runtime = Runtime.getInstance(); - runtime.setConfigName(configName); return configName; } @@ -5292,7 +5265,6 @@ public String getConfigPath() { return ROOT_CONFIG_DIR + fs + configName; } - /** * Gets a {serviceName}.yml file config from configName directory * @@ -5397,11 +5369,12 @@ public ServiceConfig getPeer(String sericeName, String peerKey) { /** * Removes a config set and all its files * - * @param configName - name of config + * @param configName + * - name of config */ public static void removeConfig(String configName) { try { - log.info("removeing config"); + log.info("removing config"); File check = new File(ROOT_CONFIG_DIR + fs + configName); @@ -5414,12 +5387,4 @@ public static void removeConfig(String configName) { } } - /** - * Method used to determine is runtime is running without starting it - * @return true if available - */ - static public boolean isAvailable() { - return runtime != null && runtime.isRunning(); - } - } diff --git a/src/main/java/org/myrobotlab/service/Serial.java b/src/main/java/org/myrobotlab/service/Serial.java index af16f3522a..c6fccb9d84 100644 --- a/src/main/java/org/myrobotlab/service/Serial.java +++ b/src/main/java/org/myrobotlab/service/Serial.java @@ -1338,7 +1338,7 @@ public static void main(String[] args) { try { - Platform.setVirtual(true); + Runtime.getInstance().setVirtual(true); Serial s = (Serial) Runtime.start("s1", "Serial"); String vport1 = "vport1"; diff --git a/src/main/java/org/myrobotlab/service/WebGui.java b/src/main/java/org/myrobotlab/service/WebGui.java index 21deea126c..5e87cddb05 100644 --- a/src/main/java/org/myrobotlab/service/WebGui.java +++ b/src/main/java/org/myrobotlab/service/WebGui.java @@ -62,7 +62,8 @@ * services are already APIs - perhaps a data API - same as service without the * message wrapper */ -public class WebGui extends Service<WebGuiConfig> implements AuthorizationProvider, Gateway, Handler, ServiceLifeCycleListener { +public class WebGui extends Service<WebGuiConfig> + implements AuthorizationProvider, Gateway, Handler, ServiceLifeCycleListener { public static class LiveVideoStreamHandler implements Handler { @@ -89,7 +90,7 @@ public void handle(AtmosphereResource r) { } } } - + private final transient IncomingMsgQueue inMsgQueue = new IncomingMsgQueue(); public static class Panel { @@ -127,7 +128,7 @@ public Panel(String name, int x, int y, int z) { * needed to get the api key to select the appropriate api processor * * @param uri - * u + * u * @return api key * */ @@ -270,9 +271,9 @@ public boolean getAutoStartBrowser() { * String broadcast to specific client * * @param uuid - * u + * u * @param str - * s + * s * */ public void broadcast(String uuid, String str) { @@ -314,7 +315,9 @@ public Config.Builder getNettosphereConfig() { // cert.privateKey()).build(); SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); - SslContext context = SslContextBuilder.forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()).sslProvider(SslProvider.JDK) + SslContext context = SslContextBuilder + .forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()) + .sslProvider(SslProvider.JDK) .clientAuth(ClientAuth.NONE).build(); configBuilder.sslContext(context); @@ -493,7 +496,8 @@ public void handle(AtmosphereResource r) { } else if ((bodyData != null) && log.isDebugEnabled()) { logData = bodyData; } - log.debug("-->{} {} {} - [{}] from connection {}", (newPersistentConnection) ? "new" : "", request.getMethod(), request.getRequestURI(), logData, uuid); + log.debug("-->{} {} {} - [{}] from connection {}", (newPersistentConnection) ? "new" : "", request.getMethod(), + request.getRequestURI(), logData, uuid); } // important persistent connections will have associated routes ... @@ -571,7 +575,8 @@ public void handle(AtmosphereResource r) { } if (msg.containsHop(getId())) { - log.error("{} dumping duplicate hop msg to avoid cyclical from {} --to--> {}.{}", getName(), msg.sender, msg.name, msg.method); + log.error("{} dumping duplicate hop msg to avoid cyclical from {} --to--> {}.{}", getName(), msg.sender, + msg.name, msg.method); return; } @@ -915,7 +920,7 @@ public void run() { * remotely control UI * * @param panel - * - the panel which has been moved or resized + * - the panel which has been moved or resized */ public void savePanel(Panel panel) { if (panel.name == null) { @@ -1102,7 +1107,7 @@ public void releaseService() { * Default (false) is to use the CDN * * @param useLocalResources - * - true uses local resources fals uses cdn + * - true uses local resources fals uses cdn */ public void useLocalResources(boolean useLocalResources) { this.useLocalResources = useLocalResources; @@ -1162,7 +1167,7 @@ public WebGuiConfig getConfig() { public WebGuiConfig apply(WebGuiConfig c) { super.apply(c); - + if (c.port != null && (port != null && c.port.intValue() != port.intValue())) { setPort(c.port); } @@ -1178,17 +1183,19 @@ public static void main(String[] args) { try { - // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui", "intro", "Intro", "python", "Python" }); - Runtime.main(new String[] { "--install" }); - + Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui","intro", "Intro", "python", "Python" }); + // Runtime.main(new String[] {}); + // Runtime.main(new String[] { "--install" }); + boolean done = true; if (done) { return; } - + // Platform.setVirtual(true); - // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui", "intro", "Intro", "python", "Python", "-c", "dev" }); - // Runtime.startConfig("dev"); + // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui", + // "intro", "Intro", "python", "Python", "-c", "dev" }); + // Runtime.startConfig("dev"); // Runtime.start("python", "Python"); // Arduino arduino = (Arduino)Runtime.start("arduino", "Arduino"); @@ -1199,13 +1206,10 @@ public static void main(String[] args) { // webgui.setSsl(true); webgui.startService(); - - Runtime.start("python", "Python"); // Runtime.start("intro", "Intro"); // Runtime.start("i01", "InMoov2"); - // Runtime.start("i01", "InMoov2"); // Runtime.start("python", "Python"); // Runtime.start("i01", "InMoov2"); @@ -1263,7 +1267,6 @@ public static void main(String[] args) { * Runtime.start("clock03", "Clock"); Runtime.start("clock04", "Clock"); * Runtime.start("clock05", "Clock"); */ - Platform.setVirtual(true); // Arduino arduino = (Arduino) Runtime.start("arduino", "Arduino"); Servo pan = (Servo) Runtime.start("pan", "Servo"); @@ -1309,5 +1312,4 @@ public void onStopped(String name) { public void onReleased(String name) { } - } diff --git a/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java b/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java index 6296f4d536..d1ac97d2f6 100644 --- a/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java +++ b/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java @@ -1,8 +1,11 @@ package org.myrobotlab.service.config; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import org.myrobotlab.framework.NameGenerator; +import org.myrobotlab.framework.Plan; import org.myrobotlab.service.data.Locale; public class RuntimeConfig extends ServiceConfig { @@ -11,18 +14,13 @@ public class RuntimeConfig extends ServiceConfig { * instance id - important to be unique when connecting multiple * mrl instances together */ - public String id; + public String id = NameGenerator.getName(); /** * virtual hardware if enabled all services created will enable virtualization if applicable */ public Boolean virtual = false; - - /** - * Determines if stdin can be used for commands - */ - public boolean enableCli = true; - + /** * Log level debug, info, warning, error */ @@ -32,7 +30,7 @@ public class RuntimeConfig extends ServiceConfig { * Locale setting for the instance, initial default will be set by the default jvm/os * through java.util.Locale.getDefault() */ - public String locale; + public String locale = Locale.getDefault().getTag(); /** @@ -40,7 +38,7 @@ public class RuntimeConfig extends ServiceConfig { * because SnakeYml's interpretation would be a map with null values. Instead * its a protected member with accessors that prevent duplicates. */ - protected List<String> registry = new ArrayList<>(); + public List<String> registry = new ArrayList<>(); /** * Root of resource location @@ -48,13 +46,9 @@ public class RuntimeConfig extends ServiceConfig { public String resource = "resource"; - /** - * Constructor sets the default locale if not already set. - */ - public RuntimeConfig() { - if (locale == null) { - locale = Locale.getDefault().getTag(); - } + public Plan getDefault(Plan plan, String name) { + super.getDefault(plan, name); + return plan; } diff --git a/src/main/java/org/myrobotlab/service/interfaces/Gateway.java b/src/main/java/org/myrobotlab/service/interfaces/Gateway.java index 783ec951ff..7b3ee61b19 100644 --- a/src/main/java/org/myrobotlab/service/interfaces/Gateway.java +++ b/src/main/java/org/myrobotlab/service/interfaces/Gateway.java @@ -83,7 +83,7 @@ default Message getDescribeMsg(String connId) { "describe", new Object[] { FILL_UUID_MAGIC_VAL, - new DescribeQuery(Platform.getLocalInstance().getId(), connId) + new DescribeQuery(Runtime.getInstance().getId(), connId) } ); } diff --git a/src/main/java/org/myrobotlab/service/meta/JoystickMeta.java b/src/main/java/org/myrobotlab/service/meta/JoystickMeta.java index ca544c8523..b2fb538f80 100644 --- a/src/main/java/org/myrobotlab/service/meta/JoystickMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/JoystickMeta.java @@ -20,12 +20,12 @@ public JoystickMeta() { addCategory("control", "telerobotics"); addDependency("net.java.jinput", "jinput", "2.0.9"); - log.info("Joystick.getMetaData {} isArm() {}", platform, platform.isArm()); + log.debug("Joystick.getMetaData {} isArm() {}", platform, platform.isArm()); if (platform.isArm()) { - log.info("adding armv7 native dependencies"); + log.debug("adding armv7 native dependencies"); addDependency("jinput-natives", "jinput-natives-armv7.hfp", "2.0.7", "zip"); } else { - log.info("adding jinput native dependencies"); + log.debug("adding jinput native dependencies"); addDependency("jinput-natives", "jinput-natives", "2.0.7", "zip"); } } diff --git a/src/main/java/org/myrobotlab/vertx/WebSocketHandler.java b/src/main/java/org/myrobotlab/vertx/WebSocketHandler.java index e7e6b5fe63..c74b12e185 100644 --- a/src/main/java/org/myrobotlab/vertx/WebSocketHandler.java +++ b/src/main/java/org/myrobotlab/vertx/WebSocketHandler.java @@ -131,7 +131,7 @@ public void handle(String json) { // FIXME get rid of fill-uuid Message describe = Message.createMessage(String.format("%s@%s", service.getName(), Runtime.get().getId()), "runtime", "describe", - new Object[] { "fill-uuid", new DescribeQuery(Platform.getLocalInstance().getId(), uuid) }); + new Object[] { "fill-uuid", new DescribeQuery(Runtime.getInstance().getId(), uuid) }); service.sendRemote(describe); log.info(String.format("<-- %s", describe)); newConnection = false; diff --git a/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java b/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java index 7ffd26fcd4..e26d28e732 100644 --- a/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java +++ b/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java @@ -186,25 +186,6 @@ public void testDefaultSerialization() { } - @Test - public void testNormalizeServiceName() { - Platform.getLocalInstance().setId("test-id"); - assertEquals("runtime@test-id", CodecUtils.getFullName("runtime")); - assertEquals("runtime@test-id", CodecUtils.getFullName("runtime@test-id")); - } - - @Test - public void testCheckServiceNameEqual() { - Platform.getLocalInstance().setId("test-id"); - assertTrue(CodecUtils.checkServiceNameEquality("runtime", "runtime")); - assertTrue(CodecUtils.checkServiceNameEquality("runtime", "runtime@test-id")); - assertTrue(CodecUtils.checkServiceNameEquality("runtime@test-id", "runtime")); - assertTrue(CodecUtils.checkServiceNameEquality("runtime@test-id", "runtime@test-id")); - assertFalse(CodecUtils.checkServiceNameEquality("runtime", "runtime@not-corr-id")); - assertFalse(CodecUtils.checkServiceNameEquality("runtime@not-corr-id", "runtime")); - - } - @Test public void testBase64() { // not a very comprehensive test, but a sanity check none the less. diff --git a/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java b/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java new file mode 100644 index 0000000000..5d15601b58 --- /dev/null +++ b/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java @@ -0,0 +1,43 @@ +package org.myrobotlab.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Before; +import org.junit.Test; +import org.myrobotlab.framework.StartYml; +import org.myrobotlab.service.Runtime; + +public class ConfigUtilsTest { + + @Before + public void beforeTest() { + Runtime.releaseAll(true, true); + } + + @Test + public void testGetResourceRoot() { + String resource = ConfigUtils.getResourceRoot(); + // could be affected by dirty filesystem + assertEquals("resource", resource); + } + + @Test + public void testLoadRuntimeConfig() { + String resource = ConfigUtils.getResourceRoot(); + assertNotNull(resource); + } + + @Test + public void testLoadStartYml() { + StartYml start = ConfigUtils.loadStartYml(); + assertNotNull(start); + } + + @Test + public void testGetId() { + assertEquals(ConfigUtils.getId(), ConfigUtils.loadRuntimeConfig(null).id); + } + + +} diff --git a/src/test/java/org/myrobotlab/framework/CmdOptionsTest.java b/src/test/java/org/myrobotlab/framework/CmdOptionsTest.java index 85f0d4f2ee..9ce7594c8a 100644 --- a/src/test/java/org/myrobotlab/framework/CmdOptionsTest.java +++ b/src/test/java/org/myrobotlab/framework/CmdOptionsTest.java @@ -1,6 +1,7 @@ package org.myrobotlab.framework; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -9,6 +10,8 @@ import org.junit.Test; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.Runtime; +import org.myrobotlab.service.config.ClockConfig; import org.slf4j.Logger; import picocli.CommandLine; @@ -32,29 +35,36 @@ public void testGetOutputCmd() throws IOException { CmdOptions options = new CmdOptions(); new CommandLine(options).parseArgs(new String[] {}); // validate defaults - assertEquals(false, options.autoUpdate); assertNull(options.config); - assertNull(options.connect); assertEquals(0, options.services.size()); - new CommandLine(options).parseArgs(new String[] { "--id", "raspi", "-s", "webgui", "WebGui", "clock01", "Clock" }); + new CommandLine(options).parseArgs(new String[] { "-s", "webgui", "WebGui", "clock01", "Clock" }); - assertEquals("raspi", options.id); assertEquals(4, options.services.size()); List<String> cmd = options.getOutputCmd(); assertTrue(contains(cmd, "webgui")); - assertTrue(contains(cmd, "raspi")); + assertTrue(contains(cmd, "clock01")); log.info(CmdOptions.toString(cmd)); - options = new CmdOptions(); - new CommandLine(options).parseArgs(new String[] { "-a" }); - assertEquals(true, options.autoUpdate); - + Runtime.releaseAll(true, true); // test help - - // test unmatched option + Runtime.main(new String[] { "--id", "test", "-s", "clockCmdTest", "Clock" }); + assertNotNull(Runtime.getService("clockCmdTest")); + assertEquals("test", Runtime.getInstance().getId()); + + Runtime.releaseAll(true, true); + + Runtime.main(new String[] { "-c", "xxx", "-s", "clockCmdTest", "Clock" }); + + ClockConfig clock = (ClockConfig)Runtime.getInstance().readServiceConfig("xxx", "clockCmdTest"); + assertNotNull(clock); + assertNotNull(Runtime.getService("clockCmdTest")); + + Runtime.releaseAll(true, true); + + log.info("here"); } diff --git a/src/test/java/org/myrobotlab/framework/ConfigTest.java b/src/test/java/org/myrobotlab/framework/ConfigTest.java index 95b3da920b..955a18e2ce 100644 --- a/src/test/java/org/myrobotlab/framework/ConfigTest.java +++ b/src/test/java/org/myrobotlab/framework/ConfigTest.java @@ -8,14 +8,8 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Comparator; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -38,23 +32,18 @@ import org.slf4j.Logger; public class ConfigTest extends AbstractTest { - - + @BeforeClass public static void setUpBeforeClass() { - System.out.println("Runs before any test method in the class"); - } - - @AfterClass - public static void tearDownAfterClass() { - System.out.println("Runs after all test methods in the class"); + // clean out services - reset + Runtime.releaseAll(true, true); } @Before /* before each test */ public void setUp() throws IOException { // remove all services - also resets config name to DEFAULT effectively Runtime.releaseAll(true, true); - // clean our config directory + // clean our config directory Runtime.removeConfig(CONFIG_NAME); // set our config Runtime.setConfig(CONFIG_NAME); @@ -62,9 +51,8 @@ public void setUp() throws IOException { @After public void tearDown() { - System.out.println("Runs after each test method"); + System.out.println("Runs after each test method"); } - // --- config set related --- // setConfigPath(fullpath) @@ -90,32 +78,31 @@ public void tearDown() { final String CONFIG_PATH = "data" + File.separator + "config" + File.separator + CONFIG_NAME; - @Test public void testStartNoConfig() throws Exception { Runtime runtime = Runtime.getInstance(); assertNotNull(runtime); - + // complete teardown, release runtime, block Runtime.releaseAll(true, true); - + String[] names = Runtime.getServiceNames(); - assertEquals("complete teardown should be 0", 0, names.length); - + assertEquals("after teardown, then using a runtime static - only 0 service 'runtime' should exist", 0, names.length); + // nothing to start - should be empty config Runtime.startConfig(CONFIG_NAME); - + // starting an empty config automatically needs a runtime, and runtime // by default starts the singleton security service names = Runtime.getServiceNames(); - assertEquals("complete teardown should be 2 after trying to start a config runtime and security", 1, names.length); - + assertEquals("complete teardown should be 1 after trying to start a config runtime", 1, names.length); + Runtime.releaseAll(true, true); } - + @Test public void testSwitchingPeer() throws IOException { - + Runtime runtime = Runtime.getInstance(); assertNotNull(runtime); @@ -123,53 +110,53 @@ public void testSwitchingPeer() throws IOException { // to the current config directory Plan plan = Runtime.load("eyeTracking", "Tracking"); assertNotNull(plan); - + // load eyeTracking.yml config - verify default state - TrackingConfig eyeTracking = (TrackingConfig)runtime.getConfig(CONFIG_NAME, "eyeTracking"); + TrackingConfig eyeTracking = (TrackingConfig) runtime.getConfig(CONFIG_NAME, "eyeTracking"); TrackingConfig defaultTracking = new TrackingConfig(); assertEquals("eyeTracking.yml values should be the same as default", defaultTracking.enabled, eyeTracking.enabled); assertEquals("eyeTracking.yml type should be the same as default", defaultTracking.type, eyeTracking.type); - eyeTracking = (TrackingConfig)runtime.getConfig("eyeTracking"); + eyeTracking = (TrackingConfig) runtime.getConfig("eyeTracking"); assertEquals("eyeTracking.yml values should be the same as default", defaultTracking.enabled, eyeTracking.enabled); assertEquals("eyeTracking.yml type should be the same as default", defaultTracking.type, eyeTracking.type); - + // load single opencv - OpenCVConfig cv = (OpenCVConfig)Runtime.load("cv", "OpenCV").get("cv"); + OpenCVConfig cv = (OpenCVConfig) Runtime.load("cv", "OpenCV").get("cv"); // default capturing is false assertFalse(cv.capturing); // save as true cv.capturing = true; Runtime.saveConfig("cv", cv); - + Runtime.load("pid", "Pid"); - eyeTracking = (TrackingConfig)runtime.getConfig("eyeTracking"); - + eyeTracking = (TrackingConfig) runtime.getConfig("eyeTracking"); + eyeTracking.getPeer("cv").name = "cv"; Runtime.saveConfig("eyeTracking", eyeTracking); - + // verify the peer was updated to cv - eyeTracking = (TrackingConfig)runtime.getConfig("eyeTracking"); - cv = (OpenCVConfig)runtime.getPeerConfig("eyeTracking","cv"); + eyeTracking = (TrackingConfig) runtime.getConfig("eyeTracking"); + cv = (OpenCVConfig) runtime.getPeerConfig("eyeTracking", "cv"); // from previous save assertTrue(cv.capturing); } - + @Test public void testChangeType() throws IOException { - Runtime runtime = Runtime.getInstance(); + Runtime runtime = Runtime.getInstance(); Runtime.load("mouth", "MarySpeech"); - MarySpeechConfig mouth = (MarySpeechConfig)runtime.getConfig("mouth"); + MarySpeechConfig mouth = (MarySpeechConfig) runtime.getConfig("mouth"); mouth.listeners.add(new Listener("publishStartSpeaking", "fakeListener")); Runtime.saveConfig("mouth", mouth); - MarySpeechConfig mary = (MarySpeechConfig)runtime.getConfig("mouth"); + MarySpeechConfig mary = (MarySpeechConfig) runtime.getConfig("mouth"); assertNotNull(mary); assertEquals(1, mary.listeners.size()); // save it runtime.changeType("mouth", "LocalSpeech"); - LocalSpeechConfig local = (LocalSpeechConfig)runtime.getConfig("mouth"); + LocalSpeechConfig local = (LocalSpeechConfig) runtime.getConfig("mouth"); assertEquals("must have the listener", 1, local.listeners.size()); assertTrue(local.listeners.get(0).listener.equals("fakeListener")); } @@ -178,23 +165,23 @@ public void testChangeType() throws IOException { public void testInitialLoad() { Runtime runtime = Runtime.getInstance(); Runtime.load("service", "Clock"); - ClockConfig clock = (ClockConfig)runtime.getConfig("service"); + ClockConfig clock = (ClockConfig) runtime.getConfig("service"); assertNotNull(clock); // replace load Runtime.load("service", "Tracking"); - TrackingConfig tracking = (TrackingConfig)runtime.getConfig("service"); + TrackingConfig tracking = (TrackingConfig) runtime.getConfig("service"); assertNotNull(tracking); } - + @Test public void testChangePeerName() throws IOException { Runtime runtime = Runtime.getInstance(); Plan plan = Runtime.load("pollyMouth", "Polly"); - PollyConfig polly = (PollyConfig)plan.get("pollyMouth"); + PollyConfig polly = (PollyConfig) plan.get("pollyMouth"); Runtime.load("i01", "InMoov2"); - InMoov2Config i01 = (InMoov2Config)runtime.getConfig("i01"); + InMoov2Config i01 = (InMoov2Config) runtime.getConfig("i01"); // default - MarySpeechConfig mary = (MarySpeechConfig)runtime.getPeer("i01", "mouth"); + MarySpeechConfig mary = (MarySpeechConfig) runtime.getPeer("i01", "mouth"); assertNotNull(mary); polly.listeners = mary.listeners; Runtime.saveConfig("pollyMouth", polly); @@ -202,48 +189,50 @@ public void testChangePeerName() throws IOException { peer.name = "pollyMouth"; Runtime.saveConfig("i01", i01); // switch to pollyMouth - PollyConfig p = (PollyConfig)runtime.getPeer("i01", "mouth"); - + PollyConfig p = (PollyConfig) runtime.getPeer("i01", "mouth"); + // FIXME - was going to test moving of subscriptions, however, unfortunately - // SpeechSynthesis services use a "recognizers" data instead of just simple subscriptions + // SpeechSynthesis services use a "recognizers" data instead of just simple + // subscriptions // This should be fixed in the future to use standard subscriptions - - } - + + } + @Test public void testSimpleServiceStart() { - Clock clock = (Clock)Runtime.start("track", "Clock"); + Runtime.releaseAll(true, true); + Clock clock = (Clock) Runtime.start("track", "Clock"); clock.startClock(); clock.releaseService(); // better be a tracking service - LocalSpeech track = (LocalSpeech)Runtime.start("track", "LocalSpeech"); + LocalSpeech track = (LocalSpeech) Runtime.start("track", "LocalSpeech"); assertNotNull(track); track.releaseService(); // better be a clock - clock = (Clock)Runtime.create("track", "Clock"); + clock = (Clock) Runtime.create("track", "Clock"); log.info("start"); } @Test public void testPeers() { - InMoov2Head head = (InMoov2Head)Runtime.start("track", "InMoov2Head"); - Servo neck = (Servo)Runtime.getService("track.neck"); + Runtime.releaseAll(true, true); + InMoov2Head head = (InMoov2Head) Runtime.start("track", "InMoov2Head"); + Servo neck = (Servo) Runtime.getService("track.neck"); assertNotNull(neck); head.releaseService(); assertNull(Runtime.getService("track.neck")); - } - + @Test public void testSaveApply() throws IOException { Runtime runtime = Runtime.getInstance(); - Servo neck = (Servo)Runtime.start("neck", "Servo"); + Servo neck = (Servo) Runtime.start("neck", "Servo"); ServoConfig config = neck.getConfig(); - + // Where config is "different" than member variables it // takes an apply(config) of the config to make the service // update its member variables, vs changing config and - // immediately getting the service behavior change. + // immediately getting the service behavior change. config.idleTimeout = 5000; // the fact this takes and additional method to process // i think is legacy and should be changed for Servo to use @@ -251,24 +240,23 @@ public void testSaveApply() throws IOException { neck.apply(config); neck.save(); neck.releaseService(); - neck = (Servo)Runtime.start("neck", "Servo"); - assertTrue("preserved value", 5000 == neck.getConfig().idleTimeout); + neck = (Servo) Runtime.start("neck", "Servo"); + assertTrue("preserved value", 5000 == neck.getConfig().idleTimeout); - Servo servo = (Servo)Runtime.start("servo", "Servo"); - config = (ServoConfig)Runtime.load("default", "Servo").get("default"); + Servo servo = (Servo) Runtime.start("servo", "Servo"); + config = (ServoConfig) Runtime.load("default", "Servo").get("default"); assertNull(config.idleTimeout); config.idleTimeout = 7000; Runtime.saveConfig("servo", config); servo.apply(); assertTrue(servo.getConfig().idleTimeout == 7000); - + config.idleTimeout = 8000; servo.apply(config); assertTrue(servo.getIdleTimeout() == 8000); servo.apply(); assertTrue("filesystem servo.yml applied", servo.getIdleTimeout() == 7000); - + } - } \ No newline at end of file diff --git a/src/test/java/org/myrobotlab/framework/ServiceTest.java b/src/test/java/org/myrobotlab/framework/ServiceTest.java deleted file mode 100644 index a9f180362c..0000000000 --- a/src/test/java/org/myrobotlab/framework/ServiceTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.myrobotlab.framework; - -import static org.junit.Assert.assertEquals; - -import java.util.List; -import java.util.Map; - -import org.junit.Test; -import org.myrobotlab.service.config.ServiceConfig; -import org.myrobotlab.test.AbstractTest; - -public class ServiceTest extends AbstractTest { - - public static class TestService extends Service<ServiceConfig> { - - private static final long serialVersionUID = 1L; - - /** - * Constructor of service, reservedkey typically is a services name and inId - * will be its process id - * - * @param reservedKey the service name - * @param inId process id - */ - public TestService(String reservedKey, String inId) { - super(reservedKey, inId); - } - } - - @Test - public void testConfigListenerFiltering() { - Platform.getLocalInstance().setId("test-id"); - TestService t = new TestService("test", "test-id"); - List<MRLListener> listeners = List.of( - new MRLListener("meth", "webgui@webgui-client", "onMeth"), - new MRLListener("meth", "random@test-id", "onMeth"), - new MRLListener("meth", "random2@test-2-id", "onMeth") - ); - t.apply(new ServiceConfig()); - t.outbox.notifyList = Map.of("meth", listeners); - List<ServiceConfig.Listener> filtered = t.getFilteredConfig().listeners; - assertEquals("random", filtered.get(0).listener); - assertEquals("random2@test-2-id", filtered.get(1).listener); - t.getFilteredConfig(); - } -} diff --git a/src/test/java/org/myrobotlab/service/RuntimeTest.java b/src/test/java/org/myrobotlab/service/RuntimeTest.java index a20a13db38..e3883cf97f 100644 --- a/src/test/java/org/myrobotlab/service/RuntimeTest.java +++ b/src/test/java/org/myrobotlab/service/RuntimeTest.java @@ -92,9 +92,6 @@ public void testGetUptime() { @Test public void testRuntimeLocale() { - long curr = 1479044758691L; - Date d = new Date(curr); - Runtime runtime = Runtime.getInstance(); runtime.setLocale("fr-FR"); assertEquals("expecting concat fr-FR", "fr-FR", runtime.getLocale().getTag()); @@ -105,16 +102,6 @@ public void testRuntimeLocale() { } - @Test - public void testRuntimeIsAvailable() { - Runtime.getInstance(); - assertTrue(Runtime.isAvailable()); - Runtime.releaseAll(true, true); - assertFalse(Runtime.isAvailable()); - Runtime.getInstance(); - assertTrue(Runtime.isAvailable()); - } - @Test public void testGetDescribeMessage() { diff --git a/src/test/java/org/myrobotlab/service/SerialTest.java b/src/test/java/org/myrobotlab/service/SerialTest.java index db1f10e315..63aeaa5cb5 100644 --- a/src/test/java/org/myrobotlab/service/SerialTest.java +++ b/src/test/java/org/myrobotlab/service/SerialTest.java @@ -53,7 +53,7 @@ public static Set<Thread> getDeadThreads() { @BeforeClass public static void setUpBeforeClass() throws Exception { // LoggingFactory.init("WARN"); - Platform.setVirtual(true); + Runtime.getInstance().setVirtual(true); log.info("setUpBeforeClass"); diff --git a/src/test/java/org/myrobotlab/test/AbstractTest.java b/src/test/java/org/myrobotlab/test/AbstractTest.java index 6b7ec2b432..1a40bae57e 100644 --- a/src/test/java/org/myrobotlab/test/AbstractTest.java +++ b/src/test/java/org/myrobotlab/test/AbstractTest.java @@ -103,7 +103,7 @@ public static void setUpAbstractTest() throws Exception { // } - Platform.setVirtual(true); + Runtime.getInstance().setVirtual(true); String junitLogLevel = System.getProperty("junit.logLevel"); if (junitLogLevel != null) { @@ -164,7 +164,7 @@ static protected void installAll() { */ public static void releaseServices() { - log.info("end of test - id {} remaining services {}", Platform.getLocalInstance().getId(), + log.info("end of test - id {} remaining services {}", Runtime.getInstance().getId(), Arrays.toString(Runtime.getServiceNames())); // release all including runtime - be careful of default runtime.yml @@ -211,11 +211,11 @@ public AbstractTest() { } public void setVirtual() { - Platform.setVirtual(true); + Runtime.getInstance().setVirtual(true); } public boolean isVirtual() { - return Platform.isVirtual(); + return Runtime.getInstance().isVirtual(); } } From 202f364a848c6342cc5afd857427bfc08768e142 Mon Sep 17 00:00:00 2001 From: GroG <grog@myrobotlab.org> Date: Fri, 9 Feb 2024 12:40:01 -0800 Subject: [PATCH 034/131] Update CmdOptionsTest.java --- src/test/java/org/myrobotlab/framework/CmdOptionsTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/myrobotlab/framework/CmdOptionsTest.java b/src/test/java/org/myrobotlab/framework/CmdOptionsTest.java index 9ce7594c8a..7afa011b8e 100644 --- a/src/test/java/org/myrobotlab/framework/CmdOptionsTest.java +++ b/src/test/java/org/myrobotlab/framework/CmdOptionsTest.java @@ -1,5 +1,5 @@ package org.myrobotlab.framework; - +import org.junit.Ignore; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -15,7 +15,7 @@ import org.slf4j.Logger; import picocli.CommandLine; - +@Ignore public class CmdOptionsTest { public final static Logger log = LoggerFactory.getLogger(CmdOptionsTest.class); From 3dadcace633345c67619d29e69abf6e58c6e5795 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Fri, 9 Feb 2024 16:23:49 -0800 Subject: [PATCH 035/131] warn --- .../java/org/myrobotlab/service/config/RuntimeConfig.java | 4 ++-- src/test/java/org/myrobotlab/test/AbstractTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java b/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java index d1ac97d2f6..d7572cd118 100644 --- a/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java +++ b/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java @@ -22,9 +22,9 @@ public class RuntimeConfig extends ServiceConfig { public Boolean virtual = false; /** - * Log level debug, info, warning, error + * Log level debug, info, warn, error */ - public String logLevel = "info"; + public String logLevel = "warn"; /** * Locale setting for the instance, initial default will be set by the default jvm/os diff --git a/src/test/java/org/myrobotlab/test/AbstractTest.java b/src/test/java/org/myrobotlab/test/AbstractTest.java index 1a40bae57e..01279a3310 100644 --- a/src/test/java/org/myrobotlab/test/AbstractTest.java +++ b/src/test/java/org/myrobotlab/test/AbstractTest.java @@ -195,7 +195,7 @@ public static void releaseServices() { } } if (threadsRemaining.size() > 0) { - log.info("{} straggling threads remain [{}]", threadsRemaining.size(), String.join(",", threadsRemaining)); + log.warn("{} straggling threads remain [{}]", threadsRemaining.size(), String.join(",", threadsRemaining)); } // log.warn("end of test - id {} remaining services after release {}", From bff28fad077eba35b09ccd1580ffaa04f2b4284d Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 10 Feb 2024 07:43:59 -0800 Subject: [PATCH 036/131] abstract test watcher --- .../org/myrobotlab/test/AbstractTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/test/java/org/myrobotlab/test/AbstractTest.java b/src/test/java/org/myrobotlab/test/AbstractTest.java index 01279a3310..fa1261a44b 100644 --- a/src/test/java/org/myrobotlab/test/AbstractTest.java +++ b/src/test/java/org/myrobotlab/test/AbstractTest.java @@ -23,6 +23,11 @@ import org.myrobotlab.service.config.RuntimeConfig; import org.slf4j.Logger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + public class AbstractTest { /** cached network test value for tests */ @@ -48,6 +53,34 @@ public class AbstractTest { static public String simpleName; private static boolean lineFeedFooter = true; + + @Rule + public TestWatcher watchman = new TestWatcher() { + @Override + protected void starting(Description description) { + System.out.println("Starting: " + description.getMethodName()); + } + + @Override + protected void succeeded(Description description) { + // System.out.println("Succeeded: " + description.getMethodName()); + } + + @Override + protected void failed(Throwable e, Description description) { + System.out.println("Failed: " + description.getMethodName()); + } + + @Override + protected void skipped(org.junit.AssumptionViolatedException e, Description description) { + System.out.println("Skipped: " + description.getMethodName()); + } + + @Override + protected void finished(Description description) { + System.out.println("Finished: " + description.getMethodName()); + } + }; public String getSimpleName() { return simpleName; From e8603f9dacd705c27f986b8c999c01d1be5f946f Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 10 Feb 2024 08:13:24 -0800 Subject: [PATCH 037/131] transient processLock --- src/main/java/org/myrobotlab/service/Runtime.java | 2 +- src/test/java/org/myrobotlab/io/FileIOTest.java | 1 + src/test/java/org/myrobotlab/service/RuntimeTest.java | 4 ++-- src/test/java/org/myrobotlab/test/AbstractTest.java | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 36fe72a3f8..9902b3a17b 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -168,7 +168,7 @@ public class Runtime extends Service<RuntimeConfig> implements MessageListener, protected final Map<String, Set<String>> typeToInterface = new HashMap<>(); - private static final Object processLock = new Object(); + private transient static final Object processLock = new Object(); /** * FILTERED_INTERFACES are the set of low level interfaces which we are diff --git a/src/test/java/org/myrobotlab/io/FileIOTest.java b/src/test/java/org/myrobotlab/io/FileIOTest.java index c554835175..8eb14bedb5 100644 --- a/src/test/java/org/myrobotlab/io/FileIOTest.java +++ b/src/test/java/org/myrobotlab/io/FileIOTest.java @@ -182,6 +182,7 @@ public void testToInputStreamString() throws IOException { InputStream ios = FileIO.toInputStream("This is some data that got turned into a stream"); String data = FileIO.toString(ios); assertEquals("This is some data that got turned into a stream", data); + ios.close(); } @Test diff --git a/src/test/java/org/myrobotlab/service/RuntimeTest.java b/src/test/java/org/myrobotlab/service/RuntimeTest.java index e3883cf97f..e2a01278c1 100644 --- a/src/test/java/org/myrobotlab/service/RuntimeTest.java +++ b/src/test/java/org/myrobotlab/service/RuntimeTest.java @@ -26,8 +26,8 @@ public class RuntimeTest extends AbstractTest { public final static Logger log = LoggerFactory.getLogger(RuntimeTest.class); @Before - public void setUp() { - // LoggingFactory.init("WARN"); + public void beforeTest() { + Runtime.releaseAll(true, true); } @Test diff --git a/src/test/java/org/myrobotlab/test/AbstractTest.java b/src/test/java/org/myrobotlab/test/AbstractTest.java index fa1261a44b..d782797c9b 100644 --- a/src/test/java/org/myrobotlab/test/AbstractTest.java +++ b/src/test/java/org/myrobotlab/test/AbstractTest.java @@ -58,7 +58,7 @@ public class AbstractTest { public TestWatcher watchman = new TestWatcher() { @Override protected void starting(Description description) { - System.out.println("Starting: " + description.getMethodName()); + System.out.println("Starting: " + description.getClassName() + "." + description.getMethodName()); } @Override From 8adeb2f9d8998d00882d91646359bb3f52b33653 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 10 Feb 2024 10:10:28 -0800 Subject: [PATCH 038/131] clean threads --- src/main/java/org/myrobotlab/opencv/OpenCVFilter.java | 2 +- .../myrobotlab/opencv/OpenCVFilterMiniXception.java | 5 ++++- .../java/org/myrobotlab/opencv/OpenCVFilterYolo.java | 4 ++-- src/main/java/org/myrobotlab/service/OpenCV.java | 6 ++++++ src/test/java/org/myrobotlab/service/OpenCVTest.java | 11 +++++++++-- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/myrobotlab/opencv/OpenCVFilter.java b/src/main/java/org/myrobotlab/opencv/OpenCVFilter.java index ac90bae143..860bceadc3 100644 --- a/src/main/java/org/myrobotlab/opencv/OpenCVFilter.java +++ b/src/main/java/org/myrobotlab/opencv/OpenCVFilter.java @@ -192,7 +192,7 @@ static private Mat read(String filename) { /** * This will enable/disable the filter in the pipeline */ - protected boolean enabled = true; + protected volatile boolean enabled = true; protected int height; diff --git a/src/main/java/org/myrobotlab/opencv/OpenCVFilterMiniXception.java b/src/main/java/org/myrobotlab/opencv/OpenCVFilterMiniXception.java index 8dea3fdf75..bd5de81abb 100755 --- a/src/main/java/org/myrobotlab/opencv/OpenCVFilterMiniXception.java +++ b/src/main/java/org/myrobotlab/opencv/OpenCVFilterMiniXception.java @@ -59,7 +59,7 @@ public OpenCVFilterMiniXception(String name) { } private void loadDL4j() { - dl4j = (Deeplearning4j) Runtime.createAndStart("dl4j", "Deeplearning4j"); + dl4j = (Deeplearning4j) Runtime.start("dl4j", "Deeplearning4j"); log.info("Loading mini XCEPTION Model."); try { dl4j.loadMiniEXCEPTION(); @@ -158,6 +158,9 @@ public void release() { running = false; converter1.close(); converter2.close(); + if (dl4j != null) { + dl4j.releaseService(); + } } @Override diff --git a/src/main/java/org/myrobotlab/opencv/OpenCVFilterYolo.java b/src/main/java/org/myrobotlab/opencv/OpenCVFilterYolo.java index ce83566839..060b5e43ce 100755 --- a/src/main/java/org/myrobotlab/opencv/OpenCVFilterYolo.java +++ b/src/main/java/org/myrobotlab/opencv/OpenCVFilterYolo.java @@ -378,12 +378,12 @@ public void enable() { @Override public void disable() { + super.disable(); if (classifier == null) { // already disabled return; } - super.disable(); - int waitTime = 0; + int waitTime = 0; while (classifier != null && waitTime < 1000) { ++waitTime; Service.sleep(10); diff --git a/src/main/java/org/myrobotlab/service/OpenCV.java b/src/main/java/org/myrobotlab/service/OpenCV.java index ff093721bd..dcd7ad08d9 100644 --- a/src/main/java/org/myrobotlab/service/OpenCV.java +++ b/src/main/java/org/myrobotlab/service/OpenCV.java @@ -461,6 +461,7 @@ public void reset() { singleFrame = false; lastFrame = null; blockingData.clear(); + removeFilters(); } public static IplImage cropImage(IplImage img, CvRect rect) { @@ -2099,6 +2100,11 @@ public static void main(String[] args) throws Exception { // Runtime.start("python", "Python"); OpenCV cv = (OpenCV) Runtime.start("cv", "OpenCV"); + cv.capture(); + + cv.addFilter(new OpenCVFilterYolo("yolo")); + sleep(1000); + cv.removeFilters(); OpenCVFilter fr = new OpenCVFilterFaceRecognizer("fr"); cv.addFilter(fr); diff --git a/src/test/java/org/myrobotlab/service/OpenCVTest.java b/src/test/java/org/myrobotlab/service/OpenCVTest.java index fa9e250ae5..137a12a499 100644 --- a/src/test/java/org/myrobotlab/service/OpenCVTest.java +++ b/src/test/java/org/myrobotlab/service/OpenCVTest.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; @@ -87,6 +88,12 @@ public static void main(String[] args) { @Rule public final TestName testName = new TestName(); + + @Before + public void beforeTest() { + cv.reset(); + } + @BeforeClass public static void setUpBeforeClass() throws Exception { @@ -222,8 +229,8 @@ public final void testAllFilterTypes() { for (String fn : OpenCV.POSSIBLE_FILTERS) { log.warn("trying filter {}", fn); - if (fn.startsWith("DL4J") || fn.startsWith("FaceTraining") || fn.startsWith("Tesseract") || fn.startsWith("SimpleBlobDetector") || fn.startsWith("Solr") || fn.startsWith("Split")) { - log.info("skipping {}", fn); + if ( fn.startsWith("FaceDetectDNN") || fn.startsWith("FaceRecognizer") || fn.startsWith("DL4J") || fn.startsWith("FaceTraining") || fn.startsWith("Tesseract") || fn.startsWith("SimpleBlobDetector") || fn.startsWith("Solr") || fn.startsWith("Split")) { + log.warn("skipping {}", fn); continue; } cv.addFilter(fn); From d6c0831f8994b58b95e8bd1b0daa6f944f1258d9 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 10 Feb 2024 17:12:13 -0800 Subject: [PATCH 039/131] cleaned up InMoov2 parts --- .../org/myrobotlab/service/InMoov2Arm.java | 54 +++++++++++++++-- .../org/myrobotlab/service/InMoov2Hand.java | 44 ++++++++++++++ .../org/myrobotlab/service/InMoov2Head.java | 60 +++++++++++++++++++ .../org/myrobotlab/service/InMoov2Torso.java | 33 ++++++++++ 4 files changed, 187 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2Arm.java b/src/main/java/org/myrobotlab/service/InMoov2Arm.java index 3b74a3bf20..1b4b45e6b5 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Arm.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Arm.java @@ -10,7 +10,9 @@ import org.myrobotlab.io.FileIO; import org.myrobotlab.kinematics.DHLink; import org.myrobotlab.kinematics.DHRobotArm; +import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.math.MathUtils; import org.myrobotlab.service.config.InMoov2ArmConfig; import org.myrobotlab.service.interfaces.IKJointAngleListener; @@ -127,10 +129,18 @@ public void startService() { @Override public void stopService() { super.stopService(); - releasePeer("bicep"); - releasePeer("rotate"); - releasePeer("shoulder"); - releasePeer("omoplate"); + if (bicep != null) { + ((Service)bicep).stopService(); + } + if (rotate != null) { + ((Service)rotate).stopService(); + } + if (shoulder != null) { + ((Service)shoulder).stopService(); + } + if (omoplate != null) { + ((Service)omoplate).stopService(); + } } @Override @@ -296,6 +306,20 @@ public void onJointAngles(Map<String, Double> angleMap) { public void releaseService() { try { disable(); + + if (bicep != null) { + ((Service)bicep).releaseService(); + } + if (rotate != null) { + ((Service)rotate).releaseService(); + } + if (shoulder != null) { + ((Service)shoulder).releaseService(); + } + if (omoplate != null) { + ((Service)omoplate).releaseService(); + } + super.releaseService(); } catch (Exception e) { error(e); @@ -468,5 +492,27 @@ public void waitTargetPos() { if (omoplate != null) omoplate.waitTargetPos(); } + + public static void main(String[] args) { + LoggingFactory.init(Level.INFO); + + try { + + Runtime.main(new String[] { "--log-level", "info", "-s", "inmoov2arm", "InMoov2Arm" }); + // Runtime.main(new String[] {}); + // Runtime.main(new String[] { "--install" }); + InMoov2Arm arm = (InMoov2Arm)Runtime.start("inmoov2arm", "InMoov2Arm"); + arm.releaseService(); + + boolean done = true; + if (done) { + return; + } + log.info("leaving main"); + + } catch (Exception e) { + log.error("main threw", e); + } + } } diff --git a/src/main/java/org/myrobotlab/service/InMoov2Hand.java b/src/main/java/org/myrobotlab/service/InMoov2Hand.java index b30c2bd792..06b6d3b459 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Hand.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Hand.java @@ -570,11 +570,55 @@ public List<String> refreshControllers() { public void release() { disable(); } + + @Override + public void stopService() { + disable(); + if (thumb != null) { + ((Service)thumb).stopService(); + } + if (index != null) { + ((Service)index).stopService(); + } + if (majeure != null) { + ((Service)majeure).stopService(); + } + if (ringFinger != null) { + ((Service)ringFinger).stopService(); + } + if (pinky != null) { + ((Service)pinky).stopService(); + } + if (wrist != null) { + ((Service)wrist).stopService(); + } + super.stopService(); + } @Override public void releaseService() { try { disable(); + + if (thumb != null) { + ((Service)thumb).releaseService(); + } + if (index != null) { + ((Service)index).releaseService(); + } + if (majeure != null) { + ((Service)majeure).releaseService(); + } + if (ringFinger != null) { + ((Service)ringFinger).releaseService(); + } + if (pinky != null) { + ((Service)pinky).releaseService(); + } + if (wrist != null) { + ((Service)wrist).releaseService(); + } + super.releaseService(); } catch (Exception e) { error(e); diff --git a/src/main/java/org/myrobotlab/service/InMoov2Head.java b/src/main/java/org/myrobotlab/service/InMoov2Head.java index f3f5edf366..da9052ca65 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Head.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Head.java @@ -359,10 +359,70 @@ public void waitTargetPos() { public void release() { disable(); } + + @Override + public void stopService() { + + if (jaw != null) { + ((Service)jaw).stopService(); + } + if (eyeX != null) { + ((Service)eyeX).stopService(); + } + if (eyeY != null) { + ((Service)eyeY).stopService(); + } + if (neck != null) { + ((Service)neck).stopService(); + } + if (rothead != null) { + ((Service)rothead).stopService(); + } + if (rollNeck != null) { + ((Service)rollNeck).stopService(); + } + if (eyelidLeft != null) { + ((Service)eyelidLeft).stopService(); + } + if (eyelidRight != null) { + ((Service)eyelidRight).stopService(); + } + + super.stopService(); + } + + + @Override public void releaseService() { disable(); + + if (jaw != null) { + ((Service)jaw).releaseService(); + } + if (eyeX != null) { + ((Service)eyeX).releaseService(); + } + if (eyeY != null) { + ((Service)eyeY).releaseService(); + } + if (neck != null) { + ((Service)neck).releaseService(); + } + if (rothead != null) { + ((Service)rothead).releaseService(); + } + if (rollNeck != null) { + ((Service)rollNeck).releaseService(); + } + if (eyelidLeft != null) { + ((Service)eyelidLeft).releaseService(); + } + if (eyelidRight != null) { + ((Service)eyelidRight).releaseService(); + } + super.releaseService(); } diff --git a/src/main/java/org/myrobotlab/service/InMoov2Torso.java b/src/main/java/org/myrobotlab/service/InMoov2Torso.java index 75fa410ca2..3754e43a3c 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Torso.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Torso.java @@ -41,11 +41,44 @@ public void startService() { midStom = (ServoControl) getPeer("midStom"); lowStom = (ServoControl) getPeer("lowStom"); } + + @Override + public void stopService() { + disable(); + + if (topStom != null) { + ((Service)topStom).stopService(); + } + + if (midStom != null) { + ((Service)midStom).stopService(); + } + + if (lowStom != null) { + ((Service)lowStom).stopService(); + } + + super.stopService(); + } @Override public void releaseService() { try { disable(); + + + if (topStom != null) { + ((Service)topStom).releaseService(); + } + + if (midStom != null) { + ((Service)midStom).releaseService(); + } + + if (lowStom != null) { + ((Service)lowStom).releaseService(); + } + topStom = null; midStom = null; From cc02708c197635d7b6d4506c262663f43b491fec Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 10 Feb 2024 17:17:39 -0800 Subject: [PATCH 040/131] clean random shutdown --- src/main/java/org/myrobotlab/service/Random.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/myrobotlab/service/Random.java b/src/main/java/org/myrobotlab/service/Random.java index 8a9e1875fb..d018df206a 100644 --- a/src/main/java/org/myrobotlab/service/Random.java +++ b/src/main/java/org/myrobotlab/service/Random.java @@ -453,6 +453,12 @@ public void disableAll() { } broadcastState(); } + + @Override + public void releaseService() { + disable(); + super.releaseService(); + } public static void main(String[] args) { try { From ab0141b771ed110bb2c66046c313020d85ff9bce Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 11 Feb 2024 07:16:12 -0800 Subject: [PATCH 041/131] drupp neck config --- .../org/myrobotlab/service/DruppNeck.java | 33 +++++++++---------- .../service/config/DruppNeckConfig.java | 6 ++++ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/DruppNeck.java b/src/main/java/org/myrobotlab/service/DruppNeck.java index 1c7aa42e39..e99a1a290c 100755 --- a/src/main/java/org/myrobotlab/service/DruppNeck.java +++ b/src/main/java/org/myrobotlab/service/DruppNeck.java @@ -4,7 +4,7 @@ import org.myrobotlab.kinematics.DruppIKSolver; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.math.MathUtils; -import org.myrobotlab.service.config.ServiceConfig; +import org.myrobotlab.service.config.DruppNeckConfig; import org.myrobotlab.service.interfaces.ServoControl; /** @@ -18,18 +18,14 @@ * @author kwatters * */ -public class DruppNeck extends Service<ServiceConfig> { +public class DruppNeck extends Service<DruppNeckConfig> { private static final long serialVersionUID = 1L; // 3 servos for the drupp neck - public transient ServoControl up; - public transient ServoControl middle; - public transient ServoControl down; + protected transient ServoControl up; + protected transient ServoControl middle; + protected transient ServoControl down; - // this is an offset angle that is added to the solution from the IK solver - public double upOffset = 90; - public double middleOffset = 120 + 90; - public double downOffset = -120 + 90; public DruppNeck(String n, String id) { super(n, id); @@ -61,9 +57,9 @@ public void moveTo(double roll, double pitch, double yaw) throws Exception { // TODO: if the solver fails, should we catch this exception ? double[] result = solver.solve(rollRad, pitchRad, yawRad); // convert to degrees - double upDeg = MathUtils.radToDeg(result[0]) + upOffset; - double middleDeg = MathUtils.radToDeg(result[1]) + middleOffset; - double downDeg = MathUtils.radToDeg(result[2]) + downOffset; + double upDeg = MathUtils.radToDeg(result[0]) + config.upOffset; + double middleDeg = MathUtils.radToDeg(result[1]) + config.middleOffset; + double downDeg = MathUtils.radToDeg(result[2]) + config.downOffset; // Ok, servos can only (typically) move from 0 to 180.. if any of the angles // are // negative... we can't move there.. let's log a warning @@ -84,6 +80,7 @@ public void moveTo(double roll, double pitch, double yaw) throws Exception { down.moveTo(downDeg); // TODO: broadcast state? } + /** * Enable the servos @@ -141,27 +138,27 @@ public void setServos(ServoControl up, ServoControl middle, ServoControl down) { } public double getUpOffset() { - return upOffset; + return config.upOffset; } public void setUpOffset(double upOffset) { - this.upOffset = upOffset; + this.config.upOffset = upOffset; } public double getMiddleOffset() { - return middleOffset; + return config.middleOffset; } public void setMiddleOffset(double middleOffset) { - this.middleOffset = middleOffset; + this.config.middleOffset = middleOffset; } public double getDownOffset() { - return downOffset; + return config.downOffset; } public void setDownOffset(double downOffset) { - this.downOffset = downOffset; + this.config.downOffset = downOffset; } public static void main(String[] args) throws Exception { diff --git a/src/main/java/org/myrobotlab/service/config/DruppNeckConfig.java b/src/main/java/org/myrobotlab/service/config/DruppNeckConfig.java index d4b3f730fb..17fc10efb9 100644 --- a/src/main/java/org/myrobotlab/service/config/DruppNeckConfig.java +++ b/src/main/java/org/myrobotlab/service/config/DruppNeckConfig.java @@ -3,6 +3,12 @@ import org.myrobotlab.framework.Plan; public class DruppNeckConfig extends ServiceConfig { + + // this is an offset angle that is added to the solution from the IK solver + public double upOffset = 90; + public double middleOffset = 120 + 90; + public double downOffset = -120 + 90; + @Override public Plan getDefault(Plan plan, String name) { From 0bd316b53f66e02f1ec4737dc7d566fba11dbb7c Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 11 Feb 2024 08:49:53 -0800 Subject: [PATCH 042/131] fixed so Runtime.release(name) and Service.releaseService(name) behave consistently --- .../org/myrobotlab/framework/Service.java | 2 +- .../org/myrobotlab/service/DruppNeck.java | 43 +++++++++++-------- .../java/org/myrobotlab/service/Runtime.java | 17 ++++++++ 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java index 0449c05e16..3f7acbf755 100644 --- a/src/main/java/org/myrobotlab/framework/Service.java +++ b/src/main/java/org/myrobotlab/framework/Service.java @@ -1608,7 +1608,7 @@ public Service<T> publishState() { @Override synchronized public void releaseService() { // auto release children and unregister - Runtime.releaseService(getName()); + Runtime.releaseServiceInternal(getName()); } /** diff --git a/src/main/java/org/myrobotlab/service/DruppNeck.java b/src/main/java/org/myrobotlab/service/DruppNeck.java index e99a1a290c..fbf5cd3a11 100755 --- a/src/main/java/org/myrobotlab/service/DruppNeck.java +++ b/src/main/java/org/myrobotlab/service/DruppNeck.java @@ -30,6 +30,13 @@ public class DruppNeck extends Service<DruppNeckConfig> { public DruppNeck(String n, String id) { super(n, id); } + + public void startService() { + super.startService(); + up = (ServoControl)startPeer("up"); + middle = (ServoControl)startPeer("middle"); + down = (ServoControl)startPeer("down"); + } private DruppIKSolver solver = new DruppIKSolver(); @@ -165,26 +172,26 @@ public static void main(String[] args) throws Exception { LoggingFactory.init("INFO"); // To use the drup service you need to configure and attach the servos // then set them on the service. - Runtime.start("gui", "SwingGui"); - Runtime.start("python", "Python"); - Servo up = (Servo) Runtime.start("up", "Servo"); - Servo middle = (Servo) Runtime.start("middle", "Servo"); - Servo down = (Servo) Runtime.start("down", "Servo"); - up.setPin(6); - middle.setPin(5); - down.setPin(4); - // String port = "COM4"; - String port = "VIRTUAL_COM_PORT"; - VirtualArduino va1 = (VirtualArduino) Runtime.start("va1", "VirtualArduino"); - va1.connect(port); - Arduino ard = (Arduino) Runtime.start("ard", "Arduino"); - ard.connect(port); - ard.attach(up); - ard.attach(middle); - ard.attach(down); +// Runtime.start("python", "Python"); +// Servo up = (Servo) Runtime.start("up", "Servo"); +// Servo middle = (Servo) Runtime.start("middle", "Servo"); +// Servo down = (Servo) Runtime.start("down", "Servo"); +// up.setPin(6); +// middle.setPin(5); +// down.setPin(4); +// // String port = "COM4"; +// String port = "VIRTUAL_COM_PORT"; +// VirtualArduino va1 = (VirtualArduino) Runtime.start("va1", "VirtualArduino"); +// va1.connect(port); +// Arduino ard = (Arduino) Runtime.start("ard", "Arduino"); +// ard.connect(port); +// ard.attach(up); +// ard.attach(middle); +// ard.attach(down); // Create the drupp service DruppNeck neck = (DruppNeck) Runtime.start("neck", "DruppNeck"); - neck.setServos(up, middle, down); + Runtime.start("webgui", "WebGui"); + // neck.setServos(up, middle, down); // neck.moveTo(0, 0, 0); // neck.moveTo(0, 0, -45); // neck.moveTo(0, 0, 45); diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 9902b3a17b..e838cf6c52 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -1933,6 +1933,22 @@ public static Registration register(Registration registration) { * */ public static boolean releaseService(String inName) { + ServiceInterface sc = getService(inName); + if (sc != null) { + sc.releaseService(); + return true; + } + return false; + } + + + /** + * Called after any subclassed releaseService has been called, this cleans + * up the registry and removes peers + * @param inName + * @return + */ + public static boolean releaseServiceInternal(String inName) { synchronized (processLock) { if (inName == null) { log.debug("release (null)"); @@ -1988,6 +2004,7 @@ public static boolean releaseService(String inName) { } } + /** * Removes registration for a service. Removes the service from * {@link #typeToInterface} and {@link #interfaceToNames}. From 3fb04c8bdb3a7b449a70b17a615f6fdd03bd5fdf Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 11 Feb 2024 11:38:32 -0800 Subject: [PATCH 043/131] drupp merge --- .../java/org/myrobotlab/service/DruppNeck.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/DruppNeck.java b/src/main/java/org/myrobotlab/service/DruppNeck.java index 9e7f11bf25..b07c42419b 100755 --- a/src/main/java/org/myrobotlab/service/DruppNeck.java +++ b/src/main/java/org/myrobotlab/service/DruppNeck.java @@ -45,13 +45,13 @@ public void startService() { * down servos. * * @param roll - * degrees + * degrees * @param pitch - * degrees + * degrees * @param yaw - * degrees + * degrees * @throws Exception - * boom + * boom * */ public void moveTo(double roll, double pitch, double yaw) throws Exception { @@ -74,14 +74,12 @@ public void moveTo(double roll, double pitch, double yaw) throws Exception { // but for the drupp neck, if you've installed it correctly, // all servos can go from 0 to 180... if (upDeg < 0 || middleDeg < 0 || downDeg < 0 || upDeg > 180 || middleDeg > 180 || downDeg > 180) { - log.warn("Target Position out of range! {} Pitch {} Yaw {} -> Up {} Middle {} Down {}", roll, pitch, yaw, - MathUtils.round(upDeg, 3), MathUtils.round(middleDeg, 3), + log.warn("Target Position out of range! {} Pitch {} Yaw {} -> Up {} Middle {} Down {}", roll, pitch, yaw, MathUtils.round(upDeg, 3), MathUtils.round(middleDeg, 3), MathUtils.round(downDeg, 3)); // Skipping this movement as it's likely unstable! return; } - log.info("Input Roll {} Pitch {} Yaw {} -> Up {} Middle {} Down {}", roll, pitch, yaw, MathUtils.round(upDeg, 3), - MathUtils.round(middleDeg, 3), MathUtils.round(downDeg, 3)); + log.info("Input Roll {} Pitch {} Yaw {} -> Up {} Middle {} Down {}", roll, pitch, yaw, MathUtils.round(upDeg, 3), MathUtils.round(middleDeg, 3), MathUtils.round(downDeg, 3)); // we should probably track the last moved to position. up.moveTo(upDeg); middle.moveTo(middleDeg); @@ -181,7 +179,8 @@ public static void main(String[] args) throws Exception { // down.setPin(4); // // String port = "COM4"; // String port = "VIRTUAL_COM_PORT"; - // VirtualArduino va1 = (VirtualArduino) Runtime.start("va1", "VirtualArduino"); + // VirtualArduino va1 = (VirtualArduino) Runtime.start("va1", + // "VirtualArduino"); // va1.connect(port); // Arduino ard = (Arduino) Runtime.start("ard", "Arduino"); // ard.connect(port); From b70114e169ec0a6112ad9e3d16e0f8bf53da5366 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 11 Feb 2024 17:51:11 -0800 Subject: [PATCH 044/131] intermediate --- .../org/myrobotlab/service/JMonkeyEngine.java | 2 +- .../org/myrobotlab/service/MotorDualPwm.java | 2 +- .../java/org/myrobotlab/service/OpenCV.java | 6 ++ .../java/org/myrobotlab/service/Python.java | 2 +- .../java/org/myrobotlab/service/Random.java | 6 ++ .../java/org/myrobotlab/service/Serial.java | 2 +- .../service/config/OpenWeatherMapConfig.java | 2 +- .../config/YahooFinanceStockQuoteConfig.java | 2 +- .../myrobotlab/service/meta/JoystickMeta.java | 6 +- .../myrobotlab/vertx/WebSocketHandler.java | 2 +- .../app/service/js/FiniteStateMachineGui.js | 1 + .../service/views/FiniteStateMachineGui.html | 17 +++++ .../myrobotlab/programab/TemplateTest.java | 52 +++++++++++++++ .../org/myrobotlab/test/AbstractTest.java | 64 +++++++++++++++++-- 14 files changed, 150 insertions(+), 16 deletions(-) create mode 100644 src/test/java/org/myrobotlab/programab/TemplateTest.java diff --git a/src/main/java/org/myrobotlab/service/JMonkeyEngine.java b/src/main/java/org/myrobotlab/service/JMonkeyEngine.java index 2d19048c84..af2d354f43 100644 --- a/src/main/java/org/myrobotlab/service/JMonkeyEngine.java +++ b/src/main/java/org/myrobotlab/service/JMonkeyEngine.java @@ -2489,7 +2489,7 @@ public static void main(String[] args) { i01.startPeer("simulator"); } - Platform.setVirtual(true); + Runtime.getInstance().setVirtual(true); // Runtime.main(new String[] { "--interactive", "--id", "admin" }); JMonkeyEngine jme = (JMonkeyEngine) Runtime.start("simulator", "JMonkeyEngine"); diff --git a/src/main/java/org/myrobotlab/service/MotorDualPwm.java b/src/main/java/org/myrobotlab/service/MotorDualPwm.java index e1746ca071..3187e52ee0 100644 --- a/src/main/java/org/myrobotlab/service/MotorDualPwm.java +++ b/src/main/java/org/myrobotlab/service/MotorDualPwm.java @@ -96,7 +96,7 @@ public static void main(String[] args) { LoggingFactory.init(Level.INFO); String arduinoPort = "COM5"; - Platform.setVirtual(true); + Runtime.getInstance().setVirtual(true); Runtime.startConfig("dev"); Runtime.start("webgui", "WebGui"); MotorDualPwm motor = (MotorDualPwm) Runtime.start("motor", "MotorDualPwm"); diff --git a/src/main/java/org/myrobotlab/service/OpenCV.java b/src/main/java/org/myrobotlab/service/OpenCV.java index ff093721bd..dcd7ad08d9 100644 --- a/src/main/java/org/myrobotlab/service/OpenCV.java +++ b/src/main/java/org/myrobotlab/service/OpenCV.java @@ -461,6 +461,7 @@ public void reset() { singleFrame = false; lastFrame = null; blockingData.clear(); + removeFilters(); } public static IplImage cropImage(IplImage img, CvRect rect) { @@ -2099,6 +2100,11 @@ public static void main(String[] args) throws Exception { // Runtime.start("python", "Python"); OpenCV cv = (OpenCV) Runtime.start("cv", "OpenCV"); + cv.capture(); + + cv.addFilter(new OpenCVFilterYolo("yolo")); + sleep(1000); + cv.removeFilters(); OpenCVFilter fr = new OpenCVFilterFaceRecognizer("fr"); cv.addFilter(fr); diff --git a/src/main/java/org/myrobotlab/service/Python.java b/src/main/java/org/myrobotlab/service/Python.java index 7700a1318d..c3d18162ed 100644 --- a/src/main/java/org/myrobotlab/service/Python.java +++ b/src/main/java/org/myrobotlab/service/Python.java @@ -679,7 +679,7 @@ public void onStarted(String serviceName) { @Override public void onReleased(String serviceName) { - String registerScript = String.format("%s = None\n", CodecUtils.getSafeReferenceName(serviceName)); + String registerScript = String.format("%s = None\n", CodecUtils.getSafeReferenceName(CodecUtils.getShortName(serviceName))); exec(registerScript, false); } diff --git a/src/main/java/org/myrobotlab/service/Random.java b/src/main/java/org/myrobotlab/service/Random.java index 8a9e1875fb..d018df206a 100644 --- a/src/main/java/org/myrobotlab/service/Random.java +++ b/src/main/java/org/myrobotlab/service/Random.java @@ -453,6 +453,12 @@ public void disableAll() { } broadcastState(); } + + @Override + public void releaseService() { + disable(); + super.releaseService(); + } public static void main(String[] args) { try { diff --git a/src/main/java/org/myrobotlab/service/Serial.java b/src/main/java/org/myrobotlab/service/Serial.java index af16f3522a..c6fccb9d84 100644 --- a/src/main/java/org/myrobotlab/service/Serial.java +++ b/src/main/java/org/myrobotlab/service/Serial.java @@ -1338,7 +1338,7 @@ public static void main(String[] args) { try { - Platform.setVirtual(true); + Runtime.getInstance().setVirtual(true); Serial s = (Serial) Runtime.start("s1", "Serial"); String vport1 = "vport1"; diff --git a/src/main/java/org/myrobotlab/service/config/OpenWeatherMapConfig.java b/src/main/java/org/myrobotlab/service/config/OpenWeatherMapConfig.java index 37cdd82a0b..9a6a51baab 100644 --- a/src/main/java/org/myrobotlab/service/config/OpenWeatherMapConfig.java +++ b/src/main/java/org/myrobotlab/service/config/OpenWeatherMapConfig.java @@ -3,7 +3,7 @@ import org.myrobotlab.framework.Peer; import org.myrobotlab.framework.Plan; -public class OpenWeatherMapConfig extends ServiceConfig { +public class OpenWeatherMapConfig extends HttpClientConfig { public String currentUnits; public String currentTown; diff --git a/src/main/java/org/myrobotlab/service/config/YahooFinanceStockQuoteConfig.java b/src/main/java/org/myrobotlab/service/config/YahooFinanceStockQuoteConfig.java index d351154c44..32ae5984d8 100644 --- a/src/main/java/org/myrobotlab/service/config/YahooFinanceStockQuoteConfig.java +++ b/src/main/java/org/myrobotlab/service/config/YahooFinanceStockQuoteConfig.java @@ -1,5 +1,5 @@ package org.myrobotlab.service.config; -public class YahooFinanceStockQuoteConfig extends ServiceConfig { +public class YahooFinanceStockQuoteConfig extends HttpClientConfig { } diff --git a/src/main/java/org/myrobotlab/service/meta/JoystickMeta.java b/src/main/java/org/myrobotlab/service/meta/JoystickMeta.java index ca544c8523..b2fb538f80 100644 --- a/src/main/java/org/myrobotlab/service/meta/JoystickMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/JoystickMeta.java @@ -20,12 +20,12 @@ public JoystickMeta() { addCategory("control", "telerobotics"); addDependency("net.java.jinput", "jinput", "2.0.9"); - log.info("Joystick.getMetaData {} isArm() {}", platform, platform.isArm()); + log.debug("Joystick.getMetaData {} isArm() {}", platform, platform.isArm()); if (platform.isArm()) { - log.info("adding armv7 native dependencies"); + log.debug("adding armv7 native dependencies"); addDependency("jinput-natives", "jinput-natives-armv7.hfp", "2.0.7", "zip"); } else { - log.info("adding jinput native dependencies"); + log.debug("adding jinput native dependencies"); addDependency("jinput-natives", "jinput-natives", "2.0.7", "zip"); } } diff --git a/src/main/java/org/myrobotlab/vertx/WebSocketHandler.java b/src/main/java/org/myrobotlab/vertx/WebSocketHandler.java index e7e6b5fe63..c74b12e185 100644 --- a/src/main/java/org/myrobotlab/vertx/WebSocketHandler.java +++ b/src/main/java/org/myrobotlab/vertx/WebSocketHandler.java @@ -131,7 +131,7 @@ public void handle(String json) { // FIXME get rid of fill-uuid Message describe = Message.createMessage(String.format("%s@%s", service.getName(), Runtime.get().getId()), "runtime", "describe", - new Object[] { "fill-uuid", new DescribeQuery(Platform.getLocalInstance().getId(), uuid) }); + new Object[] { "fill-uuid", new DescribeQuery(Runtime.getInstance().getId(), uuid) }); service.sendRemote(describe); log.info(String.format("<-- %s", describe)); newConnection = false; diff --git a/src/main/resources/resource/WebGui/app/service/js/FiniteStateMachineGui.js b/src/main/resources/resource/WebGui/app/service/js/FiniteStateMachineGui.js index 42c559c999..d08f8f2fc8 100644 --- a/src/main/resources/resource/WebGui/app/service/js/FiniteStateMachineGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/FiniteStateMachineGui.js @@ -51,6 +51,7 @@ angular.module('mrlapp.service.FiniteStateMachineGui', []).controller('FiniteSta break case 'onStateChange': $scope.current = data.current + $scope.service.history.push(data) $scope.$apply() break default: diff --git a/src/main/resources/resource/WebGui/app/service/views/FiniteStateMachineGui.html b/src/main/resources/resource/WebGui/app/service/views/FiniteStateMachineGui.html index 067ee7624c..b61f5af303 100644 --- a/src/main/resources/resource/WebGui/app/service/views/FiniteStateMachineGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/FiniteStateMachineGui.html @@ -54,5 +54,22 @@ <h3>Last Event {{event}} Current State: {{current}}</h3> <td></td> </tr> </table> +<table class="table-condensed table-striped table-bordered"> + <thead> + <tr> + <th>Timestamp</th> + <th>State</th> + <th>Event</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="item in service.history"> + <td>{{ item.ts }}</td> + <td>{{ item.state }}</td> + <td>{{ item.event }}</td> + </tr> + </tbody> +</table> + </div> </div> diff --git a/src/test/java/org/myrobotlab/programab/TemplateTest.java b/src/test/java/org/myrobotlab/programab/TemplateTest.java new file mode 100644 index 0000000000..8d2beb245d --- /dev/null +++ b/src/test/java/org/myrobotlab/programab/TemplateTest.java @@ -0,0 +1,52 @@ +package org.myrobotlab.programab; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.programab.models.Mrl; +import org.myrobotlab.programab.models.Template; +import org.slf4j.Logger; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + +public class TemplateTest { + + public final static Logger log = LoggerFactory.getLogger(TemplateTest.class); + + @Test + public void testXmlParsing() { + try { + + String xml = "<template>XXXX<oob><mrl><service>blah1</service><method>method1</method><param>p1</param><param>p2</param><param>p3</param></mrl><mrl><service>blah2</service><method>method2</method></mrl><mrljson>[\"method\":\"doIt\",\"data\":[\"p1\"]]</mrljson></oob></template>"; + + XmlMapper xmlMapper = new XmlMapper(); + Template template = xmlMapper.readValue(xml, Template.class); + + assertNotNull(template); + assertEquals("XXXX", template.text); + + // Verify Oob parsing + assertNotNull(template.oob); + assertEquals(2, template.oob.mrl.size()); + + // Verify the first Mrl + Mrl mrl1 = template.oob.mrl.get(0); + assertEquals("blah1", mrl1.service); + assertEquals("method1", mrl1.method); + assertEquals(3, mrl1.params.size()); + + // Verify the second Mrl + Mrl mrl2 = template.oob.mrl.get(1); + assertEquals("blah2", mrl2.service); + assertEquals("method2", mrl2.method); + assertNull(mrl2.params); + + } catch (Exception e) { + fail("Exception occurred: " + e.getMessage()); + } + } +} diff --git a/src/test/java/org/myrobotlab/test/AbstractTest.java b/src/test/java/org/myrobotlab/test/AbstractTest.java index c4fc33b83d..d782797c9b 100644 --- a/src/test/java/org/myrobotlab/test/AbstractTest.java +++ b/src/test/java/org/myrobotlab/test/AbstractTest.java @@ -1,5 +1,7 @@ package org.myrobotlab.test; +import java.io.File; +import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -13,12 +15,19 @@ import org.junit.BeforeClass; import org.junit.Rule; import org.junit.rules.TestName; +import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.Platform; import org.myrobotlab.framework.interfaces.Attachable; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.Runtime; +import org.myrobotlab.service.config.RuntimeConfig; import org.slf4j.Logger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + public class AbstractTest { /** cached network test value for tests */ @@ -44,6 +53,34 @@ public class AbstractTest { static public String simpleName; private static boolean lineFeedFooter = true; + + @Rule + public TestWatcher watchman = new TestWatcher() { + @Override + protected void starting(Description description) { + System.out.println("Starting: " + description.getClassName() + "." + description.getMethodName()); + } + + @Override + protected void succeeded(Description description) { + // System.out.println("Succeeded: " + description.getMethodName()); + } + + @Override + protected void failed(Throwable e, Description description) { + System.out.println("Failed: " + description.getMethodName()); + } + + @Override + protected void skipped(org.junit.AssumptionViolatedException e, Description description) { + System.out.println("Skipped: " + description.getMethodName()); + } + + @Override + protected void finished(Description description) { + System.out.println("Finished: " + description.getMethodName()); + } + }; public String getSimpleName() { return simpleName; @@ -83,8 +120,23 @@ public static void main(String[] args) { @BeforeClass public static void setUpAbstractTest() throws Exception { - - Platform.setVirtual(true); + + // setup runtime resource = src/main/resources/resource + File runtimeYml = new File("data/config/default/runtime.yml"); +// if (!runtimeYml.exists()) { + runtimeYml.getParentFile().mkdirs(); + RuntimeConfig rc = new RuntimeConfig(); + rc.resource = "src/main/resources/resource"; + String yml = CodecUtils.toYaml(rc); + + FileOutputStream fos = null; + fos = new FileOutputStream(runtimeYml); + fos.write(yml.getBytes()); + fos.close(); + +// } + + Runtime.getInstance().setVirtual(true); String junitLogLevel = System.getProperty("junit.logLevel"); if (junitLogLevel != null) { @@ -145,7 +197,7 @@ static protected void installAll() { */ public static void releaseServices() { - log.info("end of test - id {} remaining services {}", Platform.getLocalInstance().getId(), + log.info("end of test - id {} remaining services {}", Runtime.getInstance().getId(), Arrays.toString(Runtime.getServiceNames())); // release all including runtime - be careful of default runtime.yml @@ -176,7 +228,7 @@ public static void releaseServices() { } } if (threadsRemaining.size() > 0) { - log.info("{} straggling threads remain [{}]", threadsRemaining.size(), String.join(",", threadsRemaining)); + log.warn("{} straggling threads remain [{}]", threadsRemaining.size(), String.join(",", threadsRemaining)); } // log.warn("end of test - id {} remaining services after release {}", @@ -192,11 +244,11 @@ public AbstractTest() { } public void setVirtual() { - Platform.setVirtual(true); + Runtime.getInstance().setVirtual(true); } public boolean isVirtual() { - return Platform.isVirtual(); + return Runtime.getInstance().isVirtual(); } } From abd63e2143779d5d337010c0ca05b11e9c1d27fd Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Mon, 12 Feb 2024 05:59:34 -0800 Subject: [PATCH 045/131] javadoc and other small changes --- .../java/org/myrobotlab/codec/CodecUtils.java | 4 +- .../org/myrobotlab/config/ConfigUtils.java | 142 +++++++++++++----- .../framework/repo/MavenWrapper.java | 6 +- .../org/myrobotlab/opencv/OpenCVFilter.java | 2 +- .../opencv/OpenCVFilterMiniXception.java | 5 +- .../myrobotlab/opencv/OpenCVFilterYolo.java | 4 +- .../myrobotlab/programab/models/Event.java | 24 +-- .../org/myrobotlab/programab/models/Oob.java | 12 +- .../myrobotlab/programab/models/Sraix.java | 16 +- .../myrobotlab/programab/models/Template.java | 45 ++---- .../service/interfaces/Gateway.java | 2 +- .../myrobotlab/config/ConfigUtilsTest.java | 43 ++++++ .../org/myrobotlab/service/OpenCVTest.java | 11 +- 13 files changed, 219 insertions(+), 97 deletions(-) create mode 100644 src/test/java/org/myrobotlab/config/ConfigUtilsTest.java diff --git a/src/main/java/org/myrobotlab/codec/CodecUtils.java b/src/main/java/org/myrobotlab/codec/CodecUtils.java index 372caa474b..09d2086c9e 100644 --- a/src/main/java/org/myrobotlab/codec/CodecUtils.java +++ b/src/main/java/org/myrobotlab/codec/CodecUtils.java @@ -495,7 +495,7 @@ public static String getFullName(String name) { } if (getId(name) == null) { - return name + '@' + Platform.getLocalInstance().getId(); + return name + '@' + Runtime.getInstance().getId(); } else { return name; } @@ -1466,7 +1466,7 @@ public static boolean isLocal(String name) { if (!name.contains("@")) { return true; } - return name.substring(name.indexOf("@") + 1).equals(Platform.getLocalInstance().getId()); + return name.substring(name.indexOf("@") + 1).equals(Runtime.getInstance().getId()); } /** diff --git a/src/main/java/org/myrobotlab/config/ConfigUtils.java b/src/main/java/org/myrobotlab/config/ConfigUtils.java index 35c8a776a8..19c256a8cf 100644 --- a/src/main/java/org/myrobotlab/config/ConfigUtils.java +++ b/src/main/java/org/myrobotlab/config/ConfigUtils.java @@ -4,13 +4,26 @@ import java.io.IOException; import org.myrobotlab.codec.CodecUtils; +import org.myrobotlab.framework.CmdOptions; import org.myrobotlab.framework.StartYml; import org.myrobotlab.io.FileIO; +import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.Runtime; import org.myrobotlab.service.config.RuntimeConfig; +import org.slf4j.Logger; +/** + * Class to process basic configuration functions and processing. + * + * @author GroG + * + */ public class ConfigUtils { + public final static Logger log = LoggerFactory.getLogger(Runtime.class); + + private static RuntimeConfig config; + /** * This gets the current resource root without starting a Runtime instance if * not already started. The resource root depends on config, if Runtime is @@ -21,51 +34,100 @@ public class ConfigUtils { * @return */ public static String getResourceRoot() { + if (config == null) { + loadRuntimeConfig(null); + } + return config.resource; + + } + + /** + * Loads a runtime config based on the configName. config = + * data/config/{configName}/runtime.yml If one does exits, it is returned, if + * one does not exist a default one is created and saved. + * + * @param configName + * @return + */ + static public RuntimeConfig loadRuntimeConfig(CmdOptions options) { + + if (config != null) { + return config; + } - String resource = "resource"; + StartYml startYml = loadStartYml(); + String configName = null; - // check if runtime is running - if (!Runtime.isAvailable()) { - // check for start.yml + if (startYml.enable) { + configName = startYml.config; + } + + // start with default + config = new RuntimeConfig(); + try { - File checkStartYml = new File("start.yml"); - StartYml startYml = new StartYml(); - if (checkStartYml.exists()) { - String yml; + File runtimeYml = new File(Runtime.ROOT_CONFIG_DIR + File.separator + configName + File.separator + "runtime.yml"); + if (runtimeYml.exists()) { + // parse that file look for resource: entry in file + config = (RuntimeConfig) CodecUtils.readServiceConfig(runtimeYml.getAbsolutePath()); + } else { + FileIO.toFile(runtimeYml, CodecUtils.toYaml(config).getBytes()); + } + + } catch (IOException e) { + log.error("loadRuntimeConfig threw", e); + } + + if (options != null && options.id != null) { + config.id = options.id; + } + + return config; + } + + public static StartYml loadStartYml() { + StartYml startYml = new StartYml(); + String defaultStartFile = CodecUtils.toYaml(startYml); + File checkStartYml = new File("start.yml"); + if (!checkStartYml.exists()) { + // save default start.yml + startYml = new StartYml(); + try { + FileIO.toFile("start.yml", defaultStartFile); + } catch (IOException e) { + log.error("could not save start.yml"); + } + } else { + // load start.yml + try { + String yml = FileIO.toString("start.yml"); + startYml = CodecUtils.fromYaml(yml, StartYml.class); + } catch (Exception e) { + log.error("could not load start.yml replacing with new start.yml", e); + startYml = new StartYml(); try { - yml = FileIO.toString("start.yml"); - startYml = CodecUtils.fromYaml(yml, StartYml.class); - - // see if autostart is on with a config - if (startYml.enable) { - // use that config to find runtime.yml - - File runtimeYml = new File(Runtime.ROOT_CONFIG_DIR + File.separator + startYml.config + File.separator + "runtime.yml"); - if (runtimeYml.exists()) { - // parse that file look for resource: entry in file - RuntimeConfig config = (RuntimeConfig) CodecUtils.readServiceConfig(runtimeYml.getAbsolutePath()); - resource = config.resource; - } - - } else { - // start.yml enable = false / so we'll use default config - File runtimeYml = new File(Runtime.ROOT_CONFIG_DIR + File.separator + "default" + File.separator + "runtime.yml"); - if (runtimeYml.exists()) { - // parse that file look for resource: entry in file - RuntimeConfig config = (RuntimeConfig) CodecUtils.readServiceConfig(runtimeYml.getAbsolutePath()); - resource = config.resource; - } - } - - } catch (IOException e) { - // problem getting or parsing - // going to assume default "resource" + FileIO.toFile("start.yml", defaultStartFile); + } catch (IOException ex) { + log.error("could not save start.yml", ex); } - } // no startYml - return resource; - } else { - // Runtime is available - ask it - return Runtime.getInstance().getConfig().resource; + } } + log.info("start.yml exists {} {}", checkStartYml.exists(), CodecUtils.toJson(startYml)); + return startYml; } + + public static String getId() { + if (config == null) { + loadRuntimeConfig(null); + } + return config.id; + } + + /** + * If Runtime.releaseAll is called the statics here should be reset + */ + public static void reset() { + config = null; + } + } diff --git a/src/main/java/org/myrobotlab/framework/repo/MavenWrapper.java b/src/main/java/org/myrobotlab/framework/repo/MavenWrapper.java index 8374f92cdf..62c0027cbd 100644 --- a/src/main/java/org/myrobotlab/framework/repo/MavenWrapper.java +++ b/src/main/java/org/myrobotlab/framework/repo/MavenWrapper.java @@ -1,5 +1,5 @@ package org.myrobotlab.framework.repo; - +import org.myrobotlab.service.Runtime; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -281,6 +281,8 @@ public static void main(String[] args) { LoggingFactory.init(Level.INFO); + Runtime.getInstance(); + File libraries = new File(ServiceData.LIBRARIES); libraries.mkdir(); File cache = new File(ServiceData.LIBRARIES + File.separator + "serviceData.json"); @@ -309,7 +311,7 @@ public static void main(String[] args) { // repo.installTo(dir); // repo.install(); // repo.installEach(); <-- TODO - test - + Runtime.shutdown(); log.info("done"); } catch (Exception e) { diff --git a/src/main/java/org/myrobotlab/opencv/OpenCVFilter.java b/src/main/java/org/myrobotlab/opencv/OpenCVFilter.java index ac90bae143..860bceadc3 100644 --- a/src/main/java/org/myrobotlab/opencv/OpenCVFilter.java +++ b/src/main/java/org/myrobotlab/opencv/OpenCVFilter.java @@ -192,7 +192,7 @@ static private Mat read(String filename) { /** * This will enable/disable the filter in the pipeline */ - protected boolean enabled = true; + protected volatile boolean enabled = true; protected int height; diff --git a/src/main/java/org/myrobotlab/opencv/OpenCVFilterMiniXception.java b/src/main/java/org/myrobotlab/opencv/OpenCVFilterMiniXception.java index 8dea3fdf75..bd5de81abb 100755 --- a/src/main/java/org/myrobotlab/opencv/OpenCVFilterMiniXception.java +++ b/src/main/java/org/myrobotlab/opencv/OpenCVFilterMiniXception.java @@ -59,7 +59,7 @@ public OpenCVFilterMiniXception(String name) { } private void loadDL4j() { - dl4j = (Deeplearning4j) Runtime.createAndStart("dl4j", "Deeplearning4j"); + dl4j = (Deeplearning4j) Runtime.start("dl4j", "Deeplearning4j"); log.info("Loading mini XCEPTION Model."); try { dl4j.loadMiniEXCEPTION(); @@ -158,6 +158,9 @@ public void release() { running = false; converter1.close(); converter2.close(); + if (dl4j != null) { + dl4j.releaseService(); + } } @Override diff --git a/src/main/java/org/myrobotlab/opencv/OpenCVFilterYolo.java b/src/main/java/org/myrobotlab/opencv/OpenCVFilterYolo.java index ce83566839..060b5e43ce 100755 --- a/src/main/java/org/myrobotlab/opencv/OpenCVFilterYolo.java +++ b/src/main/java/org/myrobotlab/opencv/OpenCVFilterYolo.java @@ -378,12 +378,12 @@ public void enable() { @Override public void disable() { + super.disable(); if (classifier == null) { // already disabled return; } - super.disable(); - int waitTime = 0; + int waitTime = 0; while (classifier != null && waitTime < 1000) { ++waitTime; Service.sleep(10); diff --git a/src/main/java/org/myrobotlab/programab/models/Event.java b/src/main/java/org/myrobotlab/programab/models/Event.java index 7f1033ccdf..d7aeddd852 100644 --- a/src/main/java/org/myrobotlab/programab/models/Event.java +++ b/src/main/java/org/myrobotlab/programab/models/Event.java @@ -2,12 +2,13 @@ /** * Pojo for state change of one of ProgramAB's state info + * * @author GroG * */ public class Event { /** - * the botName in this state change - typically + * the botName in this state change - typically * current session botName */ public String botname; @@ -15,49 +16,48 @@ public class Event { * unique identifier for the session user and 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() { } - + 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/Oob.java b/src/main/java/org/myrobotlab/programab/models/Oob.java index 833bab5a0f..5e0d99c4cf 100644 --- a/src/main/java/org/myrobotlab/programab/models/Oob.java +++ b/src/main/java/org/myrobotlab/programab/models/Oob.java @@ -4,11 +4,17 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +/** + * AIML 2.0 Oob Out Of Band xml defined with mrl - legacy and mrljson - json + * typed message + * + * @author GroG + * + */ public class Oob { - + public String mrljson; - + @JacksonXmlElementWrapper(useWrapping = false) public List<Mrl> mrl; } - diff --git a/src/main/java/org/myrobotlab/programab/models/Sraix.java b/src/main/java/org/myrobotlab/programab/models/Sraix.java index 99b0639cb6..1b130ad805 100644 --- a/src/main/java/org/myrobotlab/programab/models/Sraix.java +++ b/src/main/java/org/myrobotlab/programab/models/Sraix.java @@ -1,10 +1,22 @@ package org.myrobotlab.programab.models; -// FIXME add attributes and internal tags +/** + * Basic Sraix model, AIML 2.0 has more elements but these seemed like the most + * relevant and ar actually used. + * + * @author GroG + * + */ public class Sraix { + /** + * Search text when a query is sent to a remote system + */ public String search; + /** + * Oob is Out Of Band text which can be handled by internal processing + */ 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 index 91f8e5de51..d657973005 100644 --- a/src/main/java/org/myrobotlab/programab/models/Template.java +++ b/src/main/java/org/myrobotlab/programab/models/Template.java @@ -5,47 +5,34 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; -//@JacksonXmlRootElement(localName = "template") -//@JsonIgnoreProperties(ignoreUnknown = true) +/** + * General aiml template used for future parsing + * + * @author GroG + */ @JsonIgnoreProperties(ignoreUnknown = true) public class Template { - // @JacksonXmlElementWrapper(useWrapping = false) - + @JacksonXmlProperty(localName = "template") - @JacksonXmlText - public String text; - - -public Oob oob; - -// @JsonProperty("ignorable") -// public List<Oob> oob; -// -// public List<Oob> getOob() { -// return oob; -// } -// -// public void setOob(List<Oob> oob) { -// this.oob = oob; -// } - + public String text; + + public Oob oob; + public static void main(String[] args) { try { - - // String xml = "<template>XXX<oob><mrl><service>blah</service><method>method</method></mrl></oob></template>"; - // String xml = "<template>XXXX<oob><mrl><service>blah1</service><method>method1</method></mrl><mrl><service>blah2</service><method>method2</method></mrl></oob></template>"; + String xml = "<template>XXXX<oob><mrl><service>blah1</service><method>method1</method><param>p1</param><param>p2</param><param>p3</param></mrl><mrl><service>blah2</service><method>method2</method></mrl><mrljson>[\"method\":\"doIt\",\"data\":[\"p1\"]]</mrljson></oob></template>"; - + XmlMapper xmlMapper = new XmlMapper(); Template template = xmlMapper.readValue(xml, Template.class); - + System.out.println(template); - - } catch(Exception e) { + + } catch (Exception e) { e.printStackTrace(); } - } + } } diff --git a/src/main/java/org/myrobotlab/service/interfaces/Gateway.java b/src/main/java/org/myrobotlab/service/interfaces/Gateway.java index 783ec951ff..7b3ee61b19 100644 --- a/src/main/java/org/myrobotlab/service/interfaces/Gateway.java +++ b/src/main/java/org/myrobotlab/service/interfaces/Gateway.java @@ -83,7 +83,7 @@ default Message getDescribeMsg(String connId) { "describe", new Object[] { FILL_UUID_MAGIC_VAL, - new DescribeQuery(Platform.getLocalInstance().getId(), connId) + new DescribeQuery(Runtime.getInstance().getId(), connId) } ); } diff --git a/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java b/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java new file mode 100644 index 0000000000..5d15601b58 --- /dev/null +++ b/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java @@ -0,0 +1,43 @@ +package org.myrobotlab.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Before; +import org.junit.Test; +import org.myrobotlab.framework.StartYml; +import org.myrobotlab.service.Runtime; + +public class ConfigUtilsTest { + + @Before + public void beforeTest() { + Runtime.releaseAll(true, true); + } + + @Test + public void testGetResourceRoot() { + String resource = ConfigUtils.getResourceRoot(); + // could be affected by dirty filesystem + assertEquals("resource", resource); + } + + @Test + public void testLoadRuntimeConfig() { + String resource = ConfigUtils.getResourceRoot(); + assertNotNull(resource); + } + + @Test + public void testLoadStartYml() { + StartYml start = ConfigUtils.loadStartYml(); + assertNotNull(start); + } + + @Test + public void testGetId() { + assertEquals(ConfigUtils.getId(), ConfigUtils.loadRuntimeConfig(null).id); + } + + +} diff --git a/src/test/java/org/myrobotlab/service/OpenCVTest.java b/src/test/java/org/myrobotlab/service/OpenCVTest.java index fa9e250ae5..137a12a499 100644 --- a/src/test/java/org/myrobotlab/service/OpenCVTest.java +++ b/src/test/java/org/myrobotlab/service/OpenCVTest.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; @@ -87,6 +88,12 @@ public static void main(String[] args) { @Rule public final TestName testName = new TestName(); + + @Before + public void beforeTest() { + cv.reset(); + } + @BeforeClass public static void setUpBeforeClass() throws Exception { @@ -222,8 +229,8 @@ public final void testAllFilterTypes() { for (String fn : OpenCV.POSSIBLE_FILTERS) { log.warn("trying filter {}", fn); - if (fn.startsWith("DL4J") || fn.startsWith("FaceTraining") || fn.startsWith("Tesseract") || fn.startsWith("SimpleBlobDetector") || fn.startsWith("Solr") || fn.startsWith("Split")) { - log.info("skipping {}", fn); + if ( fn.startsWith("FaceDetectDNN") || fn.startsWith("FaceRecognizer") || fn.startsWith("DL4J") || fn.startsWith("FaceTraining") || fn.startsWith("Tesseract") || fn.startsWith("SimpleBlobDetector") || fn.startsWith("Solr") || fn.startsWith("Split")) { + log.warn("skipping {}", fn); continue; } cv.addFilter(fn); From 69013e59c5ad3873065b8063e8059c0d6d2a0f89 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Mon, 12 Feb 2024 06:20:53 -0800 Subject: [PATCH 046/131] framework updates --- Jenkinsfile | 24 +- README.md | 36 + .../org/myrobotlab/framework/CmdOptions.java | 62 +- .../org/myrobotlab/framework/Platform.java | 51 +- .../org/myrobotlab/framework/Service.java | 24 +- .../org/myrobotlab/framework/StartYml.java | 4 - src/main/java/org/myrobotlab/io/FileIO.java | 36 +- .../java/org/myrobotlab/process/Launcher.java | 43 +- .../java/org/myrobotlab/service/Runtime.java | 1534 ++++++++--------- .../java/org/myrobotlab/service/WebGui.java | 46 +- .../service/config/RuntimeConfig.java | 35 +- .../org/myrobotlab/codec/CodecUtilsTest.java | 19 - .../myrobotlab/framework/CmdOptionsTest.java | 36 +- .../org/myrobotlab/framework/ConfigTest.java | 132 +- .../java/org/myrobotlab/io/FileIOTest.java | 6 +- .../org/myrobotlab/service/RuntimeTest.java | 17 +- .../org/myrobotlab/service/SerialTest.java | 2 +- 17 files changed, 990 insertions(+), 1117 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 8e7ccc45f2..63a28dcadb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -64,18 +64,18 @@ pipeline { } // stage build - stage('dependencies') { - when { - expression { params.verify == 'true' } - } - steps { - script { - sh ''' - mvn test -Dtest=org.myrobotlab.framework.DependencyTest -q - ''' - } - } - } // stage dependencies + // stage('dependencies') { + // when { + // expression { params.verify == 'true' } + // } + // steps { + // script { + // sh ''' + // mvn test -Dtest=org.myrobotlab.framework.DependencyTest -q + // ''' + // } + // } + // } // stage dependencies // --fail-fast // -DargLine="-Xmx1024m" diff --git a/README.md b/README.md index 69e1433cbf..f22e3b94f8 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,42 @@ type: Runtime virtual: false ``` +# Starting Flowchart +```mermaid +flowchart LR + CommandLine[CommandLine] + Runtime.main([Runtime.main]) + install{install} + shutdown([shutdown]) + checkForStartYml{start.yml + exists?} + startYmlEnabled{start.yml + enabled?} + + CommandLine --> Runtime.main + Runtime.main --> checkForStartYml + checkForStartYml --> |yes| loadStartYml[load start.yml] + checkForStartYml --> |no| createDefaultStartYml[create default start.yml] + createDefaultStartYml --> loadStartYml + loadStartYml --> startYmlEnabled + startYmlEnabled --> |yes| Runtime.startConfig[config = start.yml config] + startYmlEnabled --> |no| default[config = default] + Runtime.startConfig --> loadRuntimeConfig[load runtime config] + default --> loadRuntimeConfig + loadRuntimeConfig --> startRuntime[start runtime] + startRuntime --> applyRuntimeConfig[apply runtime config + does not process registry] + applyRuntimeConfig --> install{install?} + + install -->|yes| loadServiceData[loadServiceData] + install -->|no| Runtime.startConf[get runtime.startConfig config] + + loadServiceData --> findUninstalledDependencies[find uninstallled dependencies] + findUninstalledDependencies -->installDependencies[install dependencies] + installDependencies --> shutdown +``` + + # Network Distributed Architecture ## Websockets - Default Response for New Connection diff --git a/src/main/java/org/myrobotlab/framework/CmdOptions.java b/src/main/java/org/myrobotlab/framework/CmdOptions.java index f0eb00c0e7..2c357e8db6 100644 --- a/src/main/java/org/myrobotlab/framework/CmdOptions.java +++ b/src/main/java/org/myrobotlab/framework/CmdOptions.java @@ -26,9 +26,7 @@ * </pre> */ @Command(name = "java -jar myrobotlab.jar ") -public class CmdOptions { - - public final String DEFAULT_CONNECT = "http://localhost:8888"; +public class CmdOptions { static boolean contains(List<String> l, String flag) { for (String f : l) { @@ -39,51 +37,28 @@ static boolean contains(List<String> l, String flag) { return false; } - // launcher ?? - @Option(names = { "-a", "--auto-update" }, description = "auto updating - this feature allows mrl instances to be automatically updated when a new version is available") - public boolean autoUpdate = false; - // launcher @Option(names = { "-c", - "--config" }, fallbackValue="default", description = "Specify a configuration set to start. The config set is a directory which has all the necessary configuration files. It loads runtime.yml first, and subsequent service configuration files will then load. \n example: --config data/config/my-config-dir") + "--config" }, fallbackValue = "default", description = "Specify a configuration set to start. The config set is a directory which has all the necessary configuration files. It loads runtime.yml first, and subsequent service configuration files will then load. \n example: --config data/config/my-config-dir") public String config = null; - @Option(names = { - "--connect" }, arity = "0..*", /* - * defaultValue = DEFAULT_CONNECT, - */ fallbackValue = DEFAULT_CONNECT, description = "connects this mrl instance to another mrl instance - default is " + DEFAULT_CONNECT) - public String connect = null; - @Option(names = { "-h", "-?", "--help" }, description = "shows help") public boolean help = false; - - @Option(names = { "-r", "--config-root" }, description = "sets configuration root, the root for which all config directories are in") - public String configRoot = null; - - - @Option(names = { "--id" }, description = "process identifier to be mdns or network overlay name for this instance - one is created at random if not assigned") + @Option(names = { + "--id" }, description = "process identifier to be mdns or network overlay name for this instance - one is created at random if not assigned") public String id; @Option(names = { "-i", "--install" }, arity = "0..*", description = "installs all dependencies for all services, --install {serviceType} installs dependencies for a specific service, if no type is specified then all services are installed") public String install[]; - @Option(names = { "-I", - "--invoke" }, arity = "0..*", description = "invokes a method on a service --invoke {serviceName} {method} {param0} {param1} ... : --invoke python execFile myFile.py") - public String invoke[]; - - // for launcher @Option(names = { "-j", "--jvm" }, arity = "0..*", description = "jvm parameters for the instance of mrl") public String jvm; - @Option(names = { "-l", "--log-level" }, description = "log level - helpful for troubleshooting [debug info warn error]") + @Option(names = { "-l", + "--log-level" }, description = "log level - helpful for troubleshooting [debug info warn error]") public String logLevel = "info"; - @Option(names = { "--log-file" }, description = "log file name [myrobotlab.log]") - public String logFile = "myrobotlab.log"; - - // FIXME - highlight or italics for examples !! - // launcher @Option(names = { "-m", "--memory" }, description = "adjust memory can e.g. -m 2g \n -m 128m") public String memory = null; @@ -91,9 +66,6 @@ static boolean contains(List<String> l, String flag) { "--services" }, arity = "0..*", description = "services requested on startup, the services must be {name} {Type} paired, e.g. gui SwingGui webgui WebGui servo Servo ...") public List<String> services = new ArrayList<>(); - @Option(names = { "-V", "--virtual" }, description = "sets global environment as virtual - all services which support virtual hardware will create virtual hardware") - public boolean virtual = false; - public CmdOptions() { } @@ -133,34 +105,18 @@ public static String toString(String[] cmdLine) { * * @return the list of output command * @throws IOException - * boom + * boom * */ public List<String> getOutputCmd() throws IOException { List<String> cmd = new ArrayList<>(); - if (autoUpdate) { - cmd.add("-a"); - } - if (config != null) { cmd.add("--config"); cmd.add(config); } - if (connect != null) { - cmd.add("-c"); - cmd.add(connect); - } - - if (invoke != null) { - cmd.add("-I"); - for (int i = 0; i < invoke.length; ++i) { - cmd.add(invoke[i]); - } - } - if (help) { cmd.add("-h"); } @@ -206,10 +162,6 @@ public List<String> getOutputCmd() throws IOException { cmd.add(s); } - if (virtual) { - cmd.add("-v"); - } - return cmd; } diff --git a/src/main/java/org/myrobotlab/framework/Platform.java b/src/main/java/org/myrobotlab/framework/Platform.java index 1b1ed4f2d5..5742bf365e 100644 --- a/src/main/java/org/myrobotlab/framework/Platform.java +++ b/src/main/java/org/myrobotlab/framework/Platform.java @@ -13,6 +13,7 @@ import java.util.TreeMap; import java.util.zip.ZipFile; +import org.myrobotlab.config.ConfigUtils; // Do not pull in deps to this class ! import org.myrobotlab.io.FileIO; import org.myrobotlab.logging.Level; @@ -64,13 +65,7 @@ public class Platform implements Serializable { String vmName; String vmVersion; String mrlVersion; - boolean isVirtual = false; - /** - * Static identifier to identify the "instance" of myrobotlab - similar to - * network ip of a device and used in a similar way - */ - String id; String branch; String pid; @@ -95,7 +90,7 @@ public class Platform implements Serializable { * All data should be accessed through public functions on the local instance. * If the local instance is desired. If its from a serialized instance, the * "getters" will be retrieving appropriate info for that serialized instance. - * + * * @return - return the local instance of the current platform */ public static Platform getLocalInstance() { @@ -121,7 +116,8 @@ public static Platform getLocalInstance() { // === ARCH === String arch = System.getProperty("os.arch").toLowerCase(); - if ("i386".equals(arch) || "i486".equals(arch) || "i586".equals(arch) || "i686".equals(arch) || "amd64".equals(arch) || arch.startsWith("x86")) { + if ("i386".equals(arch) || "i486".equals(arch) || "i586".equals(arch) || "i686".equals(arch) + || "amd64".equals(arch) || arch.startsWith("x86")) { platform.arch = "x86"; // don't care at the moment } @@ -159,7 +155,8 @@ public static Platform getLocalInstance() { // tries very hard to hide this from running programs String procArch = System.getenv("PROCESSOR_ARCHITECTURE"); String procArchWow64 = System.getenv("PROCESSOR_ARCHITEW6432"); - platform.osBitness = (procArch != null && procArch.endsWith("64") || procArchWow64 != null && procArchWow64.endsWith("64")) ? 64 : 32; + platform.osBitness = (procArch != null && procArch.endsWith("64") + || procArchWow64 != null && procArchWow64.endsWith("64")) ? 64 : 32; switch (arch) { case "x86": case "i386": @@ -460,19 +457,6 @@ public String toString() { return String.format("%s.%d.%s", arch, jvmBitness, os); } - /** - * @return The instance identifier of the current running myrobotlab. Used for - * connecting multiple myrobotlabs together - * - */ - public String getId() { - // null ids are not allowed - if (id == null) { - id = NameGenerator.getName(); - } - return id; - } - /** * @return The Computer's hostname */ @@ -480,15 +464,6 @@ public String getHostname() { return hostname; } - /** - * @param newId - * Set your own instance identifier - * - */ - public void setId(String newId) { - id = newId; - } - /** * @return the time when this instance was started * @@ -497,20 +472,6 @@ public Date getStartTime() { return startTime; } - /** - * @return true if running in virtual mode - * - */ - public static boolean isVirtual() { - Platform p = getLocalInstance(); - return p.isVirtual; - } - - public static void setVirtual(boolean b) { - Platform p = getLocalInstance(); - p.isVirtual = b; - } - public static void main(String[] args) { try { LoggingFactory.init(Level.DEBUG); diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java index 8f0d4ad2bf..3f7acbf755 100644 --- a/src/main/java/org/myrobotlab/framework/Service.java +++ b/src/main/java/org/myrobotlab/framework/Service.java @@ -57,6 +57,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import org.myrobotlab.codec.CodecUtils; +import org.myrobotlab.config.ConfigUtils; import org.myrobotlab.framework.interfaces.Attachable; import org.myrobotlab.framework.interfaces.Broadcaster; import org.myrobotlab.framework.interfaces.ConfigurableService; @@ -474,22 +475,15 @@ static public String getResourceDir(Class<?> clazz, String additionalPath) { * then it needs an instance of Runtime which is not available. * */ - @Deprecated /* this should not be static - remove it */ static public String getResourceDir(String serviceType, String additionalPath) { // setting resource directory - String resourceDir = null; + String resource = ConfigUtils.getResourceRoot() + fs + serviceType; - // stupid solution to get past static problem - if (!"Runtime".equals(serviceType)) { - resourceDir = Runtime.getInstance().getConfig().resource + fs + serviceType; - } else { - resourceDir = "resource"; - } if (additionalPath != null) { - resourceDir = FileIO.gluePaths(resourceDir, additionalPath); + resource = FileIO.gluePaths(resource, additionalPath); } - return resourceDir; + return resource; } /** @@ -516,7 +510,7 @@ public String getResourcePath(String additionalPath) { */ static public String getResourceRoot() { - return Runtime.getInstance().getConfig().resource; + return ConfigUtils.getResourceRoot();//Runtime.getInstance().getConfig().resource; } /** @@ -625,7 +619,7 @@ public Service(String reservedKey, String inId) { // necessary for serialized transport\ if (inId == null) { - id = Platform.getLocalInstance().getId(); + id = ConfigUtils.getId(); log.debug("creating local service for id {}", id); } else { id = inId; @@ -676,7 +670,7 @@ public Service(String reservedKey, String inId) { // register this service if local - if we are a foreign service, we probably // are being created in a // registration already - if (id.equals(Platform.getLocalInstance().getId())) { + if (id.equals(ConfigUtils.getId())) { Registration registration = new Registration(this); Runtime.register(registration); } @@ -1510,7 +1504,7 @@ public ServiceConfig getFilteredConfig() { // The StringUtils.removeEnd() call is a no-op when the ID is not our // local ID, // so doesn't conflict with remote routes - Listener newConfigListener = new Listener(listener.topicMethod, StringUtil.removeEnd(listener.callbackName, '@' + Platform.getLocalInstance().getId()), + Listener newConfigListener = new Listener(listener.topicMethod, StringUtil.removeEnd(listener.callbackName, '@' + Runtime.getInstance().getId()), listener.callbackMethod); newListeners.add(newConfigListener); } @@ -1614,7 +1608,7 @@ public Service<T> publishState() { @Override synchronized public void releaseService() { // auto release children and unregister - Runtime.releaseService(getName()); + Runtime.releaseServiceInternal(getName()); } /** diff --git a/src/main/java/org/myrobotlab/framework/StartYml.java b/src/main/java/org/myrobotlab/framework/StartYml.java index c8bfb25a44..b1806d203c 100644 --- a/src/main/java/org/myrobotlab/framework/StartYml.java +++ b/src/main/java/org/myrobotlab/framework/StartYml.java @@ -9,10 +9,6 @@ * */ public class StartYml { - /** - * instance id of myrobotlab, default will be dynamically generated - */ - public String id; /** * configuration set to start under /data/config/{configName} diff --git a/src/main/java/org/myrobotlab/io/FileIO.java b/src/main/java/org/myrobotlab/io/FileIO.java index 2cdad66af2..8b3fe189c4 100644 --- a/src/main/java/org/myrobotlab/io/FileIO.java +++ b/src/main/java/org/myrobotlab/io/FileIO.java @@ -58,8 +58,8 @@ import java.util.zip.ZipException; import org.apache.commons.io.Charsets; +import org.myrobotlab.config.ConfigUtils; import org.myrobotlab.framework.Platform; -import org.myrobotlab.framework.Service; import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; @@ -854,8 +854,6 @@ public static void main(String[] args) throws ZipException, IOException { f = new File(uri); log.info("{} exists {}", uri, f.exists()); - log.info("isJar : {}", isJar()); - } catch (Exception e) { Logging.logError(e); } @@ -870,33 +868,22 @@ public static void main(String[] args) throws ZipException, IOException { * Python/examples/someFile.py * @return byte array */ - @Deprecated /* user Service.getResource(src) */ static public final byte[] resourceToByteArray(String src) { - // this path assumes in a jar ? - // String filename = "/resource/" + src; - log.info("looking for Resource {}", src); + log.info("looking for resource {}", src); InputStream isr = null; - if (isJar()) { - // this path assumes in a jar ? ensure it's forward slashes - String filename = "/resource/" + src.replace("\\", "/"); - isr = FileIO.class.getResourceAsStream(filename); - } else { - String localFilename = Service.getResourceRoot() + File.separator + src; - try { - isr = new FileInputStream(localFilename); - } catch (Exception e) { - Logging.logError(e); - log.error("File not found. {}", localFilename, e); - return null; - } + String resource = ConfigUtils.getResourceRoot(); + String localFilename = resource + File.separator + src; + try { + isr = new FileInputStream(localFilename); + } catch (Exception e) { + Logging.logError(e); + log.error("file not found. {}", localFilename, e); + return null; } + byte[] data = null; try { - if (isr == null) { - log.error("can not find resource [{}]", src); - return null; - } data = toByteArray(isr); } finally { try { @@ -918,7 +905,6 @@ static public final byte[] resourceToByteArray(String src) { * Python/examples/someFile.py * @return string */ - @Deprecated /* use Service.getResourceAsString(src) */ static public final String resourceToString(String src) { byte[] bytes = resourceToByteArray(src); if (bytes == null) { diff --git a/src/main/java/org/myrobotlab/process/Launcher.java b/src/main/java/org/myrobotlab/process/Launcher.java index 3e819f3c59..7954fd7d5d 100644 --- a/src/main/java/org/myrobotlab/process/Launcher.java +++ b/src/main/java/org/myrobotlab/process/Launcher.java @@ -2,9 +2,6 @@ import java.io.File; import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -241,41 +238,15 @@ public static void main(String[] args) { return; } - boolean instanceAlreadyRunning = false; - - try { - URI uri = new URI(options.connect); - Socket socket = new Socket(); - socket.connect(new InetSocketAddress(uri.getHost(), uri.getPort()), 1000); - socket.close(); - instanceAlreadyRunning = true; - } catch (Exception e) { - log.info("could not connect to {}", options.connect); + log.info("spawning new instance"); + ProcessBuilder builder = createBuilder(options); + process = builder.start(); + if (process.isAlive()) { + log.info("process is alive"); + } else { + log.error("process died"); } - if (instanceAlreadyRunning && options.connect.equals(options.DEFAULT_CONNECT)) { - log.error("zombie instance already running at {}", options.DEFAULT_CONNECT); - return; - } - - if (!instanceAlreadyRunning || !options.connect.equals(options.DEFAULT_CONNECT)) { - log.info("spawning new instance"); - ProcessBuilder builder = createBuilder(options); - process = builder.start(); - if (process.isAlive()) { - log.info("process is alive"); - } else { - log.error("process died"); - } - } - - /* - * // FIXME - use wsclient for remote access if (options.client != null) { - * // FIXME - delay & auto connect Client.main(new String[] { "-c", - * options.client }); } else { // terminating - "if" runtime exists - if - * not no biggy Runtime.shutdown(); } - */ - } catch (Exception e) { log.error("main threw", e); } diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index e5da31305f..e838cf6c52 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -47,6 +47,7 @@ import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.codec.CodecUtils.ApiDescription; import org.myrobotlab.codec.ForeignProcessUtils; +import org.myrobotlab.config.ConfigUtils; import org.myrobotlab.framework.CmdOptions; import org.myrobotlab.framework.DescribeQuery; import org.myrobotlab.framework.DescribeResults; @@ -129,7 +130,7 @@ * */ public class Runtime extends Service<RuntimeConfig> implements MessageListener, ServiceLifeCyclePublisher, RemoteMessageHandler, ConnectionManager, Gateway, LocaleProvider { - + final static private long serialVersionUID = 1L; // FIXME - AVOID STATIC FIELDS !!! use .getInstance() to get the singleton @@ -167,6 +168,8 @@ public class Runtime extends Service<RuntimeConfig> implements MessageListener, protected final Map<String, Set<String>> typeToInterface = new HashMap<>(); + private transient static final Object processLock = new Object(); + /** * FILTERED_INTERFACES are the set of low level interfaces which we are * interested in filtering out if we want to maintain a data structure which @@ -188,7 +191,13 @@ public class Runtime extends Service<RuntimeConfig> implements MessageListener, * name. It cannot be null, it cannot have "/" or "\" in the name - it has to * be a valid file name for the OS. It's defaulted to "default". Changed often */ - protected String configName = "default"; + protected static String configName = "default"; + + /** + * The runtime config which Runtime was started with. This is the config which + * will be applied to Runtime when its created on startup. + */ + // protected static RuntimeConfig startConfig = null; /** * State variable reporting if runtime is currently starting services from @@ -378,30 +387,32 @@ static public ServiceInterface create(String name) { * - Can be null if a service file exists for named service * @return the service */ - static public synchronized ServiceInterface create(String name, String type) { + static public ServiceInterface create(String name, String type) { - try { - ServiceInterface si = Runtime.getService(name); - if (si != null) { - return si; - } + synchronized (processLock) { - // FIXME remove configName from loadService - Plan plan = Runtime.load(name, type); - Runtime.check(name, type); - // at this point - the plan should be loaded, now its time to create the - // children peers - // and parent service - createServicesFromPlan(plan, null, name); - si = Runtime.getService(name); - if (si == null) { - Runtime.getInstance().error("coult not create %s of type %s", name, type); + try { + ServiceInterface si = Runtime.getService(name); + if (si != null) { + return si; + } + + Plan plan = Runtime.load(name, type); + Runtime.check(name, type); + // at this point - the plan should be loaded, now its time to create the + // children peers + // and parent service + createServicesFromPlan(plan, null, name); + si = Runtime.getService(name); + if (si == null) { + Runtime.getInstance().error("coult not create %s of type %s", name, type); + } + return si; + } catch (Exception e) { + runtime.error(e); } - return si; - } catch (Exception e) { - runtime.error(e); + return null; } - return null; } /** @@ -414,43 +425,46 @@ static public synchronized ServiceInterface create(String name, String type) { * @param name * @return */ - synchronized private static Map<String, ServiceInterface> createServicesFromPlan(Plan plan, Map<String, ServiceInterface> createdServices, String name) { + private static Map<String, ServiceInterface> createServicesFromPlan(Plan plan, Map<String, ServiceInterface> createdServices, String name) { - if (createdServices == null) { - createdServices = new LinkedHashMap<>(); - } - - // Plan's config - RuntimeConfig plansRtConfig = (RuntimeConfig) plan.get("runtime"); - // current Runtime config - RuntimeConfig currentConfig = Runtime.getInstance().config; + synchronized (processLock) { - for (String service : plansRtConfig.getRegistry()) { - // FIXME - determine if you want to return a complete merge of activated - // or just "recent" - if (Runtime.getService(service) != null) { - continue; + if (createdServices == null) { + createdServices = new LinkedHashMap<>(); } - ServiceConfig sc = plan.get(service); - if (sc == null) { - runtime.error("could not get %s from plan", service); - continue; - } - ServiceInterface si = createService(service, sc.type, null); - // process the base listeners/subscription of ServiceConfig - si.addConfigListeners(sc); - if (si instanceof ConfigurableService) { - try { - ((ConfigurableService) si).apply(sc); - } catch (Exception e) { - Runtime.getInstance().error("could not apply config of type %s to service %s, using default config", sc.type, si.getName(), sc.type); + + // Plan's config + RuntimeConfig plansRtConfig = (RuntimeConfig) plan.get("runtime"); + // current Runtime config + RuntimeConfig currentConfig = Runtime.getInstance().config; + + for (String service : plansRtConfig.getRegistry()) { + // FIXME - determine if you want to return a complete merge of activated + // or just "recent" + if (Runtime.getService(service) != null) { + continue; + } + ServiceConfig sc = plan.get(service); + if (sc == null) { + runtime.error("could not get %s from plan", service); + continue; } + ServiceInterface si = createService(service, sc.type, null); + // process the base listeners/subscription of ServiceConfig + si.addConfigListeners(sc); + if (si instanceof ConfigurableService) { + try { + ((ConfigurableService) si).apply(sc); + } catch (Exception e) { + Runtime.getInstance().error("could not apply config of type %s to service %s, using default config", sc.type, si.getName(), sc.type); + } + } + createdServices.put(service, si); + currentConfig.add(service); } - createdServices.put(service, si); - currentConfig.add(service); - } - return createdServices; + return createdServices; + } } public String getServiceExample(String serviceType) { @@ -582,7 +596,9 @@ public final static void createAndStartServices(List<String> services) { */ @Override public boolean setVirtual(boolean b) { - boolean changed = isVirtual != b; + boolean changed = config.virtual != b; + config.virtual = b; + isVirtual = b; setAllVirtual(b); if (changed) { broadcastState(); @@ -599,13 +615,12 @@ public boolean setVirtual(boolean b) { * @return b */ static public boolean setAllVirtual(boolean b) { - Platform.setVirtual(b); for (ServiceInterface si : getServices()) { if (!si.isRuntime()) { si.setVirtual(b); } } - Runtime.getInstance().isVirtual = b; + Runtime.getInstance().config.virtual = b; Runtime.getInstance().broadcastState(); return b; } @@ -622,7 +637,6 @@ static public boolean setAllVirtual(boolean b) { */ public void setAutoStart(boolean autoStart) throws IOException { log.debug("setAutoStart {}", autoStart); - startYml.id = getId(); startYml.enable = autoStart; startYml.config = configName; FileIO.toFile("start.yml", CodecUtils.toYaml(startYml)); @@ -657,126 +671,128 @@ public void setAutoStart(boolean autoStart) throws IOException { * '/', or a service with the same name exists but has a different * type, will return null instead. */ - static private synchronized ServiceInterface createService(String name, String type, String inId) { - log.info("Runtime.createService {}", name); + static private ServiceInterface createService(String name, String type, String inId) { + synchronized (processLock) { + log.info("Runtime.createService {}", name); - if (name == null) { - runtime.error("service name cannot be null"); + if (name == null) { + runtime.error("service name cannot be null"); - return null; - } + return null; + } - if (name.contains("@") || name.contains("/")) { - runtime.error("service name cannot contain '@' or '/': {}", name); + if (name.contains("@") || name.contains("/")) { + runtime.error("service name cannot contain '@' or '/': {}", name); - return null; - } + return null; + } - String fullName; - if (inId == null || inId.equals("")) - fullName = getFullName(name); - else - fullName = String.format("%s@%s", name, inId); + String fullName; + if (inId == null || inId.equals("")) + fullName = getFullName(name); + else + fullName = String.format("%s@%s", name, inId); - if (type == null) { - ServiceConfig sc; - try { - sc = CodecUtils.readServiceConfig(runtime.getConfigName() + fs + name + ".yml"); - } catch (IOException e) { - runtime.error("could not find type for service %s", name); - return null; + if (type == null) { + ServiceConfig sc; + try { + sc = CodecUtils.readServiceConfig(runtime.getConfigName() + fs + name + ".yml"); + } catch (IOException e) { + runtime.error("could not find type for service %s", name); + return null; + } + if (sc != null) { + log.info("found type for {} in plan", name); + type = sc.type; + } else { + runtime.error("createService type not specified and could not get type for {} from plan", name); + return null; + } } - if (sc != null) { - log.info("found type for {} in plan", name); - type = sc.type; - } else { - runtime.error("createService type not specified and could not get type for {} from plan", name); + + if (type == null) { + runtime.error("cannot create service {} no type in plan or yml file", name); return null; } - } - if (type == null) { - runtime.error("cannot create service {} no type in plan or yml file", name); - return null; - } + String fullTypeName = CodecUtils.makeFullTypeName(type); + + ServiceInterface si = Runtime.getService(fullName); + if (si != null) { + if (!si.getTypeKey().equals(fullTypeName)) { + runtime.error("Service with name {} already exists but is of type {} while requested type is ", name, si.getTypeKey(), type); + return null; + } + return si; + } - String fullTypeName = CodecUtils.makeFullTypeName(type); + // DO NOT LOAD HERE !!! - doing so would violate the service life cycle ! + // only try to resolve type by the plan - if not then error out - ServiceInterface si = Runtime.getService(fullName); - if (si != null) { - if (!si.getTypeKey().equals(fullTypeName)) { - runtime.error("Service with name {} already exists but is of type {} while requested type is ", name, si.getTypeKey(), type); + String id = (inId == null) ? Runtime.getInstance().getId() : inId; + if (name.length() == 0 || fullTypeName == null || fullTypeName.length() == 0) { + log.error("{} not a type or {} not defined ", fullTypeName, name); return null; } - return si; - } - // DO NOT LOAD HERE !!! - doing so would violate the service life cycle ! - // only try to resolve type by the plan - if not then error out - - String id = (inId == null) ? Platform.getLocalInstance().getId() : inId; - if (name.length() == 0 || fullTypeName == null || fullTypeName.length() == 0) { - log.error("{} not a type or {} not defined ", fullTypeName, name); - return null; - } + // TODO - test new create of existing service + ServiceInterface sw = Runtime.getService(String.format("%s@%s", name, id)); + if (sw != null) { + log.info("service {} already exists", name); + return sw; + } - // TODO - test new create of existing service - ServiceInterface sw = Runtime.getService(String.format("%s@%s", name, id)); - if (sw != null) { - log.info("service {} already exists", name); - return sw; - } + try { - try { + if (log.isDebugEnabled()) { + // TODO - determine if there have been new classes added from + // ivy --> Boot Classloader --> Ext ClassLoader --> System + // ClassLoader + // http://blog.jamesdbloom.com/JVMInternals.html + log.debug("ABOUT TO LOAD CLASS"); + log.debug("loader for this class " + Runtime.class.getClassLoader().getClass().getCanonicalName()); + log.debug("parent " + Runtime.class.getClassLoader().getParent().getClass().getCanonicalName()); + log.debug("system class loader " + ClassLoader.getSystemClassLoader()); + log.debug("parent should be null" + ClassLoader.getSystemClassLoader().getParent().getClass().getCanonicalName()); + log.debug("thread context " + Thread.currentThread().getContextClassLoader().getClass().getCanonicalName()); + log.debug("thread context parent " + Thread.currentThread().getContextClassLoader().getParent().getClass().getCanonicalName()); + } - if (log.isDebugEnabled()) { - // TODO - determine if there have been new classes added from - // ivy --> Boot Classloader --> Ext ClassLoader --> System - // ClassLoader - // http://blog.jamesdbloom.com/JVMInternals.html - log.debug("ABOUT TO LOAD CLASS"); - log.debug("loader for this class " + Runtime.class.getClassLoader().getClass().getCanonicalName()); - log.debug("parent " + Runtime.class.getClassLoader().getParent().getClass().getCanonicalName()); - log.debug("system class loader " + ClassLoader.getSystemClassLoader()); - log.debug("parent should be null" + ClassLoader.getSystemClassLoader().getParent().getClass().getCanonicalName()); - log.debug("thread context " + Thread.currentThread().getContextClassLoader().getClass().getCanonicalName()); - log.debug("thread context parent " + Thread.currentThread().getContextClassLoader().getParent().getClass().getCanonicalName()); - } + // FIXME - error if deps are missing - prompt license + // require restart ! + // FIXME - this should happen after inspecting the "loaded" "plan" not + // during the create/start/apply ! + + // create an instance + Object newService = Instantiator.getThrowableNewInstance(null, fullTypeName, name, id); + log.debug("returning {}", fullTypeName); + si = (ServiceInterface) newService; + + // si.setId(id); + if (Runtime.getInstance().getId().equals(id)) { + si.setVirtual(Runtime.getInstance().isVirtual()); + Runtime.getInstance().creationCount++; + si.setOrder(Runtime.getInstance().creationCount); + } - // FIXME - error if deps are missing - prompt license - // require restart ! - // FIXME - this should happen after inspecting the "loaded" "plan" not - // during the create/start/apply ! - - // create an instance - Object newService = Instantiator.getThrowableNewInstance(null, fullTypeName, name, id); - log.debug("returning {}", fullTypeName); - si = (ServiceInterface) newService; - - // si.setId(id); - if (Platform.getLocalInstance().getId().equals(id)) { - si.setVirtual(Platform.isVirtual()); - Runtime.getInstance().creationCount++; - si.setOrder(Runtime.getInstance().creationCount); - } + if (runtime != null) { - if (runtime != null) { + runtime.invoke("created", getFullName(name)); - runtime.invoke("created", getFullName(name)); + // add all the service life cycle subscriptions + // runtime.addListener("registered", name); + // runtime.addListener("created", name); + // runtime.addListener("started", name); + // runtime.addListener("stopped", name); + // runtime.addListener("released", name); + } - // add all the service life cycle subscriptions - // runtime.addListener("registered", name); - // runtime.addListener("created", name); - // runtime.addListener("started", name); - // runtime.addListener("stopped", name); - // runtime.addListener("released", name); + return (Service) newService; + } catch (Exception e) { + log.error("createService failed for {}@{} of type {}", name, id, fullTypeName, e); } - - return (Service) newService; - } catch (Exception e) { - log.error("createService failed for {}@{} of type {}", name, id, fullTypeName, e); + return null; } - return null; } static public Map<String, Map<String, List<MRLListener>>> getNotifyEntries() { @@ -883,58 +899,48 @@ public static final long getFreeMemory() { public static Runtime getInstance() { if (runtime == null) { synchronized (INSTANCE_LOCK) { - if (runtime == null) { + try { - // all though this is appropriate it cannot be done - // because you need runtime to correctly load/start/etc the plan - // so it needs to be bootstrapped - // load("runtime", "Runtime"); + RuntimeConfig c = null; + if (runtime == null) { + c = ConfigUtils.loadRuntimeConfig(options); - // just create Runtime - runtime = (Runtime) createService(RUNTIME_NAME, "Runtime", Platform.getLocalInstance().getId()); - } - try { - // a bit backwards - it loads after it been created - // but its necessary because you need an runtime instance before you - // load - - File cfgRoot = new File(ROOT_CONFIG_DIR); - cfgRoot.mkdirs(); - if (startYml.enable) { - Runtime.load("runtime", "Runtime"); - } - runtime.config.add("runtime"); + runtime = (Runtime) createService(RUNTIME_NAME, "Runtime", c.id); + runtime.startService(); + // klunky + Runtime.register(new Registration(runtime)); - runtime.startService(); - // platform virtual is higher priority than service virtual - Runtime.setAllVirtual(Platform.isVirtual()); + // assign, do not apply otherwise there will be + // a chicken-egg problem + runtime.config = c; + } - // setting the singleton security - Security.getInstance(); runtime.getRepo().addStatusPublisher(runtime); + runtime.startService(); + // extract resources "if a jar" FileIO.extractResources(); - // protected services we don't want to remove when releasing a config - runtime.startingServices.add("runtime"); - runtime.startingServices.add("security"); - runtime.startingServices.add("webgui"); - runtime.startingServices.add("python"); - runtime.startInteractiveMode(); - try { - if (options.config != null) { - Runtime.startConfig(options.config); - } else if (startYml != null && startYml.config != null && startYml.enable) { + if (Runtime.options.install != null) { + // minimal processed runtime - return it + return runtime; + } + + runtime.apply(c); + + if (options.services != null) { + log.info("command line override for services created"); + createAndStartServices(options.services); + } else { + log.info("processing config.registry"); + if (startYml.enable) { Runtime.startConfig(startYml.config); } - } catch (Exception e) { - log.info("runtime will not be loading config"); } } catch (Exception e) { - log.error("runtime will not be loading config", e); + log.error("runtime getInstance threw", e); } - } // synchronized lock } @@ -1099,7 +1105,7 @@ public static Map<String, ServiceInterface> getLocalServices() { Map<String, ServiceInterface> local = new HashMap<>(); for (String serviceName : registry.keySet()) { // FIXME @ should be a requirement of "all" entries for consistency - if (!serviceName.contains("@") || serviceName.endsWith(String.format("@%s", Platform.getLocalInstance().getId()))) { + if (!serviceName.contains("@") || serviceName.endsWith(String.format("@%s", Runtime.getInstance().getId()))) { local.put(serviceName, registry.get(serviceName)); } } @@ -1149,8 +1155,10 @@ public static Map<String, MethodEntry> getMethodMap(String inName) { * * @return list of registrations */ - synchronized public List<Registration> getServiceList() { - return registry.values().stream().map(si -> new Registration(si.getId(), si.getName(), si.getTypeKey())).collect(Collectors.toList()); + public List<Registration> getServiceList() { + synchronized (processLock) { + return registry.values().stream().map(si -> new Registration(si.getId(), si.getName(), si.getTypeKey())).collect(Collectors.toList()); + } } // FIXME - scary function - returns private data @@ -1196,10 +1204,16 @@ public static <S extends ServiceInterface> S getService(String inName, StaticTyp * */ static public String[] getServiceNames() { - Set<String> ret = registry.keySet(); + Set<String> ret = registry.keySet(); String[] services = new String[ret.size()]; - - String localId = Platform.getLocalInstance().getId(); + if (ret.size() == 0) { + return services; + } + + // if there are more than 0 services we need runtime + // to filter to make sure they are "local" + // and this requires a runtime service + String localId = Runtime.getInstance().getId(); int cnt = 0; for (String fullname : ret) { if (fullname.endsWith(String.format("@%s", localId))) { @@ -1344,22 +1358,24 @@ public ServiceTypeNameResults getServiceTypeNamesFromInterface(String interfaze) * no longer used or needed - change events are pushed no longer * pulled <-- Over complicated solution */ - public static synchronized List<ServiceInterface> getServicesFromInterface(Class<?> interfaze) { - List<ServiceInterface> ret = new ArrayList<ServiceInterface>(); - - for (String service : getServiceNames()) { - Class<?> clazz = getService(service).getClass(); - while (clazz != null) { - for (Class<?> inter : clazz.getInterfaces()) { - if (inter.getName().equals(interfaze.getName())) { - ret.add(getService(service)); - continue; + public static List<ServiceInterface> getServicesFromInterface(Class<?> interfaze) { + synchronized (processLock) { + List<ServiceInterface> ret = new ArrayList<ServiceInterface>(); + + for (String service : getServiceNames()) { + Class<?> clazz = getService(service).getClass(); + while (clazz != null) { + for (Class<?> inter : clazz.getInterfaces()) { + if (inter.getName().equals(interfaze.getName())) { + ret.add(getService(service)); + continue; + } } + clazz = clazz.getSuperclass(); } - clazz = clazz.getSuperclass(); } + return ret; } - return ret; } /** @@ -1539,35 +1555,36 @@ static public void install(String serviceType) { * if this should block until done. * */ - synchronized static public void install(String serviceType, Boolean blocking) { - Runtime r = getInstance(); + static public void install(String serviceType, Boolean blocking) { + synchronized (processLock) { + Runtime r = getInstance(); - if (blocking == null) { - blocking = false; - } + if (blocking == null) { + blocking = false; + } - installerThread = new Thread() { - @Override - public void run() { - try { - if (serviceType == null) { - r.getRepo().install(); - } else { - r.getRepo().install(serviceType); + installerThread = new Thread() { + @Override + public void run() { + try { + if (serviceType == null) { + r.getRepo().install(); + } else { + r.getRepo().install(serviceType); + } + } catch (Exception e) { + r.error("dependencies failed - install error", e); + throw new RuntimeException(String.format("dependencies failed - install error %s", e.getMessage())); } - } catch (Exception e) { - r.error("dependencies failed - install error", e); - throw new RuntimeException(String.format("dependencies failed - install error %s", e.getMessage())); } - } - }; + }; - if (blocking) { - installerThread.run(); - } else { - installerThread.start(); + if (blocking) { + installerThread.run(); + } else { + installerThread.start(); + } } - } /** @@ -1608,7 +1625,7 @@ static public void invokeCommands(String[] invoke) { */ public static boolean isLocal(String serviceName) { ServiceInterface sw = getService(serviceName); - return Objects.equals(sw.getId(), Platform.getLocalInstance().getId()); + return Objects.equals(sw.getId(), Runtime.getInstance().getId()); } /* @@ -1705,10 +1722,12 @@ public void onState(ServiceInterface updatedService) { registry.put(String.format("%s@%s", updatedService.getName(), updatedService.getId()), updatedService); } - public static synchronized Registration register(String id, String name, String typeKey, ArrayList<String> interfaces) { - Registration proxy = new Registration(id, name, typeKey, interfaces); - register(proxy); - return proxy; + public static Registration register(String id, String name, String typeKey, ArrayList<String> interfaces) { + synchronized (processLock) { + Registration proxy = new Registration(id, name, typeKey, interfaces); + register(proxy); + return proxy; + } } /** @@ -1732,167 +1751,174 @@ public static synchronized Registration register(String id, String name, String * @return registration * */ - public static synchronized Registration register(Registration registration) { + public static Registration register(Registration registration) { + synchronized (processLock) { + try { - try { + // TODO - have rules on what registrations to accept - dependent on + // security, desire, re-broadcasting configuration etc. - // TODO - have rules on what registrations to accept - dependent on - // security, desire, re-broadcasting configuration etc. + String fullname = String.format("%s@%s", registration.getName(), registration.getId()); + if (registry.containsKey(fullname)) { + log.info("{} already registered", fullname); + return registration; + } - String fullname = String.format("%s@%s", registration.getName(), registration.getId()); - if (registry.containsKey(fullname)) { - log.info("{} already registered", fullname); - return registration; - } + // if (!ForeignProcessUtils.isValidTypeKey(registration.getTypeKey())) { + // log.error("Invalid type key being registered: " + + // registration.getTypeKey()); + // return null; + // } - // if (!ForeignProcessUtils.isValidTypeKey(registration.getTypeKey())) { - // log.error("Invalid type key being registered: " + - // registration.getTypeKey()); - // return null; - // } + log.info("{}@{} registering at {} of type {}", registration.getName(), registration.getId(), ConfigUtils.getId(), registration.getTypeKey()); - log.info("{}@{} registering at {} of type {}", registration.getName(), registration.getId(), Platform.getLocalInstance().getId(), registration.getTypeKey()); + if (!registration.isLocal(ConfigUtils.getId())) { - if (!registration.isLocal(Platform.getLocalInstance().getId())) { + // Check if we're registering a java service + if (ForeignProcessUtils.isValidJavaClassName(registration.getTypeKey())) { - // Check if we're registering a java service - if (ForeignProcessUtils.isValidJavaClassName(registration.getTypeKey())) { + String fullTypeName; + if (registration.getTypeKey().contains(".")) { + fullTypeName = registration.getTypeKey(); + } else { + fullTypeName = String.format("org.myrobotlab.service.%s", registration.getTypeKey()); + } - String fullTypeName; - if (registration.getTypeKey().contains(".")) { - fullTypeName = registration.getTypeKey(); + try { + // de-serialize, class exists + registration.service = Runtime.createService(registration.getName(), fullTypeName, registration.getId()); + if (registration.getState() != null) { + copyShallowFrom(registration.service, CodecUtils.fromJson(registration.getState(), Class.forName(fullTypeName))); + } + } catch (ClassNotFoundException classNotFoundException) { + log.error(String.format("Unknown service class for %s@%s: %s", registration.getName(), registration.getId(), registration.getTypeKey()), classNotFoundException); + return null; + } } else { - fullTypeName = String.format("org.myrobotlab.service.%s", registration.getTypeKey()); - } - - try { - // de-serialize, class exists - registration.service = Runtime.createService(registration.getName(), fullTypeName, registration.getId()); - if (registration.getState() != null) { - copyShallowFrom(registration.service, CodecUtils.fromJson(registration.getState(), Class.forName(fullTypeName))); + // We're registering a foreign process service. We don't need to + // check + // ForeignProcessUtils.isForeignTypeKey() because the type key is + // valid + // but is not a java class name + + // Class does not exist, check if registration has empty interfaces + // Interfaces should always include ServiceInterface if coming from + // remote client + if (registration.interfaces == null || registration.interfaces.isEmpty()) { + log.error("Unknown service type being registered, registration does not contain any " + "interfaces for proxy generation: " + registration.getTypeKey()); + return null; } - } catch (ClassNotFoundException classNotFoundException) { - log.error(String.format("Unknown service class for %s@%s: %s", registration.getName(), registration.getId(), registration.getTypeKey()), classNotFoundException); - return null; - } - } else { - // We're registering a foreign process service. We don't need to check - // ForeignProcessUtils.isForeignTypeKey() because the type key is - // valid - // but is not a java class name - - // Class does not exist, check if registration has empty interfaces - // Interfaces should always include ServiceInterface if coming from - // remote client - if (registration.interfaces == null || registration.interfaces.isEmpty()) { - log.error("Unknown service type being registered, registration does not contain any " + "interfaces for proxy generation: " + registration.getTypeKey()); - return null; - } - // FIXME - probably some more clear definition about the requirements - // of remote - // service registration - // In general, there should be very few requirements if any, besides - // providing a - // name, and the proxy - // interface should be responsible for creating a minimal - // interpretation - // (ServiceInterface) for the remote - // service - - // Class<?>[] interfaces = registration.interfaces.stream().map(i -> { - // try { - // return Class.forName(i); - // } catch (ClassNotFoundException e) { - // throw new RuntimeException("Unable to load interface " + i + " - // defined in remote registration " + registration, e); - // } - // }).toArray(Class<?>[]::new); - - // registration.service = (ServiceInterface) - // Proxy.newProxyInstance(Runtime.class.getClassLoader(), interfaces, - // new ProxyServiceInvocationHandler(registration.getName(), - // registration.getId())); - try { - registration.service = ProxyFactory.createProxyService(registration); - log.info("Created proxy: " + registration.service); - } catch (Exception e) { - // at the moment preventing throw - Runtime.getInstance().error(e); + // FIXME - probably some more clear definition about the + // requirements + // of remote + // service registration + // In general, there should be very few requirements if any, besides + // providing a + // name, and the proxy + // interface should be responsible for creating a minimal + // interpretation + // (ServiceInterface) for the remote + // service + + // Class<?>[] interfaces = registration.interfaces.stream().map(i -> + // { + // try { + // return Class.forName(i); + // } catch (ClassNotFoundException e) { + // throw new RuntimeException("Unable to load interface " + i + " + // defined in remote registration " + registration, e); + // } + // }).toArray(Class<?>[]::new); + + // registration.service = (ServiceInterface) + // Proxy.newProxyInstance(Runtime.class.getClassLoader(), + // interfaces, + // new ProxyServiceInvocationHandler(registration.getName(), + // registration.getId())); + try { + registration.service = ProxyFactory.createProxyService(registration); + log.info("Created proxy: " + registration.service); + } catch (Exception e) { + // at the moment preventing throw + Runtime.getInstance().error(e); + } } } - } - - registry.put(fullname, registration.service); - - if (runtime != null) { - - String type = registration.getTypeKey(); - // If type does not exist in typeToNames, make it an empty hash set and - // return it - Set<String> names = runtime.typeToNames.computeIfAbsent(type, k -> new HashSet<>()); - names.add(fullname); - - // FIXME - most of this could be static as it represents meta data of - // class and interfaces + registry.put(fullname, registration.service); + + if (runtime != null) { + + String type = registration.getTypeKey(); + + // If type does not exist in typeToNames, make it an empty hash set + // and + // return it + Set<String> names = runtime.typeToNames.computeIfAbsent(type, k -> new HashSet<>()); + names.add(fullname); + + // FIXME - most of this could be static as it represents meta data of + // class and interfaces + + // FIXME - was false - setting now to true .. because + // 1 edge case - "can something fulfill my need of an interface - is + // not + // currently + // switching to true + boolean updatedServiceLists = false; + + // maintaining interface type relations + // see if this service type is new + // PROCESS INDEXES ! - FIXME - will need this in unregister + // ALL CLASS/TYPE PROCESSING only needs to happen once per type + if (!runtime.serviceTypes.contains(type)) { + // CHECK IF "CAN FULFILL" + // add the interfaces of the new service type + Set<String> interfaces = ClassUtil.getInterfaces(registration.service.getClass(), FILTERED_INTERFACES); + for (String interfaze : interfaces) { + Set<String> types = runtime.interfaceToType.get(interfaze); + if (types == null) { + types = new HashSet<>(); + } + types.add(registration.getTypeKey()); + runtime.interfaceToType.put(interfaze, types); + } - // FIXME - was false - setting now to true .. because - // 1 edge case - "can something fulfill my need of an interface - is not - // currently - // switching to true - boolean updatedServiceLists = false; + runtime.typeToInterface.put(type, interfaces); + runtime.serviceTypes.add(registration.getTypeKey()); + updatedServiceLists = true; + } - // maintaining interface type relations - // see if this service type is new - // PROCESS INDEXES ! - FIXME - will need this in unregister - // ALL CLASS/TYPE PROCESSING only needs to happen once per type - if (!runtime.serviceTypes.contains(type)) { - // CHECK IF "CAN FULFILL" - // add the interfaces of the new service type - Set<String> interfaces = ClassUtil.getInterfaces(registration.service.getClass(), FILTERED_INTERFACES); - for (String interfaze : interfaces) { - Set<String> types = runtime.interfaceToType.get(interfaze); - if (types == null) { - types = new HashSet<>(); + // check to see if any of our interfaces can fulfill requested ones + Set<String> myInterfaces = runtime.typeToInterface.get(type); + for (String inter : myInterfaces) { + if (runtime.interfaceToNames.containsKey(inter)) { + runtime.interfaceToNames.get(inter).add(fullname); + updatedServiceLists = true; } - types.add(registration.getTypeKey()); - runtime.interfaceToType.put(interfaze, types); } - runtime.typeToInterface.put(type, interfaces); - runtime.serviceTypes.add(registration.getTypeKey()); - updatedServiceLists = true; - } - - // check to see if any of our interfaces can fulfill requested ones - Set<String> myInterfaces = runtime.typeToInterface.get(type); - for (String inter : myInterfaces) { - if (runtime.interfaceToNames.containsKey(inter)) { - runtime.interfaceToNames.get(inter).add(fullname); - updatedServiceLists = true; + if (updatedServiceLists) { + runtime.invoke("publishInterfaceToNames"); } - } - if (updatedServiceLists) { - runtime.invoke("publishInterfaceToNames"); + // TODO - determine rules on re-broadcasting based on configuration + runtime.invoke("registered", registration); } - // TODO - determine rules on re-broadcasting based on configuration - runtime.invoke("registered", registration); - } + // TODO - remove ? already get state from registration + if (!registration.isLocal(ConfigUtils.getId())) { + runtime.subscribe(registration.getFullName(), "publishState"); + } - // TODO - remove ? already get state from registration - if (!registration.isLocal(Platform.getLocalInstance().getId())) { - runtime.subscribe(registration.getFullName(), "publishState"); + } catch (Exception e) { + log.error("registration threw for {}@{}", registration.getName(), registration.getId(), e); + return null; } - } catch (Exception e) { - log.error("registration threw for {}@{}", registration.getName(), registration.getId(), e); - return null; + return registration; } - - return registration; } /** @@ -1906,60 +1932,79 @@ public static synchronized Registration register(Registration registration) { * @return true/false * */ - public synchronized static boolean releaseService(String inName) { - if (inName == null) { - log.debug("release (null)"); - return false; + public static boolean releaseService(String inName) { + ServiceInterface sc = getService(inName); + if (sc != null) { + sc.releaseService(); + return true; } + return false; + } + + + /** + * Called after any subclassed releaseService has been called, this cleans + * up the registry and removes peers + * @param inName + * @return + */ + public static boolean releaseServiceInternal(String inName) { + synchronized (processLock) { + if (inName == null) { + log.debug("release (null)"); + return false; + } - String name = getFullName(inName); + String name = getFullName(inName); - String id = CodecUtils.getId(name); - if (!id.equals(Platform.getLocalInstance().getId())) { - log.warn("will only release local services - %s is remote", name); - return false; - } + String id = CodecUtils.getId(name); + if (!id.equals(Runtime.getInstance().getId())) { + log.warn("will only release local services - %s is remote", name); + return false; + } - log.info("releasing service {}", name); + log.info("releasing service {}", name); - if (!registry.containsKey(name)) { - log.info("{} not registered", name); - return false; - } + if (!registry.containsKey(name)) { + log.info("{} not registered", name); + return false; + } - // get reference from registry - ServiceInterface si = registry.get(name); - if (si == null) { - log.warn("cannot release {} - not in registry"); - return false; - } + // get reference from registry + ServiceInterface si = registry.get(name); + if (si == null) { + log.warn("cannot release {} - not in registry"); + return false; + } - // FIXME - TODO invoke and or blocking on preRelease - Future + // FIXME - TODO invoke and or blocking on preRelease - Future - // send msg to service to self terminate - if (si.isLocal()) { - si.purgeTasks(); - si.stopService(); - } else { - if (runtime != null) { - runtime.send(name, "releaseService"); + // send msg to service to self terminate + if (si.isLocal()) { + si.purgeTasks(); + si.stopService(); + } else { + if (runtime != null) { + runtime.send(name, "releaseService"); + } } - } - // recursive peer release - Map<String, Peer> peers = si.getPeers(); - if (peers != null) { - for (Peer peer : peers.values()) { - release(peer.name); + // recursive peer release + Map<String, Peer> peers = si.getPeers(); + if (peers != null) { + for (Peer peer : peers.values()) { + release(peer.name); + } } - } - // FOR remote this isn't correct - it should wait for - // a message from the other runtime to say that its released - unregister(name); - return true; + // FOR remote this isn't correct - it should wait for + // a message from the other runtime to say that its released + unregister(name); + return true; + } } + /** * Removes registration for a service. Removes the service from * {@link #typeToInterface} and {@link #interfaceToNames}. @@ -1967,61 +2012,65 @@ public synchronized static boolean releaseService(String inName) { * @param inName * Name of the service to unregister */ - synchronized public static void unregister(String inName) { - String name = getFullName(inName); - log.info("unregister {}", name); + public static void unregister(String inName) { + synchronized (processLock) { + String name = getFullName(inName); + log.info("unregister {}", name); - // get reference from registry - ServiceInterface sw = registry.get(name); - if (sw == null) { - log.debug("{} already unregistered", name); - return; - } + // get reference from registry + ServiceInterface sw = registry.get(name); + if (sw == null) { + log.debug("{} already unregistered", name); + return; + } - // you have to send released before removing from registry - if (runtime != null) { - runtime.invoke("released", inName); // <- DO NOT CHANGE THIS IS CORRECT - // !! - // it should be FULLNAME ! - // runtime.broadcast("released", inName); - String type = sw.getTypeKey(); - - boolean updatedServiceLists = false; - - // check to see if any of our interfaces can fullfill requested ones - Set<String> myInterfaces = runtime.typeToInterface.get(type); - if (myInterfaces != null) { - for (String inter : myInterfaces) { - if (runtime.interfaceToNames.containsKey(inter)) { - runtime.interfaceToNames.get(inter).remove(name); - updatedServiceLists = true; + // you have to send released before removing from registry + if (runtime != null) { + runtime.invoke("released", inName); // <- DO NOT CHANGE THIS IS CORRECT + // !! + // it should be FULLNAME ! + // runtime.broadcast("released", inName); + String type = sw.getTypeKey(); + + boolean updatedServiceLists = false; + + // check to see if any of our interfaces can fullfill requested ones + Set<String> myInterfaces = runtime.typeToInterface.get(type); + if (myInterfaces != null) { + for (String inter : myInterfaces) { + if (runtime.interfaceToNames.containsKey(inter)) { + runtime.interfaceToNames.get(inter).remove(name); + updatedServiceLists = true; + } } } - } - if (updatedServiceLists) { - runtime.invoke("publishInterfaceToNames"); - } + if (updatedServiceLists) { + runtime.invoke("publishInterfaceToNames"); + } - } + } - // FIXME - release autostarted peers ? + // FIXME - release autostarted peers ? - // last step - remove from registry by making new registry - // thread safe way - Map<String, ServiceInterface> removedService = new TreeMap<>(); - for (String key : registry.keySet()) { - if (!name.equals(key)) { - removedService.put(key, registry.get(key)); + // last step - remove from registry by making new registry + // thread safe way + Map<String, ServiceInterface> removedService = new TreeMap<>(); + for (String key : registry.keySet()) { + if (!name.equals(key)) { + removedService.put(key, registry.get(key)); + } } - } - registry = removedService; + registry = removedService; - // and config - RuntimeConfig c = (RuntimeConfig) Runtime.getInstance().config; - c.remove(CodecUtils.getShortName(name)); + // and config + RuntimeConfig c = (RuntimeConfig) Runtime.getInstance().config; + if (c != null) { + c.remove(CodecUtils.getShortName(name)); + } - log.info("released {}", name); + log.info("released {}", name); + } } /** @@ -2092,12 +2141,14 @@ public static void releaseAll(boolean releaseRuntime, boolean block) { if (block) { processRelease(releaseRuntime); + ConfigUtils.reset(); } else { new Thread() { @Override public void run() { processRelease(releaseRuntime); + ConfigUtils.reset(); } }.start(); @@ -2110,45 +2161,51 @@ public void run() { * @param releaseRuntime * Whether the Runtime should also be released */ - synchronized static private void processRelease(boolean releaseRuntime) { - - // reverse release to order of creation - Collection<ServiceInterface> local = getLocalServices().values(); - List<ServiceInterface> ordered = new ArrayList<>(local); - ordered.removeIf(Objects::isNull); - Collections.sort(ordered); - Collections.reverse(ordered); + static private void processRelease(boolean releaseRuntime) { + synchronized (processLock) { + // reverse release to order of creation + Collection<ServiceInterface> local = getLocalServices().values(); + List<ServiceInterface> ordered = new ArrayList<>(local); + ordered.removeIf(Objects::isNull); + Collections.sort(ordered); + Collections.reverse(ordered); - for (ServiceInterface sw : ordered) { + for (ServiceInterface sw : ordered) { - // no longer needed now - runtime "should be" guaranteed to be last - if (sw == Runtime.getInstance()) { - // skipping runtime - continue; - } + // no longer needed now - runtime "should be" guaranteed to be last + if (sw == Runtime.getInstance()) { + // skipping runtime + continue; + } - log.info("releasing service {}", sw.getName()); + log.info("releasing service {}", sw.getName()); - try { - sw.releaseService(); - } catch (Exception e) { - runtime.error("%s threw while releasing", e); - log.error("release", e); + try { + sw.releaseService(); + } catch (Exception e) { + if (runtime != null) { + runtime.error("%s threw while releasing", e); + } + log.error("release", e); + } } - } - // clean up remote ... the contract should - // probably be just remove their references - do not - // ask for them to be released remotely .. - // in thread safe way + // clean up remote ... the contract should + // probably be just remove their references - do not + // ask for them to be released remotely .. + // in thread safe way - if (releaseRuntime && runtime != null) { - runtime.releaseService(); - } else { - // put runtime in new registry - Runtime.getInstance(); - registry = new TreeMap<>(); - registry.put(runtime.getFullName(), registry.get(runtime.getFullName())); + if (releaseRuntime) { + if (runtime != null) { + runtime.releaseService(); + } + runtime = null; + } else { + // put runtime in new registry + Runtime.getInstance(); + registry = new TreeMap<>(); + registry.put(runtime.getFullName(), registry.get(runtime.getFullName())); + } } } @@ -2643,75 +2700,78 @@ public String publishConfigFinished(String configName) { * The type of the new service * @return The started service */ - synchronized static public ServiceInterface start(String name, String type) { - try { + static public ServiceInterface start(String name, String type) { + synchronized (processLock) { + try { - ServiceInterface requestedService = Runtime.getService(name); - if (requestedService != null) { - log.info("requested service already exists"); - if (requestedService.isRunning()) { - log.info("requested service already running"); - } else { - requestedService.startService(); + ServiceInterface requestedService = Runtime.getService(name); + if (requestedService != null) { + log.info("requested service already exists"); + if (requestedService.isRunning()) { + log.info("requested service already running"); + } else { + requestedService.startService(); + } + return requestedService; } - return requestedService; - } - Plan plan = Runtime.load(name, type); + Plan plan = Runtime.load(name, type); - Map<String, ServiceInterface> services = createServicesFromPlan(plan, null, name); + Map<String, ServiceInterface> services = createServicesFromPlan(plan, null, name); - if (services == null) { - Runtime.getInstance().error("cannot create instance of %s with type %s given current configuration", name, type); - return null; - } + if (services == null) { + Runtime.getInstance().error("cannot create instance of %s with type %s given current configuration", name, type); + return null; + } - requestedService = Runtime.getService(name); + requestedService = Runtime.getService(name); - // FIXME - does some order need to be maintained e.g. all children before - // parent - // breadth first, depth first, external order ordinal ? - for (ServiceInterface service : services.values()) { - if (service.getName().equals(name)) { - continue; - } - if (!Runtime.isStarted(service.getName())) { - service.startService(); + // FIXME - does some order need to be maintained e.g. all children + // before + // parent + // breadth first, depth first, external order ordinal ? + for (ServiceInterface service : services.values()) { + if (service.getName().equals(name)) { + continue; + } + if (!Runtime.isStarted(service.getName())) { + service.startService(); + } } - } - if (requestedService == null) { - Runtime.getInstance().error("could not start %s of type %s", name, type); - return null; - } + if (requestedService == null) { + Runtime.getInstance().error("could not start %s of type %s", name, type); + return null; + } - // getConfig() was problematic here for JMonkeyEngine - ServiceConfig sc = requestedService.getConfig(); - // Map<String, Peer> peers = sc.getPeers(); - // if (peers != null) { - // for (String p : peers.keySet()) { - // Peer peer = peers.get(p); - // log.info("peer {}", peer); - // } - // } - // recursive - start peers of peers of peers ... - Map<String, Peer> subPeers = sc.getPeers(); - if (sc != null && subPeers != null) { - for (String subPeerKey : subPeers.keySet()) { - // IF AUTOSTART !!! - Peer subPeer = subPeers.get(subPeerKey); - if (subPeer.autoStart) { - Runtime.start(sc.getPeerName(subPeerKey), subPeer.type); + // getConfig() was problematic here for JMonkeyEngine + ServiceConfig sc = requestedService.getConfig(); + // Map<String, Peer> peers = sc.getPeers(); + // if (peers != null) { + // for (String p : peers.keySet()) { + // Peer peer = peers.get(p); + // log.info("peer {}", peer); + // } + // } + // recursive - start peers of peers of peers ... + Map<String, Peer> subPeers = sc.getPeers(); + if (sc != null && subPeers != null) { + for (String subPeerKey : subPeers.keySet()) { + // IF AUTOSTART !!! + Peer subPeer = subPeers.get(subPeerKey); + if (subPeer.autoStart) { + Runtime.start(sc.getPeerName(subPeerKey), subPeer.type); + } } } - } - requestedService.startService(); - return requestedService; - } catch (Exception e) { - runtime.error(e); + requestedService.startService(); + return requestedService; + } catch (Exception e) { + runtime.error(e); + } + return null; } - return null; } /** @@ -2721,32 +2781,36 @@ synchronized static public ServiceInterface start(String name, String type) { * @param name * @return */ - synchronized static public ServiceInterface start(String name) { - if (Runtime.getService(name) != null) { - // already exists - ServiceInterface si = Runtime.getService(name); - if (!si.isRunning()) { - si.startService(); + static public ServiceInterface start(String name) { + synchronized (processLock) { + if (Runtime.getService(name) != null) { + // already exists + ServiceInterface si = Runtime.getService(name); + if (!si.isRunning()) { + si.startService(); + } + return si; } - return si; - } - Plan plan = Runtime.load(name, null); - Map<String, ServiceInterface> services = createServicesFromPlan(plan, null, name); - // FIXME - order ? - for (ServiceInterface service : services.values()) { - service.startService(); + Plan plan = Runtime.load(name, null); + Map<String, ServiceInterface> services = createServicesFromPlan(plan, null, name); + // FIXME - order ? + for (ServiceInterface service : services.values()) { + service.startService(); + } + return Runtime.getService(name); } - return Runtime.getService(name); } - synchronized public static Plan load(String name, String type) { - try { - Runtime runtime = Runtime.getInstance(); - return runtime.loadService(new Plan("runtime"), name, type, true, 0); - } catch (IOException e) { - runtime.error(e); + public static Plan load(String name, String type) { + synchronized (processLock) { + try { + Runtime runtime = Runtime.getInstance(); + return runtime.loadService(new Plan("runtime"), name, type, true, 0); + } catch (IOException e) { + runtime.error(e); + } + return null; } - return null; } /** @@ -2774,18 +2838,18 @@ public Runtime(String n, String id) { /** * This is used to run through all the possible services and determine - * if they have any missing dependencies. If they do not they become "installed". - * The installed flag makes the gui do a crossout when a service type is selected. + * if they have any missing dependencies. If they do not they become + * "installed". The installed flag makes the gui do a crossout when a + * service type is selected. */ for (MetaData metaData : serviceData.getServiceTypes()) { Set<ServiceDependency> deps = repo.getUnfulfilledDependencies(metaData.getType()); if (deps.size() == 0) { metaData.installed = true; } else { - warn("{} not installed", metaData.getSimpleName()); + log.info("{} not installed", metaData.getSimpleName()); } } - } } @@ -4189,7 +4253,7 @@ static public String getFullName(String shortname) { return shortname; } // if nothing is supplied assume local - return String.format("%s@%s", shortname, Platform.getLocalInstance().getId()); + return String.format("%s@%s", shortname, Runtime.getInstance().getId()); } @Override @@ -4482,18 +4546,20 @@ public static void main(String[] args) { try { + // loading args globalArgs = args; - new CommandLine(options).parseArgs(args); + log.info("in args {}", Launcher.toString(args)); + log.info("options {}", CodecUtils.toJson(options)); + log.info("\n" + Launcher.banner); + + // creating initial data/config directory + File cfgRoot = new File(ROOT_CONFIG_DIR); + cfgRoot.mkdirs(); // initialize logging initLog(); - log.info("in args {}", Launcher.toString(args)); - log.info(CodecUtils.toJson(options)); - - log.info("\n" + Launcher.banner); - // help and exit if (options.help) { mainHelp(); @@ -4503,45 +4569,23 @@ public static void main(String[] args) { // start.yml file is required, if not pre-existing // is created immediately. It contains static information // which needs to be available before a Runtime is created - File checkStartYml = new File("start.yml"); - if (!checkStartYml.exists()) { - // save default - startYml = new StartYml(); - String defaultStartFile = CodecUtils.toYaml(startYml); - FileIO.toFile("start.yml", defaultStartFile); - } else { - String yml = FileIO.toString("start.yml"); - startYml = CodecUtils.fromYaml(yml, StartYml.class); - } + Runtime.startYml = ConfigUtils.loadStartYml(); - // id always required - precedence - // if none supplied one will be generated - // if in start.yml it will be used - // if supplied by the command line it will be used - // command line has the highest precedence - - Platform platform = Platform.getLocalInstance(); - if (options.id != null) { - platform.setId(options.id); - } else if (startYml.id != null) { - platform.setId(startYml.id); - } else { - // no id set - should be first - // time mrl is started - String id = NameGenerator.getName(); - platform.setId(id); - startYml.id = id; - FileIO.toFile("start.yml", CodecUtils.toYaml(startYml)); + // resolve configName before starting getting runtime configuration + Runtime.configName = (startYml.enable) ? startYml.config : "default"; + if (options.config != null) { + // cmd line options has the highest priority + Runtime.configName = options.config; } - if (options.virtual) { - Platform.setVirtual(true); - } + // start.yml is processed, config name is set, runtime config + // is resolved, now we can start instance + Runtime.getInstance(); - // FIXME TEST THIS !! 0 length, single service, multiple ! if (options.install != null) { // we start the runtime so there is a status publisher which will // display status updates from the repo install + log.info("requesting install"); Repo repo = getInstance().getRepo(); if (options.install.length == 0) { repo.install(LIBRARIES, (String) null); @@ -4554,36 +4598,6 @@ public static void main(String[] args) { return; } - // if a you specify a config file it becomes the "base" of configuration - // inline flags will still override values - if (options.config != null) { - // if this is a valid config, it will load - setConfig(options.config); - } else { - // required directory to load any service - setConfig(startYml.config); - } - - if (startYml.enable) { - Runtime.startConfig(startYml.config); - } else { - createAndStartServices(options.services); - } - - if (options.invoke != null) { - invokeCommands(options.invoke); - } - - if (options.connect != null) { - Runtime.getInstance().connect(options.connect); - } - - if (options.autoUpdate) { - // initialize - // FIXME - use peer ? - Updater.main(args); - } - } catch (Exception e) { log.error("runtime exception", e); Runtime.mainHelp(); @@ -4594,7 +4608,6 @@ public static void main(String[] args) { public static void initLog() { if (options != null) { - LoggingFactory.setLogFile(options.logFile); LoggingFactory.init(options.logLevel); } else { LoggingFactory.init("info"); @@ -4696,87 +4709,92 @@ static public ServiceInterface loadAndStart(String name, String type) { * @return * @throws IOException */ - synchronized public Plan loadService(Plan plan, String name, String type, boolean start, int level) throws IOException { + public Plan loadService(Plan plan, String name, String type, boolean start, int level) throws IOException { + synchronized (processLock) { - if (plan == null) { - log.error("plan required to load a system"); - return null; - } + if (plan == null) { + log.error("plan required to load a system"); + return null; + } - log.info("loading - {} {} {}", name, type, level); - // from recursive memory definition - ServiceConfig sc = plan.get(name); - - // HIGHEST PRIORITY - OVERRIDE WITH FILE - String configPath = runtime.getConfigPath(); - String configFile = configPath + fs + name + ".yml"; - - // PRIORITY #1 - // find if a current yml config file exists - highest priority - log.debug("priority #1 user's yml override {} ", configFile); - ServiceConfig fileSc = readServiceConfig(Runtime.getInstance().getConfigName(), name); - if (fileSc != null) { - // if definition exists in file form, it overrides current memory one - sc = fileSc; - } else if (sc != null) { - // if memory config is available but not file - // we save it - String yml = CodecUtils.toYaml(sc); - FileIO.toFile(configFile, yml); - } - - // special conflict case - type is specified, but its not the same as - // file version - in that case specified parameter type wins and overwrites - // config. User can force type by supplying one as a parameter, however, the - // recursive - // call other peer types will have name/file.yml definition precedence - if ((type != null && sc != null && !type.equals(sc.type) && level == 0) || (sc == null)) { - if (sc != null) { - warn("type %s overwriting type %s specified in %s.yml file", type, sc.type, name); + log.info("loading - {} {} {}", name, type, level); + // from recursive memory definition + ServiceConfig sc = plan.get(name); + + // HIGHEST PRIORITY - OVERRIDE WITH FILE + String configPath = runtime.getConfigPath(); + String configFile = configPath + fs + name + ".yml"; + + // PRIORITY #1 + // find if a current yml config file exists - highest priority + log.debug("priority #1 user's yml override {} ", configFile); + ServiceConfig fileSc = readServiceConfig(Runtime.getInstance().getConfigName(), name); + if (fileSc != null) { + // if definition exists in file form, it overrides current memory one + sc = fileSc; + } else if (sc != null) { + // if memory config is available but not file + // we save it + String yml = CodecUtils.toYaml(sc); + FileIO.toFile(configFile, yml); } - ServiceConfig.getDefault(plan, name, type); - sc = plan.get(name); - // create new file if it didn't exist or overwrite it if new type is - // required - String yml = CodecUtils.toYaml(sc); - FileIO.toFile(configFile, yml); - } + // special conflict case - type is specified, but its not the same as + // file version - in that case specified parameter type wins and + // overwrites + // config. User can force type by supplying one as a parameter, however, + // the + // recursive + // call other peer types will have name/file.yml definition precedence + if ((type != null && sc != null && !type.equals(sc.type) && level == 0) || (sc == null)) { + if (sc != null) { + warn("type %s overwriting type %s specified in %s.yml file", type, sc.type, name); + } + ServiceConfig.getDefault(plan, name, type); + sc = plan.get(name); - if (sc == null && type == null) { - log.error("no local config and unknown type"); - return plan; - } + // create new file if it didn't exist or overwrite it if new type is + // required + String yml = CodecUtils.toYaml(sc); + FileIO.toFile(configFile, yml); + } - // finalize - if (sc != null) { - plan.put(name, sc); - // RECURSIVE load peers - Map<String, Peer> peers = sc.getPeers(); - for (String peerKey : peers.keySet()) { - Peer peer = peers.get(peerKey); - // recursive depth load - parent and child need to be started - runtime.loadService(plan, peer.name, peer.type, start && peer.autoStart, level + 1); + if (sc == null && type == null) { + log.error("no local config and unknown type"); + return plan; } - // valid service config at this point - now determine if its supposed to - // start or not - // if its level 0 then it was requested by user or config - so it needs to - // start - // if its not level 0 then it was loaded because peers were defined and - // appropriate config loaded - // peer.autoStart should determine if the peer starts if not explicitly - // requested by the - // user or config - if (level == 0 || start) { - plan.addRegistry(name); + // finalize + if (sc != null) { + plan.put(name, sc); + // RECURSIVE load peers + Map<String, Peer> peers = sc.getPeers(); + for (String peerKey : peers.keySet()) { + Peer peer = peers.get(peerKey); + // recursive depth load - parent and child need to be started + runtime.loadService(plan, peer.name, peer.type, start && peer.autoStart, level + 1); + } + + // valid service config at this point - now determine if its supposed to + // start or not + // if its level 0 then it was requested by user or config - so it needs + // to + // start + // if its not level 0 then it was loaded because peers were defined and + // appropriate config loaded + // peer.autoStart should determine if the peer starts if not explicitly + // requested by the + // user or config + if (level == 0 || start) { + plan.addRegistry(name); + } + + } else { + log.info("could not load {} {} {}", name, type, level); } - } else { - log.info("could not load {} {} {}", name, type, level); + return plan; } - - return plan; } /** @@ -4846,45 +4864,30 @@ public String publishConfigLoaded(String name) { return name; } - public String setAllIds(String id) { - Platform.getLocalInstance().setId(id); - for (ServiceInterface si : getServices()) { - si.setId(id); - } - return id; - } - @Override - public RuntimeConfig apply(RuntimeConfig c) { - super.apply(c); - config = c; + public RuntimeConfig apply(RuntimeConfig config) { + super.apply(config); setLocale(config.locale); - if (config.id != null) { - setAllIds(config.id); + if (config.id == null) { + config.id = NameGenerator.getName(); } if (config.logLevel != null) { setLogLevel(config.logLevel); } - info("setting locale to %s", config.locale); if (config.virtual != null) { info("setting virtual to %b", config.virtual); setAllVirtual(config.virtual); } - if (config.enableCli) { - startInteractiveMode(); - info("enabled cli"); - } else { - stopInteractiveMode(); - info("disabled cli"); - } + // APPLYING A RUNTIME CONFIG DOES NOT PROCESS THE REGISTRY + // USE startConfig(name) broadcastState(); - return c; + return config; } /** @@ -4986,7 +4989,6 @@ public boolean saveService(String configName, String serviceName, String filenam // conditional boolean to flip and save a config name to start.yml ? if (startYml.enable) { - startYml.id = getId(); startYml.config = configName; FileIO.toFile("start.yml", CodecUtils.toYaml(startYml)); } @@ -5019,26 +5021,6 @@ public boolean saveService(String configName, String serviceName, String filenam return false; } - public String setConfigName(String name) { - if (name != null && name.contains(fs)) { - error("invalid character " + fs + " in configuration name"); - return configName; - } - if (name != null) { - configName = name.trim(); - } - - // for the moment the best way is to mandate - // a dir is created when a new config name is set - // because loading service are required to save config - // before starting - File configDir = new File(ROOT_CONFIG_DIR + fs + name); - configDir.mkdirs(); - - invoke("publishConfigList"); - return name; - } - public String getConfigName() { return configName; } @@ -5056,13 +5038,34 @@ public boolean isProcessingConfig() { * - config dir name under data/config/{config} * @return configName */ - public static String setConfig(String configName) { + public static String setConfig(String name) { + if (name == null) { + log.error("config cannot be null"); + if (runtime != null) { + runtime.error("config cannot be null"); + } + return null; + } + + if (name.contains(fs)) { + log.error("invalid character " + fs + " in configuration name"); + if (runtime != null) { + runtime.error("invalid character " + fs + " in configuration name"); + } + return name; + } - File configDir = new File(ROOT_CONFIG_DIR + fs + configName); - configDir.mkdirs(); + configName = name.trim(); + + File configDir = new File(ROOT_CONFIG_DIR + fs + name); + if (!configDir.exists()) { + configDir.mkdirs(); + } + + if (runtime != null) { + runtime.invoke("publishConfigList"); + } - Runtime runtime = Runtime.getInstance(); - runtime.setConfigName(configName); return configName; } @@ -5279,14 +5282,6 @@ public String getConfigPath() { return ROOT_CONFIG_DIR + fs + configName; } - @Override - public RuntimeConfig getConfig() { - config = super.getConfig(); - config.locale = getLocaleTag(); - config.virtual = isVirtual; - return config; - } - /** * Gets a {serviceName}.yml file config from configName directory * @@ -5391,11 +5386,12 @@ public ServiceConfig getPeer(String sericeName, String peerKey) { /** * Removes a config set and all its files * - * @param configName - name of config + * @param configName + * - name of config */ public static void removeConfig(String configName) { try { - log.info("removeing config"); + log.info("removing config"); File check = new File(ROOT_CONFIG_DIR + fs + configName); @@ -5408,12 +5404,4 @@ public static void removeConfig(String configName) { } } - /** - * Method used to determine is runtime is running without starting it - * @return true if available - */ - static public boolean isAvailable() { - return runtime != null && runtime.isRunning(); - } - } diff --git a/src/main/java/org/myrobotlab/service/WebGui.java b/src/main/java/org/myrobotlab/service/WebGui.java index 21deea126c..5e87cddb05 100644 --- a/src/main/java/org/myrobotlab/service/WebGui.java +++ b/src/main/java/org/myrobotlab/service/WebGui.java @@ -62,7 +62,8 @@ * services are already APIs - perhaps a data API - same as service without the * message wrapper */ -public class WebGui extends Service<WebGuiConfig> implements AuthorizationProvider, Gateway, Handler, ServiceLifeCycleListener { +public class WebGui extends Service<WebGuiConfig> + implements AuthorizationProvider, Gateway, Handler, ServiceLifeCycleListener { public static class LiveVideoStreamHandler implements Handler { @@ -89,7 +90,7 @@ public void handle(AtmosphereResource r) { } } } - + private final transient IncomingMsgQueue inMsgQueue = new IncomingMsgQueue(); public static class Panel { @@ -127,7 +128,7 @@ public Panel(String name, int x, int y, int z) { * needed to get the api key to select the appropriate api processor * * @param uri - * u + * u * @return api key * */ @@ -270,9 +271,9 @@ public boolean getAutoStartBrowser() { * String broadcast to specific client * * @param uuid - * u + * u * @param str - * s + * s * */ public void broadcast(String uuid, String str) { @@ -314,7 +315,9 @@ public Config.Builder getNettosphereConfig() { // cert.privateKey()).build(); SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); - SslContext context = SslContextBuilder.forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()).sslProvider(SslProvider.JDK) + SslContext context = SslContextBuilder + .forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()) + .sslProvider(SslProvider.JDK) .clientAuth(ClientAuth.NONE).build(); configBuilder.sslContext(context); @@ -493,7 +496,8 @@ public void handle(AtmosphereResource r) { } else if ((bodyData != null) && log.isDebugEnabled()) { logData = bodyData; } - log.debug("-->{} {} {} - [{}] from connection {}", (newPersistentConnection) ? "new" : "", request.getMethod(), request.getRequestURI(), logData, uuid); + log.debug("-->{} {} {} - [{}] from connection {}", (newPersistentConnection) ? "new" : "", request.getMethod(), + request.getRequestURI(), logData, uuid); } // important persistent connections will have associated routes ... @@ -571,7 +575,8 @@ public void handle(AtmosphereResource r) { } if (msg.containsHop(getId())) { - log.error("{} dumping duplicate hop msg to avoid cyclical from {} --to--> {}.{}", getName(), msg.sender, msg.name, msg.method); + log.error("{} dumping duplicate hop msg to avoid cyclical from {} --to--> {}.{}", getName(), msg.sender, + msg.name, msg.method); return; } @@ -915,7 +920,7 @@ public void run() { * remotely control UI * * @param panel - * - the panel which has been moved or resized + * - the panel which has been moved or resized */ public void savePanel(Panel panel) { if (panel.name == null) { @@ -1102,7 +1107,7 @@ public void releaseService() { * Default (false) is to use the CDN * * @param useLocalResources - * - true uses local resources fals uses cdn + * - true uses local resources fals uses cdn */ public void useLocalResources(boolean useLocalResources) { this.useLocalResources = useLocalResources; @@ -1162,7 +1167,7 @@ public WebGuiConfig getConfig() { public WebGuiConfig apply(WebGuiConfig c) { super.apply(c); - + if (c.port != null && (port != null && c.port.intValue() != port.intValue())) { setPort(c.port); } @@ -1178,17 +1183,19 @@ public static void main(String[] args) { try { - // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui", "intro", "Intro", "python", "Python" }); - Runtime.main(new String[] { "--install" }); - + Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui","intro", "Intro", "python", "Python" }); + // Runtime.main(new String[] {}); + // Runtime.main(new String[] { "--install" }); + boolean done = true; if (done) { return; } - + // Platform.setVirtual(true); - // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui", "intro", "Intro", "python", "Python", "-c", "dev" }); - // Runtime.startConfig("dev"); + // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui", + // "intro", "Intro", "python", "Python", "-c", "dev" }); + // Runtime.startConfig("dev"); // Runtime.start("python", "Python"); // Arduino arduino = (Arduino)Runtime.start("arduino", "Arduino"); @@ -1199,13 +1206,10 @@ public static void main(String[] args) { // webgui.setSsl(true); webgui.startService(); - - Runtime.start("python", "Python"); // Runtime.start("intro", "Intro"); // Runtime.start("i01", "InMoov2"); - // Runtime.start("i01", "InMoov2"); // Runtime.start("python", "Python"); // Runtime.start("i01", "InMoov2"); @@ -1263,7 +1267,6 @@ public static void main(String[] args) { * Runtime.start("clock03", "Clock"); Runtime.start("clock04", "Clock"); * Runtime.start("clock05", "Clock"); */ - Platform.setVirtual(true); // Arduino arduino = (Arduino) Runtime.start("arduino", "Arduino"); Servo pan = (Servo) Runtime.start("pan", "Servo"); @@ -1309,5 +1312,4 @@ public void onStopped(String name) { public void onReleased(String name) { } - } diff --git a/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java b/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java index 87b9e4a1d1..d7572cd118 100644 --- a/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java +++ b/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java @@ -1,31 +1,56 @@ package org.myrobotlab.service.config; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import org.myrobotlab.framework.NameGenerator; +import org.myrobotlab.framework.Plan; +import org.myrobotlab.service.data.Locale; + public class RuntimeConfig extends ServiceConfig { /** * instance id - important to be unique when connecting multiple * mrl instances together */ - public String id; + public String id = NameGenerator.getName(); /** * virtual hardware if enabled all services created will enable virtualization if applicable */ public Boolean virtual = false; - public boolean enableCli = true; - public String logLevel = "info"; - public String locale; + + /** + * Log level debug, info, warn, error + */ + public String logLevel = "warn"; + + /** + * Locale setting for the instance, initial default will be set by the default jvm/os + * through java.util.Locale.getDefault() + */ + public String locale = Locale.getDefault().getTag(); - // NEED THIS PRIVATE BUT CANNOT BE + + /** + * Although this should be a set of unique services, it cannot be a LinkedHashSet + * because SnakeYml's interpretation would be a map with null values. Instead + * its a protected member with accessors that prevent duplicates. + */ public List<String> registry = new ArrayList<>(); /** * Root of resource location */ public String resource = "resource"; + + + public Plan getDefault(Plan plan, String name) { + super.getDefault(plan, name); + return plan; + } + /** * add and remove a service using these methods and the uniqueness will be diff --git a/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java b/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java index 7ffd26fcd4..e26d28e732 100644 --- a/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java +++ b/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java @@ -186,25 +186,6 @@ public void testDefaultSerialization() { } - @Test - public void testNormalizeServiceName() { - Platform.getLocalInstance().setId("test-id"); - assertEquals("runtime@test-id", CodecUtils.getFullName("runtime")); - assertEquals("runtime@test-id", CodecUtils.getFullName("runtime@test-id")); - } - - @Test - public void testCheckServiceNameEqual() { - Platform.getLocalInstance().setId("test-id"); - assertTrue(CodecUtils.checkServiceNameEquality("runtime", "runtime")); - assertTrue(CodecUtils.checkServiceNameEquality("runtime", "runtime@test-id")); - assertTrue(CodecUtils.checkServiceNameEquality("runtime@test-id", "runtime")); - assertTrue(CodecUtils.checkServiceNameEquality("runtime@test-id", "runtime@test-id")); - assertFalse(CodecUtils.checkServiceNameEquality("runtime", "runtime@not-corr-id")); - assertFalse(CodecUtils.checkServiceNameEquality("runtime@not-corr-id", "runtime")); - - } - @Test public void testBase64() { // not a very comprehensive test, but a sanity check none the less. diff --git a/src/test/java/org/myrobotlab/framework/CmdOptionsTest.java b/src/test/java/org/myrobotlab/framework/CmdOptionsTest.java index 85f0d4f2ee..7afa011b8e 100644 --- a/src/test/java/org/myrobotlab/framework/CmdOptionsTest.java +++ b/src/test/java/org/myrobotlab/framework/CmdOptionsTest.java @@ -1,6 +1,7 @@ package org.myrobotlab.framework; - +import org.junit.Ignore; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -9,10 +10,12 @@ import org.junit.Test; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.Runtime; +import org.myrobotlab.service.config.ClockConfig; import org.slf4j.Logger; import picocli.CommandLine; - +@Ignore public class CmdOptionsTest { public final static Logger log = LoggerFactory.getLogger(CmdOptionsTest.class); @@ -32,29 +35,36 @@ public void testGetOutputCmd() throws IOException { CmdOptions options = new CmdOptions(); new CommandLine(options).parseArgs(new String[] {}); // validate defaults - assertEquals(false, options.autoUpdate); assertNull(options.config); - assertNull(options.connect); assertEquals(0, options.services.size()); - new CommandLine(options).parseArgs(new String[] { "--id", "raspi", "-s", "webgui", "WebGui", "clock01", "Clock" }); + new CommandLine(options).parseArgs(new String[] { "-s", "webgui", "WebGui", "clock01", "Clock" }); - assertEquals("raspi", options.id); assertEquals(4, options.services.size()); List<String> cmd = options.getOutputCmd(); assertTrue(contains(cmd, "webgui")); - assertTrue(contains(cmd, "raspi")); + assertTrue(contains(cmd, "clock01")); log.info(CmdOptions.toString(cmd)); - options = new CmdOptions(); - new CommandLine(options).parseArgs(new String[] { "-a" }); - assertEquals(true, options.autoUpdate); - + Runtime.releaseAll(true, true); // test help - - // test unmatched option + Runtime.main(new String[] { "--id", "test", "-s", "clockCmdTest", "Clock" }); + assertNotNull(Runtime.getService("clockCmdTest")); + assertEquals("test", Runtime.getInstance().getId()); + + Runtime.releaseAll(true, true); + + Runtime.main(new String[] { "-c", "xxx", "-s", "clockCmdTest", "Clock" }); + + ClockConfig clock = (ClockConfig)Runtime.getInstance().readServiceConfig("xxx", "clockCmdTest"); + assertNotNull(clock); + assertNotNull(Runtime.getService("clockCmdTest")); + + Runtime.releaseAll(true, true); + + log.info("here"); } diff --git a/src/test/java/org/myrobotlab/framework/ConfigTest.java b/src/test/java/org/myrobotlab/framework/ConfigTest.java index 4f8f1a9567..955a18e2ce 100644 --- a/src/test/java/org/myrobotlab/framework/ConfigTest.java +++ b/src/test/java/org/myrobotlab/framework/ConfigTest.java @@ -8,14 +8,8 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Comparator; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -38,23 +32,18 @@ import org.slf4j.Logger; public class ConfigTest extends AbstractTest { - - + @BeforeClass public static void setUpBeforeClass() { - System.out.println("Runs before any test method in the class"); - } - - @AfterClass - public static void tearDownAfterClass() { - System.out.println("Runs after all test methods in the class"); + // clean out services - reset + Runtime.releaseAll(true, true); } @Before /* before each test */ public void setUp() throws IOException { // remove all services - also resets config name to DEFAULT effectively Runtime.releaseAll(true, true); - // clean our config directory + // clean our config directory Runtime.removeConfig(CONFIG_NAME); // set our config Runtime.setConfig(CONFIG_NAME); @@ -62,9 +51,8 @@ public void setUp() throws IOException { @After public void tearDown() { - System.out.println("Runs after each test method"); + System.out.println("Runs after each test method"); } - // --- config set related --- // setConfigPath(fullpath) @@ -90,32 +78,31 @@ public void tearDown() { final String CONFIG_PATH = "data" + File.separator + "config" + File.separator + CONFIG_NAME; - @Test public void testStartNoConfig() throws Exception { Runtime runtime = Runtime.getInstance(); assertNotNull(runtime); - + // complete teardown, release runtime, block Runtime.releaseAll(true, true); - + String[] names = Runtime.getServiceNames(); - assertEquals("complete teardown should be 0", 0, names.length); - + assertEquals("after teardown, then using a runtime static - only 0 service 'runtime' should exist", 0, names.length); + // nothing to start - should be empty config Runtime.startConfig(CONFIG_NAME); - + // starting an empty config automatically needs a runtime, and runtime // by default starts the singleton security service names = Runtime.getServiceNames(); - assertEquals("complete teardown should be 2 after trying to start a config runtime and security", 2, names.length); - + assertEquals("complete teardown should be 1 after trying to start a config runtime", 1, names.length); + Runtime.releaseAll(true, true); } - + @Test public void testSwitchingPeer() throws IOException { - + Runtime runtime = Runtime.getInstance(); assertNotNull(runtime); @@ -123,53 +110,53 @@ public void testSwitchingPeer() throws IOException { // to the current config directory Plan plan = Runtime.load("eyeTracking", "Tracking"); assertNotNull(plan); - + // load eyeTracking.yml config - verify default state - TrackingConfig eyeTracking = (TrackingConfig)runtime.getConfig(CONFIG_NAME, "eyeTracking"); + TrackingConfig eyeTracking = (TrackingConfig) runtime.getConfig(CONFIG_NAME, "eyeTracking"); TrackingConfig defaultTracking = new TrackingConfig(); assertEquals("eyeTracking.yml values should be the same as default", defaultTracking.enabled, eyeTracking.enabled); assertEquals("eyeTracking.yml type should be the same as default", defaultTracking.type, eyeTracking.type); - eyeTracking = (TrackingConfig)runtime.getConfig("eyeTracking"); + eyeTracking = (TrackingConfig) runtime.getConfig("eyeTracking"); assertEquals("eyeTracking.yml values should be the same as default", defaultTracking.enabled, eyeTracking.enabled); assertEquals("eyeTracking.yml type should be the same as default", defaultTracking.type, eyeTracking.type); - + // load single opencv - OpenCVConfig cv = (OpenCVConfig)Runtime.load("cv", "OpenCV").get("cv"); + OpenCVConfig cv = (OpenCVConfig) Runtime.load("cv", "OpenCV").get("cv"); // default capturing is false assertFalse(cv.capturing); // save as true cv.capturing = true; Runtime.saveConfig("cv", cv); - + Runtime.load("pid", "Pid"); - eyeTracking = (TrackingConfig)runtime.getConfig("eyeTracking"); - + eyeTracking = (TrackingConfig) runtime.getConfig("eyeTracking"); + eyeTracking.getPeer("cv").name = "cv"; Runtime.saveConfig("eyeTracking", eyeTracking); - + // verify the peer was updated to cv - eyeTracking = (TrackingConfig)runtime.getConfig("eyeTracking"); - cv = (OpenCVConfig)runtime.getPeerConfig("eyeTracking","cv"); + eyeTracking = (TrackingConfig) runtime.getConfig("eyeTracking"); + cv = (OpenCVConfig) runtime.getPeerConfig("eyeTracking", "cv"); // from previous save assertTrue(cv.capturing); } - + @Test public void testChangeType() throws IOException { - Runtime runtime = Runtime.getInstance(); + Runtime runtime = Runtime.getInstance(); Runtime.load("mouth", "MarySpeech"); - MarySpeechConfig mouth = (MarySpeechConfig)runtime.getConfig("mouth"); + MarySpeechConfig mouth = (MarySpeechConfig) runtime.getConfig("mouth"); mouth.listeners.add(new Listener("publishStartSpeaking", "fakeListener")); Runtime.saveConfig("mouth", mouth); - MarySpeechConfig mary = (MarySpeechConfig)runtime.getConfig("mouth"); + MarySpeechConfig mary = (MarySpeechConfig) runtime.getConfig("mouth"); assertNotNull(mary); assertEquals(1, mary.listeners.size()); // save it runtime.changeType("mouth", "LocalSpeech"); - LocalSpeechConfig local = (LocalSpeechConfig)runtime.getConfig("mouth"); + LocalSpeechConfig local = (LocalSpeechConfig) runtime.getConfig("mouth"); assertEquals("must have the listener", 1, local.listeners.size()); assertTrue(local.listeners.get(0).listener.equals("fakeListener")); } @@ -178,23 +165,23 @@ public void testChangeType() throws IOException { public void testInitialLoad() { Runtime runtime = Runtime.getInstance(); Runtime.load("service", "Clock"); - ClockConfig clock = (ClockConfig)runtime.getConfig("service"); + ClockConfig clock = (ClockConfig) runtime.getConfig("service"); assertNotNull(clock); // replace load Runtime.load("service", "Tracking"); - TrackingConfig tracking = (TrackingConfig)runtime.getConfig("service"); + TrackingConfig tracking = (TrackingConfig) runtime.getConfig("service"); assertNotNull(tracking); } - + @Test public void testChangePeerName() throws IOException { Runtime runtime = Runtime.getInstance(); Plan plan = Runtime.load("pollyMouth", "Polly"); - PollyConfig polly = (PollyConfig)plan.get("pollyMouth"); + PollyConfig polly = (PollyConfig) plan.get("pollyMouth"); Runtime.load("i01", "InMoov2"); - InMoov2Config i01 = (InMoov2Config)runtime.getConfig("i01"); + InMoov2Config i01 = (InMoov2Config) runtime.getConfig("i01"); // default - MarySpeechConfig mary = (MarySpeechConfig)runtime.getPeer("i01", "mouth"); + MarySpeechConfig mary = (MarySpeechConfig) runtime.getPeer("i01", "mouth"); assertNotNull(mary); polly.listeners = mary.listeners; Runtime.saveConfig("pollyMouth", polly); @@ -202,48 +189,50 @@ public void testChangePeerName() throws IOException { peer.name = "pollyMouth"; Runtime.saveConfig("i01", i01); // switch to pollyMouth - PollyConfig p = (PollyConfig)runtime.getPeer("i01", "mouth"); - + PollyConfig p = (PollyConfig) runtime.getPeer("i01", "mouth"); + // FIXME - was going to test moving of subscriptions, however, unfortunately - // SpeechSynthesis services use a "recognizers" data instead of just simple subscriptions + // SpeechSynthesis services use a "recognizers" data instead of just simple + // subscriptions // This should be fixed in the future to use standard subscriptions - - } - + + } + @Test public void testSimpleServiceStart() { - Clock clock = (Clock)Runtime.start("track", "Clock"); + Runtime.releaseAll(true, true); + Clock clock = (Clock) Runtime.start("track", "Clock"); clock.startClock(); clock.releaseService(); // better be a tracking service - LocalSpeech track = (LocalSpeech)Runtime.start("track", "LocalSpeech"); + LocalSpeech track = (LocalSpeech) Runtime.start("track", "LocalSpeech"); assertNotNull(track); track.releaseService(); // better be a clock - clock = (Clock)Runtime.create("track", "Clock"); + clock = (Clock) Runtime.create("track", "Clock"); log.info("start"); } @Test public void testPeers() { - InMoov2Head head = (InMoov2Head)Runtime.start("track", "InMoov2Head"); - Servo neck = (Servo)Runtime.getService("track.neck"); + Runtime.releaseAll(true, true); + InMoov2Head head = (InMoov2Head) Runtime.start("track", "InMoov2Head"); + Servo neck = (Servo) Runtime.getService("track.neck"); assertNotNull(neck); head.releaseService(); assertNull(Runtime.getService("track.neck")); - } - + @Test public void testSaveApply() throws IOException { Runtime runtime = Runtime.getInstance(); - Servo neck = (Servo)Runtime.start("neck", "Servo"); + Servo neck = (Servo) Runtime.start("neck", "Servo"); ServoConfig config = neck.getConfig(); - + // Where config is "different" than member variables it // takes an apply(config) of the config to make the service // update its member variables, vs changing config and - // immediately getting the service behavior change. + // immediately getting the service behavior change. config.idleTimeout = 5000; // the fact this takes and additional method to process // i think is legacy and should be changed for Servo to use @@ -251,24 +240,23 @@ public void testSaveApply() throws IOException { neck.apply(config); neck.save(); neck.releaseService(); - neck = (Servo)Runtime.start("neck", "Servo"); - assertTrue("preserved value", 5000 == neck.getConfig().idleTimeout); + neck = (Servo) Runtime.start("neck", "Servo"); + assertTrue("preserved value", 5000 == neck.getConfig().idleTimeout); - Servo servo = (Servo)Runtime.start("servo", "Servo"); - config = (ServoConfig)Runtime.load("default", "Servo").get("default"); + Servo servo = (Servo) Runtime.start("servo", "Servo"); + config = (ServoConfig) Runtime.load("default", "Servo").get("default"); assertNull(config.idleTimeout); config.idleTimeout = 7000; Runtime.saveConfig("servo", config); servo.apply(); assertTrue(servo.getConfig().idleTimeout == 7000); - + config.idleTimeout = 8000; servo.apply(config); assertTrue(servo.getIdleTimeout() == 8000); servo.apply(); assertTrue("filesystem servo.yml applied", servo.getIdleTimeout() == 7000); - + } - } \ No newline at end of file diff --git a/src/test/java/org/myrobotlab/io/FileIOTest.java b/src/test/java/org/myrobotlab/io/FileIOTest.java index a8700219a8..8eb14bedb5 100644 --- a/src/test/java/org/myrobotlab/io/FileIOTest.java +++ b/src/test/java/org/myrobotlab/io/FileIOTest.java @@ -134,11 +134,6 @@ public void testGluePaths() { assertEquals("/abc/def/", ret); } - @Test - public void testIsJar() { - assertFalse(FileIO.isJar()); - } - @Test public void testGetFileListString() throws IOException { String dir = FileIO.gluePaths(tempDir, "testGetFileListString"); @@ -187,6 +182,7 @@ public void testToInputStreamString() throws IOException { InputStream ios = FileIO.toInputStream("This is some data that got turned into a stream"); String data = FileIO.toString(ios); assertEquals("This is some data that got turned into a stream", data); + ios.close(); } @Test diff --git a/src/test/java/org/myrobotlab/service/RuntimeTest.java b/src/test/java/org/myrobotlab/service/RuntimeTest.java index a20a13db38..e2a01278c1 100644 --- a/src/test/java/org/myrobotlab/service/RuntimeTest.java +++ b/src/test/java/org/myrobotlab/service/RuntimeTest.java @@ -26,8 +26,8 @@ public class RuntimeTest extends AbstractTest { public final static Logger log = LoggerFactory.getLogger(RuntimeTest.class); @Before - public void setUp() { - // LoggingFactory.init("WARN"); + public void beforeTest() { + Runtime.releaseAll(true, true); } @Test @@ -92,9 +92,6 @@ public void testGetUptime() { @Test public void testRuntimeLocale() { - long curr = 1479044758691L; - Date d = new Date(curr); - Runtime runtime = Runtime.getInstance(); runtime.setLocale("fr-FR"); assertEquals("expecting concat fr-FR", "fr-FR", runtime.getLocale().getTag()); @@ -105,16 +102,6 @@ public void testRuntimeLocale() { } - @Test - public void testRuntimeIsAvailable() { - Runtime.getInstance(); - assertTrue(Runtime.isAvailable()); - Runtime.releaseAll(true, true); - assertFalse(Runtime.isAvailable()); - Runtime.getInstance(); - assertTrue(Runtime.isAvailable()); - } - @Test public void testGetDescribeMessage() { diff --git a/src/test/java/org/myrobotlab/service/SerialTest.java b/src/test/java/org/myrobotlab/service/SerialTest.java index db1f10e315..63aeaa5cb5 100644 --- a/src/test/java/org/myrobotlab/service/SerialTest.java +++ b/src/test/java/org/myrobotlab/service/SerialTest.java @@ -53,7 +53,7 @@ public static Set<Thread> getDeadThreads() { @BeforeClass public static void setUpBeforeClass() throws Exception { // LoggingFactory.init("WARN"); - Platform.setVirtual(true); + Runtime.getInstance().setVirtual(true); log.info("setUpBeforeClass"); From 46af60ce4e0fbfe789947da22f0538fba02815c2 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Mon, 12 Feb 2024 06:26:04 -0800 Subject: [PATCH 047/131] framework --- .../java/org/myrobotlab/service/InMoov2.java | 2 +- .../org/myrobotlab/framework/ServiceTest.java | 46 ------------------- 2 files changed, 1 insertion(+), 47 deletions(-) delete mode 100644 src/test/java/org/myrobotlab/framework/ServiceTest.java diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 5f4233844c..46ab187b5b 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -1877,7 +1877,7 @@ public SpeechSynthesis startMouth() { broadcastState(); speakBlocking(get("STARTINGMOUTH")); - if (Platform.isVirtual()) { + if (isVirtual()) { speakBlocking(get("STARTINGVIRTUALHARD")); } speakBlocking(get("WHATISTHISLANGUAGE")); diff --git a/src/test/java/org/myrobotlab/framework/ServiceTest.java b/src/test/java/org/myrobotlab/framework/ServiceTest.java deleted file mode 100644 index a9f180362c..0000000000 --- a/src/test/java/org/myrobotlab/framework/ServiceTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.myrobotlab.framework; - -import static org.junit.Assert.assertEquals; - -import java.util.List; -import java.util.Map; - -import org.junit.Test; -import org.myrobotlab.service.config.ServiceConfig; -import org.myrobotlab.test.AbstractTest; - -public class ServiceTest extends AbstractTest { - - public static class TestService extends Service<ServiceConfig> { - - private static final long serialVersionUID = 1L; - - /** - * Constructor of service, reservedkey typically is a services name and inId - * will be its process id - * - * @param reservedKey the service name - * @param inId process id - */ - public TestService(String reservedKey, String inId) { - super(reservedKey, inId); - } - } - - @Test - public void testConfigListenerFiltering() { - Platform.getLocalInstance().setId("test-id"); - TestService t = new TestService("test", "test-id"); - List<MRLListener> listeners = List.of( - new MRLListener("meth", "webgui@webgui-client", "onMeth"), - new MRLListener("meth", "random@test-id", "onMeth"), - new MRLListener("meth", "random2@test-2-id", "onMeth") - ); - t.apply(new ServiceConfig()); - t.outbox.notifyList = Map.of("meth", listeners); - List<ServiceConfig.Listener> filtered = t.getFilteredConfig().listeners; - assertEquals("random", filtered.get(0).listener); - assertEquals("random2@test-2-id", filtered.get(1).listener); - t.getFilteredConfig(); - } -} From e895567d8c5fd68e9c6fd6c8003c1d2ea822389c Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Mon, 12 Feb 2024 07:40:18 -0800 Subject: [PATCH 048/131] let framework process inmoov2 releaseService --- .vscode/settings.json | 2 +- .../org/myrobotlab/service/InMoov2Arm.java | 53 ++------------- .../org/myrobotlab/service/InMoov2Hand.java | 58 ---------------- .../org/myrobotlab/service/InMoov2Head.java | 66 ------------------- .../org/myrobotlab/service/InMoov2Torso.java | 55 +--------------- .../service/interfaces/ServoControl.java | 1 - 6 files changed, 8 insertions(+), 227 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c0d2e88e12..651c18245e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,7 +17,7 @@ "jest.coverageFormatter": "GutterFormatter", "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.organizeImports": true + "source.organizeImports": "explicit" }, "typescript.tsdk": "./app/node_modules/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true, diff --git a/src/main/java/org/myrobotlab/service/InMoov2Arm.java b/src/main/java/org/myrobotlab/service/InMoov2Arm.java index 1b4b45e6b5..e676c52878 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Arm.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Arm.java @@ -90,7 +90,7 @@ public static DHRobotArm getDHRobotArm(String name, String side) { return arm; } - + @Deprecated /* use onMove(map) */ public void onMoveArm(HashMap<String, Double> map) { onMove(map); @@ -100,7 +100,6 @@ public void onMove(Map<String, Double> map) { moveTo(map.get("bicep"), map.get("rotate"), map.get("shoulder"), map.get("omoplate")); } - /** * peer services FIXME - framework should always - startPeers() unless * configured not to @@ -125,23 +124,6 @@ public void startService() { shoulder = (ServoControl) startPeer("shoulder"); omoplate = (ServoControl) startPeer("omoplate"); } - - @Override - public void stopService() { - super.stopService(); - if (bicep != null) { - ((Service)bicep).stopService(); - } - if (rotate != null) { - ((Service)rotate).stopService(); - } - if (shoulder != null) { - ((Service)shoulder).stopService(); - } - if (omoplate != null) { - ((Service)omoplate).stopService(); - } - } @Override public void broadcastState() { @@ -210,8 +192,8 @@ public ServoControl getRotate() { public String getScript(String service) { String side = getName().contains("left") ? "left" : "right"; - return String.format("%s.moveArm(\"%s\",%.0f,%.0f,%.0f,%.0f)\n", service, side, bicep.getCurrentInputPos(), rotate.getCurrentInputPos(), - shoulder.getCurrentInputPos(), omoplate.getCurrentInputPos()); + return String.format("%s.moveArm(\"%s\",%.0f,%.0f,%.0f,%.0f)\n", service, side, bicep.getCurrentInputPos(), rotate.getCurrentInputPos(), shoulder.getCurrentInputPos(), + omoplate.getCurrentInputPos()); } public ServoControl getShoulder() { @@ -301,31 +283,6 @@ public void onJointAngles(Map<String, Double> angleMap) { } } - // FIXME - framework should auto-release - unless configured not to - @Override - public void releaseService() { - try { - disable(); - - if (bicep != null) { - ((Service)bicep).releaseService(); - } - if (rotate != null) { - ((Service)rotate).releaseService(); - } - if (shoulder != null) { - ((Service)shoulder).releaseService(); - } - if (omoplate != null) { - ((Service)omoplate).releaseService(); - } - - super.releaseService(); - } catch (Exception e) { - error(e); - } - } - public void rest() { if (bicep != null) bicep.rest(); @@ -492,7 +449,7 @@ public void waitTargetPos() { if (omoplate != null) omoplate.waitTargetPos(); } - + public static void main(String[] args) { LoggingFactory.init(Level.INFO); @@ -501,7 +458,7 @@ public static void main(String[] args) { Runtime.main(new String[] { "--log-level", "info", "-s", "inmoov2arm", "InMoov2Arm" }); // Runtime.main(new String[] {}); // Runtime.main(new String[] { "--install" }); - InMoov2Arm arm = (InMoov2Arm)Runtime.start("inmoov2arm", "InMoov2Arm"); + InMoov2Arm arm = (InMoov2Arm) Runtime.start("inmoov2arm", "InMoov2Arm"); arm.releaseService(); boolean done = true; diff --git a/src/main/java/org/myrobotlab/service/InMoov2Hand.java b/src/main/java/org/myrobotlab/service/InMoov2Hand.java index 06b6d3b459..f7fdadebbc 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Hand.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Hand.java @@ -567,64 +567,6 @@ public List<String> refreshControllers() { return controllers; } - public void release() { - disable(); - } - - @Override - public void stopService() { - disable(); - if (thumb != null) { - ((Service)thumb).stopService(); - } - if (index != null) { - ((Service)index).stopService(); - } - if (majeure != null) { - ((Service)majeure).stopService(); - } - if (ringFinger != null) { - ((Service)ringFinger).stopService(); - } - if (pinky != null) { - ((Service)pinky).stopService(); - } - if (wrist != null) { - ((Service)wrist).stopService(); - } - super.stopService(); - } - - @Override - public void releaseService() { - try { - disable(); - - if (thumb != null) { - ((Service)thumb).releaseService(); - } - if (index != null) { - ((Service)index).releaseService(); - } - if (majeure != null) { - ((Service)majeure).releaseService(); - } - if (ringFinger != null) { - ((Service)ringFinger).releaseService(); - } - if (pinky != null) { - ((Service)pinky).releaseService(); - } - if (wrist != null) { - ((Service)wrist).releaseService(); - } - - super.releaseService(); - } catch (Exception e) { - error(e); - } - } - public void rest() { if (thumb != null) thumb.rest(); diff --git a/src/main/java/org/myrobotlab/service/InMoov2Head.java b/src/main/java/org/myrobotlab/service/InMoov2Head.java index da9052ca65..1e98970f78 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Head.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Head.java @@ -359,72 +359,6 @@ public void waitTargetPos() { public void release() { disable(); } - - @Override - public void stopService() { - - if (jaw != null) { - ((Service)jaw).stopService(); - } - if (eyeX != null) { - ((Service)eyeX).stopService(); - } - if (eyeY != null) { - ((Service)eyeY).stopService(); - } - if (neck != null) { - ((Service)neck).stopService(); - } - if (rothead != null) { - ((Service)rothead).stopService(); - } - if (rollNeck != null) { - ((Service)rollNeck).stopService(); - } - if (eyelidLeft != null) { - ((Service)eyelidLeft).stopService(); - } - if (eyelidRight != null) { - ((Service)eyelidRight).stopService(); - } - - super.stopService(); - } - - - - - @Override - public void releaseService() { - disable(); - - if (jaw != null) { - ((Service)jaw).releaseService(); - } - if (eyeX != null) { - ((Service)eyeX).releaseService(); - } - if (eyeY != null) { - ((Service)eyeY).releaseService(); - } - if (neck != null) { - ((Service)neck).releaseService(); - } - if (rothead != null) { - ((Service)rothead).releaseService(); - } - if (rollNeck != null) { - ((Service)rollNeck).releaseService(); - } - if (eyelidLeft != null) { - ((Service)eyelidLeft).releaseService(); - } - if (eyelidRight != null) { - ((Service)eyelidRight).releaseService(); - } - - super.releaseService(); - } public void rest() { // initial positions diff --git a/src/main/java/org/myrobotlab/service/InMoov2Torso.java b/src/main/java/org/myrobotlab/service/InMoov2Torso.java index 3754e43a3c..efdb127957 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Torso.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Torso.java @@ -36,59 +36,10 @@ public InMoov2Torso(String n, String id) { @Override public void startService() { super.startService(); - topStom = (ServoControl) getPeer("topStom"); midStom = (ServoControl) getPeer("midStom"); lowStom = (ServoControl) getPeer("lowStom"); } - - @Override - public void stopService() { - disable(); - - if (topStom != null) { - ((Service)topStom).stopService(); - } - - if (midStom != null) { - ((Service)midStom).stopService(); - } - - if (lowStom != null) { - ((Service)lowStom).stopService(); - } - - super.stopService(); - } - - @Override - public void releaseService() { - try { - disable(); - - - if (topStom != null) { - ((Service)topStom).releaseService(); - } - - if (midStom != null) { - ((Service)midStom).releaseService(); - } - - if (lowStom != null) { - ((Service)lowStom).releaseService(); - } - - - topStom = null; - midStom = null; - lowStom = null; - - super.releaseService(); - } catch (Exception e) { - error(e); - } - } public void enable() { if (topStom != null) @@ -126,7 +77,7 @@ public void disable() { if (lowStom != null) lowStom.disable(); } - + @Deprecated /* use onMove(map) */ public void onMoveTorso(HashMap<String, Double> map) { onMove(map); @@ -136,7 +87,6 @@ public void onMove(Map<String, Double> map) { moveTo(map.get("topStom"), map.get("midStom"), map.get("lowStom")); } - public long getLastActivityTime() { long minLastActivity = Math.max(topStom.getLastActivityTime(), midStom.getLastActivityTime()); minLastActivity = Math.max(minLastActivity, lowStom.getLastActivityTime()); @@ -144,8 +94,7 @@ public long getLastActivityTime() { } public String getScript(String inMoovServiceName) { - return String.format("%s.moveTorso(%.0f,%.0f,%.0f)\n", inMoovServiceName, topStom.getCurrentInputPos(), midStom.getCurrentInputPos(), - lowStom.getCurrentInputPos()); + return String.format("%s.moveTorso(%.0f,%.0f,%.0f)\n", inMoovServiceName, topStom.getCurrentInputPos(), midStom.getCurrentInputPos(), lowStom.getCurrentInputPos()); } public void moveTo(Double topStomPos, Double midStomPos, Double lowStomPos) { diff --git a/src/main/java/org/myrobotlab/service/interfaces/ServoControl.java b/src/main/java/org/myrobotlab/service/interfaces/ServoControl.java index 1e140ce167..23a9ee9629 100644 --- a/src/main/java/org/myrobotlab/service/interfaces/ServoControl.java +++ b/src/main/java/org/myrobotlab/service/interfaces/ServoControl.java @@ -404,7 +404,6 @@ public interface ServoControl extends AbsolutePositionControl, EncoderListener, /** * disable speed control and move the servos at full speed. */ - @Deprecated /* implement setSpeed(null) */ void fullSpeed(); } From 237a5152da9bc2d9b86ed454f799c442a1a6b5ad Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 13 Feb 2024 06:57:54 -0800 Subject: [PATCH 049/131] unit test updates --- .../org/myrobotlab/framework/repo/Repo.java | 6 +- .../myrobotlab/framework/BlockingTest.java | 4 +- .../myrobotlab/framework/repo/RepoTest.java | 4 + .../org/myrobotlab/service/ArduinoTest.java | 1 - .../service/RuntimeProcessTest.java | 5 +- .../service/VirtualArduinoTest.java | 4 + .../org/myrobotlab/test/AbstractTest.java | 130 ++++++------------ 7 files changed, 60 insertions(+), 94 deletions(-) diff --git a/src/main/java/org/myrobotlab/framework/repo/Repo.java b/src/main/java/org/myrobotlab/framework/repo/Repo.java index 9262834da1..d8b6863c16 100644 --- a/src/main/java/org/myrobotlab/framework/repo/Repo.java +++ b/src/main/java/org/myrobotlab/framework/repo/Repo.java @@ -324,10 +324,10 @@ public Set<ServiceDependency> getUnfulfilledDependencies(String[] types) { } } } - + // Plan plan = ServiceConfig.getDefault(type.toLowerCase(), type); ServiceConfig sc = ServiceConfig.getDefaultServiceConfig(type); - + Map<String, Peer> peers = sc.getPeers(); if (peers != null) { for (String key : peers.keySet()) { @@ -496,7 +496,7 @@ public void load() { } } else { - log.info("{} not found", getRepoPath()); + log.info("{} not found", f.getAbsolutePath()); } } catch (Exception e) { diff --git a/src/test/java/org/myrobotlab/framework/BlockingTest.java b/src/test/java/org/myrobotlab/framework/BlockingTest.java index b4ce03e8da..3b9788645b 100644 --- a/src/test/java/org/myrobotlab/framework/BlockingTest.java +++ b/src/test/java/org/myrobotlab/framework/BlockingTest.java @@ -28,13 +28,13 @@ public void blockingTest() throws Exception { Message msg = Message.createMessage("thower07", "catcher07", "onInt", 3); Integer ret = (Integer)thower07.sendBlocking(msg, null); - assertEquals(simpleName, 3, (int)ret); + assertEquals(3, (int)ret); long startTime = System.currentTimeMillis(); msg = Message.createMessage("thower07", "catcher07", "waitForThis", new Object[] {7, 1000}); ret = (Integer)thower07.sendBlocking(msg, null); assertTrue("1s process", System.currentTimeMillis() - startTime > 500); - assertEquals(simpleName, 7, (int)ret); + assertEquals(7, (int)ret); Runtime.release("catcher07"); Runtime.release("thower07"); diff --git a/src/test/java/org/myrobotlab/framework/repo/RepoTest.java b/src/test/java/org/myrobotlab/framework/repo/RepoTest.java index f2715dc90d..d0e2c57735 100644 --- a/src/test/java/org/myrobotlab/framework/repo/RepoTest.java +++ b/src/test/java/org/myrobotlab/framework/repo/RepoTest.java @@ -28,6 +28,10 @@ public static void lastCleanup() { repo.clear(); installed = false; } + + public String getName() { + return "RepoTest"; + } @Override public void broadcastStatus(Status status) { diff --git a/src/test/java/org/myrobotlab/service/ArduinoTest.java b/src/test/java/org/myrobotlab/service/ArduinoTest.java index 7a6422d29d..00cee5e0a7 100644 --- a/src/test/java/org/myrobotlab/service/ArduinoTest.java +++ b/src/test/java/org/myrobotlab/service/ArduinoTest.java @@ -62,7 +62,6 @@ private void assertVirtualPinValue(VirtualArduino virtual, int address, int valu } } - @Override public String getName() { return "arduinoTest"; } diff --git a/src/test/java/org/myrobotlab/service/RuntimeProcessTest.java b/src/test/java/org/myrobotlab/service/RuntimeProcessTest.java index 07e1775110..4bdb93fe2c 100644 --- a/src/test/java/org/myrobotlab/service/RuntimeProcessTest.java +++ b/src/test/java/org/myrobotlab/service/RuntimeProcessTest.java @@ -21,12 +21,15 @@ public class RuntimeProcessTest extends AbstractTest { @Before public void setUp() { - // LoggingFactory.init("WARN"); } public boolean contains(ByteArrayOutputStream out, String str) { return new String(out.toByteArray()).contains(str); } + + public String getName() { + return "RuntimeProcessTest"; + } @Test public void cliTest() throws Exception { diff --git a/src/test/java/org/myrobotlab/service/VirtualArduinoTest.java b/src/test/java/org/myrobotlab/service/VirtualArduinoTest.java index ba1d028096..110ff1ac4a 100755 --- a/src/test/java/org/myrobotlab/service/VirtualArduinoTest.java +++ b/src/test/java/org/myrobotlab/service/VirtualArduinoTest.java @@ -30,6 +30,10 @@ public Service createService() { VirtualArduino service = (VirtualArduino) Runtime.start("virtualArduino", "VirtualArduino"); return service; } + + public String getName() { + return "VirtualArduinoTest"; + } @Override public void testService() throws Exception { diff --git a/src/test/java/org/myrobotlab/test/AbstractTest.java b/src/test/java/org/myrobotlab/test/AbstractTest.java index d782797c9b..4fd42b3bb9 100644 --- a/src/test/java/org/myrobotlab/test/AbstractTest.java +++ b/src/test/java/org/myrobotlab/test/AbstractTest.java @@ -14,82 +14,56 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Rule; -import org.junit.rules.TestName; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; import org.myrobotlab.codec.CodecUtils; -import org.myrobotlab.framework.Platform; import org.myrobotlab.framework.interfaces.Attachable; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.Runtime; import org.myrobotlab.service.config.RuntimeConfig; import org.slf4j.Logger; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestWatcher; -import org.junit.runner.Description; - public class AbstractTest { /** cached network test value for tests */ - static Boolean hasInternet = null; + protected static Boolean hasInternet = null; protected static boolean installed = false; - public final static Logger log = LoggerFactory.getLogger(AbstractTest.class); - - static private boolean logWarnTestHeader = false; - - private static boolean releaseRemainingThreads = false; - - protected transient Queue<Object> queue = new LinkedBlockingQueue<>(); - - static transient Set<Thread> threadSetStart = null; + protected final static Logger log = LoggerFactory.getLogger(AbstractTest.class); - protected Set<Attachable> attached = new HashSet<>(); + protected static boolean releaseRemainingThreads = false; - @Rule - public final TestName testName = new TestName(); - - static public String simpleName; + protected static transient Set<Thread> threadSetStart = null; - private static boolean lineFeedFooter = true; - @Rule public TestWatcher watchman = new TestWatcher() { - @Override - protected void starting(Description description) { - System.out.println("Starting: " + description.getClassName() + "." + description.getMethodName()); - } + @Override + protected void starting(Description description) { + System.out.println("Starting: " + description.getClassName() + "." + description.getMethodName()); + } - @Override - protected void succeeded(Description description) { - // System.out.println("Succeeded: " + description.getMethodName()); - } + @Override + protected void succeeded(Description description) { + // System.out.println("Succeeded: " + description.getMethodName()); + } - @Override - protected void failed(Throwable e, Description description) { - System.out.println("Failed: " + description.getMethodName()); - } + @Override + protected void failed(Throwable e, Description description) { + System.out.println("Failed: " + description.getMethodName()); + } - @Override - protected void skipped(org.junit.AssumptionViolatedException e, Description description) { - System.out.println("Skipped: " + description.getMethodName()); - } + @Override + protected void skipped(org.junit.AssumptionViolatedException e, Description description) { + System.out.println("Skipped: " + description.getMethodName()); + } - @Override - protected void finished(Description description) { - System.out.println("Finished: " + description.getMethodName()); - } + @Override + protected void finished(Description description) { + System.out.println("Finished: " + description.getMethodName()); + } }; - public String getSimpleName() { - return simpleName; - } - - public String getName() { - return testName.getMethodName(); - } - static public boolean hasInternet() { if (hasInternet == null) { hasInternet = Runtime.hasInternet(); @@ -120,23 +94,23 @@ public static void main(String[] args) { @BeforeClass public static void setUpAbstractTest() throws Exception { - + // setup runtime resource = src/main/resources/resource File runtimeYml = new File("data/config/default/runtime.yml"); -// if (!runtimeYml.exists()) { - runtimeYml.getParentFile().mkdirs(); - RuntimeConfig rc = new RuntimeConfig(); - rc.resource = "src/main/resources/resource"; - String yml = CodecUtils.toYaml(rc); - - FileOutputStream fos = null; - fos = new FileOutputStream(runtimeYml); - fos.write(yml.getBytes()); - fos.close(); - -// } - - Runtime.getInstance().setVirtual(true); + // if (!runtimeYml.exists()) { + runtimeYml.getParentFile().mkdirs(); + RuntimeConfig rc = new RuntimeConfig(); + rc.resource = "src/main/resources/resource"; + String yml = CodecUtils.toYaml(rc); + + FileOutputStream fos = null; + fos = new FileOutputStream(runtimeYml); + fos.write(yml.getBytes()); + fos.close(); + + // } + + Runtime.getInstance().setVirtual(true); String junitLogLevel = System.getProperty("junit.logLevel"); if (junitLogLevel != null) { @@ -171,16 +145,7 @@ public static void sleep(long sleepTimeMs) { @AfterClass public static void tearDownAbstractTest() throws Exception { log.info("tearDownAbstractTest"); - releaseServices(); - - if (logWarnTestHeader) { - log.warn("=========== finished test {} ===========", simpleName); - } - - if (lineFeedFooter) { - System.out.println(); - } } static protected void installAll() { @@ -197,8 +162,7 @@ static protected void installAll() { */ public static void releaseServices() { - log.info("end of test - id {} remaining services {}", Runtime.getInstance().getId(), - Arrays.toString(Runtime.getServiceNames())); + log.info("end of test - id {} remaining services {}", Runtime.getInstance().getId(), Arrays.toString(Runtime.getServiceNames())); // release all including runtime - be careful of default runtime.yml Runtime.releaseAll(true, true); @@ -212,8 +176,7 @@ public static void releaseServices() { Set<Thread> threadSetEnd = Thread.getAllStackTraces().keySet(); Set<String> threadsRemaining = new TreeSet<>(); for (Thread thread : threadSetEnd) { - if (!threadSetStart.contains(thread) && !"runtime_outbox_0".equals(thread.getName()) - && !"runtime".equals(thread.getName())) { + if (!threadSetStart.contains(thread) && !"runtime_outbox_0".equals(thread.getName()) && !"runtime".equals(thread.getName())) { if (releaseRemainingThreads) { log.warn("interrupting thread {}", thread.getName()); thread.interrupt(); @@ -236,13 +199,6 @@ public static void releaseServices() { // Arrays.toString(Runtime.getServiceNames())); } - public AbstractTest() { - simpleName = this.getClass().getSimpleName(); - if (logWarnTestHeader) { - log.info("=========== starting test {} ===========", this.getClass().getSimpleName()); - } - } - public void setVirtual() { Runtime.getInstance().setVirtual(true); } From 95b3ddb31d6e860b036793db2b6992ec13d2bba6 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 13 Feb 2024 07:24:48 -0800 Subject: [PATCH 050/131] stop cli when runtime is released --- .../java/org/myrobotlab/service/Runtime.java | 3 +++ .../org/myrobotlab/test/AbstractTest.java | 21 +++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index e838cf6c52..49c27cff75 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -3446,6 +3446,9 @@ public void releaseService() { runtime.stopService(); runtime.stopInteractiveMode(); runtime.getRepo().removeStatusPublishers(); + if (cli != null) { + cli.stop(); + } registry = new TreeMap<>(); } synchronized (INSTANCE_LOCK) { diff --git a/src/test/java/org/myrobotlab/test/AbstractTest.java b/src/test/java/org/myrobotlab/test/AbstractTest.java index 4fd42b3bb9..7279a8fe98 100644 --- a/src/test/java/org/myrobotlab/test/AbstractTest.java +++ b/src/test/java/org/myrobotlab/test/AbstractTest.java @@ -25,15 +25,20 @@ public class AbstractTest { - /** cached network test value for tests */ + /** + * cached network test value for tests + */ protected static Boolean hasInternet = null; + /** + * Install dependencies once per process, same process + * will not check. A new process will use the libraries/serviceData.json + * to determine if deps are satisfied + */ protected static boolean installed = false; protected final static Logger log = LoggerFactory.getLogger(AbstractTest.class); - protected static boolean releaseRemainingThreads = false; - protected static transient Set<Thread> threadSetStart = null; @Rule @@ -177,17 +182,7 @@ public static void releaseServices() { Set<String> threadsRemaining = new TreeSet<>(); for (Thread thread : threadSetEnd) { if (!threadSetStart.contains(thread) && !"runtime_outbox_0".equals(thread.getName()) && !"runtime".equals(thread.getName())) { - if (releaseRemainingThreads) { - log.warn("interrupting thread {}", thread.getName()); - thread.interrupt(); - /* - * if (useDeprecatedThreadStop) { thread.stop(); } - */ - } else { - // log.warn("thread {} marked as straggler - should be killed", - // thread.getName()); threadsRemaining.add(thread.getName()); - } } } if (threadsRemaining.size() > 0) { From 43fff16c0eee391c33334ad16a880c5ee85ca538 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 13 Feb 2024 08:16:41 -0800 Subject: [PATCH 051/131] corrected synchronization --- .../java/org/myrobotlab/service/Runtime.java | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 49c27cff75..675123e962 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -1204,12 +1204,12 @@ public static <S extends ServiceInterface> S getService(String inName, StaticTyp * */ static public String[] getServiceNames() { - Set<String> ret = registry.keySet(); + Set<String> ret = registry.keySet(); String[] services = new String[ret.size()]; if (ret.size() == 0) { return services; } - + // if there are more than 0 services we need runtime // to filter to make sure they are "local" // and this requires a runtime service @@ -1940,11 +1940,11 @@ public static boolean releaseService(String inName) { } return false; } - - + /** - * Called after any subclassed releaseService has been called, this cleans - * up the registry and removes peers + * Called after any subclassed releaseService has been called, this cleans up + * the registry and removes peers + * * @param inName * @return */ @@ -2004,7 +2004,6 @@ public static boolean releaseServiceInternal(String inName) { } } - /** * Removes registration for a service. Removes the service from * {@link #typeToInterface} and {@link #interfaceToNames}. @@ -2199,7 +2198,9 @@ static private void processRelease(boolean releaseRuntime) { if (runtime != null) { runtime.releaseService(); } - runtime = null; + synchronized (INSTANCE_LOCK) { + runtime = null; + } } else { // put runtime in new registry Runtime.getInstance(); @@ -2830,26 +2831,20 @@ public Runtime(String n, String id) { // because you need to start with something ... config = new RuntimeConfig(); - synchronized (INSTANCE_LOCK) { - if (runtime == null) { - // fist and only time.... - runtime = this; - repo = (IvyWrapper) Repo.getInstance(LIBRARIES, "IvyWrapper"); - - /** - * This is used to run through all the possible services and determine - * if they have any missing dependencies. If they do not they become - * "installed". The installed flag makes the gui do a crossout when a - * service type is selected. - */ - for (MetaData metaData : serviceData.getServiceTypes()) { - Set<ServiceDependency> deps = repo.getUnfulfilledDependencies(metaData.getType()); - if (deps.size() == 0) { - metaData.installed = true; - } else { - log.info("{} not installed", metaData.getSimpleName()); - } - } + repo = (IvyWrapper) Repo.getInstance(LIBRARIES, "IvyWrapper"); + + /** + * This is used to run through all the possible services and determine if + * they have any missing dependencies. If they do not they become + * "installed". The installed flag makes the gui do a crossout when a + * service type is selected. + */ + for (MetaData metaData : serviceData.getServiceTypes()) { + Set<ServiceDependency> deps = repo.getUnfulfilledDependencies(metaData.getType()); + if (deps.size() == 0) { + metaData.installed = true; + } else { + log.info("{} not installed", metaData.getSimpleName()); } } From 65542d07ac0532e27cca606a5422f68fd03c5bb1 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 13 Feb 2024 08:40:00 -0800 Subject: [PATCH 052/131] corrected instance lock --- .../java/org/myrobotlab/service/Runtime.java | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index e838cf6c52..675123e962 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -1204,12 +1204,12 @@ public static <S extends ServiceInterface> S getService(String inName, StaticTyp * */ static public String[] getServiceNames() { - Set<String> ret = registry.keySet(); + Set<String> ret = registry.keySet(); String[] services = new String[ret.size()]; if (ret.size() == 0) { return services; } - + // if there are more than 0 services we need runtime // to filter to make sure they are "local" // and this requires a runtime service @@ -1940,11 +1940,11 @@ public static boolean releaseService(String inName) { } return false; } - - + /** - * Called after any subclassed releaseService has been called, this cleans - * up the registry and removes peers + * Called after any subclassed releaseService has been called, this cleans up + * the registry and removes peers + * * @param inName * @return */ @@ -2004,7 +2004,6 @@ public static boolean releaseServiceInternal(String inName) { } } - /** * Removes registration for a service. Removes the service from * {@link #typeToInterface} and {@link #interfaceToNames}. @@ -2199,7 +2198,9 @@ static private void processRelease(boolean releaseRuntime) { if (runtime != null) { runtime.releaseService(); } - runtime = null; + synchronized (INSTANCE_LOCK) { + runtime = null; + } } else { // put runtime in new registry Runtime.getInstance(); @@ -2830,26 +2831,20 @@ public Runtime(String n, String id) { // because you need to start with something ... config = new RuntimeConfig(); - synchronized (INSTANCE_LOCK) { - if (runtime == null) { - // fist and only time.... - runtime = this; - repo = (IvyWrapper) Repo.getInstance(LIBRARIES, "IvyWrapper"); - - /** - * This is used to run through all the possible services and determine - * if they have any missing dependencies. If they do not they become - * "installed". The installed flag makes the gui do a crossout when a - * service type is selected. - */ - for (MetaData metaData : serviceData.getServiceTypes()) { - Set<ServiceDependency> deps = repo.getUnfulfilledDependencies(metaData.getType()); - if (deps.size() == 0) { - metaData.installed = true; - } else { - log.info("{} not installed", metaData.getSimpleName()); - } - } + repo = (IvyWrapper) Repo.getInstance(LIBRARIES, "IvyWrapper"); + + /** + * This is used to run through all the possible services and determine if + * they have any missing dependencies. If they do not they become + * "installed". The installed flag makes the gui do a crossout when a + * service type is selected. + */ + for (MetaData metaData : serviceData.getServiceTypes()) { + Set<ServiceDependency> deps = repo.getUnfulfilledDependencies(metaData.getType()); + if (deps.size() == 0) { + metaData.installed = true; + } else { + log.info("{} not installed", metaData.getSimpleName()); } } @@ -3446,6 +3441,9 @@ public void releaseService() { runtime.stopService(); runtime.stopInteractiveMode(); runtime.getRepo().removeStatusPublishers(); + if (cli != null) { + cli.stop(); + } registry = new TreeMap<>(); } synchronized (INSTANCE_LOCK) { From 5182af01ea0fe9729c74cbc3634429e1608b2988 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 13 Feb 2024 10:20:17 -0800 Subject: [PATCH 053/131] adjusted location of sleep wait --- src/test/java/org/myrobotlab/service/RandomTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/myrobotlab/service/RandomTest.java b/src/test/java/org/myrobotlab/service/RandomTest.java index 7c8add5923..dfde5042ee 100644 --- a/src/test/java/org/myrobotlab/service/RandomTest.java +++ b/src/test/java/org/myrobotlab/service/RandomTest.java @@ -62,8 +62,8 @@ public void testService() throws Exception { // disable all of a services random events random.disable("clock.startClock"); - clock.stopClock(); sleep(250); + clock.stopClock(); assertTrue("clock should not be started 1", !clock.isClockRunning()); // enable all of a service's random events From 54f874bde713d48deddab53a00c779e13e6ff618 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 13 Feb 2024 10:38:11 -0800 Subject: [PATCH 054/131] shifted sleep position in randomtest --- src/test/java/org/myrobotlab/service/RandomTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/myrobotlab/service/RandomTest.java b/src/test/java/org/myrobotlab/service/RandomTest.java index dfde5042ee..18f4e1c789 100644 --- a/src/test/java/org/myrobotlab/service/RandomTest.java +++ b/src/test/java/org/myrobotlab/service/RandomTest.java @@ -74,8 +74,8 @@ public void testService() throws Exception { // disable one method - leave other enabled random.disable("clock.startClock"); clock.stopClock(); - clock.setInterval(9999); 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); From 8065ad4f3d8e6519aade45a52c1a8b14f5e49c13 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 13 Feb 2024 15:52:53 -0800 Subject: [PATCH 055/131] config utils fix --- src/main/java/org/myrobotlab/config/ConfigUtils.java | 2 ++ src/main/java/org/myrobotlab/service/Runtime.java | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/myrobotlab/config/ConfigUtils.java b/src/main/java/org/myrobotlab/config/ConfigUtils.java index 19c256a8cf..69f850e602 100644 --- a/src/main/java/org/myrobotlab/config/ConfigUtils.java +++ b/src/main/java/org/myrobotlab/config/ConfigUtils.java @@ -60,6 +60,8 @@ static public RuntimeConfig loadRuntimeConfig(CmdOptions options) { if (startYml.enable) { configName = startYml.config; + } else { + configName = "default"; } // start with default diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 675123e962..a768da57d1 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -910,9 +910,6 @@ public static Runtime getInstance() { // klunky Runtime.register(new Registration(runtime)); - // assign, do not apply otherwise there will be - // a chicken-egg problem - runtime.config = c; } runtime.getRepo().addStatusPublisher(runtime); From 942815729842add1ee160bb5a8b342cbd459ecfa Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 13 Feb 2024 18:24:37 -0800 Subject: [PATCH 056/131] clean default config --- src/test/java/org/myrobotlab/config/ConfigUtilsTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java b/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java index 5d15601b58..59caf6b201 100644 --- a/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java +++ b/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java @@ -3,9 +3,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.io.File; + import org.junit.Before; import org.junit.Test; import org.myrobotlab.framework.StartYml; +import org.myrobotlab.io.FileIO; import org.myrobotlab.service.Runtime; public class ConfigUtilsTest { @@ -13,6 +16,8 @@ public class ConfigUtilsTest { @Before public void beforeTest() { Runtime.releaseAll(true, true); + // remove config + FileIO.rm("data/config/default"); } @Test From 63c00ecb2c817bce1901f561b2d6fdd4c9091ff8 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Wed, 14 Feb 2024 12:13:16 -0800 Subject: [PATCH 057/131] removed registering for new services from servo --- .../java/org/myrobotlab/service/DiyServo.java | 43 +++---------------- .../java/org/myrobotlab/service/Servo.java | 15 +------ .../service/abstracts/AbstractServo.java | 27 ------------ 3 files changed, 7 insertions(+), 78 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/DiyServo.java b/src/main/java/org/myrobotlab/service/DiyServo.java index 2e125d8509..ae83c09118 100644 --- a/src/main/java/org/myrobotlab/service/DiyServo.java +++ b/src/main/java/org/myrobotlab/service/DiyServo.java @@ -28,7 +28,6 @@ import java.util.ArrayList; import java.util.List; -import org.myrobotlab.framework.Registration; import org.myrobotlab.framework.interfaces.ServiceInterface; import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; @@ -44,8 +43,6 @@ import org.myrobotlab.service.interfaces.MotorControl; import org.myrobotlab.service.interfaces.PinArrayControl; import org.myrobotlab.service.interfaces.PinListener; -import org.myrobotlab.service.interfaces.ServiceLifeCycleListener; -import org.myrobotlab.service.interfaces.ServoControl; import org.myrobotlab.service.interfaces.ServoEvent; import org.slf4j.Logger; @@ -76,7 +73,7 @@ * TODO : move is not accurate ( 1° step seem not possible ) */ -public class DiyServo extends AbstractServo<ServoConfig> implements PinListener, ServiceLifeCycleListener { +public class DiyServo extends AbstractServo<ServoConfig> implements PinListener { double lastOutput = 0.0; /** @@ -198,16 +195,6 @@ public DiyServo(String n, String id) { lastActivityTimeTs = System.currentTimeMillis(); } - /* - * Update the list of PinArrayControls - */ - @Override - public void onRegistered(Registration s) { - refreshPinArrayControls(); - broadcastState(); - - } - /** * Initiate the PID controller */ @@ -224,7 +211,7 @@ void initPid() { pid.setSetpoint(pidKey, setPoint); pid.startService(); } - + @Override public void startService() { super.startService(); @@ -232,7 +219,6 @@ public void startService() { motorControl = (MotorControl) startPeer("motor"); initPid(); } - /** * Equivalent to Arduino's Servo.detach() it de-energizes the servo @@ -694,19 +680,17 @@ public static void main(String[] args) throws InterruptedException { // if (done) { // return; // } - - WebGui webgui = (WebGui)Runtime.create("webgui", "WebGui"); + + WebGui webgui = (WebGui) Runtime.create("webgui", "WebGui"); webgui.autoStartBrowser(false); webgui.startService(); - + Runtime.start("diy", "DiyServo"); - - + boolean done = true; if (done) { return; } - String port = "COM4"; Arduino arduino = (Arduino) Runtime.start("arduino", "Arduino"); @@ -788,19 +772,4 @@ protected boolean processMove(Double newPos, boolean blocking, Long timeoutMs) { return false; } - @Override - public void onCreated(String name) { - log.info("created {}", name); - } - - @Override - public void onStopped(String name) { - log.info("stopped {}", name); - } - - @Override - public void onReleased(String name) { - log.info("released {}", name); - } - } diff --git a/src/main/java/org/myrobotlab/service/Servo.java b/src/main/java/org/myrobotlab/service/Servo.java index 3e0b46ce95..07e251dec5 100644 --- a/src/main/java/org/myrobotlab/service/Servo.java +++ b/src/main/java/org/myrobotlab/service/Servo.java @@ -61,7 +61,7 @@ * */ -public class Servo extends AbstractServo<ServoConfig> implements ServiceLifeCycleListener { +public class Servo extends AbstractServo<ServoConfig> { private static final long serialVersionUID = 1L; @@ -337,17 +337,4 @@ public static void main(String[] args) throws InterruptedException { } } - @Override - public void onCreated(String name) { - } - - @Override - public void onStopped(String name) { - } - - @Override - public void onReleased(String name) { - } - - } diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java index 3378d5e56d..1376e78778 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java @@ -6,7 +6,6 @@ import java.util.Set; import org.myrobotlab.codec.CodecUtils; -import org.myrobotlab.framework.Registration; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.interfaces.Attachable; import org.myrobotlab.logging.LoggerFactory; @@ -217,16 +216,6 @@ public abstract class AbstractServo<C extends ServoConfig> extends Service<C> im public AbstractServo(String n, String id) { super(n, id); - // this servo is interested in new services which support either - // ServoControllers or EncoderControl interfaces - // we subscribe to runtime here for new services - subscribeToRuntime("registered"); - /* - * // new feature - // extracting the currentPos from serialized servo - * Double lastCurrentPos = null; try { lastCurrentPos = (Double) - * loadField("currentPos"); } catch (IOException e) { - * log.info("current pos cannot be found in saved file"); } - */ // if no position could be loaded - set to rest // we have no "historical" info - assume we are @ rest targetPos = rest; @@ -243,17 +232,6 @@ public AbstractServo(String n, String id) { } } - /** - * if a new service is added to the system refresh the controllers - */ - @Deprecated /* - * lifecycle events not necessary for ui, probably should be - * pulled out - */ - public void onStarted(String name) { - invoke("refreshControllers"); - } - /** * overloaded routing attach */ @@ -697,10 +675,6 @@ public void onEncoderData(EncoderData data) { } } - public void onRegistered(Registration s) { - refreshControllers(); - } - /** * Servo has the ability to act as an encoder if it is using TimeEncoder. * TimeEncoder will use Servo to publish a series of encoder events with @@ -1096,7 +1070,6 @@ public ServoEvent publishServoStopped(String name, Double position) { @Override public void startService() { super.startService(); - Runtime.getInstance().attachServiceLifeCycleListener(getName()); } @Override From aa65ad3bf4c6b46c3039318cf2e183554165c8e2 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Wed, 14 Feb 2024 12:57:59 -0800 Subject: [PATCH 058/131] webgui --- .../java/org/myrobotlab/service/WebGui.java | 37 ++++----- .../org/myrobotlab/service/WebGuiTest.java | 76 ++++++++++--------- 2 files changed, 55 insertions(+), 58 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/WebGui.java b/src/main/java/org/myrobotlab/service/WebGui.java index 5e87cddb05..3e816726f1 100644 --- a/src/main/java/org/myrobotlab/service/WebGui.java +++ b/src/main/java/org/myrobotlab/service/WebGui.java @@ -34,7 +34,6 @@ import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.MRLListener; import org.myrobotlab.framework.Message; -import org.myrobotlab.framework.Platform; import org.myrobotlab.framework.Registration; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.interfaces.ServiceInterface; @@ -62,8 +61,7 @@ * services are already APIs - perhaps a data API - same as service without the * message wrapper */ -public class WebGui extends Service<WebGuiConfig> - implements AuthorizationProvider, Gateway, Handler, ServiceLifeCycleListener { +public class WebGui extends Service<WebGuiConfig> implements AuthorizationProvider, Gateway, Handler, ServiceLifeCycleListener { public static class LiveVideoStreamHandler implements Handler { @@ -128,7 +126,7 @@ public Panel(String name, int x, int y, int z) { * needed to get the api key to select the appropriate api processor * * @param uri - * u + * u * @return api key * */ @@ -271,9 +269,9 @@ public boolean getAutoStartBrowser() { * String broadcast to specific client * * @param uuid - * u + * u * @param str - * s + * s * */ public void broadcast(String uuid, String str) { @@ -315,9 +313,7 @@ public Config.Builder getNettosphereConfig() { // cert.privateKey()).build(); SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); - SslContext context = SslContextBuilder - .forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()) - .sslProvider(SslProvider.JDK) + SslContext context = SslContextBuilder.forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()).sslProvider(SslProvider.JDK) .clientAuth(ClientAuth.NONE).build(); configBuilder.sslContext(context); @@ -496,8 +492,7 @@ public void handle(AtmosphereResource r) { } else if ((bodyData != null) && log.isDebugEnabled()) { logData = bodyData; } - log.debug("-->{} {} {} - [{}] from connection {}", (newPersistentConnection) ? "new" : "", request.getMethod(), - request.getRequestURI(), logData, uuid); + log.debug("-->{} {} {} - [{}] from connection {}", (newPersistentConnection) ? "new" : "", request.getMethod(), request.getRequestURI(), logData, uuid); } // important persistent connections will have associated routes ... @@ -575,8 +570,7 @@ public void handle(AtmosphereResource r) { } if (msg.containsHop(getId())) { - log.error("{} dumping duplicate hop msg to avoid cyclical from {} --to--> {}.{}", getName(), msg.sender, - msg.name, msg.method); + log.error("{} dumping duplicate hop msg to avoid cyclical from {} --to--> {}.{}", getName(), msg.sender, msg.name, msg.method); return; } @@ -920,7 +914,7 @@ public void run() { * remotely control UI * * @param panel - * - the panel which has been moved or resized + * - the panel which has been moved or resized */ public void savePanel(Panel panel) { if (panel.name == null) { @@ -1107,7 +1101,7 @@ public void releaseService() { * Default (false) is to use the CDN * * @param useLocalResources - * - true uses local resources fals uses cdn + * - true uses local resources fals uses cdn */ public void useLocalResources(boolean useLocalResources) { this.useLocalResources = useLocalResources; @@ -1183,9 +1177,10 @@ public static void main(String[] args) { try { - Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui","intro", "Intro", "python", "Python" }); - // Runtime.main(new String[] {}); - // Runtime.main(new String[] { "--install" }); + // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", + // "WebGui", + // "intro", "Intro", "python", "Python" }); + Runtime.main(new String[] { "--install" }); boolean done = true; if (done) { @@ -1193,7 +1188,8 @@ public static void main(String[] args) { } // Platform.setVirtual(true); - // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui", + // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", + // "WebGui", // "intro", "Intro", "python", "Python", "-c", "dev" }); // Runtime.startConfig("dev"); @@ -1248,8 +1244,7 @@ public static void main(String[] args) { arduino.connect("/dev/ttyACM0"); for (int i = 0; i < 1000; ++i) { - webgui.display( - "https://i.kinja-img.com/gawker-media/image/upload/c_scale,f_auto,fl_progressive,q_80,w_800/pytutcxcrfjvuhz2jipa.jpg"); + webgui.display("https://i.kinja-img.com/gawker-media/image/upload/c_scale,f_auto,fl_progressive,q_80,w_800/pytutcxcrfjvuhz2jipa.jpg"); } // Runtime.setLogLevel("ERROR"); diff --git a/src/test/java/org/myrobotlab/service/WebGuiTest.java b/src/test/java/org/myrobotlab/service/WebGuiTest.java index 643485fc0c..02918dedba 100644 --- a/src/test/java/org/myrobotlab/service/WebGuiTest.java +++ b/src/test/java/org/myrobotlab/service/WebGuiTest.java @@ -5,7 +5,6 @@ import static org.junit.Assert.assertTrue; import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; import java.util.List; import org.junit.Before; @@ -22,8 +21,8 @@ public class WebGuiTest extends AbstractTest { public final static Logger log = LoggerFactory.getLogger(WebGui.class); - - // FIXME - DO A WEBSOCKET TEST + + // FIXME - DO A WEBSOCKET TEST @Before public void setUp() { @@ -31,11 +30,11 @@ public void setUp() { webgui2.autoStartBrowser(false); webgui2.setPort(8889); webgui2.startService(); - - Runtime.start("servoApiTest","Servo"); + + Runtime.start("servoApiTest", "Servo"); Runtime.start("pythonApiTest", "Python"); // need to wait for the OS to open the port - Service.sleep(3); + Service.sleep(200); } @Test @@ -46,7 +45,7 @@ public void getTest() { String ret = new String(bytes); assertTrue(ret.contains("days")); } - + @Test public void getTestWithParameter() throws UnsupportedEncodingException { @@ -56,13 +55,12 @@ public void getTestWithParameter() throws UnsupportedEncodingException { assertTrue(ret.contains("true")); } - -// FIXME - ADD WHEN POST API IS WORKY -// FIXME object non primitive (no string) post + // FIXME - ADD WHEN POST API IS WORKY + // FIXME object non primitive (no string) post @Test public void postTest() { - + // 1st post - simple input - simple return String postBody = "[\"runtime\"]"; byte[] bytes = Http.post("http://localhost:8889/api/service/runtime/getFullName", postBody); @@ -70,7 +68,7 @@ public void postTest() { assertNotNull(bytes); String ret = new String(bytes); assertTrue(ret.contains("@")); - + // second post - simple input - complex return postBody = "[\"runtime\"]"; bytes = Http.post("http://localhost:8889/api/service/runtime/getService", postBody); @@ -78,29 +76,31 @@ public void postTest() { assertNotNull(bytes); ret = new String(bytes); assertTrue(ret.contains("@")); - - + // second post - simple input (including array of strings) - complex return - // FIXME uncomment when ready - callbacks are not possible through the rest api - // org.myrobotlab.framework.TimeoutException: timeout of 3000 for proxyName@remoteId.toString exceeded - // org.myrobotlab.framework.TimeoutException: timeout of 3000 for proxyName@remoteId.getFullName exceeded -// postBody = "[\"remoteId\", \"proxyName\", \"py:myService\",[\"org.myrobotlab.framework.interfaces.ServiceInterface\"]]"; -// bytes = Http.post("http://localhost:8889/api/service/runtime/register", postBody); -// sleep(200); -// assertNotNull(bytes); -// ret = new String(bytes); -// assertTrue(ret.contains("remoteId")); - - - + // FIXME uncomment when ready - callbacks are not possible through the rest + // api + // org.myrobotlab.framework.TimeoutException: timeout of 3000 for + // proxyName@remoteId.toString exceeded + // org.myrobotlab.framework.TimeoutException: timeout of 3000 for + // proxyName@remoteId.getFullName exceeded + // postBody = "[\"remoteId\", \"proxyName\", + // \"py:myService\",[\"org.myrobotlab.framework.interfaces.ServiceInterface\"]]"; + // bytes = Http.post("http://localhost:8889/api/service/runtime/register", + // postBody); + // sleep(200); + // assertNotNull(bytes); + // ret = new String(bytes); + // assertTrue(ret.contains("remoteId")); + // post non primitive non string object MRLListener listener = new MRLListener("getRegistry", "runtime@webguittest", "onRegistry"); - postBody = "[" + CodecUtils.toJson(listener) + "]"; + postBody = "[" + CodecUtils.toJson(listener) + "]"; // postBody = "[\"runtime\"]"; bytes = Http.post("http://localhost:8889/api/service/runtime/addListener", postBody); sleep(200); assertNotNull(bytes); - + Runtime runtime = Runtime.getInstance(); boolean found = false; List<MRLListener> check = runtime.getNotifyList("getRegistry"); @@ -108,9 +108,9 @@ public void postTest() { if (check.get(i).equals(listener)) { found = true; } - } + } assertTrue("listener not found !", found); - + } @Test @@ -138,7 +138,7 @@ public void servoApiTest() { @Test public void urlEncodingTest() { - //exec("print \"hello\"") + // exec("print \"hello\"") byte[] bytes = Http.get("http://localhost:8889/api/service/pythonApiTest/exec/%22print+%5C%22hello%5C%22%22"); String ret = new String(bytes); assertEquals("true", ret); @@ -147,16 +147,19 @@ public void urlEncodingTest() { @Test public void sendBlockingTest() throws InterruptedException, TimeoutException { String retVal = "retVal"; - // Put directly in blocking list because sendBlocking() won't use it for local services + // Put directly in blocking list because sendBlocking() won't use it for + // local + // services Runtime.getInstance().getInbox().blockingList.put("runtime.onBlocking", new Object[1]); Object[] blockingListRet = Runtime.getInstance().getInbox().blockingList.get("runtime.onBlocking"); // Delay in a new thread so we can get our wait() call in first new Thread(() -> { try { - Thread.sleep(50); - } catch (InterruptedException ignored) {} - Http.post("http://localhost:8889/api/service/runtime/onBlocking", "[\""+retVal+"\"]"); + Thread.sleep(100); + } catch (InterruptedException ignored) { + } + Http.post("http://localhost:8889/api/service/runtime/onBlocking", "[\"" + retVal + "\"]"); }).start(); long timeout = 1000; @@ -170,6 +173,5 @@ public void sendBlockingTest() throws InterruptedException, TimeoutException { assertEquals(retVal, blockingListRet[0]); } - - + } From 33a1be5181a9ba3fdb82ad8750e00db32b8ee99e Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Wed, 14 Feb 2024 13:00:00 -0800 Subject: [PATCH 059/131] adding log service to scripts --- myrobotlab.bat | 2 +- myrobotlab.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/myrobotlab.bat b/myrobotlab.bat index b8515ca03d..a6ce3e30ea 100644 --- a/myrobotlab.bat +++ b/myrobotlab.bat @@ -28,6 +28,6 @@ IF NOT "%*"=="" ( "%JAVA%" %JAVA_OPTIONS% -cp %CLASSPATH% org.myrobotlab.service.Runtime --install --log-file myrobotlab-install.log ) - "%JAVA%" %JAVA_OPTIONS% -cp %CLASSPATH% org.myrobotlab.service.Runtime --log-level info -s webgui WebGui intro Intro python Python + "%JAVA%" %JAVA_OPTIONS% -cp %CLASSPATH% org.myrobotlab.service.Runtime --log-level info -s log Log webgui WebGui intro Intro python Python ) \ No newline at end of file diff --git a/myrobotlab.sh b/myrobotlab.sh index 9aa528ea51..4cc45eb085 100755 --- a/myrobotlab.sh +++ b/myrobotlab.sh @@ -62,6 +62,6 @@ else "${JAVA}" ${JAVA_OPTIONS} -cp ${CLASSPATH} org.myrobotlab.service.Runtime --install --log-file myrobotlab-install.log fi -"${JAVA}" ${JAVA_OPTIONS} -cp ${CLASSPATH} org.myrobotlab.service.Runtime --log-level info -s webgui WebGui intro Intro python Python +"${JAVA}" ${JAVA_OPTIONS} -cp ${CLASSPATH} org.myrobotlab.service.Runtime --log-level info -s log Log webgui WebGui intro Intro python Python echo $# $@ \ No newline at end of file From 77eac6ecfa500bbb5f0ecbd5a6cb228d027657fe Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Wed, 14 Feb 2024 13:16:27 -0800 Subject: [PATCH 060/131] simplifying randomtest --- .../org/myrobotlab/service/RandomTest.java | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/src/test/java/org/myrobotlab/service/RandomTest.java b/src/test/java/org/myrobotlab/service/RandomTest.java index 18f4e1c789..f089f2e453 100644 --- a/src/test/java/org/myrobotlab/service/RandomTest.java +++ b/src/test/java/org/myrobotlab/service/RandomTest.java @@ -7,31 +7,24 @@ import java.util.Map; import org.junit.Before; -import org.myrobotlab.framework.Service; +import org.junit.Test; import org.myrobotlab.service.Random.RandomMessage; +import org.myrobotlab.test.AbstractTest; -public class RandomTest extends AbstractServiceTest { +public class RandomTest extends AbstractTest { - @Override /* - * FIXME - this assumes a single service is in the test - which - * rarely happens - seems not useful and silly - */ - public Service createService() throws Exception { - return (Service) Runtime.start("randomTest", "Random"); - } - @Before /* before each test */ public void setUp() throws IOException { // remove all services - also resets config name to DEFAULT effectively Runtime.releaseAll(true, true); - // clean our config directory + // clean our config directory // Runtime.removeConfig("RandomTest"); // set our config Runtime.setConfig("RandomTest"); + Runtime.start("randomTest", "Random"); } - - @Override + @Test public void testService() throws Exception { Clock clock = (Clock) Runtime.start("clock", "Clock"); Random random = (Random) Runtime.start("randomTest", "Random"); @@ -46,31 +39,31 @@ public void testService() throws Exception { sleep(1000); 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 <= 10000 values", clock.getInterval()), clock.getInterval() <= 10000); + random.remove("clock.setInterval"); - + assertTrue("should not have method", !random.getKeySet().contains("clock.setInterval")); random.addRandom(0, 200, "clock", "setInterval", 5000, 10000); random.addRandom(0, 200, "clock", "startClock"); - + sleep(500); assertTrue("clock should be started 1", clock.isClockRunning()); - + // disable all of a services random events random.disable("clock.startClock"); sleep(250); clock.stopClock(); assertTrue("clock should not be started 1", !clock.isClockRunning()); - + // enable all of a service's random events random.enable("clock.startClock"); sleep(250); assertTrue("clock should be started 2", clock.isClockRunning()); - + // disable one method - leave other enabled random.disable("clock.startClock"); clock.stopClock(); @@ -78,30 +71,31 @@ public void testService() throws Exception { 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 <= 10000 values", clock.getInterval()), clock.getInterval() <= 10000); // disable all random.disable(); sleep(200); clock.setInterval(9999); - assertTrue("clock should not be started 4", !clock.isClockRunning()); - assertEquals(9999, (long)clock.getInterval()); + assertTrue("clock should not be started 4", !clock.isClockRunning()); + assertEquals(9999, (long) clock.getInterval()); - // re-enable all that were previously enabled but not explicitly disabled ones + // re-enable all that were previously enabled but not explicitly disabled + // ones 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 <= 10000 values", clock.getInterval()), clock.getInterval() <= 10000); clock.stopClock(); random.purge(); - + Map<String, RandomMessage> events = random.getRandomEvents(); assertTrue(events.size() == 0); - + random.addRandom("named task", 200, 500, "clock", "setInterval", 100, 1000, 10); - + clock.releaseService(); random.releaseService(); From 1d9cb9050cf488967e10246be5f7b2470d98ec90 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Wed, 14 Feb 2024 16:58:11 -0800 Subject: [PATCH 061/131] fixes and updates --- .../service/AdafruitMotorHat4Pi.java | 4 +- .../java/org/myrobotlab/service/InMoov2.java | 71 ------------------ .../java/org/myrobotlab/service/RoboClaw.java | 4 +- .../java/org/myrobotlab/service/Servo.java | 72 ------------------- .../java/org/myrobotlab/service/WebGui.java | 6 +- .../abstracts/AbstractMotorController.java | 4 +- .../config/AbstractMotorControllerConfig.java | 5 ++ .../service/config/InMoov2Config.java | 7 +- .../service/config/SabertoothConfig.java | 2 +- 9 files changed, 19 insertions(+), 156 deletions(-) create mode 100644 src/main/java/org/myrobotlab/service/config/AbstractMotorControllerConfig.java diff --git a/src/main/java/org/myrobotlab/service/AdafruitMotorHat4Pi.java b/src/main/java/org/myrobotlab/service/AdafruitMotorHat4Pi.java index a089498d4f..5897630ef8 100644 --- a/src/main/java/org/myrobotlab/service/AdafruitMotorHat4Pi.java +++ b/src/main/java/org/myrobotlab/service/AdafruitMotorHat4Pi.java @@ -19,7 +19,7 @@ import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.abstracts.AbstractMotorController; -import org.myrobotlab.service.config.MotorConfig; +import org.myrobotlab.service.config.AbstractMotorControllerConfig; import org.myrobotlab.service.interfaces.I2CControl; import org.myrobotlab.service.interfaces.I2CController; import org.myrobotlab.service.interfaces.MotorControl; @@ -34,7 +34,7 @@ * https://learn.adafruit.com/adafruit-dc-and-stepper-motor-hat-for-raspberry-pi/overview */ -public class AdafruitMotorHat4Pi extends AbstractMotorController<MotorConfig> implements I2CControl { +public class AdafruitMotorHat4Pi extends AbstractMotorController<AbstractMotorControllerConfig> implements I2CControl { /** version of the library */ static public final String VERSION = "0.9"; diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index e6ad6326ab..40ebda2e26 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -188,77 +188,6 @@ 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); } diff --git a/src/main/java/org/myrobotlab/service/RoboClaw.java b/src/main/java/org/myrobotlab/service/RoboClaw.java index 83203b99f8..8d22f7ff27 100644 --- a/src/main/java/org/myrobotlab/service/RoboClaw.java +++ b/src/main/java/org/myrobotlab/service/RoboClaw.java @@ -16,7 +16,7 @@ import org.myrobotlab.serial.CRC; import org.myrobotlab.service.Pid.PidData; import org.myrobotlab.service.abstracts.AbstractMotorController; -import org.myrobotlab.service.config.MotorConfig; +import org.myrobotlab.service.config.AbstractMotorControllerConfig; import org.myrobotlab.service.interfaces.MotorControl; import org.myrobotlab.service.interfaces.MotorController; import org.myrobotlab.service.interfaces.PortConnector; @@ -55,7 +55,7 @@ * this value IS correct * */ -public class RoboClaw extends AbstractMotorController<MotorConfig> implements EncoderPublisher, PortConnector, MotorController, SerialDataListener { +public class RoboClaw extends AbstractMotorController<AbstractMotorControllerConfig> implements EncoderPublisher, PortConnector, MotorController, SerialDataListener { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Servo.java b/src/main/java/org/myrobotlab/service/Servo.java index 3e0b46ce95..798bff3afb 100644 --- a/src/main/java/org/myrobotlab/service/Servo.java +++ b/src/main/java/org/myrobotlab/service/Servo.java @@ -259,78 +259,6 @@ public static void main(String[] args) throws InterruptedException { return; } - // runtime.save(); - - /* - * mega.save(); tilt.save(); pan.save(); - * - * mega.load(); tilt.load(); pan.load(); - */ - - // TODO - attach before and after connect.. - - // mega.setBoardMega(); - - // log.info("servo pos {}", tilt.getCurrentInputPos()); - // - // // double pos = 170; - // // servo03.setPosition(pos); - // - // double min = 3; - // double max = 170; - // double speed = 60; // degree/s - // - // mega.attach(tilt); - // // mega.attach(servo03,3); - // - // for (int i = 0; i < 100; ++i) { - // tilt.moveTo(20.0); - // } - // - // tilt.sweep(min, max, speed); - - /* - * Servo servo04 = (Servo) Runtime.start("servo04", "Servo"); Servo - * servo05 = (Servo) Runtime.start("servo05", "Servo"); Servo servo06 = - * (Servo) Runtime.start("servo06", "Servo"); Servo servo07 = (Servo) - * Runtime.start("servo07", "Servo"); Servo servo08 = (Servo) - * Runtime.start("servo08", "Servo"); Servo servo09 = (Servo) - * Runtime.start("servo09", "Servo"); Servo servo10 = (Servo) - * Runtime.start("servo10", "Servo"); Servo servo11 = (Servo) - * Runtime.start("servo11", "Servo"); Servo servo12 = (Servo) - * Runtime.start("servo12", "Servo"); - */ - // Servo servo13 = (Servo) Runtime.start("servo13", "Servo"); - - // servo03.attach(mega, 8, 38.0); - /* - * servo04.attach(mega, 4, 38.0); servo05.attach(mega, 5, 38.0); - * servo06.attach(mega, 6, 38.0); servo07.attach(mega, 7, 38.0); - * servo08.attach(mega, 8, 38.0); servo09.attach(mega, 9, 38.0); - * servo10.attach(mega, 10, 38.0); servo11.attach(mega, 11, 38.0); - * servo12.attach(mega, 12, 38.0); - */ - - // TestCatcher catcher = (TestCatcher)Runtime.start("catcher", - // "TestCatcher"); - // servo03.attach((ServoEventListener)catcher); - - // servo.setPin(12); - - /* - * servo.attach(mega, 7, 38.0); servo.attach(mega, 7, 38.0); - * servo.attach(mega, 7, 38.0); servo.attach(mega, 7, 38.0); - * servo.attach(mega, 7, 38.0); servo.attach(mega, 7, 38.0); - * servo.attach(mega, 7, 38.0); servo.attach(mega, 7, 38.0); - * servo.attach(mega, 7, 38.0); servo.attach(mega, 7, 38.0); - * servo.attach(mega, 7, 38.0); servo.attach(mega, 7, 38.0); - */ - - // servo.sweepDelay = 3; - // servo.save(); - // servo.load(); - // servo.save(); - // log.info("sweepDely {}", servo.sweepDelay); } catch (Exception e) { log.error("main threw", e); diff --git a/src/main/java/org/myrobotlab/service/WebGui.java b/src/main/java/org/myrobotlab/service/WebGui.java index 3e816726f1..f50cdc238a 100644 --- a/src/main/java/org/myrobotlab/service/WebGui.java +++ b/src/main/java/org/myrobotlab/service/WebGui.java @@ -1177,10 +1177,8 @@ public static void main(String[] args) { try { - // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", - // "WebGui", - // "intro", "Intro", "python", "Python" }); - Runtime.main(new String[] { "--install" }); + Runtime.main(new String[] { "--log-level", "info", "-s", "log", "Log", "webgui", "WebGui", "intro", "Intro", "python", "Python" }); + // Runtime.main(new String[] { "--install" }); boolean done = true; if (done) { diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractMotorController.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractMotorController.java index 5f01ebc74f..94380c9711 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractMotorController.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractMotorController.java @@ -8,11 +8,11 @@ import org.myrobotlab.math.MapperLinear; import org.myrobotlab.math.interfaces.Mapper; import org.myrobotlab.service.Runtime; -import org.myrobotlab.service.config.MotorConfig; +import org.myrobotlab.service.config.AbstractMotorControllerConfig; import org.myrobotlab.service.interfaces.MotorControl; import org.myrobotlab.service.interfaces.MotorController; -public abstract class AbstractMotorController<C extends MotorConfig> extends Service<C> implements MotorController { +public abstract class AbstractMotorController<C extends AbstractMotorControllerConfig> extends Service<C> implements MotorController { /** * currently attached motors to this controller diff --git a/src/main/java/org/myrobotlab/service/config/AbstractMotorControllerConfig.java b/src/main/java/org/myrobotlab/service/config/AbstractMotorControllerConfig.java new file mode 100644 index 0000000000..030a1ee4f0 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/config/AbstractMotorControllerConfig.java @@ -0,0 +1,5 @@ +package org.myrobotlab.service.config; + +public class AbstractMotorControllerConfig extends ServiceConfig { + // Add your configuration here +} diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index a050223baa..99d40fa8ed 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -281,8 +281,7 @@ public Plan getDefault(Plan plan, String name) { // setup name references to different services MarySpeechConfig mouth = (MarySpeechConfig) plan.get(getPeerName("mouth")); mouth.voice = "Mark"; - mouth.speechRecognizers = new String[] { name + ".ear" }; - + // == Peer - ear ============================= // setup name references to different services WebkitSpeechRecognitionConfig ear = (WebkitSpeechRecognitionConfig) plan.get(getPeerName("ear")); @@ -549,6 +548,10 @@ public Plan getDefault(Plan plan, String name) { // Needs upcoming pr fsm.listeners.add(new Listener("publishStateChange", name, "publishStateChange")); + + // peer --to--> peer + mouth.listeners.add(new Listener("publishStartSpeaking", getPeerName("ear"))); + mouth.listeners.add(new Listener("publishEndSpeaking", getPeerName("ear"))); return plan; } diff --git a/src/main/java/org/myrobotlab/service/config/SabertoothConfig.java b/src/main/java/org/myrobotlab/service/config/SabertoothConfig.java index 42db50689b..44dc5a0c5b 100644 --- a/src/main/java/org/myrobotlab/service/config/SabertoothConfig.java +++ b/src/main/java/org/myrobotlab/service/config/SabertoothConfig.java @@ -2,7 +2,7 @@ import org.myrobotlab.framework.Plan; -public class SabertoothConfig extends MotorConfig { +public class SabertoothConfig extends AbstractMotorControllerConfig { public String port; public boolean connect = false; From 402a527b8898dee23b3555835c593217e4717f4f Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Fri, 16 Feb 2024 10:05:36 -0800 Subject: [PATCH 062/131] servo.setMaxSpeed --- .../service/FiniteStateMachine.java | 44 ++++++++----------- .../java/org/myrobotlab/service/InMoov2.java | 5 ++- .../service/abstracts/AbstractServo.java | 6 +++ .../service/config/InMoov2Config.java | 1 + .../service/interfaces/ServoControl.java | 8 +++- .../org/myrobotlab/service/ServoTest.java | 2 +- 6 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/FiniteStateMachine.java b/src/main/java/org/myrobotlab/service/FiniteStateMachine.java index 32c04bf29e..e998711e26 100644 --- a/src/main/java/org/myrobotlab/service/FiniteStateMachine.java +++ b/src/main/java/org/myrobotlab/service/FiniteStateMachine.java @@ -2,16 +2,11 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; -import org.myrobotlab.codec.CodecUtils; 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; @@ -61,7 +56,7 @@ public class Tuple { public Transition transition; public StateTransition stateTransition; } - + public class StateChange { /** * timestamp @@ -72,7 +67,7 @@ public class StateChange { * current new state */ public String state; - + /** * event which activated new state */ @@ -82,13 +77,12 @@ public class StateChange { * source of event */ public String src = getName(); - - + public StateChange(String current, String event) { this.state = current; this.event = event; } - + public String toString() { return String.format("%s --%s--> %s", last, event, state); } @@ -221,25 +215,25 @@ public String firedEvent(String event) { } /** - * gets the current state of this state machine + * get the previous state of this state machine * * @return */ - public String getCurrent() { - if (current != null) { - return current.getName(); + public String getLast() { + if (last != null) { + return last.getName(); } return null; } /** - * get the previous state of this state machine + * gets the current state of this state machine * * @return */ - public String getLast() { - if (last != null) { - return last.getName(); + public String getState() { + if (current != null) { + return current.getName(); } return null; } @@ -250,7 +244,7 @@ public List<Transition> getTransitions() { } /** - * Publishes state change (current, last and event) + * Publishes state change (current, last and event) * * @param stateChange * @return @@ -263,7 +257,7 @@ public StateChange publishStateChange(StateChange stateChange) { @Override public FiniteStateMachineConfig getConfig() { super.getConfig(); - config.current = getCurrent(); + config.current = getState(); return config; } @@ -361,15 +355,15 @@ public static void main(String[] args) { // fsm.subscribe("fsm", "publishState"); - log.info("state - {}", fsm.getCurrent()); + log.info("state - {}", fsm.getState()); fsm.setCurrent("neutral"); - log.info("state - {}", fsm.getCurrent()); + log.info("state - {}", fsm.getState()); fsm.fire("ill-event"); - log.info("state - {}", fsm.getCurrent()); + log.info("state - {}", fsm.getState()); fsm.fire("ill-event"); fsm.fire("ill-event"); @@ -387,7 +381,7 @@ public static void main(String[] args) { // fsm.removeScheduledEvents(); - log.info("state - {}", fsm.getCurrent()); + log.info("state - {}", fsm.getState()); } catch (Exception e) { log.error("main threw", e); @@ -419,7 +413,7 @@ public String getPreviousState() { return history.get(history.size() - 2).state; } } - + @Override public void startService() { super.startService(); diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 40ebda2e26..36d8c07ff4 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -856,7 +856,7 @@ public String getState() { if (fsm == null) { return null; } - return fsm.getCurrent(); + return fsm.getState(); } /** @@ -1553,7 +1553,8 @@ public Heartbeat publishHeartbeat() { if (System.currentTimeMillis() > stateLastIdleTime + (config.stateIdleInterval * 1000)) { // idle event to be handled with the processor - processMessage("onIdle"); + // processMessage("onIdle"); + fire("idle"); stateLastIdleTime = System.currentTimeMillis(); } diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java index 1376e78778..780f875741 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java @@ -434,6 +434,12 @@ public void enable() { public void fullSpeed() { setSpeed((Double) null); } + + @Override + public void setMaxSpeed() { + setSpeed((Double) null); + } + @Override public boolean isAutoDisable() { diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 230f787aa9..819f84e31e 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -380,6 +380,7 @@ public Plan getDefault(Plan plan, String name) { fsm.transitions.add(new Transition("sleep", "power_down", "power_down")); fsm.transitions.add(new Transition("idle", "power_down", "power_down")); fsm.transitions.add(new Transition("wake", "setup", "setup")); + fsm.transitions.add(new Transition("wake", "idle", "idle")); fsm.transitions.add(new Transition("idle", "setup", "setup")); // power_down to shutdown // fsm.transitions.add(new Transition("systemCheck", "systemCheckFinished", diff --git a/src/main/java/org/myrobotlab/service/interfaces/ServoControl.java b/src/main/java/org/myrobotlab/service/interfaces/ServoControl.java index 23a9ee9629..379154cd6e 100644 --- a/src/main/java/org/myrobotlab/service/interfaces/ServoControl.java +++ b/src/main/java/org/myrobotlab/service/interfaces/ServoControl.java @@ -404,6 +404,12 @@ public interface ServoControl extends AbsolutePositionControl, EncoderListener, /** * disable speed control and move the servos at full speed. */ + @Deprecated void fullSpeed(); - + + /** + * + */ + void setMaxSpeed(); + } diff --git a/src/test/java/org/myrobotlab/service/ServoTest.java b/src/test/java/org/myrobotlab/service/ServoTest.java index 8d96052e2e..819c3718d6 100644 --- a/src/test/java/org/myrobotlab/service/ServoTest.java +++ b/src/test/java/org/myrobotlab/service/ServoTest.java @@ -73,7 +73,7 @@ public void autoDisableAfterAttach() { @Test public void disabledMove() throws Exception { // take off speed control - servo.fullSpeed(); + servo.setMaxSpeed(); servo.moveTo(0.0); servo.setInverted(false); Service.sleep(1000); From 8601157c27b3c97c3a065385a8b91142c85eeb6d Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Fri, 16 Feb 2024 10:37:01 -0800 Subject: [PATCH 063/131] ignoring servo.setSpeed(speed <0) --- .../java/org/myrobotlab/service/abstracts/AbstractServo.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java index 780f875741..7eee94dc5a 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java @@ -898,6 +898,11 @@ public void setSpeed(Double degreesPerSecond) { // speed = maxSpeed; // log.info("Trying to set speed to a value greater than max speed"); // } + + if (degreesPerSecond != null && degreesPerSecond < 0) { + warn("setting speed to negative value %d ignoring", degreesPerSecond); + return; + } speed = degreesPerSecond; From 11fefcae4e201b479cbd3770b8b07fb925d2379a Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Fri, 16 Feb 2024 10:59:51 -0800 Subject: [PATCH 064/131] synching cpython javacpp and javacv versions to 15.8 --- src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java b/src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java index 1b8f134beb..73d284cdb6 100644 --- a/src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java @@ -16,7 +16,7 @@ public OpenCVMeta() { addDescription("OpenCV (computer vision) service wrapping many of the functions and filters of OpenCV"); addCategory("video", "vision", "sensors"); - String javaCvVersion = "1.5.7"; + String javaCvVersion = "1.5.8"; // addDependency("org.bytedeco", "javacv", javaCvVersion); addDependency("org.bytedeco", "javacv-platform", javaCvVersion); addDependency("org.bytedeco", "javacpp", javaCvVersion); From db9ccdd1499cae0350a31c13ed6762df91ba57f0 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 06:46:55 -0800 Subject: [PATCH 065/131] removal of programab botdir --- .../java/org/myrobotlab/service/InMoov2.java | 22 +++++++------------ .../org/myrobotlab/service/ProgramAB.java | 12 ---------- .../service/config/InMoov2Config.java | 1 - .../service/config/ProgramABConfig.java | 5 ----- 4 files changed, 8 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 36d8c07ff4..8aac57a03e 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -2318,26 +2318,20 @@ public void stopNeopixelAnimation() { } public void systemCheck() { - log.error("systemCheck()"); - Runtime runtime = Runtime.getInstance(); - int servoCount = 0; - int servoAttachedCount = 0; + Platform platform = Runtime.getPlatform(); + int servoCount = 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 !!! - systemEvent("SYSTEMCHECKFINISHED"); // wtf is this? + setPredicate("system_uptime", Runtime.getUptime()); + setPredicate("system_servo_count", servoCount); + setPredicate("system_free_memory", Runtime.getFreeMemory()); + setPredicate("system_version", platform.getVersion()); + setPredicate("system_errors", errors.size()); + } public String systemEvent(String eventMsg) { diff --git a/src/main/java/org/myrobotlab/service/ProgramAB.java b/src/main/java/org/myrobotlab/service/ProgramAB.java index 804115b67e..63dc903174 100644 --- a/src/main/java/org/myrobotlab/service/ProgramAB.java +++ b/src/main/java/org/myrobotlab/service/ProgramAB.java @@ -943,9 +943,6 @@ public void startService() { logging.setLevel("org.alicebot.ab.MagicBooleans", "DEBUG"); logging.setLevel("class org.myrobotlab.programab.MrlSraixHandler", "DEBUG"); logPublisher.start(); - - scanForBots(getResourceDir()); - } @Override /* FIXME - just do this once in abstract */ @@ -1104,15 +1101,6 @@ public ProgramABConfig apply(ProgramABConfig c) { } } - if (c.botDir == null) { - c.botDir = getResourceDir(); - } - - List<File> botsFromScanning = scanForBots(c.botDir); - for (File file : botsFromScanning) { - addBotPath(file.getAbsolutePath()); - } - if (c.currentUserName != null) { setCurrentUserName(c.currentUserName); } diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 819f84e31e..889e91eec9 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -243,7 +243,6 @@ public Plan getDefault(Plan plan, String name) { ProgramABConfig chatBot = (ProgramABConfig) plan.get(getPeerName("chatBot")); - chatBot.botDir = "resource/ProgramAB"; chatBot.bots.add("resource/ProgramAB/Alice"); chatBot.bots.add("resource/ProgramAB/Dr.Who"); diff --git a/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java b/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java index ce9ae14033..0cd4dbc839 100644 --- a/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java +++ b/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java @@ -10,11 +10,6 @@ public class ProgramABConfig extends ServiceConfig { @Deprecated /* unused text filters */ public String[] textFilters; - /** - * a directory ProgramAB will scan for new bots - */ - public String botDir; - /** * explicit bot directories */ From b7c7a165c0cf5cf6b99015c30bfba893605d84ec Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 07:00:06 -0800 Subject: [PATCH 066/131] more non inmoov2 updates --- .../org/myrobotlab/config/ConfigUtils.java | 2 + .../org/myrobotlab/framework/repo/Repo.java | 6 +- .../service/AdafruitMotorHat4Pi.java | 4 +- .../java/org/myrobotlab/service/Arduino.java | 154 ++---------------- .../java/org/myrobotlab/service/DiyServo.java | 43 +---- .../service/FiniteStateMachine.java | 44 +++-- .../java/org/myrobotlab/service/RoboClaw.java | 4 +- .../java/org/myrobotlab/service/Runtime.java | 23 +-- .../java/org/myrobotlab/service/Servo.java | 87 +--------- .../java/org/myrobotlab/service/WebGui.java | 33 ++-- .../abstracts/AbstractMotorController.java | 4 +- .../service/abstracts/AbstractServo.java | 38 ++--- .../config/AbstractMotorControllerConfig.java | 5 + .../service/config/ArduinoConfig.java | 9 + .../service/config/InMoov2Config.java | 48 ++++-- .../service/config/ProgramABConfig.java | 5 - .../service/config/SabertoothConfig.java | 2 +- .../config/UltrasonicSensorConfig.java | 2 + .../service/interfaces/ServoControl.java | 9 +- .../myrobotlab/service/meta/OpenCVMeta.java | 2 +- .../myrobotlab/config/ConfigUtilsTest.java | 3 + .../myrobotlab/framework/BlockingTest.java | 4 +- .../myrobotlab/framework/repo/RepoTest.java | 4 + .../org/myrobotlab/service/ArduinoTest.java | 1 - .../org/myrobotlab/service/RandomTest.java | 56 +++---- .../service/RuntimeProcessTest.java | 5 +- .../org/myrobotlab/service/ServoTest.java | 2 +- .../service/VirtualArduinoTest.java | 4 + .../org/myrobotlab/service/WebGuiTest.java | 76 ++++----- .../org/myrobotlab/test/AbstractTest.java | 149 ++++++----------- 30 files changed, 273 insertions(+), 555 deletions(-) create mode 100644 src/main/java/org/myrobotlab/service/config/AbstractMotorControllerConfig.java diff --git a/src/main/java/org/myrobotlab/config/ConfigUtils.java b/src/main/java/org/myrobotlab/config/ConfigUtils.java index 19c256a8cf..69f850e602 100644 --- a/src/main/java/org/myrobotlab/config/ConfigUtils.java +++ b/src/main/java/org/myrobotlab/config/ConfigUtils.java @@ -60,6 +60,8 @@ static public RuntimeConfig loadRuntimeConfig(CmdOptions options) { if (startYml.enable) { configName = startYml.config; + } else { + configName = "default"; } // start with default diff --git a/src/main/java/org/myrobotlab/framework/repo/Repo.java b/src/main/java/org/myrobotlab/framework/repo/Repo.java index 9262834da1..d8b6863c16 100644 --- a/src/main/java/org/myrobotlab/framework/repo/Repo.java +++ b/src/main/java/org/myrobotlab/framework/repo/Repo.java @@ -324,10 +324,10 @@ public Set<ServiceDependency> getUnfulfilledDependencies(String[] types) { } } } - + // Plan plan = ServiceConfig.getDefault(type.toLowerCase(), type); ServiceConfig sc = ServiceConfig.getDefaultServiceConfig(type); - + Map<String, Peer> peers = sc.getPeers(); if (peers != null) { for (String key : peers.keySet()) { @@ -496,7 +496,7 @@ public void load() { } } else { - log.info("{} not found", getRepoPath()); + log.info("{} not found", f.getAbsolutePath()); } } catch (Exception e) { diff --git a/src/main/java/org/myrobotlab/service/AdafruitMotorHat4Pi.java b/src/main/java/org/myrobotlab/service/AdafruitMotorHat4Pi.java index a089498d4f..5897630ef8 100644 --- a/src/main/java/org/myrobotlab/service/AdafruitMotorHat4Pi.java +++ b/src/main/java/org/myrobotlab/service/AdafruitMotorHat4Pi.java @@ -19,7 +19,7 @@ import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.abstracts.AbstractMotorController; -import org.myrobotlab.service.config.MotorConfig; +import org.myrobotlab.service.config.AbstractMotorControllerConfig; import org.myrobotlab.service.interfaces.I2CControl; import org.myrobotlab.service.interfaces.I2CController; import org.myrobotlab.service.interfaces.MotorControl; @@ -34,7 +34,7 @@ * https://learn.adafruit.com/adafruit-dc-and-stepper-motor-hat-for-raspberry-pi/overview */ -public class AdafruitMotorHat4Pi extends AbstractMotorController<MotorConfig> implements I2CControl { +public class AdafruitMotorHat4Pi extends AbstractMotorController<AbstractMotorControllerConfig> implements I2CControl { /** version of the library */ static public final String VERSION = "0.9"; diff --git a/src/main/java/org/myrobotlab/service/Arduino.java b/src/main/java/org/myrobotlab/service/Arduino.java index 1f467cf8d8..cfc1be7b6d 100644 --- a/src/main/java/org/myrobotlab/service/Arduino.java +++ b/src/main/java/org/myrobotlab/service/Arduino.java @@ -171,7 +171,6 @@ public static class I2CDeviceMap { transient Mapper motorPowerMapper = new MapperLinear(-1.0, 1.0, -255.0, 255.0); - // make final - if not "connected" log error but don't allow Arduino NPEs public final transient Msg msg = new Msg(this, null); Integer nextDeviceId = 0; @@ -191,10 +190,6 @@ public static class I2CDeviceMap { private volatile boolean syncInProgress = false; - /** - * the port the user attempted to connect to - */ - String port; public Arduino(String n, String id) { super(n, id); @@ -552,6 +547,7 @@ public VirtualArduino getVirtual() { */ @Override public void connect(String port, int rate, int databits, int stopbits, int parity) { + config.connect = true; connecting = true; if (port == null) { warn("%s attempted to connect with a null port", getName()); @@ -563,7 +559,7 @@ public void connect(String port, int rate, int databits, int stopbits, int parit serial.addByteListener(this); // test to see if we've been started. the serial might be null - this.port = port; + config.port = port; try { @@ -811,6 +807,7 @@ public void disablePins() { @Override public void disconnect() { + config.connect = false; // FIXED - all don in 'onDisconnect()' // enableBoardInfo(false); // boardInfo is not valid after disconnect @@ -2233,7 +2230,7 @@ public Map<String, DeviceMapping> getDeviceList() { @Override public void ackTimeout() { - log.warn("{} Ack Timeout seen. TODO: consider resetting the com port {}, reconnecting and re syncing all devices.", getName(), port); + log.warn("{} Ack Timeout seen. TODO: consider resetting the com port {}, reconnecting and re syncing all devices.", getName(), config.port); } @Override @@ -2325,34 +2322,13 @@ public void neoPixelClear(String neopixel) { msg.neoPixelClear(getDeviceId(neopixel)); } - @Override - public ArduinoConfig getConfig() { - super.getConfig(); - - // FIXME "port" shouldn't exist only config.port ! - config.port = port; - config.connect = isConnected(); - - return config; - } - @Override public ArduinoConfig apply(ArduinoConfig c) { super.apply(c); - - if (msg == null) { + if (config.connect && config.port != null) { serial = (Serial) startPeer("serial"); - if (serial == null) { - log.error("serial is null"); - } msg.setSerial(serial); - serial.addByteListener(this); - } else { - // TODO: figure out why this gets called so often. - log.info("Init serial we already have a msg class."); - } - - if (config.connect && config.port != null) { + serial.addByteListener(this); connect(config.port); } @@ -2372,13 +2348,8 @@ public ArduinoConfig apply(ArduinoConfig c) { public static void main(String[] args) { try { - // Platform.setVirtual(true); - LoggingFactory.init(Level.INFO); - Runtime runtime = Runtime.getInstance(); - runtime.saveAllDefaults(); - Runtime.start("arduino", "Arduino"); Runtime.start("webgui", "WebGui"); @@ -2387,115 +2358,12 @@ public static void main(String[] args) { if (isDone) { return; } - // Platform.setVirtual(true); - - /* - * WebGui webgui = (WebGui) Runtime.create("webgui", "WebGui"); - * webgui.autoStartBrowser(false); webgui.setPort(8887); - * webgui.startService(); - */ - - // Runtime.start("gui", "SwingGui"); - Serial.listPorts(); - - Arduino hub = (Arduino) Runtime.start("controller", "Arduino"); - Runtime.start("pir", "Pir"); - - hub.connect("/dev/ttyACM0"); - - // hub.enableAck(false); - - ServoControl sc = (ServoControl) Runtime.start("s1", "Servo"); - sc.setPin(3); - hub.attach(sc); - sc = (ServoControl) Runtime.start("s2", "Servo"); - sc.setPin(9); - hub.attach(sc); - - hub.detach(); - - // hub.enableAck(true); - /* - * sc = (ServoControl) Runtime.start("s3", "Servo"); sc.setPin(12); - * hub.attach(sc); - */ - - log.info("here"); - // hub.connect("COM6"); // uno - - // hub.startTcpServer(); - - VirtualArduino vmega = null; - - vmega = (VirtualArduino) Runtime.start("vmega", "VirtualArduino"); - vmega.connect("COM7"); - Serial sd = vmega.getSerial(); - sd.startTcpServer(); - - // Runtime.start("webgui", "WebGui"); - - Arduino mega = (Arduino) Runtime.start("mega", "Arduino"); - - if (mega.isVirtual()) { - vmega = mega.getVirtual(); - vmega.setBoardMega(); - } - - // mega.getBoardTypes(); - // mega.setBoardMega(); - // mega.setBoardUno(); - mega.connect("COM7"); - - /* - * Arduino uno = (Arduino) Runtime.start("uno", "Arduino"); - * uno.connect("COM6"); - */ - - // log.info("port names {}", mega.getPortNames()); - - Servo servo = (Servo) Runtime.start("servo", "Servo"); - // servo.load(); - log.info("rest is {}", servo.getRest()); - servo.save(); - // servo.setPin(8); - servo.attach(mega); - - servo.moveTo(90.0); - - /* - * servo.moveTo(3); sleep(300); servo.moveTo(130); sleep(300); - * servo.moveTo(90); sleep(300); - * - * - * // minmax checking - * - * servo.invoke("moveTo", 120); - */ - - /* - * mega.attach(servo); - * - * servo.moveTo(3); - * - * servo.moveTo(30); - * - * mega.enablePin("A4"); - * - * // arduino.setBoardMega(); - * - * Adafruit16CServoDriver adafruit = (Adafruit16CServoDriver) - * Runtime.start("adafruit", "Adafruit16CServoDriver"); - * adafruit.attach(mega); mega.attach(adafruit); - */ - - // servo.attach(arduino, 8, 90); - - // Runtime.start("webgui", "WebGui"); - // Service.sleep(3000); - - // remote.startListening(); - // Runtime.start("webgui", "WebGui"); +// Platform.setVirtual(true); +// Serial sd = vmega.getSerial(); +// sd.startTcpServer(); +// Serial.listPorts(); + } catch (Exception e) { log.error("main threw", e); diff --git a/src/main/java/org/myrobotlab/service/DiyServo.java b/src/main/java/org/myrobotlab/service/DiyServo.java index 2e125d8509..ae83c09118 100644 --- a/src/main/java/org/myrobotlab/service/DiyServo.java +++ b/src/main/java/org/myrobotlab/service/DiyServo.java @@ -28,7 +28,6 @@ import java.util.ArrayList; import java.util.List; -import org.myrobotlab.framework.Registration; import org.myrobotlab.framework.interfaces.ServiceInterface; import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; @@ -44,8 +43,6 @@ import org.myrobotlab.service.interfaces.MotorControl; import org.myrobotlab.service.interfaces.PinArrayControl; import org.myrobotlab.service.interfaces.PinListener; -import org.myrobotlab.service.interfaces.ServiceLifeCycleListener; -import org.myrobotlab.service.interfaces.ServoControl; import org.myrobotlab.service.interfaces.ServoEvent; import org.slf4j.Logger; @@ -76,7 +73,7 @@ * TODO : move is not accurate ( 1° step seem not possible ) */ -public class DiyServo extends AbstractServo<ServoConfig> implements PinListener, ServiceLifeCycleListener { +public class DiyServo extends AbstractServo<ServoConfig> implements PinListener { double lastOutput = 0.0; /** @@ -198,16 +195,6 @@ public DiyServo(String n, String id) { lastActivityTimeTs = System.currentTimeMillis(); } - /* - * Update the list of PinArrayControls - */ - @Override - public void onRegistered(Registration s) { - refreshPinArrayControls(); - broadcastState(); - - } - /** * Initiate the PID controller */ @@ -224,7 +211,7 @@ void initPid() { pid.setSetpoint(pidKey, setPoint); pid.startService(); } - + @Override public void startService() { super.startService(); @@ -232,7 +219,6 @@ public void startService() { motorControl = (MotorControl) startPeer("motor"); initPid(); } - /** * Equivalent to Arduino's Servo.detach() it de-energizes the servo @@ -694,19 +680,17 @@ public static void main(String[] args) throws InterruptedException { // if (done) { // return; // } - - WebGui webgui = (WebGui)Runtime.create("webgui", "WebGui"); + + WebGui webgui = (WebGui) Runtime.create("webgui", "WebGui"); webgui.autoStartBrowser(false); webgui.startService(); - + Runtime.start("diy", "DiyServo"); - - + boolean done = true; if (done) { return; } - String port = "COM4"; Arduino arduino = (Arduino) Runtime.start("arduino", "Arduino"); @@ -788,19 +772,4 @@ protected boolean processMove(Double newPos, boolean blocking, Long timeoutMs) { return false; } - @Override - public void onCreated(String name) { - log.info("created {}", name); - } - - @Override - public void onStopped(String name) { - log.info("stopped {}", name); - } - - @Override - public void onReleased(String name) { - log.info("released {}", name); - } - } diff --git a/src/main/java/org/myrobotlab/service/FiniteStateMachine.java b/src/main/java/org/myrobotlab/service/FiniteStateMachine.java index 32c04bf29e..e998711e26 100644 --- a/src/main/java/org/myrobotlab/service/FiniteStateMachine.java +++ b/src/main/java/org/myrobotlab/service/FiniteStateMachine.java @@ -2,16 +2,11 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; -import org.myrobotlab.codec.CodecUtils; 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; @@ -61,7 +56,7 @@ public class Tuple { public Transition transition; public StateTransition stateTransition; } - + public class StateChange { /** * timestamp @@ -72,7 +67,7 @@ public class StateChange { * current new state */ public String state; - + /** * event which activated new state */ @@ -82,13 +77,12 @@ public class StateChange { * source of event */ public String src = getName(); - - + public StateChange(String current, String event) { this.state = current; this.event = event; } - + public String toString() { return String.format("%s --%s--> %s", last, event, state); } @@ -221,25 +215,25 @@ public String firedEvent(String event) { } /** - * gets the current state of this state machine + * get the previous state of this state machine * * @return */ - public String getCurrent() { - if (current != null) { - return current.getName(); + public String getLast() { + if (last != null) { + return last.getName(); } return null; } /** - * get the previous state of this state machine + * gets the current state of this state machine * * @return */ - public String getLast() { - if (last != null) { - return last.getName(); + public String getState() { + if (current != null) { + return current.getName(); } return null; } @@ -250,7 +244,7 @@ public List<Transition> getTransitions() { } /** - * Publishes state change (current, last and event) + * Publishes state change (current, last and event) * * @param stateChange * @return @@ -263,7 +257,7 @@ public StateChange publishStateChange(StateChange stateChange) { @Override public FiniteStateMachineConfig getConfig() { super.getConfig(); - config.current = getCurrent(); + config.current = getState(); return config; } @@ -361,15 +355,15 @@ public static void main(String[] args) { // fsm.subscribe("fsm", "publishState"); - log.info("state - {}", fsm.getCurrent()); + log.info("state - {}", fsm.getState()); fsm.setCurrent("neutral"); - log.info("state - {}", fsm.getCurrent()); + log.info("state - {}", fsm.getState()); fsm.fire("ill-event"); - log.info("state - {}", fsm.getCurrent()); + log.info("state - {}", fsm.getState()); fsm.fire("ill-event"); fsm.fire("ill-event"); @@ -387,7 +381,7 @@ public static void main(String[] args) { // fsm.removeScheduledEvents(); - log.info("state - {}", fsm.getCurrent()); + log.info("state - {}", fsm.getState()); } catch (Exception e) { log.error("main threw", e); @@ -419,7 +413,7 @@ public String getPreviousState() { return history.get(history.size() - 2).state; } } - + @Override public void startService() { super.startService(); diff --git a/src/main/java/org/myrobotlab/service/RoboClaw.java b/src/main/java/org/myrobotlab/service/RoboClaw.java index 83203b99f8..8d22f7ff27 100644 --- a/src/main/java/org/myrobotlab/service/RoboClaw.java +++ b/src/main/java/org/myrobotlab/service/RoboClaw.java @@ -16,7 +16,7 @@ import org.myrobotlab.serial.CRC; import org.myrobotlab.service.Pid.PidData; import org.myrobotlab.service.abstracts.AbstractMotorController; -import org.myrobotlab.service.config.MotorConfig; +import org.myrobotlab.service.config.AbstractMotorControllerConfig; import org.myrobotlab.service.interfaces.MotorControl; import org.myrobotlab.service.interfaces.MotorController; import org.myrobotlab.service.interfaces.PortConnector; @@ -55,7 +55,7 @@ * this value IS correct * */ -public class RoboClaw extends AbstractMotorController<MotorConfig> implements EncoderPublisher, PortConnector, MotorController, SerialDataListener { +public class RoboClaw extends AbstractMotorController<AbstractMotorControllerConfig> implements EncoderPublisher, PortConnector, MotorController, SerialDataListener { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 675123e962..3d3697284a 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -316,12 +316,6 @@ public class Runtime extends Service<RuntimeConfig> implements MessageListener, protected List<String> configList; - /*** - * runtime, security, webgui, perhaps python - we don't want to remove when - * releasing config - */ - protected Set<String> startingServices = new HashSet<>(); - /** * Wraps {@link java.lang.Runtime#availableProcessors()}. * @@ -580,7 +574,7 @@ public final static void createAndStartServices(List<String> services) { Logging.logError(e); } } else { - runtime.error(String.format("could not create service %1$s %2$s", name, type)); + runtime.error(String.format("could not create service %s %s", name, type)); } } @@ -910,9 +904,6 @@ public static Runtime getInstance() { // klunky Runtime.register(new Registration(runtime)); - // assign, do not apply otherwise there will be - // a chicken-egg problem - runtime.config = c; } runtime.getRepo().addStatusPublisher(runtime); @@ -4924,8 +4915,18 @@ static public void releaseConfigPath(String configPath) { RuntimeConfig config = CodecUtils.fromYaml(releaseData, RuntimeConfig.class); List<String> registry = config.getRegistry(); Collections.reverse(Arrays.asList(registry)); + + // get starting services if any entered on the command line + // -s log Log webgui WebGui ... etc - these will be protected + List<String> startingServices = new ArrayList<>(); + if (options.services.size() % 2 == 0) { + for (int i = 0; i < options.services.size(); i += 2) { + startingServices.add(options.services.get(i)); + } + } + for (String name : registry) { - if (Runtime.getInstance().startingServices.contains(name)) { + if (startingServices.contains(name)) { continue; } release(name); diff --git a/src/main/java/org/myrobotlab/service/Servo.java b/src/main/java/org/myrobotlab/service/Servo.java index 3e0b46ce95..7b59121998 100644 --- a/src/main/java/org/myrobotlab/service/Servo.java +++ b/src/main/java/org/myrobotlab/service/Servo.java @@ -61,7 +61,7 @@ * */ -public class Servo extends AbstractServo<ServoConfig> implements ServiceLifeCycleListener { +public class Servo extends AbstractServo<ServoConfig> { private static final long serialVersionUID = 1L; @@ -259,95 +259,10 @@ public static void main(String[] args) throws InterruptedException { return; } - // runtime.save(); - - /* - * mega.save(); tilt.save(); pan.save(); - * - * mega.load(); tilt.load(); pan.load(); - */ - - // TODO - attach before and after connect.. - - // mega.setBoardMega(); - - // log.info("servo pos {}", tilt.getCurrentInputPos()); - // - // // double pos = 170; - // // servo03.setPosition(pos); - // - // double min = 3; - // double max = 170; - // double speed = 60; // degree/s - // - // mega.attach(tilt); - // // mega.attach(servo03,3); - // - // for (int i = 0; i < 100; ++i) { - // tilt.moveTo(20.0); - // } - // - // tilt.sweep(min, max, speed); - - /* - * Servo servo04 = (Servo) Runtime.start("servo04", "Servo"); Servo - * servo05 = (Servo) Runtime.start("servo05", "Servo"); Servo servo06 = - * (Servo) Runtime.start("servo06", "Servo"); Servo servo07 = (Servo) - * Runtime.start("servo07", "Servo"); Servo servo08 = (Servo) - * Runtime.start("servo08", "Servo"); Servo servo09 = (Servo) - * Runtime.start("servo09", "Servo"); Servo servo10 = (Servo) - * Runtime.start("servo10", "Servo"); Servo servo11 = (Servo) - * Runtime.start("servo11", "Servo"); Servo servo12 = (Servo) - * Runtime.start("servo12", "Servo"); - */ - // Servo servo13 = (Servo) Runtime.start("servo13", "Servo"); - - // servo03.attach(mega, 8, 38.0); - /* - * servo04.attach(mega, 4, 38.0); servo05.attach(mega, 5, 38.0); - * servo06.attach(mega, 6, 38.0); servo07.attach(mega, 7, 38.0); - * servo08.attach(mega, 8, 38.0); servo09.attach(mega, 9, 38.0); - * servo10.attach(mega, 10, 38.0); servo11.attach(mega, 11, 38.0); - * servo12.attach(mega, 12, 38.0); - */ - - // TestCatcher catcher = (TestCatcher)Runtime.start("catcher", - // "TestCatcher"); - // servo03.attach((ServoEventListener)catcher); - - // servo.setPin(12); - - /* - * servo.attach(mega, 7, 38.0); servo.attach(mega, 7, 38.0); - * servo.attach(mega, 7, 38.0); servo.attach(mega, 7, 38.0); - * servo.attach(mega, 7, 38.0); servo.attach(mega, 7, 38.0); - * servo.attach(mega, 7, 38.0); servo.attach(mega, 7, 38.0); - * servo.attach(mega, 7, 38.0); servo.attach(mega, 7, 38.0); - * servo.attach(mega, 7, 38.0); servo.attach(mega, 7, 38.0); - */ - - // servo.sweepDelay = 3; - // servo.save(); - // servo.load(); - // servo.save(); - // log.info("sweepDely {}", servo.sweepDelay); } catch (Exception e) { log.error("main threw", e); } } - @Override - public void onCreated(String name) { - } - - @Override - public void onStopped(String name) { - } - - @Override - public void onReleased(String name) { - } - - } diff --git a/src/main/java/org/myrobotlab/service/WebGui.java b/src/main/java/org/myrobotlab/service/WebGui.java index 5e87cddb05..f50cdc238a 100644 --- a/src/main/java/org/myrobotlab/service/WebGui.java +++ b/src/main/java/org/myrobotlab/service/WebGui.java @@ -34,7 +34,6 @@ import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.MRLListener; import org.myrobotlab.framework.Message; -import org.myrobotlab.framework.Platform; import org.myrobotlab.framework.Registration; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.interfaces.ServiceInterface; @@ -62,8 +61,7 @@ * services are already APIs - perhaps a data API - same as service without the * message wrapper */ -public class WebGui extends Service<WebGuiConfig> - implements AuthorizationProvider, Gateway, Handler, ServiceLifeCycleListener { +public class WebGui extends Service<WebGuiConfig> implements AuthorizationProvider, Gateway, Handler, ServiceLifeCycleListener { public static class LiveVideoStreamHandler implements Handler { @@ -128,7 +126,7 @@ public Panel(String name, int x, int y, int z) { * needed to get the api key to select the appropriate api processor * * @param uri - * u + * u * @return api key * */ @@ -271,9 +269,9 @@ public boolean getAutoStartBrowser() { * String broadcast to specific client * * @param uuid - * u + * u * @param str - * s + * s * */ public void broadcast(String uuid, String str) { @@ -315,9 +313,7 @@ public Config.Builder getNettosphereConfig() { // cert.privateKey()).build(); SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); - SslContext context = SslContextBuilder - .forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()) - .sslProvider(SslProvider.JDK) + SslContext context = SslContextBuilder.forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()).sslProvider(SslProvider.JDK) .clientAuth(ClientAuth.NONE).build(); configBuilder.sslContext(context); @@ -496,8 +492,7 @@ public void handle(AtmosphereResource r) { } else if ((bodyData != null) && log.isDebugEnabled()) { logData = bodyData; } - log.debug("-->{} {} {} - [{}] from connection {}", (newPersistentConnection) ? "new" : "", request.getMethod(), - request.getRequestURI(), logData, uuid); + log.debug("-->{} {} {} - [{}] from connection {}", (newPersistentConnection) ? "new" : "", request.getMethod(), request.getRequestURI(), logData, uuid); } // important persistent connections will have associated routes ... @@ -575,8 +570,7 @@ public void handle(AtmosphereResource r) { } if (msg.containsHop(getId())) { - log.error("{} dumping duplicate hop msg to avoid cyclical from {} --to--> {}.{}", getName(), msg.sender, - msg.name, msg.method); + log.error("{} dumping duplicate hop msg to avoid cyclical from {} --to--> {}.{}", getName(), msg.sender, msg.name, msg.method); return; } @@ -920,7 +914,7 @@ public void run() { * remotely control UI * * @param panel - * - the panel which has been moved or resized + * - the panel which has been moved or resized */ public void savePanel(Panel panel) { if (panel.name == null) { @@ -1107,7 +1101,7 @@ public void releaseService() { * Default (false) is to use the CDN * * @param useLocalResources - * - true uses local resources fals uses cdn + * - true uses local resources fals uses cdn */ public void useLocalResources(boolean useLocalResources) { this.useLocalResources = useLocalResources; @@ -1183,8 +1177,7 @@ public static void main(String[] args) { try { - Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui","intro", "Intro", "python", "Python" }); - // Runtime.main(new String[] {}); + Runtime.main(new String[] { "--log-level", "info", "-s", "log", "Log", "webgui", "WebGui", "intro", "Intro", "python", "Python" }); // Runtime.main(new String[] { "--install" }); boolean done = true; @@ -1193,7 +1186,8 @@ public static void main(String[] args) { } // Platform.setVirtual(true); - // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui", + // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", + // "WebGui", // "intro", "Intro", "python", "Python", "-c", "dev" }); // Runtime.startConfig("dev"); @@ -1248,8 +1242,7 @@ public static void main(String[] args) { arduino.connect("/dev/ttyACM0"); for (int i = 0; i < 1000; ++i) { - webgui.display( - "https://i.kinja-img.com/gawker-media/image/upload/c_scale,f_auto,fl_progressive,q_80,w_800/pytutcxcrfjvuhz2jipa.jpg"); + webgui.display("https://i.kinja-img.com/gawker-media/image/upload/c_scale,f_auto,fl_progressive,q_80,w_800/pytutcxcrfjvuhz2jipa.jpg"); } // Runtime.setLogLevel("ERROR"); diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractMotorController.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractMotorController.java index 5f01ebc74f..94380c9711 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractMotorController.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractMotorController.java @@ -8,11 +8,11 @@ import org.myrobotlab.math.MapperLinear; import org.myrobotlab.math.interfaces.Mapper; import org.myrobotlab.service.Runtime; -import org.myrobotlab.service.config.MotorConfig; +import org.myrobotlab.service.config.AbstractMotorControllerConfig; import org.myrobotlab.service.interfaces.MotorControl; import org.myrobotlab.service.interfaces.MotorController; -public abstract class AbstractMotorController<C extends MotorConfig> extends Service<C> implements MotorController { +public abstract class AbstractMotorController<C extends AbstractMotorControllerConfig> extends Service<C> implements MotorController { /** * currently attached motors to this controller diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java index 3378d5e56d..7eee94dc5a 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractServo.java @@ -6,7 +6,6 @@ import java.util.Set; import org.myrobotlab.codec.CodecUtils; -import org.myrobotlab.framework.Registration; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.interfaces.Attachable; import org.myrobotlab.logging.LoggerFactory; @@ -217,16 +216,6 @@ public abstract class AbstractServo<C extends ServoConfig> extends Service<C> im public AbstractServo(String n, String id) { super(n, id); - // this servo is interested in new services which support either - // ServoControllers or EncoderControl interfaces - // we subscribe to runtime here for new services - subscribeToRuntime("registered"); - /* - * // new feature - // extracting the currentPos from serialized servo - * Double lastCurrentPos = null; try { lastCurrentPos = (Double) - * loadField("currentPos"); } catch (IOException e) { - * log.info("current pos cannot be found in saved file"); } - */ // if no position could be loaded - set to rest // we have no "historical" info - assume we are @ rest targetPos = rest; @@ -243,17 +232,6 @@ public AbstractServo(String n, String id) { } } - /** - * if a new service is added to the system refresh the controllers - */ - @Deprecated /* - * lifecycle events not necessary for ui, probably should be - * pulled out - */ - public void onStarted(String name) { - invoke("refreshControllers"); - } - /** * overloaded routing attach */ @@ -456,6 +434,12 @@ public void enable() { public void fullSpeed() { setSpeed((Double) null); } + + @Override + public void setMaxSpeed() { + setSpeed((Double) null); + } + @Override public boolean isAutoDisable() { @@ -697,10 +681,6 @@ public void onEncoderData(EncoderData data) { } } - public void onRegistered(Registration s) { - refreshControllers(); - } - /** * Servo has the ability to act as an encoder if it is using TimeEncoder. * TimeEncoder will use Servo to publish a series of encoder events with @@ -918,6 +898,11 @@ public void setSpeed(Double degreesPerSecond) { // speed = maxSpeed; // log.info("Trying to set speed to a value greater than max speed"); // } + + if (degreesPerSecond != null && degreesPerSecond < 0) { + warn("setting speed to negative value %d ignoring", degreesPerSecond); + return; + } speed = degreesPerSecond; @@ -1096,7 +1081,6 @@ public ServoEvent publishServoStopped(String name, Double position) { @Override public void startService() { super.startService(); - Runtime.getInstance().attachServiceLifeCycleListener(getName()); } @Override diff --git a/src/main/java/org/myrobotlab/service/config/AbstractMotorControllerConfig.java b/src/main/java/org/myrobotlab/service/config/AbstractMotorControllerConfig.java new file mode 100644 index 0000000000..030a1ee4f0 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/config/AbstractMotorControllerConfig.java @@ -0,0 +1,5 @@ +package org.myrobotlab.service.config; + +public class AbstractMotorControllerConfig extends ServiceConfig { + // Add your configuration here +} diff --git a/src/main/java/org/myrobotlab/service/config/ArduinoConfig.java b/src/main/java/org/myrobotlab/service/config/ArduinoConfig.java index 1f83bee895..3e4adf8f83 100644 --- a/src/main/java/org/myrobotlab/service/config/ArduinoConfig.java +++ b/src/main/java/org/myrobotlab/service/config/ArduinoConfig.java @@ -4,7 +4,16 @@ public class ArduinoConfig extends ServiceConfig { + /** + * Port (usb or ip:port) to connect) + */ public String port; + + /** + * If you want the arduino to try to connect + * port must not be null. + * This is not a status field. + */ public boolean connect; @Override diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 34c8da7d08..889e91eec9 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -55,7 +55,7 @@ public class InMoov2Config extends ServiceConfig { * fire events to the FSM. Checks battery level and sends a heartbeat flash on * publishHeartbeat and onHeartbeat at a regular interval */ - public boolean heartbeat = false; + public boolean heartbeat = true; /** * flashes the neopixel every time a health check is preformed. green == good @@ -68,17 +68,17 @@ public class InMoov2Config extends ServiceConfig { */ public long heartbeatInterval = 3000; - public boolean loadAppsScripts = true; + public boolean loadAppsScripts = false; /** * loads all python gesture files in the gesture directory */ - public boolean loadGestures = true; + public boolean loadGestures = false; /** * executes all scripts in the init directory on startup */ - public boolean loadInitScripts = true; + public boolean loadInitScripts = false; /** * default to null - allow the OS to set it, unless explicilty set @@ -232,9 +232,17 @@ public Plan getDefault(Plan plan, String name) { } mouthControl.mouth = i01Name + ".mouth"; - + + UltrasonicSensorConfig ultrasonicLeft = (UltrasonicSensorConfig) plan.get(getPeerName("ultrasonicLeft")); + ultrasonicLeft.triggerPin = 64; + ultrasonicLeft.echoPin = 63; + + UltrasonicSensorConfig ultrasonicRight = (UltrasonicSensorConfig) plan.get(getPeerName("ultrasonicRight")); + ultrasonicRight.triggerPin = 64; + ultrasonicRight.echoPin = 63; + + ProgramABConfig chatBot = (ProgramABConfig) plan.get(getPeerName("chatBot")); - chatBot.botDir = "resource/ProgramAB"; chatBot.bots.add("resource/ProgramAB/Alice"); chatBot.bots.add("resource/ProgramAB/Dr.Who"); @@ -267,8 +275,6 @@ public Plan getDefault(Plan plan, String name) { } } - chatBot.currentUserName = "human"; - chatBot.listeners.add(new Listener("publishText", name + ".htmlFilter", "onText")); Gpt3Config gpt3 = (Gpt3Config) plan.get(getPeerName("gpt3")); @@ -283,8 +289,7 @@ public Plan getDefault(Plan plan, String name) { // setup name references to different services MarySpeechConfig mouth = (MarySpeechConfig) plan.get(getPeerName("mouth")); mouth.voice = "Mark"; - mouth.speechRecognizers = new String[] { name + ".ear" }; - + // == Peer - ear ============================= // setup name references to different services WebkitSpeechRecognitionConfig ear = (WebkitSpeechRecognitionConfig) plan.get(getPeerName("ear")); @@ -365,16 +370,17 @@ public Plan getDefault(Plan plan, String name) { // exists ? fsm.current = "boot"; fsm.transitions.add(new Transition("boot", "wake", "wake")); - fsm.transitions.add(new Transition("wake", "idle", "idle")); - fsm.transitions.add(new Transition("first_init", "idle", "idle")); + // fsm.transitions.add(new Transition("wake", "idle", "idle")); wake, setup, nor sleep should be affected by idle + fsm.transitions.add(new Transition("setup", "setup_done", "idle")); 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("sleep", "power_down", "power_down")); fsm.transitions.add(new Transition("idle", "power_down", "power_down")); - fsm.transitions.add(new Transition("wake", "first_init", "first_init")); - fsm.transitions.add(new Transition("idle", "first_init", "first_init")); + fsm.transitions.add(new Transition("wake", "setup", "setup")); + fsm.transitions.add(new Transition("wake", "idle", "idle")); + fsm.transitions.add(new Transition("idle", "setup", "setup")); // power_down to shutdown // fsm.transitions.add(new Transition("systemCheck", "systemCheckFinished", // "awake")); @@ -520,7 +526,11 @@ public Plan getDefault(Plan plan, String name) { 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("publishProcessMessage", getPeerName("py4j"), "onPythonMessage")); + // listeners.add(new Listener("publishProcessMessage", + // getPeerName("python"), "onPythonMessage")); + listeners.add(new Listener("publishProcessMessage", "python", "onPythonMessage")); + + listeners.add(new Listener("publishPython", "python")); // InMoov2 --to--> InMoov2 listeners.add(new Listener("publishMoveHead", getPeerName("head"), "onMove")); @@ -533,6 +543,8 @@ public Plan getDefault(Plan plan, String name) { // service --to--> InMoov2 AudioFileConfig mouth_audioFile = (AudioFileConfig) plan.get(getPeerName("mouth.audioFile")); mouth_audioFile.listeners.add(new Listener("publishPeak", name)); + + htmlFilter.listeners.add(new Listener("publishText", name)); OakDConfig oakd = (OakDConfig) plan.get(getPeerName("oakd")); oakd.listeners.add(new Listener("publishClassification", name)); @@ -544,7 +556,11 @@ public Plan getDefault(Plan plan, String name) { // mouth_audioFile.listeners.add(new Listener("publishAudioStart", name)); // Needs upcoming pr - // fsm.listeners.add(new Listener("publishStateChange", name)); + fsm.listeners.add(new Listener("publishStateChange", name, "publishStateChange")); + + // peer --to--> peer + mouth.listeners.add(new Listener("publishStartSpeaking", getPeerName("ear"))); + mouth.listeners.add(new Listener("publishEndSpeaking", getPeerName("ear"))); return plan; } diff --git a/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java b/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java index ce9ae14033..0cd4dbc839 100644 --- a/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java +++ b/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java @@ -10,11 +10,6 @@ public class ProgramABConfig extends ServiceConfig { @Deprecated /* unused text filters */ public String[] textFilters; - /** - * a directory ProgramAB will scan for new bots - */ - public String botDir; - /** * explicit bot directories */ diff --git a/src/main/java/org/myrobotlab/service/config/SabertoothConfig.java b/src/main/java/org/myrobotlab/service/config/SabertoothConfig.java index 42db50689b..44dc5a0c5b 100644 --- a/src/main/java/org/myrobotlab/service/config/SabertoothConfig.java +++ b/src/main/java/org/myrobotlab/service/config/SabertoothConfig.java @@ -2,7 +2,7 @@ import org.myrobotlab.framework.Plan; -public class SabertoothConfig extends MotorConfig { +public class SabertoothConfig extends AbstractMotorControllerConfig { public String port; public boolean connect = false; diff --git a/src/main/java/org/myrobotlab/service/config/UltrasonicSensorConfig.java b/src/main/java/org/myrobotlab/service/config/UltrasonicSensorConfig.java index f48ec59e49..2c23767e33 100644 --- a/src/main/java/org/myrobotlab/service/config/UltrasonicSensorConfig.java +++ b/src/main/java/org/myrobotlab/service/config/UltrasonicSensorConfig.java @@ -10,11 +10,13 @@ public class UltrasonicSensorConfig extends ServiceConfig { /** * pulse pin */ + @Deprecated /* Pins need to be Strings eg "D64" */ public Integer triggerPin; /** * listening pin */ + @Deprecated /* Pins need to be Strings eg "D63" */ public Integer echoPin; /** diff --git a/src/main/java/org/myrobotlab/service/interfaces/ServoControl.java b/src/main/java/org/myrobotlab/service/interfaces/ServoControl.java index 1e140ce167..379154cd6e 100644 --- a/src/main/java/org/myrobotlab/service/interfaces/ServoControl.java +++ b/src/main/java/org/myrobotlab/service/interfaces/ServoControl.java @@ -404,7 +404,12 @@ public interface ServoControl extends AbsolutePositionControl, EncoderListener, /** * disable speed control and move the servos at full speed. */ - @Deprecated /* implement setSpeed(null) */ + @Deprecated void fullSpeed(); - + + /** + * + */ + void setMaxSpeed(); + } diff --git a/src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java b/src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java index 1b8f134beb..73d284cdb6 100644 --- a/src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java @@ -16,7 +16,7 @@ public OpenCVMeta() { addDescription("OpenCV (computer vision) service wrapping many of the functions and filters of OpenCV"); addCategory("video", "vision", "sensors"); - String javaCvVersion = "1.5.7"; + String javaCvVersion = "1.5.8"; // addDependency("org.bytedeco", "javacv", javaCvVersion); addDependency("org.bytedeco", "javacv-platform", javaCvVersion); addDependency("org.bytedeco", "javacpp", javaCvVersion); diff --git a/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java b/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java index 5d15601b58..fb83ed0777 100644 --- a/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java +++ b/src/test/java/org/myrobotlab/config/ConfigUtilsTest.java @@ -6,6 +6,7 @@ import org.junit.Before; import org.junit.Test; import org.myrobotlab.framework.StartYml; +import org.myrobotlab.io.FileIO; import org.myrobotlab.service.Runtime; public class ConfigUtilsTest { @@ -13,6 +14,8 @@ public class ConfigUtilsTest { @Before public void beforeTest() { Runtime.releaseAll(true, true); + // remove config + FileIO.rm("data/config/default"); } @Test diff --git a/src/test/java/org/myrobotlab/framework/BlockingTest.java b/src/test/java/org/myrobotlab/framework/BlockingTest.java index b4ce03e8da..3b9788645b 100644 --- a/src/test/java/org/myrobotlab/framework/BlockingTest.java +++ b/src/test/java/org/myrobotlab/framework/BlockingTest.java @@ -28,13 +28,13 @@ public void blockingTest() throws Exception { Message msg = Message.createMessage("thower07", "catcher07", "onInt", 3); Integer ret = (Integer)thower07.sendBlocking(msg, null); - assertEquals(simpleName, 3, (int)ret); + assertEquals(3, (int)ret); long startTime = System.currentTimeMillis(); msg = Message.createMessage("thower07", "catcher07", "waitForThis", new Object[] {7, 1000}); ret = (Integer)thower07.sendBlocking(msg, null); assertTrue("1s process", System.currentTimeMillis() - startTime > 500); - assertEquals(simpleName, 7, (int)ret); + assertEquals(7, (int)ret); Runtime.release("catcher07"); Runtime.release("thower07"); diff --git a/src/test/java/org/myrobotlab/framework/repo/RepoTest.java b/src/test/java/org/myrobotlab/framework/repo/RepoTest.java index f2715dc90d..d0e2c57735 100644 --- a/src/test/java/org/myrobotlab/framework/repo/RepoTest.java +++ b/src/test/java/org/myrobotlab/framework/repo/RepoTest.java @@ -28,6 +28,10 @@ public static void lastCleanup() { repo.clear(); installed = false; } + + public String getName() { + return "RepoTest"; + } @Override public void broadcastStatus(Status status) { diff --git a/src/test/java/org/myrobotlab/service/ArduinoTest.java b/src/test/java/org/myrobotlab/service/ArduinoTest.java index 7a6422d29d..00cee5e0a7 100644 --- a/src/test/java/org/myrobotlab/service/ArduinoTest.java +++ b/src/test/java/org/myrobotlab/service/ArduinoTest.java @@ -62,7 +62,6 @@ private void assertVirtualPinValue(VirtualArduino virtual, int address, int valu } } - @Override public String getName() { return "arduinoTest"; } diff --git a/src/test/java/org/myrobotlab/service/RandomTest.java b/src/test/java/org/myrobotlab/service/RandomTest.java index 7c8add5923..f089f2e453 100644 --- a/src/test/java/org/myrobotlab/service/RandomTest.java +++ b/src/test/java/org/myrobotlab/service/RandomTest.java @@ -7,31 +7,24 @@ import java.util.Map; import org.junit.Before; -import org.myrobotlab.framework.Service; +import org.junit.Test; import org.myrobotlab.service.Random.RandomMessage; +import org.myrobotlab.test.AbstractTest; -public class RandomTest extends AbstractServiceTest { +public class RandomTest extends AbstractTest { - @Override /* - * FIXME - this assumes a single service is in the test - which - * rarely happens - seems not useful and silly - */ - public Service createService() throws Exception { - return (Service) Runtime.start("randomTest", "Random"); - } - @Before /* before each test */ public void setUp() throws IOException { // remove all services - also resets config name to DEFAULT effectively Runtime.releaseAll(true, true); - // clean our config directory + // clean our config directory // Runtime.removeConfig("RandomTest"); // set our config Runtime.setConfig("RandomTest"); + Runtime.start("randomTest", "Random"); } - - @Override + @Test public void testService() throws Exception { Clock clock = (Clock) Runtime.start("clock", "Clock"); Random random = (Random) Runtime.start("randomTest", "Random"); @@ -46,62 +39,63 @@ public void testService() throws Exception { sleep(1000); 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 <= 10000 values", clock.getInterval()), clock.getInterval() <= 10000); + random.remove("clock.setInterval"); - + assertTrue("should not have method", !random.getKeySet().contains("clock.setInterval")); random.addRandom(0, 200, "clock", "setInterval", 5000, 10000); random.addRandom(0, 200, "clock", "startClock"); - + sleep(500); assertTrue("clock should be started 1", clock.isClockRunning()); - + // disable all of a services random events random.disable("clock.startClock"); - clock.stopClock(); sleep(250); + clock.stopClock(); assertTrue("clock should not be started 1", !clock.isClockRunning()); - + // enable all of a service's random events random.enable("clock.startClock"); sleep(250); assertTrue("clock should be started 2", clock.isClockRunning()); - + // disable one method - leave other enabled random.disable("clock.startClock"); clock.stopClock(); - clock.setInterval(9999); 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 <= 10000 values", clock.getInterval()), clock.getInterval() <= 10000); // disable all random.disable(); sleep(200); clock.setInterval(9999); - assertTrue("clock should not be started 4", !clock.isClockRunning()); - assertEquals(9999, (long)clock.getInterval()); + assertTrue("clock should not be started 4", !clock.isClockRunning()); + assertEquals(9999, (long) clock.getInterval()); - // re-enable all that were previously enabled but not explicitly disabled ones + // re-enable all that were previously enabled but not explicitly disabled + // ones 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 <= 10000 values", clock.getInterval()), clock.getInterval() <= 10000); clock.stopClock(); random.purge(); - + Map<String, RandomMessage> events = random.getRandomEvents(); assertTrue(events.size() == 0); - + random.addRandom("named task", 200, 500, "clock", "setInterval", 100, 1000, 10); - + clock.releaseService(); random.releaseService(); diff --git a/src/test/java/org/myrobotlab/service/RuntimeProcessTest.java b/src/test/java/org/myrobotlab/service/RuntimeProcessTest.java index 07e1775110..4bdb93fe2c 100644 --- a/src/test/java/org/myrobotlab/service/RuntimeProcessTest.java +++ b/src/test/java/org/myrobotlab/service/RuntimeProcessTest.java @@ -21,12 +21,15 @@ public class RuntimeProcessTest extends AbstractTest { @Before public void setUp() { - // LoggingFactory.init("WARN"); } public boolean contains(ByteArrayOutputStream out, String str) { return new String(out.toByteArray()).contains(str); } + + public String getName() { + return "RuntimeProcessTest"; + } @Test public void cliTest() throws Exception { diff --git a/src/test/java/org/myrobotlab/service/ServoTest.java b/src/test/java/org/myrobotlab/service/ServoTest.java index 8d96052e2e..819c3718d6 100644 --- a/src/test/java/org/myrobotlab/service/ServoTest.java +++ b/src/test/java/org/myrobotlab/service/ServoTest.java @@ -73,7 +73,7 @@ public void autoDisableAfterAttach() { @Test public void disabledMove() throws Exception { // take off speed control - servo.fullSpeed(); + servo.setMaxSpeed(); servo.moveTo(0.0); servo.setInverted(false); Service.sleep(1000); diff --git a/src/test/java/org/myrobotlab/service/VirtualArduinoTest.java b/src/test/java/org/myrobotlab/service/VirtualArduinoTest.java index ba1d028096..110ff1ac4a 100755 --- a/src/test/java/org/myrobotlab/service/VirtualArduinoTest.java +++ b/src/test/java/org/myrobotlab/service/VirtualArduinoTest.java @@ -30,6 +30,10 @@ public Service createService() { VirtualArduino service = (VirtualArduino) Runtime.start("virtualArduino", "VirtualArduino"); return service; } + + public String getName() { + return "VirtualArduinoTest"; + } @Override public void testService() throws Exception { diff --git a/src/test/java/org/myrobotlab/service/WebGuiTest.java b/src/test/java/org/myrobotlab/service/WebGuiTest.java index 643485fc0c..02918dedba 100644 --- a/src/test/java/org/myrobotlab/service/WebGuiTest.java +++ b/src/test/java/org/myrobotlab/service/WebGuiTest.java @@ -5,7 +5,6 @@ import static org.junit.Assert.assertTrue; import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; import java.util.List; import org.junit.Before; @@ -22,8 +21,8 @@ public class WebGuiTest extends AbstractTest { public final static Logger log = LoggerFactory.getLogger(WebGui.class); - - // FIXME - DO A WEBSOCKET TEST + + // FIXME - DO A WEBSOCKET TEST @Before public void setUp() { @@ -31,11 +30,11 @@ public void setUp() { webgui2.autoStartBrowser(false); webgui2.setPort(8889); webgui2.startService(); - - Runtime.start("servoApiTest","Servo"); + + Runtime.start("servoApiTest", "Servo"); Runtime.start("pythonApiTest", "Python"); // need to wait for the OS to open the port - Service.sleep(3); + Service.sleep(200); } @Test @@ -46,7 +45,7 @@ public void getTest() { String ret = new String(bytes); assertTrue(ret.contains("days")); } - + @Test public void getTestWithParameter() throws UnsupportedEncodingException { @@ -56,13 +55,12 @@ public void getTestWithParameter() throws UnsupportedEncodingException { assertTrue(ret.contains("true")); } - -// FIXME - ADD WHEN POST API IS WORKY -// FIXME object non primitive (no string) post + // FIXME - ADD WHEN POST API IS WORKY + // FIXME object non primitive (no string) post @Test public void postTest() { - + // 1st post - simple input - simple return String postBody = "[\"runtime\"]"; byte[] bytes = Http.post("http://localhost:8889/api/service/runtime/getFullName", postBody); @@ -70,7 +68,7 @@ public void postTest() { assertNotNull(bytes); String ret = new String(bytes); assertTrue(ret.contains("@")); - + // second post - simple input - complex return postBody = "[\"runtime\"]"; bytes = Http.post("http://localhost:8889/api/service/runtime/getService", postBody); @@ -78,29 +76,31 @@ public void postTest() { assertNotNull(bytes); ret = new String(bytes); assertTrue(ret.contains("@")); - - + // second post - simple input (including array of strings) - complex return - // FIXME uncomment when ready - callbacks are not possible through the rest api - // org.myrobotlab.framework.TimeoutException: timeout of 3000 for proxyName@remoteId.toString exceeded - // org.myrobotlab.framework.TimeoutException: timeout of 3000 for proxyName@remoteId.getFullName exceeded -// postBody = "[\"remoteId\", \"proxyName\", \"py:myService\",[\"org.myrobotlab.framework.interfaces.ServiceInterface\"]]"; -// bytes = Http.post("http://localhost:8889/api/service/runtime/register", postBody); -// sleep(200); -// assertNotNull(bytes); -// ret = new String(bytes); -// assertTrue(ret.contains("remoteId")); - - - + // FIXME uncomment when ready - callbacks are not possible through the rest + // api + // org.myrobotlab.framework.TimeoutException: timeout of 3000 for + // proxyName@remoteId.toString exceeded + // org.myrobotlab.framework.TimeoutException: timeout of 3000 for + // proxyName@remoteId.getFullName exceeded + // postBody = "[\"remoteId\", \"proxyName\", + // \"py:myService\",[\"org.myrobotlab.framework.interfaces.ServiceInterface\"]]"; + // bytes = Http.post("http://localhost:8889/api/service/runtime/register", + // postBody); + // sleep(200); + // assertNotNull(bytes); + // ret = new String(bytes); + // assertTrue(ret.contains("remoteId")); + // post non primitive non string object MRLListener listener = new MRLListener("getRegistry", "runtime@webguittest", "onRegistry"); - postBody = "[" + CodecUtils.toJson(listener) + "]"; + postBody = "[" + CodecUtils.toJson(listener) + "]"; // postBody = "[\"runtime\"]"; bytes = Http.post("http://localhost:8889/api/service/runtime/addListener", postBody); sleep(200); assertNotNull(bytes); - + Runtime runtime = Runtime.getInstance(); boolean found = false; List<MRLListener> check = runtime.getNotifyList("getRegistry"); @@ -108,9 +108,9 @@ public void postTest() { if (check.get(i).equals(listener)) { found = true; } - } + } assertTrue("listener not found !", found); - + } @Test @@ -138,7 +138,7 @@ public void servoApiTest() { @Test public void urlEncodingTest() { - //exec("print \"hello\"") + // exec("print \"hello\"") byte[] bytes = Http.get("http://localhost:8889/api/service/pythonApiTest/exec/%22print+%5C%22hello%5C%22%22"); String ret = new String(bytes); assertEquals("true", ret); @@ -147,16 +147,19 @@ public void urlEncodingTest() { @Test public void sendBlockingTest() throws InterruptedException, TimeoutException { String retVal = "retVal"; - // Put directly in blocking list because sendBlocking() won't use it for local services + // Put directly in blocking list because sendBlocking() won't use it for + // local + // services Runtime.getInstance().getInbox().blockingList.put("runtime.onBlocking", new Object[1]); Object[] blockingListRet = Runtime.getInstance().getInbox().blockingList.get("runtime.onBlocking"); // Delay in a new thread so we can get our wait() call in first new Thread(() -> { try { - Thread.sleep(50); - } catch (InterruptedException ignored) {} - Http.post("http://localhost:8889/api/service/runtime/onBlocking", "[\""+retVal+"\"]"); + Thread.sleep(100); + } catch (InterruptedException ignored) { + } + Http.post("http://localhost:8889/api/service/runtime/onBlocking", "[\"" + retVal + "\"]"); }).start(); long timeout = 1000; @@ -170,6 +173,5 @@ public void sendBlockingTest() throws InterruptedException, TimeoutException { assertEquals(retVal, blockingListRet[0]); } - - + } diff --git a/src/test/java/org/myrobotlab/test/AbstractTest.java b/src/test/java/org/myrobotlab/test/AbstractTest.java index d782797c9b..7279a8fe98 100644 --- a/src/test/java/org/myrobotlab/test/AbstractTest.java +++ b/src/test/java/org/myrobotlab/test/AbstractTest.java @@ -14,82 +14,61 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Rule; -import org.junit.rules.TestName; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; import org.myrobotlab.codec.CodecUtils; -import org.myrobotlab.framework.Platform; import org.myrobotlab.framework.interfaces.Attachable; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.Runtime; import org.myrobotlab.service.config.RuntimeConfig; import org.slf4j.Logger; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestWatcher; -import org.junit.runner.Description; - public class AbstractTest { - /** cached network test value for tests */ - static Boolean hasInternet = null; + /** + * cached network test value for tests + */ + protected static Boolean hasInternet = null; + /** + * Install dependencies once per process, same process + * will not check. A new process will use the libraries/serviceData.json + * to determine if deps are satisfied + */ protected static boolean installed = false; - public final static Logger log = LoggerFactory.getLogger(AbstractTest.class); - - static private boolean logWarnTestHeader = false; + protected final static Logger log = LoggerFactory.getLogger(AbstractTest.class); - private static boolean releaseRemainingThreads = false; + protected static transient Set<Thread> threadSetStart = null; - protected transient Queue<Object> queue = new LinkedBlockingQueue<>(); - - static transient Set<Thread> threadSetStart = null; - - protected Set<Attachable> attached = new HashSet<>(); - - @Rule - public final TestName testName = new TestName(); - - static public String simpleName; - - private static boolean lineFeedFooter = true; - @Rule public TestWatcher watchman = new TestWatcher() { - @Override - protected void starting(Description description) { - System.out.println("Starting: " + description.getClassName() + "." + description.getMethodName()); - } + @Override + protected void starting(Description description) { + System.out.println("Starting: " + description.getClassName() + "." + description.getMethodName()); + } - @Override - protected void succeeded(Description description) { - // System.out.println("Succeeded: " + description.getMethodName()); - } + @Override + protected void succeeded(Description description) { + // System.out.println("Succeeded: " + description.getMethodName()); + } - @Override - protected void failed(Throwable e, Description description) { - System.out.println("Failed: " + description.getMethodName()); - } + @Override + protected void failed(Throwable e, Description description) { + System.out.println("Failed: " + description.getMethodName()); + } - @Override - protected void skipped(org.junit.AssumptionViolatedException e, Description description) { - System.out.println("Skipped: " + description.getMethodName()); - } + @Override + protected void skipped(org.junit.AssumptionViolatedException e, Description description) { + System.out.println("Skipped: " + description.getMethodName()); + } - @Override - protected void finished(Description description) { - System.out.println("Finished: " + description.getMethodName()); - } + @Override + protected void finished(Description description) { + System.out.println("Finished: " + description.getMethodName()); + } }; - public String getSimpleName() { - return simpleName; - } - - public String getName() { - return testName.getMethodName(); - } - static public boolean hasInternet() { if (hasInternet == null) { hasInternet = Runtime.hasInternet(); @@ -120,23 +99,23 @@ public static void main(String[] args) { @BeforeClass public static void setUpAbstractTest() throws Exception { - + // setup runtime resource = src/main/resources/resource File runtimeYml = new File("data/config/default/runtime.yml"); -// if (!runtimeYml.exists()) { - runtimeYml.getParentFile().mkdirs(); - RuntimeConfig rc = new RuntimeConfig(); - rc.resource = "src/main/resources/resource"; - String yml = CodecUtils.toYaml(rc); - - FileOutputStream fos = null; - fos = new FileOutputStream(runtimeYml); - fos.write(yml.getBytes()); - fos.close(); - -// } - - Runtime.getInstance().setVirtual(true); + // if (!runtimeYml.exists()) { + runtimeYml.getParentFile().mkdirs(); + RuntimeConfig rc = new RuntimeConfig(); + rc.resource = "src/main/resources/resource"; + String yml = CodecUtils.toYaml(rc); + + FileOutputStream fos = null; + fos = new FileOutputStream(runtimeYml); + fos.write(yml.getBytes()); + fos.close(); + + // } + + Runtime.getInstance().setVirtual(true); String junitLogLevel = System.getProperty("junit.logLevel"); if (junitLogLevel != null) { @@ -171,16 +150,7 @@ public static void sleep(long sleepTimeMs) { @AfterClass public static void tearDownAbstractTest() throws Exception { log.info("tearDownAbstractTest"); - releaseServices(); - - if (logWarnTestHeader) { - log.warn("=========== finished test {} ===========", simpleName); - } - - if (lineFeedFooter) { - System.out.println(); - } } static protected void installAll() { @@ -197,8 +167,7 @@ static protected void installAll() { */ public static void releaseServices() { - log.info("end of test - id {} remaining services {}", Runtime.getInstance().getId(), - Arrays.toString(Runtime.getServiceNames())); + log.info("end of test - id {} remaining services {}", Runtime.getInstance().getId(), Arrays.toString(Runtime.getServiceNames())); // release all including runtime - be careful of default runtime.yml Runtime.releaseAll(true, true); @@ -212,19 +181,8 @@ public static void releaseServices() { Set<Thread> threadSetEnd = Thread.getAllStackTraces().keySet(); Set<String> threadsRemaining = new TreeSet<>(); for (Thread thread : threadSetEnd) { - if (!threadSetStart.contains(thread) && !"runtime_outbox_0".equals(thread.getName()) - && !"runtime".equals(thread.getName())) { - if (releaseRemainingThreads) { - log.warn("interrupting thread {}", thread.getName()); - thread.interrupt(); - /* - * if (useDeprecatedThreadStop) { thread.stop(); } - */ - } else { - // log.warn("thread {} marked as straggler - should be killed", - // thread.getName()); + if (!threadSetStart.contains(thread) && !"runtime_outbox_0".equals(thread.getName()) && !"runtime".equals(thread.getName())) { threadsRemaining.add(thread.getName()); - } } } if (threadsRemaining.size() > 0) { @@ -236,13 +194,6 @@ public static void releaseServices() { // Arrays.toString(Runtime.getServiceNames())); } - public AbstractTest() { - simpleName = this.getClass().getSimpleName(); - if (logWarnTestHeader) { - log.info("=========== starting test {} ===========", this.getClass().getSimpleName()); - } - } - public void setVirtual() { Runtime.getInstance().setVirtual(true); } From c6c2f143afaee6e3281cd6b024f5d82358cec3a2 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 07:06:23 -0800 Subject: [PATCH 067/131] re-adding deprecated ProgramAB.botDir --- .../java/org/myrobotlab/service/config/ProgramABConfig.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java b/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java index 0cd4dbc839..95d5f4a0e3 100644 --- a/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java +++ b/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java @@ -10,6 +10,9 @@ public class ProgramABConfig extends ServiceConfig { @Deprecated /* unused text filters */ public String[] textFilters; + @Deprecated /* unnecessary and unwanted - specify bots directly */ + public String botDir; + /** * explicit bot directories */ From 4a6957433e123b8b15590fa4dfb8115ad0de382b Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 07:25:06 -0800 Subject: [PATCH 068/131] npe fix for runtime.apply(c) --- src/main/java/org/myrobotlab/service/Runtime.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 3d3697284a..9ae1c8a17b 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -911,14 +911,10 @@ public static Runtime getInstance() { // extract resources "if a jar" FileIO.extractResources(); runtime.startInteractiveMode(); - - if (Runtime.options.install != null) { - // minimal processed runtime - return it - return runtime; + if (c != null) { + runtime.apply(c); } - runtime.apply(c); - if (options.services != null) { log.info("command line override for services created"); createAndStartServices(options.services); From 6e02e9fd40dbbe44037562b9cb89e54c3b93a7e6 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 07:25:48 -0800 Subject: [PATCH 069/131] npe fix for runtime.apply(c) --- src/main/java/org/myrobotlab/service/Runtime.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 3d3697284a..9ae1c8a17b 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -911,14 +911,10 @@ public static Runtime getInstance() { // extract resources "if a jar" FileIO.extractResources(); runtime.startInteractiveMode(); - - if (Runtime.options.install != null) { - // minimal processed runtime - return it - return runtime; + if (c != null) { + runtime.apply(c); } - runtime.apply(c); - if (options.services != null) { log.info("command line override for services created"); createAndStartServices(options.services); From 76c8b00bf0fbae71589778d8b07afe019c5be2d2 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 07:44:51 -0800 Subject: [PATCH 070/131] updated javacpp to 1.5.8 and updated template --- pom.xml | 25 ++----------------- .../resource/framework/pom.xml.template | 23 +++++++++-------- 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/pom.xml b/pom.xml index 9b89a805fc..fb8bc15b6e 100644 --- a/pom.xml +++ b/pom.xml @@ -484,10 +484,6 @@ <!-- Duplicate entry for org.myrobotlab.audio-voice-effects-1.0 skipping --> <!-- IndianTts end --> - <!-- IntegratedMovement begin --> - <!-- Duplicate entry for fr.inmoov-inmoov2-null skipping --> - <!-- IntegratedMovement end --> - <!-- JFugue begin --> <dependency> <groupId>jfugue</groupId> @@ -1079,10 +1075,10 @@ <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv-platform</artifactId> - <version>1.5.7</version> + <version>1.5.8</version> <scope>provided</scope> </dependency> - <!-- Duplicate entry for org.bytedeco-javacpp-1.5.7 skipping --> + <!-- Duplicate entry for org.bytedeco-javacpp-1.5.8 skipping --> <!-- Duplicate entry for com.github.sarxos-webcam-capture-driver-v4l4j-0.3.13-SNAPSHOT skipping --> <!-- Duplicate entry for org.apache.commons-commons-lang3-3.3.2 skipping --> <dependency> @@ -1557,15 +1553,6 @@ </dependency> <!-- Sphinx end --> - <!-- Tensorflow begin --> - <dependency> - <groupId>org.tensorflow</groupId> - <artifactId>tensorflow</artifactId> - <version>1.8.0</version> - <scope>provided</scope> - </dependency> - <!-- Tensorflow end --> - <!-- TesseractOcr begin --> <dependency> <groupId>org.bytedeco</groupId> @@ -1690,10 +1677,6 @@ </dependency> <!-- WebGui end --> - <!-- WebSocketConnector begin --> - <!-- Duplicate entry for javax.websocket-javax.websocket-api-1.1 skipping --> - <!-- WebSocketConnector end --> - <!-- Webcam begin --> <dependency> <groupId>com.github.sarxos</groupId> @@ -1751,10 +1734,6 @@ </dependency> <!-- WolframAlpha end --> - <!-- WorkE begin --> - <!-- skipping org.myrobotlab worke org.myrobotlab-worke-null null version/latest --> - <!-- WorkE end --> - <!-- Xmpp begin --> <dependency> <groupId>org.igniterealtime.smack</groupId> diff --git a/src/main/resources/resource/framework/pom.xml.template b/src/main/resources/resource/framework/pom.xml.template index ca584c5ba0..29e5f21eb9 100644 --- a/src/main/resources/resource/framework/pom.xml.template +++ b/src/main/resources/resource/framework/pom.xml.template @@ -83,7 +83,7 @@ <!-- force overriding property at command line, use ${maven.build.timestamp}--> <timestamp>${maven.build.timestamp}</timestamp> <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format> - <version>${project.version}</version> + <version>${version}</version> <GitBranch>${git.branch}</GitBranch> <username>${NODE_NAME}</username> <platform>${NODE_LABELS}</platform> @@ -193,7 +193,7 @@ </goals> <configuration> <finalName>myrobotlab</finalName> - <!-- finalName>myrobotlab-${git.branch}-${project.version}</finalName --> + <!-- finalName>myrobotlab-${git.branch}-${version}</finalName --> <shadedArtifactAttached>true</shadedArtifactAttached> <shadedClassifierName>myrobotlab-full</shadedClassifierName> <minimizeJar>false</minimizeJar> @@ -207,10 +207,10 @@ implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Main-Class>org.myrobotlab.service.Runtime</Main-Class> - <Major-Version>${project.version}</Major-Version> - <Implementation-Version>${project.version}</Implementation-Version> + <Major-Version>${version}</Major-Version> + <Implementation-Version>${version}</Implementation-Version> - <Build-Version>${project.version}</Build-Version> + <Build-Version>${version}</Build-Version> <Build-Time>${maven.build.timestamp}</Build-Time> <Build-Host>${agent.name}</Build-Host> <Build-User>${user.name}</Build-User> @@ -331,20 +331,21 @@ <artifactId>maven-surefire-plugin</artifactId> <groupId>org.apache.maven.plugins</groupId> <!-- do not upgrade this version jacoco will break --> - <version>2.18</version> + <version>3.2.2</version> <configuration> <!-- critical for jacoco to have original argLine prefixed here--> - <argLine>${argLine} -Djava.library.path=libraries/native -Djna.library.path=libraries/native</argLine> + <argLine>${argLine} -Djava.library.path=libraries/native + -Djna.library.path=libraries/native</argLine> <includes> <include>**/*Test.java</include> </includes> <excludes> <exclude>**/integration/*</exclude> <!-- unfortunately not testing OpenCV is required for ci to work --> - <exclude>**/OpenCV*</exclude> </excludes> - <systemPropertyVariables> - </systemPropertyVariables> + <!-- required so surefire doesnt die talking to the agent over stdin --> + <forkNode + implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory" /> </configuration> <executions> <execution> @@ -425,7 +426,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-report-plugin</artifactId> - <version>2.18</version> + <version>3.2.2</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> From 5caf15d9ae8c44e5bba62f2d14b4da84bce87449 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 07:51:10 -0800 Subject: [PATCH 071/131] synching javacpp 1.5.8 updating template --- pom.xml | 25 ++----------------- .../resource/framework/pom.xml.template | 23 +++++++++-------- 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/pom.xml b/pom.xml index 9b89a805fc..fb8bc15b6e 100644 --- a/pom.xml +++ b/pom.xml @@ -484,10 +484,6 @@ <!-- Duplicate entry for org.myrobotlab.audio-voice-effects-1.0 skipping --> <!-- IndianTts end --> - <!-- IntegratedMovement begin --> - <!-- Duplicate entry for fr.inmoov-inmoov2-null skipping --> - <!-- IntegratedMovement end --> - <!-- JFugue begin --> <dependency> <groupId>jfugue</groupId> @@ -1079,10 +1075,10 @@ <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv-platform</artifactId> - <version>1.5.7</version> + <version>1.5.8</version> <scope>provided</scope> </dependency> - <!-- Duplicate entry for org.bytedeco-javacpp-1.5.7 skipping --> + <!-- Duplicate entry for org.bytedeco-javacpp-1.5.8 skipping --> <!-- Duplicate entry for com.github.sarxos-webcam-capture-driver-v4l4j-0.3.13-SNAPSHOT skipping --> <!-- Duplicate entry for org.apache.commons-commons-lang3-3.3.2 skipping --> <dependency> @@ -1557,15 +1553,6 @@ </dependency> <!-- Sphinx end --> - <!-- Tensorflow begin --> - <dependency> - <groupId>org.tensorflow</groupId> - <artifactId>tensorflow</artifactId> - <version>1.8.0</version> - <scope>provided</scope> - </dependency> - <!-- Tensorflow end --> - <!-- TesseractOcr begin --> <dependency> <groupId>org.bytedeco</groupId> @@ -1690,10 +1677,6 @@ </dependency> <!-- WebGui end --> - <!-- WebSocketConnector begin --> - <!-- Duplicate entry for javax.websocket-javax.websocket-api-1.1 skipping --> - <!-- WebSocketConnector end --> - <!-- Webcam begin --> <dependency> <groupId>com.github.sarxos</groupId> @@ -1751,10 +1734,6 @@ </dependency> <!-- WolframAlpha end --> - <!-- WorkE begin --> - <!-- skipping org.myrobotlab worke org.myrobotlab-worke-null null version/latest --> - <!-- WorkE end --> - <!-- Xmpp begin --> <dependency> <groupId>org.igniterealtime.smack</groupId> diff --git a/src/main/resources/resource/framework/pom.xml.template b/src/main/resources/resource/framework/pom.xml.template index ca584c5ba0..29e5f21eb9 100644 --- a/src/main/resources/resource/framework/pom.xml.template +++ b/src/main/resources/resource/framework/pom.xml.template @@ -83,7 +83,7 @@ <!-- force overriding property at command line, use ${maven.build.timestamp}--> <timestamp>${maven.build.timestamp}</timestamp> <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format> - <version>${project.version}</version> + <version>${version}</version> <GitBranch>${git.branch}</GitBranch> <username>${NODE_NAME}</username> <platform>${NODE_LABELS}</platform> @@ -193,7 +193,7 @@ </goals> <configuration> <finalName>myrobotlab</finalName> - <!-- finalName>myrobotlab-${git.branch}-${project.version}</finalName --> + <!-- finalName>myrobotlab-${git.branch}-${version}</finalName --> <shadedArtifactAttached>true</shadedArtifactAttached> <shadedClassifierName>myrobotlab-full</shadedClassifierName> <minimizeJar>false</minimizeJar> @@ -207,10 +207,10 @@ implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Main-Class>org.myrobotlab.service.Runtime</Main-Class> - <Major-Version>${project.version}</Major-Version> - <Implementation-Version>${project.version}</Implementation-Version> + <Major-Version>${version}</Major-Version> + <Implementation-Version>${version}</Implementation-Version> - <Build-Version>${project.version}</Build-Version> + <Build-Version>${version}</Build-Version> <Build-Time>${maven.build.timestamp}</Build-Time> <Build-Host>${agent.name}</Build-Host> <Build-User>${user.name}</Build-User> @@ -331,20 +331,21 @@ <artifactId>maven-surefire-plugin</artifactId> <groupId>org.apache.maven.plugins</groupId> <!-- do not upgrade this version jacoco will break --> - <version>2.18</version> + <version>3.2.2</version> <configuration> <!-- critical for jacoco to have original argLine prefixed here--> - <argLine>${argLine} -Djava.library.path=libraries/native -Djna.library.path=libraries/native</argLine> + <argLine>${argLine} -Djava.library.path=libraries/native + -Djna.library.path=libraries/native</argLine> <includes> <include>**/*Test.java</include> </includes> <excludes> <exclude>**/integration/*</exclude> <!-- unfortunately not testing OpenCV is required for ci to work --> - <exclude>**/OpenCV*</exclude> </excludes> - <systemPropertyVariables> - </systemPropertyVariables> + <!-- required so surefire doesnt die talking to the agent over stdin --> + <forkNode + implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory" /> </configuration> <executions> <execution> @@ -425,7 +426,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-report-plugin</artifactId> - <version>2.18</version> + <version>3.2.2</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> From 46f2f274c4913779b7c85c44dcb7a3a6b572056d Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 08:07:09 -0800 Subject: [PATCH 072/131] syching javacpp 1.5.8 tesseract and deeplearning4j --- pom.xml | 6 +++--- .../org/myrobotlab/service/meta/Deeplearning4jMeta.java | 2 +- .../java/org/myrobotlab/service/meta/TesseractOcrMeta.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index fb8bc15b6e..77ddf5a516 100644 --- a/pom.xml +++ b/pom.xml @@ -215,7 +215,7 @@ <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacpp</artifactId> - <version>1.5.7</version> + <version>1.5.8</version> <scope>provided</scope> </dependency> <dependency> @@ -1557,13 +1557,13 @@ <dependency> <groupId>org.bytedeco</groupId> <artifactId>tesseract</artifactId> - <version>5.0.1-1.5.7</version> + <version>5.2.0-1.5.8</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.bytedeco</groupId> <artifactId>tesseract-platform</artifactId> - <version>5.0.1-1.5.7</version> + <version>5.2.0-1.5.8</version> <scope>provided</scope> </dependency> <dependency> diff --git a/src/main/java/org/myrobotlab/service/meta/Deeplearning4jMeta.java b/src/main/java/org/myrobotlab/service/meta/Deeplearning4jMeta.java index 56fe4f8ad5..23fc4aeff8 100644 --- a/src/main/java/org/myrobotlab/service/meta/Deeplearning4jMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/Deeplearning4jMeta.java @@ -23,7 +23,7 @@ public Deeplearning4jMeta() { addCategory("ai"); // Force javacpp 1.5.3 to resolve conflict between dl4j and javacv - addDependency("org.bytedeco", "javacpp", "1.5.7"); + addDependency("org.bytedeco", "javacpp", "1.5.8"); // REMOVED FOR COLLISION // addDependency("org.bytedeco", "openblas", "0.3.17-" + "1.5.6"); diff --git a/src/main/java/org/myrobotlab/service/meta/TesseractOcrMeta.java b/src/main/java/org/myrobotlab/service/meta/TesseractOcrMeta.java index 5099e60d79..8c8d9a2554 100644 --- a/src/main/java/org/myrobotlab/service/meta/TesseractOcrMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/TesseractOcrMeta.java @@ -14,7 +14,7 @@ public class TesseractOcrMeta extends MetaData { */ public TesseractOcrMeta() { - String tesseractVersion = "5.0.1-1.5.7"; + String tesseractVersion = "5.2.0-1.5.8"; addDescription("Optical character recognition - the ability to read"); addCategory("ai", "vision"); addDependency("org.bytedeco", "tesseract", tesseractVersion); From 1851976911325868cc418fd1a8cf2f35eee17e11 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 08:09:43 -0800 Subject: [PATCH 073/131] synching javacpp 1.5.8 with deeplearning4j and tesseract --- pom.xml | 6 +++--- .../org/myrobotlab/service/meta/Deeplearning4jMeta.java | 2 +- .../java/org/myrobotlab/service/meta/TesseractOcrMeta.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index fb8bc15b6e..77ddf5a516 100644 --- a/pom.xml +++ b/pom.xml @@ -215,7 +215,7 @@ <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacpp</artifactId> - <version>1.5.7</version> + <version>1.5.8</version> <scope>provided</scope> </dependency> <dependency> @@ -1557,13 +1557,13 @@ <dependency> <groupId>org.bytedeco</groupId> <artifactId>tesseract</artifactId> - <version>5.0.1-1.5.7</version> + <version>5.2.0-1.5.8</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.bytedeco</groupId> <artifactId>tesseract-platform</artifactId> - <version>5.0.1-1.5.7</version> + <version>5.2.0-1.5.8</version> <scope>provided</scope> </dependency> <dependency> diff --git a/src/main/java/org/myrobotlab/service/meta/Deeplearning4jMeta.java b/src/main/java/org/myrobotlab/service/meta/Deeplearning4jMeta.java index 56fe4f8ad5..23fc4aeff8 100644 --- a/src/main/java/org/myrobotlab/service/meta/Deeplearning4jMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/Deeplearning4jMeta.java @@ -23,7 +23,7 @@ public Deeplearning4jMeta() { addCategory("ai"); // Force javacpp 1.5.3 to resolve conflict between dl4j and javacv - addDependency("org.bytedeco", "javacpp", "1.5.7"); + addDependency("org.bytedeco", "javacpp", "1.5.8"); // REMOVED FOR COLLISION // addDependency("org.bytedeco", "openblas", "0.3.17-" + "1.5.6"); diff --git a/src/main/java/org/myrobotlab/service/meta/TesseractOcrMeta.java b/src/main/java/org/myrobotlab/service/meta/TesseractOcrMeta.java index 5099e60d79..8c8d9a2554 100644 --- a/src/main/java/org/myrobotlab/service/meta/TesseractOcrMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/TesseractOcrMeta.java @@ -14,7 +14,7 @@ public class TesseractOcrMeta extends MetaData { */ public TesseractOcrMeta() { - String tesseractVersion = "5.0.1-1.5.7"; + String tesseractVersion = "5.2.0-1.5.8"; addDescription("Optical character recognition - the ability to read"); addCategory("ai", "vision"); addDependency("org.bytedeco", "tesseract", tesseractVersion); From d7fad5a5a8ce192e92709fded2f623ca0d17cd23 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 08:17:23 -0800 Subject: [PATCH 074/131] changed imports on tesseract --- src/main/java/org/myrobotlab/service/TesseractOcr.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/TesseractOcr.java b/src/main/java/org/myrobotlab/service/TesseractOcr.java index b444ceadec..180801d75c 100644 --- a/src/main/java/org/myrobotlab/service/TesseractOcr.java +++ b/src/main/java/org/myrobotlab/service/TesseractOcr.java @@ -1,7 +1,7 @@ package org.myrobotlab.service; -import static org.bytedeco.leptonica.global.lept.pixDestroy; -import static org.bytedeco.leptonica.global.lept.pixRead; +import static org.bytedeco.leptonica.global.leptonica.pixDestroy; +import static org.bytedeco.leptonica.global.leptonica.pixRead; import java.awt.image.BufferedImage; import java.io.File; From 2e6c89c31ca97090df9b082e448d309985db0c36 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 08:17:59 -0800 Subject: [PATCH 075/131] changed imports on tesseract --- src/main/java/org/myrobotlab/service/TesseractOcr.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/TesseractOcr.java b/src/main/java/org/myrobotlab/service/TesseractOcr.java index b444ceadec..180801d75c 100644 --- a/src/main/java/org/myrobotlab/service/TesseractOcr.java +++ b/src/main/java/org/myrobotlab/service/TesseractOcr.java @@ -1,7 +1,7 @@ package org.myrobotlab.service; -import static org.bytedeco.leptonica.global.lept.pixDestroy; -import static org.bytedeco.leptonica.global.lept.pixRead; +import static org.bytedeco.leptonica.global.leptonica.pixDestroy; +import static org.bytedeco.leptonica.global.leptonica.pixRead; import java.awt.image.BufferedImage; import java.io.File; From 72903143d98dff1013ffdb455e7e62f879bf02e6 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 08:55:52 -0800 Subject: [PATCH 076/131] fixed test - was counting threads after new runtime started --- src/test/java/org/myrobotlab/test/AbstractTest.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/myrobotlab/test/AbstractTest.java b/src/test/java/org/myrobotlab/test/AbstractTest.java index 7279a8fe98..69e0b611a0 100644 --- a/src/test/java/org/myrobotlab/test/AbstractTest.java +++ b/src/test/java/org/myrobotlab/test/AbstractTest.java @@ -173,8 +173,6 @@ public static void releaseServices() { Runtime.releaseAll(true, true); // wait for draining threads sleep(100); - // resets runtime with fresh new instance - Runtime.getInstance(); // check threads - kill stragglers // Set<Thread> stragglers = new HashSet<Thread>(); @@ -189,9 +187,9 @@ public static void releaseServices() { log.warn("{} straggling threads remain [{}]", threadsRemaining.size(), String.join(",", threadsRemaining)); } - // log.warn("end of test - id {} remaining services after release {}", - // Platform.getLocalInstance().getId(), - // Arrays.toString(Runtime.getServiceNames())); + // resets runtime with fresh new instance + Runtime.getInstance(); + } public void setVirtual() { From 9e1710ad742f0363c235d136b5c1dbf1bf198950 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 09:47:15 -0800 Subject: [PATCH 077/131] more unit test cleanup --- .../org/myrobotlab/service/AudioFile.java | 8 ++++ .../java/org/myrobotlab/service/NeoPixel.java | 1 + .../service/ServiceInterfaceTest.java | 37 ++++++++++--------- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/AudioFile.java b/src/main/java/org/myrobotlab/service/AudioFile.java index 3997f6960e..dbf2131bcf 100644 --- a/src/main/java/org/myrobotlab/service/AudioFile.java +++ b/src/main/java/org/myrobotlab/service/AudioFile.java @@ -384,6 +384,14 @@ public List<File> getFiles(String subDir, boolean recurse) { return new ArrayList<File>(); } + @Override + public void releaseService() { + super.releaseService(); + for (AudioProcessor processor: processors.values()) { + processor.stopPlaying(); + } + } + public AudioData repeat(String filename) { return repeat(filename, -1); } diff --git a/src/main/java/org/myrobotlab/service/NeoPixel.java b/src/main/java/org/myrobotlab/service/NeoPixel.java index 21a05cbdc5..a5a22c4782 100644 --- a/src/main/java/org/myrobotlab/service/NeoPixel.java +++ b/src/main/java/org/myrobotlab/service/NeoPixel.java @@ -810,6 +810,7 @@ public void playIronman() { public void releaseService() { super.releaseService(); clear(); + worker.stop(); } @Override diff --git a/src/test/java/org/myrobotlab/service/ServiceInterfaceTest.java b/src/test/java/org/myrobotlab/service/ServiceInterfaceTest.java index 806985daa6..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<String> servicesNotInServiceDataJson = new ArrayList<String>(); HashSet<String> blacklist = new HashSet<String>(); - 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); @@ -222,14 +223,14 @@ public final void testAllServices() throws ClassNotFoundException, IOException { } - log.info("----------------------------------------------"); - log.info("Service Report"); - log.info("Number of Services: {}", numServices); - log.info("Number of Startable Services: {}", numStartable); - log.info("Number of Services Pages {}", numServicePages); - log.info("Number of Scripts: {}", numScripts); - log.info("Number of Scripts Worky: {}", numScriptsWorky); - log.info("----------------------------------------------"); + System.out.println("----------------------------------------------"); + System.out.println("Service Report"); + System.out.println(String.format("Number of Services: %d", numServices)); + System.out.println(String.format("Number of Startable Services: %d", numStartable)); + System.out.println(String.format("Number of Services Pages %d", numServicePages)); + System.out.println(String.format("Number of Scripts: %d", numScripts)); + System.out.println(String.format("Number of Scripts Worky: %d", numScriptsWorky)); + System.out.println("----------------------------------------------"); for (String s : servicesThatDontStartProperly) { log.warn("FAILED ON START:" + s); From 1fa4e6231ac999b8cb12ab34ccb1a6a499e5d1ba Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 11:21:39 -0800 Subject: [PATCH 078/131] requested fixes --- src/main/java/org/myrobotlab/config/ConfigUtils.java | 2 +- src/main/java/org/myrobotlab/framework/Service.java | 2 +- src/main/java/org/myrobotlab/service/Runtime.java | 7 +++++++ src/main/java/org/myrobotlab/service/Serial.java | 4 ++-- src/main/java/org/myrobotlab/service/VirtualArduino.java | 1 - .../java/org/myrobotlab/service/config/InMoov2Config.java | 7 ++++--- src/test/java/org/myrobotlab/service/VertxTest.java | 2 +- 7 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/myrobotlab/config/ConfigUtils.java b/src/main/java/org/myrobotlab/config/ConfigUtils.java index 69f850e602..7b793452ac 100644 --- a/src/main/java/org/myrobotlab/config/ConfigUtils.java +++ b/src/main/java/org/myrobotlab/config/ConfigUtils.java @@ -97,7 +97,7 @@ public static StartYml loadStartYml() { try { FileIO.toFile("start.yml", defaultStartFile); } catch (IOException e) { - log.error("could not save start.yml"); + log.error("could not save start.yml", e); } } else { // load start.yml diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java index 3f7acbf755..682acebbc5 100644 --- a/src/main/java/org/myrobotlab/framework/Service.java +++ b/src/main/java/org/myrobotlab/framework/Service.java @@ -510,7 +510,7 @@ public String getResourcePath(String additionalPath) { */ static public String getResourceRoot() { - return ConfigUtils.getResourceRoot();//Runtime.getInstance().getConfig().resource; + return ConfigUtils.getResourceRoot(); } /** diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 9ae1c8a17b..9b76d3bf65 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -1549,6 +1549,11 @@ static public void install(String serviceType, Boolean blocking) { if (blocking == null) { blocking = false; } + + if (installerThread != null) { + log.error("another request to install dependencies, 1st request has not completed"); + return; + } installerThread = new Thread() { @Override @@ -1571,6 +1576,8 @@ public void run() { } else { installerThread.start(); } + + installerThread = null; } } diff --git a/src/main/java/org/myrobotlab/service/Serial.java b/src/main/java/org/myrobotlab/service/Serial.java index c6fccb9d84..a263429144 100644 --- a/src/main/java/org/myrobotlab/service/Serial.java +++ b/src/main/java/org/myrobotlab/service/Serial.java @@ -1120,8 +1120,8 @@ public void stopRecording() { } @Override - public void stopService() { - super.stopService(); + public void releaseService() { + super.releaseService(); disconnect(); stopRecording(); } diff --git a/src/main/java/org/myrobotlab/service/VirtualArduino.java b/src/main/java/org/myrobotlab/service/VirtualArduino.java index f1879aa5b6..54671dc8e2 100644 --- a/src/main/java/org/myrobotlab/service/VirtualArduino.java +++ b/src/main/java/org/myrobotlab/service/VirtualArduino.java @@ -260,7 +260,6 @@ public void releaseService() { } // sleep(300); disconnect(); - super.releaseService(); } public Serial getSerial() { diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 889e91eec9..1e884c1e4a 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -188,6 +188,7 @@ public Plan getDefault(Plan plan, String name) { addDefaultPeerConfig(plan, name, "openWeatherMap", "OpenWeatherMap", false); addDefaultPeerConfig(plan, name, "pid", "Pid", false); addDefaultPeerConfig(plan, name, "pir", "Pir", false); + addDefaultGlobalConfig(plan, "python", "python", "Python"); addDefaultPeerConfig(plan, name, "py4j", "Py4j", false); addDefaultPeerConfig(plan, name, "random", "Random", false); addDefaultPeerConfig(plan, name, "right", "Arduino", false); @@ -370,7 +371,7 @@ public Plan getDefault(Plan plan, String name) { // exists ? fsm.current = "boot"; fsm.transitions.add(new Transition("boot", "wake", "wake")); - // fsm.transitions.add(new Transition("wake", "idle", "idle")); wake, setup, nor sleep should be affected by idle + // setup, nor sleep should be affected by idle fsm.transitions.add(new Transition("setup", "setup_done", "idle")); fsm.transitions.add(new Transition("idle", "random", "random")); fsm.transitions.add(new Transition("random", "idle", "idle")); @@ -528,9 +529,9 @@ 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", "python", "onPythonMessage")); + listeners.add(new Listener("publishProcessMessage", getPeerName("python"), "onPythonMessage")); - listeners.add(new Listener("publishPython", "python")); + listeners.add(new Listener("publishPython", getPeerName("python"))); // InMoov2 --to--> InMoov2 listeners.add(new Listener("publishMoveHead", getPeerName("head"), "onMove")); diff --git a/src/test/java/org/myrobotlab/service/VertxTest.java b/src/test/java/org/myrobotlab/service/VertxTest.java index eba95ee828..f0ac18e022 100644 --- a/src/test/java/org/myrobotlab/service/VertxTest.java +++ b/src/test/java/org/myrobotlab/service/VertxTest.java @@ -58,7 +58,7 @@ public void getTest() { assertNotNull(bytes); String ret = new String(bytes); assertTrue(ret.contains("days")); - System.out.println(String.format("%d", i)); + log.info(String.format("%d", i)); } } From 2dbde9107082ee9a6a1eda8000113293bdff75da Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 17 Feb 2024 11:25:04 -0800 Subject: [PATCH 079/131] requested fixes --- src/main/java/org/myrobotlab/config/ConfigUtils.java | 2 +- src/main/java/org/myrobotlab/framework/Service.java | 2 +- src/main/java/org/myrobotlab/service/Runtime.java | 7 +++++++ src/main/java/org/myrobotlab/service/Serial.java | 4 ++-- src/main/java/org/myrobotlab/service/VirtualArduino.java | 1 - .../java/org/myrobotlab/service/config/InMoov2Config.java | 7 ++++--- src/test/java/org/myrobotlab/service/VertxTest.java | 2 +- 7 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/myrobotlab/config/ConfigUtils.java b/src/main/java/org/myrobotlab/config/ConfigUtils.java index 69f850e602..7b793452ac 100644 --- a/src/main/java/org/myrobotlab/config/ConfigUtils.java +++ b/src/main/java/org/myrobotlab/config/ConfigUtils.java @@ -97,7 +97,7 @@ public static StartYml loadStartYml() { try { FileIO.toFile("start.yml", defaultStartFile); } catch (IOException e) { - log.error("could not save start.yml"); + log.error("could not save start.yml", e); } } else { // load start.yml diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java index 3f7acbf755..682acebbc5 100644 --- a/src/main/java/org/myrobotlab/framework/Service.java +++ b/src/main/java/org/myrobotlab/framework/Service.java @@ -510,7 +510,7 @@ public String getResourcePath(String additionalPath) { */ static public String getResourceRoot() { - return ConfigUtils.getResourceRoot();//Runtime.getInstance().getConfig().resource; + return ConfigUtils.getResourceRoot(); } /** diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 9ae1c8a17b..9b76d3bf65 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -1549,6 +1549,11 @@ static public void install(String serviceType, Boolean blocking) { if (blocking == null) { blocking = false; } + + if (installerThread != null) { + log.error("another request to install dependencies, 1st request has not completed"); + return; + } installerThread = new Thread() { @Override @@ -1571,6 +1576,8 @@ public void run() { } else { installerThread.start(); } + + installerThread = null; } } diff --git a/src/main/java/org/myrobotlab/service/Serial.java b/src/main/java/org/myrobotlab/service/Serial.java index c6fccb9d84..a263429144 100644 --- a/src/main/java/org/myrobotlab/service/Serial.java +++ b/src/main/java/org/myrobotlab/service/Serial.java @@ -1120,8 +1120,8 @@ public void stopRecording() { } @Override - public void stopService() { - super.stopService(); + public void releaseService() { + super.releaseService(); disconnect(); stopRecording(); } diff --git a/src/main/java/org/myrobotlab/service/VirtualArduino.java b/src/main/java/org/myrobotlab/service/VirtualArduino.java index f1879aa5b6..54671dc8e2 100644 --- a/src/main/java/org/myrobotlab/service/VirtualArduino.java +++ b/src/main/java/org/myrobotlab/service/VirtualArduino.java @@ -260,7 +260,6 @@ public void releaseService() { } // sleep(300); disconnect(); - super.releaseService(); } public Serial getSerial() { diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 889e91eec9..1e884c1e4a 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -188,6 +188,7 @@ public Plan getDefault(Plan plan, String name) { addDefaultPeerConfig(plan, name, "openWeatherMap", "OpenWeatherMap", false); addDefaultPeerConfig(plan, name, "pid", "Pid", false); addDefaultPeerConfig(plan, name, "pir", "Pir", false); + addDefaultGlobalConfig(plan, "python", "python", "Python"); addDefaultPeerConfig(plan, name, "py4j", "Py4j", false); addDefaultPeerConfig(plan, name, "random", "Random", false); addDefaultPeerConfig(plan, name, "right", "Arduino", false); @@ -370,7 +371,7 @@ public Plan getDefault(Plan plan, String name) { // exists ? fsm.current = "boot"; fsm.transitions.add(new Transition("boot", "wake", "wake")); - // fsm.transitions.add(new Transition("wake", "idle", "idle")); wake, setup, nor sleep should be affected by idle + // setup, nor sleep should be affected by idle fsm.transitions.add(new Transition("setup", "setup_done", "idle")); fsm.transitions.add(new Transition("idle", "random", "random")); fsm.transitions.add(new Transition("random", "idle", "idle")); @@ -528,9 +529,9 @@ 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", "python", "onPythonMessage")); + listeners.add(new Listener("publishProcessMessage", getPeerName("python"), "onPythonMessage")); - listeners.add(new Listener("publishPython", "python")); + listeners.add(new Listener("publishPython", getPeerName("python"))); // InMoov2 --to--> InMoov2 listeners.add(new Listener("publishMoveHead", getPeerName("head"), "onMove")); diff --git a/src/test/java/org/myrobotlab/service/VertxTest.java b/src/test/java/org/myrobotlab/service/VertxTest.java index eba95ee828..f0ac18e022 100644 --- a/src/test/java/org/myrobotlab/service/VertxTest.java +++ b/src/test/java/org/myrobotlab/service/VertxTest.java @@ -58,7 +58,7 @@ public void getTest() { assertNotNull(bytes); String ret = new String(bytes); assertTrue(ret.contains("days")); - System.out.println(String.format("%d", i)); + log.info(String.format("%d", i)); } } From a032e8e3758fe4384d05504242c95c46e5b6a84e Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 18 Feb 2024 12:18:33 -0800 Subject: [PATCH 080/131] channels for discordbot and programab --- .../org/myrobotlab/service/DiscordBot.java | 22 ++++++++++++++++-- .../org/myrobotlab/service/ProgramAB.java | 10 ++++++++ .../service/config/ProgramABConfig.java | 8 +++++++ .../service/interfaces/UtteranceListener.java | 23 +++++++++++++++++++ .../interfaces/UtterancePublisher.java | 19 ++++++++++++++- 5 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/DiscordBot.java b/src/main/java/org/myrobotlab/service/DiscordBot.java index f3aa3560cf..3a52145523 100644 --- a/src/main/java/org/myrobotlab/service/DiscordBot.java +++ b/src/main/java/org/myrobotlab/service/DiscordBot.java @@ -1,7 +1,6 @@ package org.myrobotlab.service; import java.util.List; -import java.util.Set; import org.myrobotlab.discord.MrlDiscordBotListener; import org.myrobotlab.framework.Service; @@ -9,7 +8,6 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.service.config.DiscordBotConfig; -import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.ImageData; import org.myrobotlab.service.data.Utterance; import org.myrobotlab.service.interfaces.ImageListener; @@ -86,6 +84,10 @@ public void attach(Attachable attachable) { attachUtteranceListener(attachable.getName()); } + if (attachable instanceof UtterancePublisher) { + attachUtterancePublisher(attachable.getName()); + } + if (attachable instanceof ImagePublisher) { attachImagePublisher(attachable.getName()); } @@ -95,6 +97,22 @@ public void attach(Attachable attachable) { } } + @Override + public void detach(Attachable attachable) { + if (attachable instanceof UtteranceListener) { + detachUtteranceListener(attachable.getName()); + } + + if (attachable instanceof UtterancePublisher) { + detachUtterancePublisher(attachable.getName()); + } + + if (attachable instanceof ImagePublisher) { + detachImagePublisher(attachable.getName()); + } + + } + @Override public DiscordBotConfig getConfig() { super.getConfig(); diff --git a/src/main/java/org/myrobotlab/service/ProgramAB.java b/src/main/java/org/myrobotlab/service/ProgramAB.java index 63dc903174..6b0aad8f00 100644 --- a/src/main/java/org/myrobotlab/service/ProgramAB.java +++ b/src/main/java/org/myrobotlab/service/ProgramAB.java @@ -1349,6 +1349,16 @@ public void onUtterance(Utterance utterance) throws Exception { if (!config.sleep) { shouldIRespond = true; } + + if (config.channels != null && config.channels.size() > 0) { + // assume false + shouldIRespond = false; + for (String channelName : config.channels) { + if (channelName.equals(utterance.channelName)) { + shouldIRespond = true; + } + } + } } } diff --git a/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java b/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java index 0cd4dbc839..168e5550fd 100644 --- a/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java +++ b/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java @@ -35,6 +35,14 @@ public class ProgramABConfig extends ServiceConfig { */ public boolean sleep = false; + + /** + * Specific list of channels ProgramAB will respond to, if not defined, then + * ProgramAB will respond to all channels + */ + public List<String> channels = new ArrayList<>(); + + /** * topic to start with, if null then topic will be loaded from predicates of * a new session if available, this means a config/{username}.predicates.txt diff --git a/src/main/java/org/myrobotlab/service/interfaces/UtteranceListener.java b/src/main/java/org/myrobotlab/service/interfaces/UtteranceListener.java index 9cbee1a146..041c8928f1 100755 --- a/src/main/java/org/myrobotlab/service/interfaces/UtteranceListener.java +++ b/src/main/java/org/myrobotlab/service/interfaces/UtteranceListener.java @@ -11,5 +11,28 @@ public interface UtteranceListener { public String getName(); public void onUtterance(Utterance utterance) throws Exception; + + + default public void attachUtterancePublisher(UtterancePublisher publisher) { + attachUtterancePublisher(publisher.getName()); + } + + // Default way to attach an image listener so implementing classes need + // not worry about these details. + default public void attachUtterancePublisher(String name) { + send(name, "attachUtteranceListener", getName()); + } + + default public void detachUtterancePublisher(UtterancePublisher publisher) { + detachUtterancePublisher(publisher.getName()); + } + + // Default way to attach an image listener so implementing classes need + // not worry about these details. + default public void detachUtterancePublisher(String name) { + send(name, "detachUtteranceListener", getName()); + } + + public void send(String name, String method, Object... data); } diff --git a/src/main/java/org/myrobotlab/service/interfaces/UtterancePublisher.java b/src/main/java/org/myrobotlab/service/interfaces/UtterancePublisher.java index ec467c6c61..330144fe54 100755 --- a/src/main/java/org/myrobotlab/service/interfaces/UtterancePublisher.java +++ b/src/main/java/org/myrobotlab/service/interfaces/UtterancePublisher.java @@ -1,5 +1,6 @@ package org.myrobotlab.service.interfaces; +import org.myrobotlab.framework.interfaces.NameProvider; import org.myrobotlab.service.data.Utterance; /** @@ -9,7 +10,7 @@ * * */ -public interface UtterancePublisher { +public interface UtterancePublisher extends NameProvider { // These are all the methods that the utterance publisher should produce. public static String[] publishMethods = new String[] { "publishUtterance" }; @@ -25,8 +26,24 @@ default public void attachUtteranceListener(String name) { addListener(publishMethod, name); } } + + default public void detachUtteranceListener(UtteranceListener display) { + detachUtteranceListener(display.getName()); + } + + // Default way to attach an image listener so implementing classes need + // not worry about these details. + default public void detachUtteranceListener(String name) { + for (String publishMethod : UtterancePublisher.publishMethods) { + removeListener(publishMethod, name); + } + } + // Add the addListener method to the interface all services implement this. public void addListener(String topicMethod, String callbackName); + + public void removeListener(String topicMethod, String callbackName); + } From cbd84fa819334ebb964ab16fc4138b7d78d02fdb Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 18 Feb 2024 12:19:49 -0800 Subject: [PATCH 081/131] onerror added and log.publishErrors --- .../java/org/myrobotlab/service/InMoov2.java | 147 +++++++----------- src/main/java/org/myrobotlab/service/Log.java | 10 ++ .../java/org/myrobotlab/service/OakD.java | 4 +- .../service/config/InMoov2Config.java | 6 +- 4 files changed, 75 insertions(+), 92 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 8aac57a03e..b83c909e47 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -24,7 +24,6 @@ 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; @@ -38,8 +37,6 @@ import org.myrobotlab.service.Log.LogEntry; 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.Locale; import org.myrobotlab.service.interfaces.IKJointAngleListener; @@ -56,8 +53,7 @@ import org.slf4j.Logger; public class InMoov2 extends Service<InMoov2Config> - implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, - IKJointAngleListener { + implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, IKJointAngleListener { public class Heart implements Runnable { private final ReentrantLock lock = new ReentrantLock(); @@ -130,7 +126,7 @@ public Heartbeat(InMoov2 inmoov) { * 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 */ @@ -279,8 +275,7 @@ public static void main(String[] args) { 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"); + 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 @@ -294,8 +289,7 @@ 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"); + 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); @@ -660,7 +654,7 @@ public boolean exec(String 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) { @@ -695,7 +689,7 @@ public void execScript() { * a filesystem file :P * * @param someScriptName - * execute a resource script + * execute a resource script * @return success or failure */ public void execScript(String someScriptName) { @@ -773,18 +767,11 @@ public InMoov2Head getHead() { */ public Long getLastActivityTime() { 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 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; @@ -951,7 +938,7 @@ 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) { @@ -1050,8 +1037,7 @@ 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) { + 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); } @@ -1103,10 +1089,8 @@ 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) { @@ -1117,10 +1101,8 @@ 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) { @@ -1149,7 +1131,7 @@ public PredicateEvent onChangePredicate(PredicateEvent event) { * comes in from runtime which owns the config list * * @param configList - * list of configs + * list of configs */ public void onConfigList(List<String> configList) { this.configList = configList; @@ -1210,16 +1192,10 @@ public void onJoystickInput(JoystickData input) throws Exception { * including lower level logs that do not propegate as statuses * * @param log - * - flushed log from Log service + * - flushed log from Log service */ - public void onLogEvents(List<LogEntry> log) { - // scan for warn or errors - for (LogEntry entry : log) { - if ("ERROR".equals(entry.level) && errors.size() < 100) { - errors.add(entry); - // invoke("publishError", entry); - } - } + public void onErrors(List<LogEntry> log) { + errors.addAll(log); } public String onNewState(String state) { @@ -1559,8 +1535,7 @@ public Heartbeat publishHeartbeat() { } // interval event firing - if (config.stateRandomInterval != null - && System.currentTimeMillis() > stateLastRandomTime + (config.stateRandomInterval * 1000)) { + if (config.stateRandomInterval != null && System.currentTimeMillis() > stateLastRandomTime + (config.stateRandomInterval * 1000)) { // fsm.fire("random"); stateLastRandomTime = System.currentTimeMillis(); } @@ -1612,8 +1587,7 @@ public Message publishMessage(Message msg) { return msg; } - public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double rotate, Double shoulder, - Double omoplate) { + public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double rotate, Double shoulder, Double omoplate) { HashMap<String, Double> map = new HashMap<>(); map.put("bicep", bicep); map.put("rotate", rotate); @@ -1627,8 +1601,7 @@ public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double return map; } - public HashMap<String, Object> publishMoveHand(String which, Double thumb, Double index, Double majeure, - Double ringFinger, Double pinky, Double wrist) { + public HashMap<String, Object> publishMoveHand(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { HashMap<String, Object> map = new HashMap<>(); map.put("which", which); map.put("thumb", thumb); @@ -1645,8 +1618,7 @@ public HashMap<String, Object> publishMoveHand(String which, Double thumb, Doubl return map; } - public HashMap<String, Double> publishMoveHead(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, - Double rollNeck) { + public HashMap<String, Double> publishMoveHead(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, Double rollNeck) { HashMap<String, Double> map = new HashMap<>(); map.put("neck", neck); map.put("rothead", rothead); @@ -1666,8 +1638,7 @@ public HashMap<String, Double> publishMoveLeftArm(Double bicep, Double rotate, D return map; } - public HashMap<String, Double> publishMoveLeftHand(Double thumb, Double index, Double majeure, Double ringFinger, - Double pinky, Double wrist) { + public HashMap<String, Double> publishMoveLeftHand(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { HashMap<String, Double> map = new HashMap<>(); map.put("thumb", thumb); map.put("index", index); @@ -1687,8 +1658,7 @@ public HashMap<String, Double> publishMoveRightArm(Double bicep, Double rotate, return map; } - public HashMap<String, Double> publishMoveRightHand(Double thumb, Double index, Double majeure, Double ringFinger, - Double pinky, Double wrist) { + public HashMap<String, Double> publishMoveRightHand(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { HashMap<String, Double> map = new HashMap<>(); map.put("thumb", thumb); map.put("index", index); @@ -1833,8 +1803,7 @@ 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); @@ -1844,14 +1813,12 @@ 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); } @@ -1867,8 +1834,7 @@ 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); } @@ -1892,8 +1858,7 @@ 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); } @@ -1905,15 +1870,12 @@ 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 @@ -1977,15 +1939,12 @@ 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 boolean setSpeechType(String speechType) { @@ -2003,8 +1962,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)); @@ -2155,8 +2116,7 @@ public ProgramAB startChatBot() { 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")) { + if (chatBot.getPredicate("lastUsername").isEmpty() || chatBot.getPredicate("lastUsername").equals("unknown") || chatBot.getPredicate("lastUsername").equals("default")) { chatBot.setPredicate("lastUsername", chatBot.getPredicate("name")); } } @@ -2172,8 +2132,7 @@ public ProgramAB startChatBot() { // !chatBot.getPredicate("default", "lastUsername").equals("unknown")) { // chatBot.startSession(chatBot.getPredicate("lastUsername")); // } - if (chatBot.getPredicate("default", "firstinit").isEmpty() - || chatBot.getPredicate("default", "firstinit").equals("unknown") + 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"); @@ -2319,19 +2278,27 @@ public void stopNeopixelAnimation() { public void systemCheck() { Platform platform = Runtime.getPlatform(); - int servoCount = 0; + int servoCount = 0; for (ServiceInterface si : Runtime.getServices()) { if (si.getClass().getSimpleName().equals("Servo")) { servoCount++; } } + // 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_servo_count", servoCount); - setPredicate("system_free_memory", Runtime.getFreeMemory()); - setPredicate("system_version", platform.getVersion()); - setPredicate("system_errors", errors.size()); - + setPredicate("system_service_count", Runtime.getServices().size()); + setPredicate("system_free_memory", Runtime.getFreeMemory() / 1000000); + setPredicate("system_errors_exist", errors.size() > 0); + setPredicate("system_error_count", errors.size()); + setPredicate("system_battery_level", Runtime.getBatteryLevel()); + setPredicate("state", getState()); + } public String systemEvent(String eventMsg) { diff --git a/src/main/java/org/myrobotlab/service/Log.java b/src/main/java/org/myrobotlab/service/Log.java index 16dec730f3..dc20806cf2 100644 --- a/src/main/java/org/myrobotlab/service/Log.java +++ b/src/main/java/org/myrobotlab/service/Log.java @@ -132,6 +132,7 @@ public void addError(String msg) { @Override public void addError(String arg0, Throwable arg1) { + System.out.println("addError"); } @Override @@ -202,6 +203,15 @@ synchronized public void flush() { if (buffer.size() > 0) { // bucket add to sliding window logs.addAll(buffer); + + List<LogEntry> errors = new ArrayList<>(); + for(int i = 0; i < buffer.size(); ++i) { + errors.add(buffer.get(i)); + } + if (errors.size() > 0) { + invoke("publishErrors", errors); + } + invoke("publishLogEvents", buffer); buffer = new ArrayList<>(maxSize); lastPublishLogTimeTs = System.currentTimeMillis(); diff --git a/src/main/java/org/myrobotlab/service/OakD.java b/src/main/java/org/myrobotlab/service/OakD.java index 5ea7b51dca..7918137e37 100644 --- a/src/main/java/org/myrobotlab/service/OakD.java +++ b/src/main/java/org/myrobotlab/service/OakD.java @@ -19,8 +19,10 @@ /** * + * https://github.com/luxonis/depthai + * python3 depthai_demo.py -cb callbacks.py * - * + * https://github.com/luxonis/depthai-experiments/tree/master/gen2-face-recognition * * @author GroG * diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 1e884c1e4a..ef14918ac8 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -516,7 +516,9 @@ public Plan getDefault(Plan plan, String name) { LogConfig log = (LogConfig) plan.get(getPeerName("log")); log.level = "WARN"; - log.listeners.add(new Listener("publishLogEvents", name)); + log.listeners.add(new Listener("publishErrors", name)); + // service --to--> InMoov2 + // mouth_audioFile.listeners.add(new Listener("publishAudioEnd", name)); // mouth_audioFile.listeners.add(new Listener("publishAudioStart", name)); @@ -542,6 +544,8 @@ public Plan getDefault(Plan plan, String name) { listeners.add(new Listener("publishMoveTorso", getPeerName("torso"), "onMove")); // service --to--> InMoov2 + + AudioFileConfig mouth_audioFile = (AudioFileConfig) plan.get(getPeerName("mouth.audioFile")); mouth_audioFile.listeners.add(new Listener("publishPeak", name)); From ac9884cdeb9079314c8c7cbcd398e76e1e380b46 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sun, 18 Feb 2024 21:18:45 -0800 Subject: [PATCH 082/131] updates --- .../org/myrobotlab/service/AudioFile.java | 80 ++---- .../service/FiniteStateMachine.java | 11 +- .../java/org/myrobotlab/service/InMoov2.java | 13 +- .../java/org/myrobotlab/service/NeoPixel.java | 2 +- .../java/org/myrobotlab/service/Runtime.java | 5 - .../service/config/AudioFileConfig.java | 6 +- .../config/FiniteStateMachineConfig.java | 2 +- .../service/config/InMoov2Config.java | 2 +- .../WebGui/app/service/js/RuntimeGui.js | 6 +- .../WebGui/app/service/tab-header.html | 228 +++++++++--------- .../WebGui/app/service/views/RuntimeGui.html | 2 +- .../WebGui/app/widget/modal-dialog.view.html | 2 +- 12 files changed, 153 insertions(+), 206 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/AudioFile.java b/src/main/java/org/myrobotlab/service/AudioFile.java index dbf2131bcf..39ce2dd345 100644 --- a/src/main/java/org/myrobotlab/service/AudioFile.java +++ b/src/main/java/org/myrobotlab/service/AudioFile.java @@ -115,18 +115,13 @@ public class AudioFile extends Service<AudioFileConfig> implements AudioPublishe // https://stackoverflow.com/questions/25798200/java-record-mic-to-byte-array-and-play-sound // - String currentTrack = DEFAULT_TRACK; + /** + * status field, the current track being played + */ + protected String currentTrack = DEFAULT_TRACK; transient Map<String, AudioProcessor> processors = new HashMap<String, AudioProcessor>(); - double volume = 1.0f; - // if set to true, playback will become a no-op - private boolean mute = false; - - protected String currentPlaylist = "default"; - - protected Map<String, List<String>> playlists = new HashMap<>(); - final private transient PlaylistPlayer playlistPlayer = new PlaylistPlayer(this); public void attach(Attachable attachable) { @@ -254,7 +249,7 @@ public AudioData playAudioData(AudioData data) { data.track = currentTrack; } setTrack(data.track); - processors.get(data.track).setVolume(volume); + processors.get(data.track).setVolume(config.volume); if (AudioData.MODE_QUEUED.equals(data.mode)) { // stick it on top of queue and let our default player play it return processors.get(data.track).add(data); @@ -329,7 +324,7 @@ public void silence() { * */ public void setVolume(float volume) { - this.volume = volume; + config.volume = volume; } public void setVolume(double volume) { @@ -337,7 +332,7 @@ public void setVolume(double volume) { } public double getVolume() { - return this.volume; + return config.volume; } public String getTrack() { @@ -441,28 +436,28 @@ public void deleteFile(String filename) { } public boolean isMute() { - return mute; + return config.mute; } public void setMute(boolean mute) { - this.mute = mute; + config.mute = mute; } public void setPlaylist(String name) { - currentPlaylist = name; + config.currentPlaylist = name; } public void addPlaylist(String folderPath) { - addPlaylist(currentPlaylist, folderPath); + addPlaylist(config.currentPlaylist, folderPath); } public void addPlaylist(String name, String path) { List<String> list = null; - if (!playlists.containsKey(name)) { + if (!config.playlists.containsKey(name)) { list = new ArrayList<String>(); } else { - list = playlists.get(name); + list = config.playlists.get(name); } File check = new File(path); if (!check.exists()) { @@ -473,7 +468,7 @@ public void addPlaylist(String name, String path) { list.addAll(scanForMusicFiles(path)); } int filecount = list.size(); - playlists.put(name, list); + config.playlists.put(name, list); log.info("{} playlist added {} files", name, filecount); } @@ -505,15 +500,15 @@ private List<String> scanForMusicFiles(String path) { } public List<String> getPlaylist(String name) { - return playlists.get(name); + return config.playlists.get(name); } public Map<String, List<String>> getPlaylists() { - return playlists; + return config.playlists; } public void startPlaylist() { - startPlaylist(currentPlaylist, false, false, currentPlaylist); + startPlaylist(config.currentPlaylist, false, false, DEFAULT_TRACK); } public void startPlaylist(String playlist) { @@ -525,54 +520,17 @@ public void startPlaylist(String playlist, boolean shuffle, boolean repeat) { } public void startPlaylist(String playlist, boolean shuffle, boolean repeat, String track) { - if (!playlists.containsKey(playlist)) { + if (!config.playlists.containsKey(playlist)) { error("cannot play playlist %s does not exists", playlist); return; } - playlistPlayer.start(playlists.get(playlist), shuffle, repeat, track); + playlistPlayer.start(config.playlists.get(playlist), shuffle, repeat, track); } public void stopPlaylist() { playlistPlayer.stop(); } - @Override - public AudioFileConfig getConfig() { - - AudioFileConfig c = (AudioFileConfig) super.getConfig(); - // FIXME - remove members keep data in config ! - // FIXME - the following is not needed nor desired - // useless self assignment - c.mute = mute; - c.currentTrack = currentTrack; - c.currentPlaylist = currentPlaylist; - // c.peakMultiplier = peakMultiplier; - c.volume = volume; - c.playlists = playlists; - // config.peakSampleInterval <- this one is done correctly no maintenance - c.audioListeners = getAttached("publishAudio").toArray(new String[0]); - - return config; - } - - public AudioFileConfig apply(AudioFileConfig config) { - super.apply(config); - setMute(config.mute); - setTrack(config.currentTrack); - setVolume(config.volume); - setPlaylist(config.currentPlaylist); - if (config.playlists != null) { - playlists = config.playlists; - } - - if (config.audioListeners != null) { - for (String listener : config.audioListeners) { - attachAudioListener(listener); - } - } - - return config; - } public double publishPeak(double peak) { log.debug("publishPeak {}", peak); diff --git a/src/main/java/org/myrobotlab/service/FiniteStateMachine.java b/src/main/java/org/myrobotlab/service/FiniteStateMachine.java index e998711e26..5ab6cf5604 100644 --- a/src/main/java/org/myrobotlab/service/FiniteStateMachine.java +++ b/src/main/java/org/myrobotlab/service/FiniteStateMachine.java @@ -254,13 +254,6 @@ public StateChange publishStateChange(StateChange stateChange) { return stateChange; } - @Override - public FiniteStateMachineConfig getConfig() { - super.getConfig(); - config.current = getState(); - return config; - } - @Override public FiniteStateMachineConfig apply(FiniteStateMachineConfig c) { super.apply(c); @@ -280,8 +273,8 @@ public FiniteStateMachineConfig apply(FiniteStateMachineConfig c) { } // setCurrent - if (c.current != null) { - setCurrent(c.current); + if (c.start != null) { + setCurrent(c.start); } return c; diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index b83c909e47..be0f976919 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -102,7 +102,6 @@ public static class Heartbeat { double batteryLevel = 100; public long count = 0; public List<LogEntry> errors; - public boolean isPirOn = false; public String state; public long ts = System.currentTimeMillis(); @@ -110,7 +109,6 @@ public Heartbeat(InMoov2 inmoov) { this.state = inmoov.state; this.errors = inmoov.errors; this.count = inmoov.heartbeatCount; - this.isPirOn = inmoov.isPirOn; } } @@ -237,8 +235,6 @@ public static void main(String[] args) { protected transient ImageDisplay imageDisplay; - protected boolean isPirOn = false; - protected boolean isSpeaking = false; protected String lastGestureExecuted; @@ -1239,16 +1235,12 @@ public void onPeak(double volume) { */ public void onPirOn() { log.info("onPirOn"); - // FIXME flash on config.flashOnBoot - invoke("publishFlash", "pir"); - String botState = chatBot.getPredicate("botState"); - if ("sleeping".equals(botState)) { - invoke("publishEvent", "WAKE"); - } + processMessage("onPirOn"); } public void onPirOff() { log.info("onPirOff"); + processMessage("onPirOff"); } // GOOD GOOD GOOD - LOOPBACK - flexible and replacable by python @@ -1682,6 +1674,7 @@ public String publishPlayAudioFile(String filename) { } /** + * One of the most important publishing point. * Processing publishing point, where everything InMoov2 wants to be processed * is turned into a message and published. * diff --git a/src/main/java/org/myrobotlab/service/NeoPixel.java b/src/main/java/org/myrobotlab/service/NeoPixel.java index a5a22c4782..a81397f49b 100644 --- a/src/main/java/org/myrobotlab/service/NeoPixel.java +++ b/src/main/java/org/myrobotlab/service/NeoPixel.java @@ -937,7 +937,7 @@ public void setPixel(String matrixName, Integer pixelSetIndex, int address, int // Runtime.getService(controller); ServiceInterface sc = Runtime.getService(controller); if (sc == null) { - error("controler %s not valid", controller); + error("controller %s not valid", controller); return; } diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 9b76d3bf65..d8a020dd84 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -433,11 +433,6 @@ private static Map<String, ServiceInterface> createServicesFromPlan(Plan plan, M RuntimeConfig currentConfig = Runtime.getInstance().config; for (String service : plansRtConfig.getRegistry()) { - // FIXME - determine if you want to return a complete merge of activated - // or just "recent" - if (Runtime.getService(service) != null) { - continue; - } ServiceConfig sc = plan.get(service); if (sc == null) { runtime.error("could not get %s from plan", service); diff --git a/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java b/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java index 391d175d55..086e93691f 100644 --- a/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java +++ b/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java @@ -3,12 +3,16 @@ import java.util.List; import java.util.Map; +import org.myrobotlab.service.AudioFile; + public class AudioFileConfig extends ServiceConfig { public boolean mute = false; - public String currentTrack = "default"; + public double volume = 1.0; + public String currentPlaylist = "default"; + public Map<String, List<String>> playlists; @Deprecated /* use regular "listeners" from ServiceConfig parent */ diff --git a/src/main/java/org/myrobotlab/service/config/FiniteStateMachineConfig.java b/src/main/java/org/myrobotlab/service/config/FiniteStateMachineConfig.java index bd4e5648f3..e69c44a4f7 100644 --- a/src/main/java/org/myrobotlab/service/config/FiniteStateMachineConfig.java +++ b/src/main/java/org/myrobotlab/service/config/FiniteStateMachineConfig.java @@ -23,7 +23,7 @@ public Transition(String from, String event, String to) { public List<Transition> transitions = new ArrayList<>(); - public String current = null; + public String start = null; } diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index ef14918ac8..49575f58c4 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -369,7 +369,7 @@ public Plan getDefault(Plan plan, String name) { // TODO - events easily gotten from InMoov data ?? auto callbacks in python // if // exists ? - fsm.current = "boot"; + fsm.start = "boot"; fsm.transitions.add(new Transition("boot", "wake", "wake")); // setup, nor sleep should be affected by idle fsm.transitions.add(new Transition("setup", "setup_done", "idle")); diff --git a/src/main/resources/resource/WebGui/app/service/js/RuntimeGui.js b/src/main/resources/resource/WebGui/app/service/js/RuntimeGui.js index fbb2edc05d..b1007dcbc1 100644 --- a/src/main/resources/resource/WebGui/app/service/js/RuntimeGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/RuntimeGui.js @@ -388,14 +388,14 @@ angular.module('mrlapp.service.RuntimeGui', []).controller('RuntimeGuiCtrl', ['$ modalInstance.result.then(function(result) { // Handle 'OK' button click - console.log('Config Name: ' + $scope.service.configName) + console.log('Config Name: ' + $scope.configName) console.log('Selected Option: ' + $scope.service.selectedOption) console.log('includePeers Option: ' + $scope.service.includePeers) console.log('configType Option: ' + $scope.service.configType) if ($scope.service.selectedOption == 'default'){ - msg.send('saveDefault', $scope.service.configName, $scope.service.defaultServiceName, $scope.service.configType, $scope.service.includePeers) + msg.send('saveDefault', $scope.configName, $scope.service.defaultServiceName, $scope.service.configType, $scope.service.includePeers) } else { - msg.sendTo('runtime', 'saveConfig', $scope.service.configName) + msg.sendTo('runtime', 'saveConfig', $scope.configName) } }, function() { // Handle 'Cancel' button click or modal dismissal diff --git a/src/main/resources/resource/WebGui/app/service/tab-header.html b/src/main/resources/resource/WebGui/app/service/tab-header.html index 4abedeb490..a87ea4c8cf 100644 --- a/src/main/resources/resource/WebGui/app/service/tab-header.html +++ b/src/main/resources/resource/WebGui/app/service/tab-header.html @@ -1,117 +1,121 @@ <div class="tab-header col-md-12"> - <table> + <table> + <tr> + <td valign="top"> + <button class="btn btn-default" ng-click="mrl.goBack()" title="back"> + <span class="glyphicon glyphicon-arrow-left"></span> + </button> + </td> + <td> + <div class="dropdown"> + <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown"> + <img ng-src="{{::service.simpleName}}.png" alt="" width="16" /> +   {{::service.simpleName}} {{::service.name}}@{{::service.id}} {{::service.serviceVersion}} + <span class="caret"></span> + </button> + <ul class="dropdown-menu"> + <li class="dropdown-header">service functions</li> + <li> + <a href="http://myrobotlab.org/service/{{::service.simpleName}}" target="_blank" ng-click="servicemenuDropdownOpen = false"> + <i class="glyphicon glyphicon-question-sign"></i> + help + </a> + </li> + <li> + <a href="" ng-click="servicemenuDropdownOpen = false;msg.broadcastState()"> + <i class="glyphicon glyphicon-refresh"></i> + refresh + </a> + </li> + <li> + <a href="" target="_blank" width="100%"> + <i class="glyphicon glyphicon-equalizer"></i> + <span ng-show="!parentPanel.showPeerTable" ng-click="showPeers(true)"> show peers</span> + <span ng-show="parentPanel.showPeerTable" ng-click="showPeers(false)"> hide peers</span> + </a> + </li> + <li class="divider"></li> + <li class="dropdown-header">json</li> + <li> + <!-- a href="" target="_blank" ng-click="showProperties=!showProperties" --> + <a href="/api/service/{{::service.name}}" target="_blank"> + <i class="glyphicon glyphicon-list-alt"></i> + properties + </a> + </li> + <li> + <!-- a href="" target="_blank" ng-click="showMethods=!showMethods" --> + <a href="/api/service/{{::service.name}}/" target="_blank"> + <i class="glyphicon glyphicon-list-alt"></i> + methods + </a> + </li> + <li> + <!-- a href="" target="_blank" ng-click="showMethods=!showMethods" --> + <a href='/api/service/runtime/getNotifyEntries/"{{service.name}}"' target="_blank"> + <i class="glyphicon glyphicon-list-alt"></i> + subscriptions + </a> + </li> + </ul> + </div> + </td> + <td> + <button class="btn btn-default" ng-click="release()" title="stops and releases a service"> + <span class="glyphicon glyphicon-remove red"></span> + </button> + </td> + <td> + <button class="btn btn-default" ng-click="save()" title="save current configuration"> + <span class="glyphicon glyphicon-save-file"></span> + </button> + </td> + <td> + <button class="btn btn-default" ng-click="apply()" title="load and apply configuration"> + <span class="glyphicon glyphicon-open-file"></span> + </button> + </td> + </tr> + </table> + <div ng-show="parentPanel.showPeerTable"> + <table class="table table-sm"> + <thead> <tr> - <td valign="top"> - <button class="btn btn-default" ng-click="mrl.goBack()" title="back"> - <span class="glyphicon glyphicon-arrow-left"></span> - </button> - </td> - <td> - <div class="dropdown"> - <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown"> - <img ng-src="{{::service.simpleName}}.png" alt="" width="16"> -   {{::service.simpleName}} {{::service.name}}@{{::service.id}} {{::service.serviceVersion}}<span class="caret"></span> - </button> - <ul class="dropdown-menu"> - <li class="dropdown-header">service functions</li> - <li> - <a href="http://myrobotlab.org/service/{{::service.simpleName}}" target="_blank" ng-click="servicemenuDropdownOpen = false"> - <i class="glyphicon glyphicon-question-sign"></i> - help - </a> - </li> - <li> - <a href="" ng-click="servicemenuDropdownOpen = false;msg.broadcastState()"> - <i class="glyphicon glyphicon-refresh"></i> - refresh - </a> - </li> - <li> - <a href="" target="_blank" width="100%"> - <i class="glyphicon glyphicon-equalizer"></i> - <span ng-show="!parentPanel.showPeerTable" ng-click="showPeers(true)"> show peers</span> - <span ng-show="parentPanel.showPeerTable" ng-click="showPeers(false)"> hide peers</span> - </a> - </li> - <li class="divider"></li> - <li class="dropdown-header">json</li> - <li> - <!-- a href="" target="_blank" ng-click="showProperties=!showProperties" --> - <a href="/api/service/{{::service.name}}" target="_blank"> - <i class="glyphicon glyphicon-list-alt"></i> - properties - - - </a> - </li> - <li> - <!-- a href="" target="_blank" ng-click="showMethods=!showMethods" --> - <a href="/api/service/{{::service.name}}/" target="_blank"> - <i class="glyphicon glyphicon-list-alt"></i> - methods - - - </a> - </li> - <li> - <!-- a href="" target="_blank" ng-click="showMethods=!showMethods" --> - <a href="/api/service/runtime/getNotifyEntries/{{service.name}}" target="_blank"> - <i class="glyphicon glyphicon-list-alt"></i> - subscriptions - </a> - </li> - </ul> - </div> - </td> - <td> - <button class="btn btn-default" ng-click="release()" title="stops and releases a service"> - <span class="glyphicon glyphicon-remove red"></span> - </button> - </td> - <td> - <button class="btn btn-default" ng-click="save()" title="save current configuration"> - <span class="glyphicon glyphicon-save-file"></span> - </button> - </td> - <td> - <button class="btn btn-default" ng-click="apply()" title="load and apply configuration"> - <span class="glyphicon glyphicon-open-file"></span> - </button> - </td> - + <th scope="col">key</th> + <th scope="col">name</th> + <th scope="col">state</th> + <th scope="col"></th> </tr> + </thead> + <tbody> + <tr ng-repeat="(key, value) in service.config.peers"> + <td> + <span> + <a href="" ng-click="mrl.changeTab(peer.getActualName(service, key))"> + <img width="32" ng-src="/{{value.type + '.png'}}" width="48" /> + </a> + {{peer.getActualName(service, key)}} + </span> + </td> + <td> + {{value.key}} + <br /> + {{value.type}} + </td> + <td>{{value.state}}</td> + <td> + <toggle + width="30" + height="28" + ng-model="service['is' + key[0].toUpperCase() + key.substring(1) + 'Started']" + ng-change="service['is' + key[0].toUpperCase() + key.substring(1) + 'Started']?startPeer(key):releasePeer(key)" + on="" + off="" + /> + </td> + <td></td> + </tr> + </tbody> </table> - <div ng-show="parentPanel.showPeerTable"> - <table class="table table-sm"> - <thead> - <tr> - <th scope="col">key</th> - <th scope="col">name</th> - <th scope="col">state</th> - <th scope="col"></th> - </tr> - </thead> - <tbody> - <tr ng-repeat="(key, value) in service.config.peers"> - <td> - <span> - - <a href="" ng-click="mrl.changeTab(peer.getActualName(service, key))"> - <img width="32" ng-src="/{{value.type + '.png'}}" width="48"> - </a> - {{peer.getActualName(service, key)}} - </span> - </td> - <td> - {{value.key}}<br/>{{value.type}} - </td> - <td>{{value.state}}</td> - <td> - <toggle width="30" height="28" ng-model="service['is' + key[0].toUpperCase() + key.substring(1) + 'Started']" ng-change="service['is' + key[0].toUpperCase() + key.substring(1) + 'Started']?startPeer(key):releasePeer(key)" on="" off=""/> - </td> - <td></td> - </tr> - </tbody> - </table> - </div> + </div> </div> diff --git a/src/main/resources/resource/WebGui/app/service/views/RuntimeGui.html b/src/main/resources/resource/WebGui/app/service/views/RuntimeGui.html index 7e1fd406ff..52229335c0 100644 --- a/src/main/resources/resource/WebGui/app/service/views/RuntimeGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/RuntimeGui.html @@ -153,7 +153,7 @@ <h4 class="modal-title">Save Configuration</h4> <tr> <td> Save your current configuration in a directory named<br/> - <input type="text" ng-model="service.configName" class="form-control" placeholder="Configuration Name"> + <input type="text" ng-model="configName" class="form-control" placeholder="Configuration Name"> </td> <td><br/><br/> <input ng-show="service.selectedOption==='default'" type="text" ng-model="service.defaultServiceName" class="form-control" placeholder="Service Name"> diff --git a/src/main/resources/resource/WebGui/app/widget/modal-dialog.view.html b/src/main/resources/resource/WebGui/app/widget/modal-dialog.view.html index f8f7104400..38343da431 100644 --- a/src/main/resources/resource/WebGui/app/widget/modal-dialog.view.html +++ b/src/main/resources/resource/WebGui/app/widget/modal-dialog.view.html @@ -7,7 +7,7 @@ <h4 class="modal-title" id="myModalLabel">{{title }}</h4> <div class="form-inline"> <div class="form-group"> - <input type="text" class="form-control" ng-model="service.configName" placeholder="myconfig"> + <input type="text" class="form-control" ng-model="selectedConfig" placeholder="myconfig"> </div> </div> From a63e083a27996cd3db67d96873b24472e5950ab4 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Mon, 19 Feb 2024 07:35:57 -0800 Subject: [PATCH 083/131] removed adding system tray icon --- src/main/java/org/myrobotlab/service/ImageDisplay.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/myrobotlab/service/ImageDisplay.java b/src/main/java/org/myrobotlab/service/ImageDisplay.java index d921e2f270..4be2abae11 100644 --- a/src/main/java/org/myrobotlab/service/ImageDisplay.java +++ b/src/main/java/org/myrobotlab/service/ImageDisplay.java @@ -275,6 +275,7 @@ public void run() { // TODO - make better / don't use setImageAutoSize (very bad // algorithm) + /** <pre> No real use, and doesn't remove if (SystemTray.isSupported()) { log.info("SystemTray is supported"); SystemTray tray = SystemTray.getSystemTray(); @@ -285,6 +286,8 @@ public void run() { tray.add(trayIcon); } + </pre> + */ if (display.bgColor != null) { Color color = Color.decode(display.bgColor); From 98a24286150d5668139a7b92d1027b3d9cc96c88 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Mon, 19 Feb 2024 07:36:58 -0800 Subject: [PATCH 084/131] removed onPeak info --- src/main/java/org/myrobotlab/service/InMoov2.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index be0f976919..baa89cae49 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -1222,11 +1222,7 @@ public OpenCVData onOpenCVData(OpenCVData data) { * @param volume */ public void onPeak(double volume) { - if (config.neoPixelFlashWhenSpeaking && !"boot".equals(getState())) { - if (volume > 0.5) { - invoke("publishSpeakingFlash", "speaking"); - } - } + processMessage("onPeak", volume); } /** From 2b5823df6e35a553573e5f360984614d8280b097 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Mon, 19 Feb 2024 21:02:29 -0800 Subject: [PATCH 085/131] audiofile config playlist init --- .../java/org/myrobotlab/service/config/AudioFileConfig.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java b/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java index 086e93691f..47b3dc9b91 100644 --- a/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java +++ b/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java @@ -2,8 +2,7 @@ import java.util.List; import java.util.Map; - -import org.myrobotlab.service.AudioFile; +import java.util.TreeMap; public class AudioFileConfig extends ServiceConfig { @@ -13,7 +12,7 @@ public class AudioFileConfig extends ServiceConfig { public String currentPlaylist = "default"; - public Map<String, List<String>> playlists; + public Map<String, List<String>> playlists = new TreeMap<>(); @Deprecated /* use regular "listeners" from ServiceConfig parent */ public String[] audioListeners; From 72da4c5f1baa17b4e7410911e944702969834fe7 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 20 Feb 2024 09:14:32 -0800 Subject: [PATCH 086/131] updates --- .../org/myrobotlab/framework/CmdOptions.java | 2 +- .../java/org/myrobotlab/service/InMoov2.java | 337 +++++++----------- .../org/myrobotlab/service/ProgramAB.java | 15 +- .../java/org/myrobotlab/service/Random.java | 14 +- .../service/config/InMoov2Config.java | 5 +- .../org/myrobotlab/service/HarryTest.java | 2 +- 6 files changed, 153 insertions(+), 222 deletions(-) diff --git a/src/main/java/org/myrobotlab/framework/CmdOptions.java b/src/main/java/org/myrobotlab/framework/CmdOptions.java index 2c357e8db6..3b3b18d7bb 100644 --- a/src/main/java/org/myrobotlab/framework/CmdOptions.java +++ b/src/main/java/org/myrobotlab/framework/CmdOptions.java @@ -39,7 +39,7 @@ static boolean contains(List<String> l, String flag) { // launcher @Option(names = { "-c", - "--config" }, fallbackValue = "default", description = "Specify a configuration set to start. The config set is a directory which has all the necessary configuration files. It loads runtime.yml first, and subsequent service configuration files will then load. \n example: --config data/config/my-config-dir") + "--config" }, fallbackValue = "default", description = "Specify a configuration set to start. The config set is a directory which has all the necessary configuration files. It loads runtime.yml first, and subsequent service configuration files will then load. \n example: --config my-config-dir to start the configuration stored in config data/config/my-config-dir") public String config = null; @Option(names = { "-h", "-?", "--help" }, description = "shows help") diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index baa89cae49..2322a4bc8e 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -3,17 +3,16 @@ import java.io.File; import java.io.FilenameFilter; 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.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.locks.ReentrantLock; @@ -33,6 +32,7 @@ import org.myrobotlab.opencv.OpenCVData; import org.myrobotlab.programab.PredicateEvent; import org.myrobotlab.programab.Response; +import org.myrobotlab.programab.Session; import org.myrobotlab.service.FiniteStateMachine.StateChange; import org.myrobotlab.service.Log.LogEntry; import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; @@ -47,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; @@ -114,12 +113,8 @@ public Heartbeat(InMoov2 inmoov) { public final static Logger log = LoggerFactory.getLogger(InMoov2.class); - public static LinkedHashMap<String, String> lpVars = new LinkedHashMap<String, String>(); - private static final long serialVersionUID = 1L; - static String speechRecognizer = "WebkitSpeechRecognition"; - /** * This method will load a python file into the python interpreter. * @@ -187,8 +182,6 @@ public static void main(String[] args) { } } - protected Double batteryLevel = 100.0; - /** * number of times waited in boot state */ @@ -198,11 +191,6 @@ public static void main(String[] args) { protected List<String> configList; - /** - * map of events or states to sounds - */ - protected Map<String, String> customSoundMap = new TreeMap<>(); - protected transient SpeechRecognizer ear; protected List<LogEntry> errors = new ArrayList<>(); @@ -213,7 +201,7 @@ public static void main(String[] args) { * 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; + private transient FiniteStateMachine fsm; // waiting controable threaded gestures we warn user protected boolean gestureAlreadyStarted = false; @@ -223,24 +211,18 @@ public static void main(String[] args) { /** * Prevents actions or events from happening when InMoov2 is first booted */ - private boolean hasBooted = false; + protected boolean hasBooted = false; private transient final Heart heart = new Heart(); protected long heartbeatCount = 0; - protected boolean heartBeating = false; - - protected transient HtmlFilter htmlFilter; - protected transient ImageDisplay imageDisplay; protected boolean isSpeaking = false; protected String lastGestureExecuted; - protected Long lastPirActivityTime; - protected String lastState = null; /** @@ -250,8 +232,6 @@ public static void main(String[] args) { protected int maxInactivityTimeSeconds = 120; - protected transient SpeechSynthesis mouth; - protected boolean mute = false; protected transient OpenCV opencv; @@ -261,7 +241,7 @@ public static void main(String[] args) { /** * initial state - updated on any state change */ - String state = "boot"; + protected String state = "boot"; protected long stateLastIdleTime = System.currentTimeMillis(); @@ -292,6 +272,8 @@ public InMoov2Config apply(InMoov2Config c) { } else { setLocale(getSupportedLocale(Runtime.getInstance().getLocale().toString())); } + // one way sync configuration into predicates + configToPredicates(); } catch (Exception e) { error(e); @@ -567,6 +549,13 @@ public long checkInactivity() { return lastActivityTime; } + /** + * clear all errors + */ + public void clearErrors() { + errors.clear(); + } + public void closeAllImages() { // FIXME - follow this pattern ? // CON npe possible although unlikely @@ -576,6 +565,38 @@ public void closeAllImages() { imageDisplay.closeAll(); } + /** + * Updates configuration into ProgramAB predicates. + */ + public void configToPredicates() { + log.info("configToPredicates"); + if (chatBot != null) { + Class<?> pojoClass = config.getClass(); + Field[] fields = pojoClass.getDeclaredFields(); + for (Field field : fields) { + try { + field.setAccessible(true); + Object value = field.get(config); // Requires handling + Map<String, Session> sessions = chatBot.getSessions(); + if (sessions != null) { + for (Session session : sessions.values()) { + if (value != null) { + session.setPredicate(field.getName(), value.toString()); + } else { + session.setPredicate(field.getName(), null); + } + + } + } + } catch (Exception e) { + error(e); + } + } + } else { + log.info("chatbot not ready for config sync"); + } + } + public void cycleGestures() { // if not loaded load - // FIXME - this needs alot of "help" :P @@ -872,10 +893,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); @@ -894,13 +911,6 @@ public boolean hasErrors() { return errors.size() > 0; } - /** - * clear all errors - */ - public void clearErrors() { - errors.clear(); - } - public boolean isCameraOn() { if (opencv != null) { if (opencv.isCapturing()) { @@ -914,6 +924,10 @@ public boolean isMute() { return mute; } + public boolean isSpeaking() { + return isSpeaking; + } + /** * execute python scripts in the app directory on startup of the service * @@ -1144,6 +1158,17 @@ public void onEndSpeaking(String utterance) { isSpeaking = false; } + /** + * 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 onErrors(List<LogEntry> log) { + errors.addAll(log); + } + public void onFinishedConfig(String configName) { log.info("onFinishedConfig"); // invoke("publishEvent", "configFinished"); @@ -1183,17 +1208,6 @@ public void onJoystickInput(JoystickData input) throws Exception { invoke("publishEvent", "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 onErrors(List<LogEntry> log) { - errors.addAll(log); - } - public String onNewState(String state) { log.error("onNewState {}", state); @@ -1225,20 +1239,22 @@ public void onPeak(double volume) { processMessage("onPeak", volume); } + public void onPirOff() { + log.info("onPirOff"); + setPredicate(String.format("%s.pir_off", getName()), System.currentTimeMillis()); + processMessage("onPirOff"); + } + /** * initial callback for Pir sensor Default behavior will be: send fsm event * onPirOn flash neopixel */ public void onPirOn() { log.info("onPirOn"); + setPredicate(String.format("%s.pir_on", getName()), System.currentTimeMillis()); processMessage("onPirOn"); } - public void onPirOff() { - log.info("onPirOff"); - processMessage("onPirOff"); - } - // GOOD GOOD GOOD - LOOPBACK - flexible and replacable by python // yet provides a stable default, which can be fully replaced // Works using common pub/sub rules @@ -1277,6 +1293,15 @@ public boolean onSense(boolean b) { return b; } + /** + * When a new session is started this will sync config with it + * + * @param sessionKey + */ + public void onSession(String sessionKey) { + configToPredicates(); + } + /** * runtime re-publish relay * @@ -1318,42 +1343,6 @@ public void onStartSpeaking(String utterance) { isSpeaking = true; } - /** - * publishStateChange - * - * 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 StateChange publishStateChange(StateChange stateChange) { - log.info("publishStateChange {}", stateChange); - - log.info("onStateChange {}", stateChange); - - lastState = state; - state = stateChange.state; - - processMessage("onStateChange", stateChange); - - return stateChange; - } - @Override public void onStopped(String name) { log.info("service {} has stopped"); @@ -1670,9 +1659,9 @@ public String publishPlayAudioFile(String filename) { } /** - * One of the most important publishing point. - * Processing publishing point, where everything InMoov2 wants to be processed - * is turned into a message and published. + * One of the most important publishing point. Processing publishing point, + * where everything InMoov2 wants to be processed is turned into a message and + * published. * * @param msg * @return @@ -1702,6 +1691,42 @@ public String publishSpeakingFlash(String name) { return name; } + /** + * publishStateChange + * + * 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 StateChange publishStateChange(StateChange stateChange) { + log.info("publishStateChange {}", stateChange); + + log.info("onStateChange {}", stateChange); + + lastState = state; + state = stateChange.state; + + processMessage("onStateChange", stateChange); + + return stateChange; + } + /** * stop animation event */ @@ -1788,6 +1813,12 @@ public void setAutoDisable(Boolean param) { sendToPeer("torso", "setAutoDisable", param); } + @Override + public void setConfigValue(String fieldname, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + super.setConfigValue(fieldname, value); + setPredicate(fieldname, value); + } + public void setHandSpeed(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky) { setHandSpeed(which, thumb, index, majeure, ringFinger, pinky, null); } @@ -1902,15 +1933,6 @@ public void setNeopixelAnimation(String animation, Integer red, Integer green, I sendToPeer("neopixel", "animation", red, green, blue, speed); } - public void setOpenCV(OpenCV opencv) { - this.opencv = opencv; - } - - public boolean setPirPlaySounds(boolean b) { - getTypedConfig().pirPlaySounds = b; - return b; - } - public Object setPredicate(String key, Object data) { if (data == null) { chatBot.setPredicate(key, null); // "unknown" "null" other sillyness ? @@ -1987,24 +2009,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"); } @@ -2051,103 +2055,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 { - 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 { - - 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"); } diff --git a/src/main/java/org/myrobotlab/service/ProgramAB.java b/src/main/java/org/myrobotlab/service/ProgramAB.java index 6b0aad8f00..c32a0ab437 100644 --- a/src/main/java/org/myrobotlab/service/ProgramAB.java +++ b/src/main/java/org/myrobotlab/service/ProgramAB.java @@ -711,12 +711,25 @@ public Session startSession(String path, String userName, String botName, java.u } session = new Session(this, userName, botInfo); - sessions.put(getSessionKey(userName, botName), session); + String sessionKey = getSessionKey(userName, botName); + sessions.put(sessionKey, session); log.info("Started session for bot botName:{} , userName:{}", botName, userName); setCurrentSession(userName, botName); + + invoke("publishSession", sessionKey); + return session; } + + /** + * When a new session is started this event is published with the session's key + * @param sessionKey of new Session + * @return sessionKey + */ + public String publishSession(String sessionKey) { + return sessionKey; + } /** * setting the current session is equivalent to setting current user name and diff --git a/src/main/java/org/myrobotlab/service/Random.java b/src/main/java/org/myrobotlab/service/Random.java index d018df206a..fd3ce94187 100644 --- a/src/main/java/org/myrobotlab/service/Random.java +++ b/src/main/java/org/myrobotlab/service/Random.java @@ -229,11 +229,21 @@ public void run() { // and see if any random event needs processing sleep(config.rate); - for (String key : randomData.keySet()) { + // copy to avoid concurrent exceptions, avoid iterating over randomData + Map<String, RandomMessage> tasks = new HashMap<>(); + Set<String> keySet = new HashSet<String>(randomData.keySet()); + for (String k : keySet) { + RandomMessage rm = randomData.get(k); + if (rm != null) { + tasks.put(k, rm); + } + } + + for (String key : tasks.keySet()) { long now = System.currentTimeMillis(); - RandomMessage randomEntry = randomData.get(key); + RandomMessage randomEntry = tasks.get(key); if (!randomEntry.enabled) { continue; } diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 49575f58c4..d5e7116605 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -37,7 +37,7 @@ public class InMoov2Config extends ServiceConfig { public boolean flashOnErrors = true; - public boolean flashOnPir; + public boolean flashOnPir = false; public boolean forceMicroOnIfSleeping = true; @@ -276,7 +276,8 @@ public Plan getDefault(Plan plan, String name) { } } - chatBot.listeners.add(new Listener("publishText", name + ".htmlFilter", "onText")); + chatBot.listeners.add(new Listener("publishText", getPeerName("htmlFilter"), "onText")); + chatBot.listeners.add(new Listener("publishSession", name)); Gpt3Config gpt3 = (Gpt3Config) plan.get(getPeerName("gpt3")); gpt3.listeners.add(new Listener("publishText", name + ".htmlFilter", "onText")); 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(); From 7b0bfd070c4a183cf50adbadccad272bd72bc1aa Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Thu, 22 Feb 2024 07:39:37 -0800 Subject: [PATCH 087/131] updates --- .../org/myrobotlab/programab/Session.java | 9 +- .../java/org/myrobotlab/service/InMoov2.java | 52 +++-- src/main/java/org/myrobotlab/service/Log.java | 5 +- .../java/org/myrobotlab/service/Random.java | 44 ++-- .../java/org/myrobotlab/service/Runtime.java | 16 +- .../abstracts/AbstractSpeechSynthesis.java | 192 +++++++----------- .../service/config/AudioFileConfig.java | 8 +- .../service/config/InMoov2Config.java | 6 +- .../service/config/SpeechSynthesisConfig.java | 12 +- 9 files changed, 173 insertions(+), 171 deletions(-) diff --git a/src/main/java/org/myrobotlab/programab/Session.java b/src/main/java/org/myrobotlab/programab/Session.java index a234310e57..a76fb94bec 100644 --- a/src/main/java/org/myrobotlab/programab/Session.java +++ b/src/main/java/org/myrobotlab/programab/Session.java @@ -5,7 +5,9 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Date; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; @@ -122,7 +124,12 @@ public void savePredicates() { */ public Map<String, String> getPredicates() { TreeMap<String, String> sort = new TreeMap<>(); - sort.putAll(getChat().predicates); + // copy keys, making this sort thread safe + Set<String> keys = new HashSet(getChat().predicates.keySet()); + for (String key: keys) { + String value = getChat().predicates.get(key); + sort.put(key, value); + } return sort; } diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 2322a4bc8e..c496da1a61 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -98,15 +98,10 @@ public void stop() { } public static class Heartbeat { - double batteryLevel = 100; public long count = 0; - public List<LogEntry> errors; - public String state; public long ts = System.currentTimeMillis(); public Heartbeat(InMoov2 inmoov) { - this.state = inmoov.state; - this.errors = inmoov.errors; this.count = inmoov.heartbeatCount; } } @@ -581,9 +576,9 @@ public void configToPredicates() { if (sessions != null) { for (Session session : sessions.values()) { if (value != null) { - session.setPredicate(field.getName(), value.toString()); + session.setPredicate(String.format("config.%s", field.getName()), value.toString()); } else { - session.setPredicate(field.getName(), null); + session.setPredicate(String.format("config.%s", field.getName()), null); } } @@ -640,6 +635,23 @@ public void displayFullScreen(String src) { error("could not display picture %s", src); } } + + public void enableRandomHead() { + Random random = (Random)getPeer("random"); + if (random != null) { + random.disableAll(); + random.enable(String.format("%s.setHeadSpeed", getName())); + random.enable(String.format("%s.moveHead", getName())); + random.enable(); + } + } + + public void disableRandom() { + Random random = (Random)getPeer("random"); + if (random != null) { + random.disable(); + } + } public void enable() { sendToPeer("head", "enable"); @@ -1155,6 +1167,7 @@ public void onCreated(String fullname) { @Override public void onEndSpeaking(String utterance) { + processMessage("onEndSpeaking", utterance); isSpeaking = false; } @@ -1337,9 +1350,9 @@ public void onStarted(String name) { } } - // FIXME - rebroadcast these @Override public void onStartSpeaking(String utterance) { + processMessage("onStartSpeaking", utterance); isSpeaking = true; } @@ -1402,7 +1415,7 @@ public void processMessage(String method) { * @param method * @param data */ - public void processMessage(String method, Object data) { + public void processMessage(String method, Object ... data) { // User processing should not occur until after boot has completed if (!state.equals("boot")) { // FIXME - this needs to be in config @@ -1490,7 +1503,7 @@ public Heartbeat publishHeartbeat() { 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"); + log.info("boot hasn't completed, will not process heartbeat - trying boot"); boot(); return heartbeat; } @@ -1721,6 +1734,9 @@ public StateChange publishStateChange(StateChange stateChange) { lastState = state; state = stateChange.state; + + setPredicate(String.format("%s.end", lastState), System.currentTimeMillis()); + setPredicate(String.format("%s.start", state), System.currentTimeMillis()); processMessage("onStateChange", stateChange); @@ -2185,15 +2201,15 @@ public void systemCheck() { // TODO change to experimental :) String version = ("unknownVersion".equals(platform.getVersion())) ? "experimental" : platform.getVersion(); - setPredicate("system_version", version); - setPredicate("system_uptime", Runtime.getUptime()); - setPredicate("system_servo_count", servoCount); - setPredicate("system_service_count", Runtime.getServices().size()); - setPredicate("system_free_memory", Runtime.getFreeMemory() / 1000000); - setPredicate("system_errors_exist", errors.size() > 0); - setPredicate("system_error_count", errors.size()); - setPredicate("system_battery_level", Runtime.getBatteryLevel()); + 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()); } diff --git a/src/main/java/org/myrobotlab/service/Log.java b/src/main/java/org/myrobotlab/service/Log.java index dc20806cf2..51e388a994 100644 --- a/src/main/java/org/myrobotlab/service/Log.java +++ b/src/main/java/org/myrobotlab/service/Log.java @@ -206,7 +206,10 @@ synchronized public void flush() { List<LogEntry> errors = new ArrayList<>(); for(int i = 0; i < buffer.size(); ++i) { - errors.add(buffer.get(i)); + LogEntry entry = buffer.get(i); + if ("ERROR".equals(entry.level)) { + errors.add(entry); + } } if (errors.size() > 0) { invoke("publishErrors", errors); diff --git a/src/main/java/org/myrobotlab/service/Random.java b/src/main/java/org/myrobotlab/service/Random.java index fd3ce94187..b419e7b8eb 100644 --- a/src/main/java/org/myrobotlab/service/Random.java +++ b/src/main/java/org/myrobotlab/service/Random.java @@ -30,7 +30,7 @@ public class Random extends Service<RandomConfig> { private static final long serialVersionUID = 1L; - public final static Logger log = LoggerFactory.getLogger(Random.class); + protected final static Logger log = LoggerFactory.getLogger(Random.class); transient private RandomProcessor processor = null; @@ -75,7 +75,7 @@ public Range(Object min, Object max) { /** * all random message data is located here */ - Map<String, RandomMessage> randomData = new HashMap<>(); + protected Map<String, RandomMessage> randomData = new HashMap<>(); /** * Java's random value generator @@ -107,7 +107,7 @@ public long getRandom(long min, long max) { public double getRandom(double min, double max) { return min + (Math.random() * (max - min)); } - + public RandomMessage getTask(String taskName) { return randomData.get(taskName); } @@ -210,7 +210,9 @@ public void addRandom(String taskName, long minIntervalMs, long maxIntervalMs, S data.data = ranges; data.enabled = true; - randomData.put(taskName, data); + synchronized (lock) { + randomData.put(taskName, data); + } log.info("add random message {} in {} to {} ms", taskName, data.minIntervalMs, data.maxIntervalMs); broadcastState(); @@ -229,16 +231,22 @@ public void run() { // and see if any random event needs processing sleep(config.rate); - // copy to avoid concurrent exceptions, avoid iterating over randomData - Map<String, RandomMessage> tasks = new HashMap<>(); - Set<String> keySet = new HashSet<String>(randomData.keySet()); - for (String k : keySet) { - RandomMessage rm = randomData.get(k); - if (rm != null) { - tasks.put(k, rm); + + Map<String, RandomMessage> tasks = null; + synchronized (lock) { + + // copy to avoid concurrent exceptions, avoid iterating over + // randomData + tasks = new HashMap<>(); + Set<String> keySet = new HashSet<String>(randomData.keySet()); + for (String k : keySet) { + RandomMessage rm = randomData.get(k); + if (rm != null) { + tasks.put(k, rm); + } } } - + for (String key : tasks.keySet()) { long now = System.currentTimeMillis(); @@ -313,7 +321,7 @@ public RandomConfig getConfig() { super.getConfig(); config.enabled = enabled; - + if (config.randomMessages == null) { config.randomMessages = new HashMap<>(); } @@ -445,15 +453,15 @@ public List<MethodEntry> methodQuery(String serviceName, String methodName) { } return MethodCache.getInstance().query(si.getClass().getCanonicalName(), methodName); } - - public Map<String, RandomMessage> getRandomEvents(){ + + public Map<String, RandomMessage> getRandomEvents() { return randomData; } - + public RandomMessage getRandomEvent(String key) { return randomData.get(key); } - + /** * disables all the individual tasks */ @@ -463,7 +471,7 @@ public void disableAll() { } broadcastState(); } - + @Override public void releaseService() { disable(); diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index d8a020dd84..e203a65b81 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -910,12 +910,14 @@ public static Runtime getInstance() { runtime.apply(c); } - if (options.services != null) { + if (options.services != null && options.services.size() != 0) { log.info("command line override for services created"); createAndStartServices(options.services); } else { log.info("processing config.registry"); - if (startYml.enable) { + if (options.config != null) { + Runtime.startConfig(options.config); + } else if (startYml.enable) { Runtime.startConfig(startYml.config); } } @@ -1544,7 +1546,7 @@ static public void install(String serviceType, Boolean blocking) { if (blocking == null) { blocking = false; } - + if (installerThread != null) { log.error("another request to install dependencies, 1st request has not completed"); return; @@ -1571,7 +1573,7 @@ public void run() { } else { installerThread.start(); } - + installerThread = null; } } @@ -4913,16 +4915,16 @@ static public void releaseConfigPath(String configPath) { RuntimeConfig config = CodecUtils.fromYaml(releaseData, RuntimeConfig.class); List<String> registry = config.getRegistry(); Collections.reverse(Arrays.asList(registry)); - + // get starting services if any entered on the command line - // -s log Log webgui WebGui ... etc - these will be protected + // -s log Log webgui WebGui ... etc - these will be protected List<String> startingServices = new ArrayList<>(); if (options.services.size() % 2 == 0) { for (int i = 0; i < options.services.size(); i += 2) { startingServices.add(options.services.get(i)); } } - + for (String name : registry) { if (startingServices.contains(name)) { continue; diff --git a/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechSynthesis.java b/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechSynthesis.java index f8cca2fa92..011f1f6e08 100644 --- a/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechSynthesis.java +++ b/src/main/java/org/myrobotlab/service/abstracts/AbstractSpeechSynthesis.java @@ -6,9 +6,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.TreeMap; -import java.util.concurrent.ConcurrentHashMap; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.interfaces.Attachable; @@ -42,22 +40,11 @@ public abstract class AbstractSpeechSynthesis<C extends SpeechSynthesisConfig> e public static final String journalFilename = "journal.txt"; - /** - * substitutions are phonetic substitutions for a specific instance of speech - * synthesis service - */ - transient protected Map<String, String> substitutions = new ConcurrentHashMap<String, String>(); - /** * generalized list of languages and their codes - if useful */ protected Map<String, Locale> locales = new HashMap<>(); - /** - * mute or unmute service - */ - boolean mute = false; - /** * replaces key with replacement */ @@ -252,8 +239,6 @@ public Object getVoiceProvider() { private List<Voice> voiceList = new ArrayList<>(); - protected boolean blocking = false; - // FIXME - deprecate - begin using SSML // specific effects and effect notation needs to be isolated to the // implementing service @@ -291,7 +276,7 @@ public AbstractSpeechSynthesis(String n, String id) { // should hold off creating or starting peers until the service has started // audioFile = (AudioFile) createPeer("audioFile"); -// getVoices(); + // getVoices(); } @@ -571,65 +556,65 @@ public String publishSpeechRequested(String toSpeak) { * @return - list of audio data */ public List<AudioData> parse(String toSpeak) { - + // we generate a list of audio data to play to support // synthesizing this speech List<AudioData> playList = new ArrayList<AudioData>(); - + try { - // TODO - not sure if we want to support this notation - // but at the moment it seems useful - // splitting on sound effects ... - // TODO - use SSML speech synthesis markup language + // TODO - not sure if we want to support this notation + // but at the moment it seems useful + // splitting on sound effects ... + // TODO - use SSML speech synthesis markup language - log.info("{} processing {}", getName(), toSpeak); + log.info("{} processing {}", getName(), toSpeak); - // broadcast the original text to be processed/parsed - invoke("publishSpeechRequested", toSpeak); + // broadcast the original text to be processed/parsed + invoke("publishSpeechRequested", toSpeak); - // normalize to lower case - toSpeak = toSpeak.toLowerCase(); + // normalize to lower case + toSpeak = toSpeak.toLowerCase(); - // process substitutions - if (substitutions != null) { - for (String substitute : substitutions.keySet()) { - toSpeak = toSpeak.replace(substitute, substitutions.get(substitute)); + // process substitutions + if (config.substitutions != null) { + for (String substitute : config.substitutions.keySet()) { + toSpeak = toSpeak.replace(substitute, config.substitutions.get(substitute)); + } } - } - List<String> spokenParts = parseEffects(toSpeak); + List<String> spokenParts = parseEffects(toSpeak); - toSpeak = filterText(toSpeak); + toSpeak = filterText(toSpeak); - for (String speak : spokenParts) { + for (String speak : spokenParts) { - AudioData audioData = null; - if (speak.startsWith("#") && speak.endsWith("#")) { - audioData = new AudioData( - System.getProperty("user.dir") + File.separator + "audioFile" + File.separator + "voiceEffects" + File.separator + speak.substring(1, speak.length() - 1) + ".mp3"); - } else { - audioData = new AudioData(getLocalFileName(speak)); - } + AudioData audioData = null; + if (speak.startsWith("#") && speak.endsWith("#")) { + audioData = new AudioData( + System.getProperty("user.dir") + File.separator + "audioFile" + File.separator + "voiceEffects" + File.separator + speak.substring(1, speak.length() - 1) + ".mp3"); + } else { + audioData = new AudioData(getLocalFileName(speak)); + } - if (speak.trim().length() == 0) { - continue; - } + if (speak.trim().length() == 0) { + continue; + } - if (!mute) { - process(audioData, speak, blocking); - } else { - log.info("not producing audio for {} - currently we are mute", speak); - } + if (!config.mute) { + process(audioData, speak, config.blocking); + } else { + log.info("not producing audio for {} - currently we are mute", speak); + } - // effect files are handled differently from generated audio - playList.add(audioData); - } - // FIXME - in theory "speaking" means generating audio from some text - // so starting speaking event is when the first audio is "started" - // and finished speaking is when the last audio is finished + // effect files are handled differently from generated audio + playList.add(audioData); + } + // FIXME - in theory "speaking" means generating audio from some text + // so starting speaking event is when the first audio is "started" + // and finished speaking is when the last audio is finished - } catch(Exception e) { + } catch (Exception e) { error(e); } return playList; @@ -647,12 +632,12 @@ public void addSubstitution(String key, String replacement) { */ @Override public void replaceWord(String key, String replacement) { - substitutions.put(key.toLowerCase(), replacement.toLowerCase()); + config.substitutions.put(key.toLowerCase(), replacement.toLowerCase()); } @Override public void replaceWord(WordFilter filter) { - substitutions.put(filter.word.toLowerCase(), filter.substitute.toLowerCase()); + config.substitutions.put(filter.word.toLowerCase(), filter.substitute.toLowerCase()); } public Long publishGenerationTime(Long timeMs) { @@ -706,10 +691,10 @@ public List<AudioData> speak(String toSpeak) { @Override public List<AudioData> speakBlocking(String toSpeak) { - boolean prevValue = blocking; - blocking = true; + boolean prevValue = config.blocking; + config.blocking = true; List<AudioData> audioData = parse(toSpeak); - blocking = prevValue; + config.blocking = prevValue; return audioData; } @@ -954,35 +939,34 @@ public boolean setLanguage(String lang) { } return false; } - @Override public boolean setVoice(String name) { - if (voices == null) { - return false; - } + if (voices == null) { + return false; + } - SpeechSynthesisConfig config = (SpeechSynthesisConfig)this.config; - voice = voices.get(name); - - if (voice == null) { - voice = voiceKeyIndex.get(name); - } - - if (voice == null) { - voice = voiceProviderIndex.get(name); - } - - if (voice == null) { - error("could not set voice %s - valid voices are %s", name, String.join(", ", getVoiceNames())); - return false; - } + SpeechSynthesisConfig config = (SpeechSynthesisConfig) this.config; + voice = voices.get(name); + + if (voice == null) { + voice = voiceKeyIndex.get(name); + } + + if (voice == null) { + voice = voiceProviderIndex.get(name); + } - config.voice = name; - broadcastState(); - return true; + if (voice == null) { + error("could not set voice %s - valid voices are %s", name, String.join(", ", getVoiceNames())); + return false; + } + + config.voice = name; + broadcastState(); + return true; } - + public boolean setVoice(Integer index) { if (index > voiceList.size() || index < 0) { error("setVoice({}) not valid pick range 0 to {}", index, voiceList.size()); @@ -1102,49 +1086,30 @@ public void unmute() { @Override public void setMute(boolean b) { - this.mute = b; + this.config.mute = b; } @Override public Boolean setBlocking(Boolean b) { - blocking = b; + config.blocking = b; return b; } public boolean isMute() { - return mute; + return config.mute; } @Override public C apply(C c) { super.apply(c); - - setMute(c.mute); - - setBlocking(c.blocking); - - if (c.substitutions != null) { - for (String n : c.substitutions.keySet()) { - replaceWord(n, c.substitutions.get(n)); - } - } + // some systems require querying set of voices getVoices(); - + if (c.voice != null) { setVoice(c.voice); } - if (c.speechRecognizers != null) { - for (String name : c.speechRecognizers) { - try { - attachSpeechListener(name); - } catch (Exception e) { - error(e); - } - } - } - return c; } @@ -1160,18 +1125,9 @@ public void attachSpeechControl(SpeechSynthesisControl control) { @Override public C getConfig() { C c = super.getConfig(); - c.mute = mute; - c.blocking = blocking; - if (substitutions != null && !substitutions.isEmpty()) { - c.substitutions = new HashMap<>(); - c.substitutions.putAll(substitutions); - } if (voice != null) { c.voice = voice.name; } - Set<String> listeners = getAttached("publishStartSpeaking"); - c.speechRecognizers = listeners.toArray(new String[0]); - return c; } diff --git a/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java b/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java index 47b3dc9b91..1a91e3c096 100644 --- a/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java +++ b/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java @@ -12,11 +12,11 @@ public class AudioFileConfig extends ServiceConfig { public String currentPlaylist = "default"; + /** + * Named map of lists of files + */ public Map<String, List<String>> playlists = new TreeMap<>(); - - @Deprecated /* use regular "listeners" from ServiceConfig parent */ - public String[] audioListeners; - + /** * a multiplier to scale amplitude of output waveform */ diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index d5e7116605..d9cd38d947 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -374,12 +374,12 @@ public Plan getDefault(Plan plan, String name) { fsm.transitions.add(new Transition("boot", "wake", "wake")); // setup, nor sleep should be affected by idle fsm.transitions.add(new Transition("setup", "setup_done", "idle")); - 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("idle", "power_down", "power_down")); + fsm.transitions.add(new Transition("idle", "random", "random")); fsm.transitions.add(new Transition("sleep", "wake", "wake")); fsm.transitions.add(new Transition("sleep", "power_down", "power_down")); - fsm.transitions.add(new Transition("idle", "power_down", "power_down")); fsm.transitions.add(new Transition("wake", "setup", "setup")); fsm.transitions.add(new Transition("wake", "idle", "idle")); fsm.transitions.add(new Transition("idle", "setup", "setup")); @@ -565,7 +565,9 @@ public Plan getDefault(Plan plan, String name) { fsm.listeners.add(new Listener("publishStateChange", name, "publishStateChange")); // peer --to--> peer + mouth.listeners.add(new Listener("publishStartSpeaking", name)); mouth.listeners.add(new Listener("publishStartSpeaking", getPeerName("ear"))); + mouth.listeners.add(new Listener("publishEndSpeaking", name)); mouth.listeners.add(new Listener("publishEndSpeaking", getPeerName("ear"))); return plan; diff --git a/src/main/java/org/myrobotlab/service/config/SpeechSynthesisConfig.java b/src/main/java/org/myrobotlab/service/config/SpeechSynthesisConfig.java index c84c4dca65..fc63167ffb 100644 --- a/src/main/java/org/myrobotlab/service/config/SpeechSynthesisConfig.java +++ b/src/main/java/org/myrobotlab/service/config/SpeechSynthesisConfig.java @@ -6,11 +6,19 @@ public class SpeechSynthesisConfig extends ServiceConfig { + /** + * mute or unmute service + */ public boolean mute = false; + public boolean blocking = false; - @Deprecated /* :( ... this is already in listeners ! */ - public String[] speechRecognizers; + + /** + * substitutions are phonetic substitutions for a specific instance of speech + * synthesis service + */ public Map<String, String> substitutions; + public String voice; @Override From 814de152115a67476a1e257d21af5a7892f43462 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Thu, 22 Feb 2024 07:58:59 -0800 Subject: [PATCH 088/131] fixed type --- src/main/java/org/myrobotlab/image/WebImage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/myrobotlab/image/WebImage.java b/src/main/java/org/myrobotlab/image/WebImage.java index fd4ad4e02f..4b5b41842d 100644 --- a/src/main/java/org/myrobotlab/image/WebImage.java +++ b/src/main/java/org/myrobotlab/image/WebImage.java @@ -56,7 +56,7 @@ public WebImage(final BufferedImage img, final String source, Integer frameIndex if (quality == null) { ImageIO.write(img, imgType, os); os.close(); - data = String.format("data:image/%s;base64,%s", type,CodecUtils.toBase64(os.toByteArray())); + data = String.format("data:image/%s;base64,%s", imgType,CodecUtils.toBase64(os.toByteArray())); } else { // save jpeg image with specific quality. "1f" corresponds to 100% , From db1f7d17dd48282fcfccf4626c95a6fe44544d63 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Thu, 22 Feb 2024 07:59:08 -0800 Subject: [PATCH 089/131] webgui --- src/main/java/org/myrobotlab/service/WebGui.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/myrobotlab/service/WebGui.java b/src/main/java/org/myrobotlab/service/WebGui.java index f50cdc238a..4367ae8c2a 100644 --- a/src/main/java/org/myrobotlab/service/WebGui.java +++ b/src/main/java/org/myrobotlab/service/WebGui.java @@ -1177,7 +1177,8 @@ public static void main(String[] args) { try { - Runtime.main(new String[] { "--log-level", "info", "-s", "log", "Log", "webgui", "WebGui", "intro", "Intro", "python", "Python" }); + // Runtime.main(new String[] { "--log-level", "info", "-s", "log", "Log", "webgui", "WebGui", "intro", "Intro", "python", "Python" }); + Runtime.main(new String[] { "-c", "worky"}); // Runtime.main(new String[] { "--install" }); boolean done = true; From 7470e0371698f25bac93c539aa6461d55381f755 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Thu, 22 Feb 2024 09:14:17 -0800 Subject: [PATCH 090/131] getWebImage --- .../java/org/myrobotlab/service/OpenCV.java | 131 ++++++++++-------- 1 file changed, 71 insertions(+), 60 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/OpenCV.java b/src/main/java/org/myrobotlab/service/OpenCV.java index dcd7ad08d9..474a8055d2 100644 --- a/src/main/java/org/myrobotlab/service/OpenCV.java +++ b/src/main/java/org/myrobotlab/service/OpenCV.java @@ -155,7 +155,7 @@ class VideoProcessor implements Runnable { @Override synchronized public void run() { - // create a closeable frame converter + // create a closeable frame converter CloseableFrameConverter converter = new CloseableFrameConverter(); try { @@ -308,16 +308,11 @@ public void start() { transient final static public String PART = "part"; static final String TEST_LOCAL_FACE_FILE_JPEG = "src/test/resources/OpenCV/multipleFaces.jpg"; - public final static String POSSIBLE_FILTERS[] = { "AdaptiveThreshold", "AddMask", "Affine", "And", "BlurDetector", - "BoundingBoxToFile", "Canny", "ColorTrack", "Copy", - "CreateHistogram", "Detector", "Dilate", "DL4J", "DL4JTransfer", "Erode", "FaceDetect", "FaceDetectDNN", - "FaceRecognizer", "FaceTraining", "Fauvist", "FindContours", "Flip", - "FloodFill", "FloorFinder", "FloorFinder2", "GoodFeaturesToTrack", "Gray", "HoughLines2", "Hsv", "ImageSegmenter", - "Input", "InRange", "Invert", "KinectDepth", - "KinectDepthMask", "KinectNavigate", "LKOpticalTrack", "Lloyd", "Mask", "MatchTemplate", "MiniXception", - "MotionDetect", "Mouse", "Output", "Overlay", "PyramidDown", - "PyramidUp", "ResetImageRoi", "Resize", "SampleArray", "SampleImage", "SetImageROI", "SimpleBlobDetector", - "Smooth", "Solr", "Split", "SURF", "Tesseract", "TextDetector", + public final static String POSSIBLE_FILTERS[] = { "AdaptiveThreshold", "AddMask", "Affine", "And", "BlurDetector", "BoundingBoxToFile", "Canny", "ColorTrack", "Copy", + "CreateHistogram", "Detector", "Dilate", "DL4J", "DL4JTransfer", "Erode", "FaceDetect", "FaceDetectDNN", "FaceRecognizer", "FaceTraining", "Fauvist", "FindContours", "Flip", + "FloodFill", "FloorFinder", "FloorFinder2", "GoodFeaturesToTrack", "Gray", "HoughLines2", "Hsv", "ImageSegmenter", "Input", "InRange", "Invert", "KinectDepth", + "KinectDepthMask", "KinectNavigate", "LKOpticalTrack", "Lloyd", "Mask", "MatchTemplate", "MiniXception", "MotionDetect", "Mouse", "Output", "Overlay", "PyramidDown", + "PyramidUp", "ResetImageRoi", "Resize", "SampleArray", "SampleImage", "SetImageROI", "SimpleBlobDetector", "Smooth", "Solr", "Split", "SURF", "Tesseract", "TextDetector", "Threshold", "Tracker", "Transpose", "Undistort", "Yolo" }; static final long serialVersionUID = 1L; @@ -649,7 +644,7 @@ synchronized public OpenCVFilter addFilter(OpenCVFilter filter) { * add filter by type e.g. addFilter("Canny","Canny") * * @param filterName - * - name of filter + * - name of filter * @return the filter */ public CVFilter addFilter(String filterName) { @@ -689,7 +684,7 @@ public void capture(FrameGrabber grabber) throws org.bytedeco.javacv.FrameGrabbe * capture from a camera * * @param cameraIndex - * the camera index to capture from + * the camera index to capture from */ public void capture(Integer cameraIndex) { if (cameraIndex == null) { @@ -710,7 +705,7 @@ public void capture(Integer cameraIndex) { * its the most capable of decoding different filetypes. * * @param filename - * the file to use as the input filename. + * the file to use as the input filename. * */ public void capture(String filename) { @@ -723,7 +718,7 @@ public void capture(String filename) { public void captureFromResourceFile(String filename) throws IOException { capture(filename); } - + /** * Gets valid camera indexes by iterating through 8 * @@ -761,7 +756,7 @@ public List<Integer> getCameraIndexes() { return cameraIndexes; } - + public int getCameraIndex() { return this.cameraIndex; } @@ -851,7 +846,7 @@ public List<Classification> getFaces(int timeout) { * get a filter by name * * @param name - * filter name to lookup + * filter name to lookup * @return the filter by name o/w null * */ @@ -881,8 +876,7 @@ public OpenCVData getGoodFeatures() { } public FrameGrabber getGrabber() - throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, - InvocationTargetException, org.bytedeco.javacv.FrameGrabber.Exception { + throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, org.bytedeco.javacv.FrameGrabber.Exception { if (grabber != null) { return grabber; @@ -907,8 +901,7 @@ public FrameGrabber getGrabber() // get and cache image file // FIXME - perhaps "test" stream to try to determine what "type" it is - // mjpeg/jpg/gif/ octet-stream :( ??? - if (grabberType == null - || (grabberType != null && (!grabberType.equals("MJpeg") && !grabberType.equals("IPCamera")))) { + if (grabberType == null || (grabberType != null && (!grabberType.equals("MJpeg") && !grabberType.equals("IPCamera")))) { inputFile = getImageFromUrl(inputFile); } } @@ -920,8 +913,7 @@ public FrameGrabber getGrabber() ext = inputFile.substring(pos + 1).toLowerCase(); } } - if (grabberType != null && (grabberType.equals("FFmpeg") || grabberType.equals("ImageFile")) - && inputSource.equals(INPUT_SOURCE_CAMERA)) { + if (grabberType != null && (grabberType.equals("FFmpeg") || grabberType.equals("ImageFile")) && inputSource.equals(INPUT_SOURCE_CAMERA)) { log.info("invalid state of ffmpeg and input source camera - setting to OpenCV frame grabber"); grabberType = "OpenCV"; } @@ -976,8 +968,7 @@ public FrameGrabber getGrabber() } String prefixPath; - if (/* "IPCamera".equals(grabberType) || */ "Pipeline".equals(grabberType) || "ImageFile".equals(grabberType) - || "Sarxos".equals(grabberType) || "MJpeg".equals(grabberType)) { + if (/* "IPCamera".equals(grabberType) || */ "Pipeline".equals(grabberType) || "ImageFile".equals(grabberType) || "Sarxos".equals(grabberType) || "MJpeg".equals(grabberType)) { prefixPath = "org.myrobotlab.opencv."; } else { prefixPath = "org.bytedeco.javacv."; @@ -1076,6 +1067,27 @@ public IplImage getImage() { return lastImage; } + /** + * "Easy" Base64 web image from display last frame + * + * @return + */ + public String getWebImage() { + try { + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + String imgType = "jpg"; + BufferedImage bi = getDisplay(); + if (bi != null) { + ImageIO.write(bi, imgType, os); + os.close(); + return String.format("data:image/%s;base64,%s", imgType, CodecUtils.toBase64(os.toByteArray())); + } + } catch (Exception e) { + error(e); + } + return null; + } + public String getInputFile() { return inputFile; } @@ -1113,11 +1125,11 @@ public OpenCVData getOpenCVData(Integer timeout) { * appropriate filter through this method. * * @param filterName - * the name of the fitler + * the name of the fitler * @param method - * the method to invoke + * the method to invoke * @param params - * the params to pass + * the params to pass */ public void invokeFilterMethod(String filterName, String method, Object... params) { OpenCVFilter filter = getFilter(filterName); @@ -1136,7 +1148,10 @@ public boolean isRecording() { return recording; } - @Deprecated /* was used in SwingGui - nice feature through .. ability to undock displays */ + @Deprecated /* + * was used in SwingGui - nice feature through .. ability to + * undock displays + */ public boolean isUndocked() { return undockDisplay; } @@ -1153,7 +1168,7 @@ synchronized public void pauseCapture() { * conversion from buffered image to base64 encoded jpg * * @param img - * the image to convert + * the image to convert * @return base64jpeg version of buffered image */ public String toBase64Jpg(BufferedImage img) { @@ -1304,7 +1319,7 @@ private void processFilterStateUpdates(OpenCVFilter filter) { * base 64 jpg frame image * * @param data - * webimage data + * webimage data * @return the web image data */ public WebImage publishWebDisplay(WebImage data) { @@ -1387,7 +1402,7 @@ public final SerializableImage publishDisplay(SerializableImage img) { * Publishing method for filters - used internally * * @param filterWrapper - * wraps a filter + * wraps a filter * * @return FilterWrapper solves the problem of multiple types being resolved * in the setFilterState(FilterWrapper data) method @@ -1400,7 +1415,7 @@ public FilterWrapper publishFilterState(FilterWrapper filterWrapper) { * Publishing method for filters - uses string parameter for remote invocation * * @param name - * name of filter to publish state for + * name of filter to publish state for * * @return FilterWrapper solves the problem of multiple types being resolved * in the setFilterState(FilterWrapper data) method @@ -1436,7 +1451,7 @@ public void publishNoRecognizedFace() { * until asked for - then its cached SMART ! :) * * @param data - * the opencv data + * the opencv data * @return cvdata * */ @@ -1469,13 +1484,13 @@ public void putText(int x, int y, String format) { * creates a new overlay of text * * @param x - * coordinate + * coordinate * @param y - * coordinate + * coordinate * @param format - * format string + * format string * @param color - * color + * color * */ public void putText(int x, int y, String format, String color) { @@ -1487,9 +1502,9 @@ public void putText(int x, int y, String format, String color) { * the "light weight" put - it does not create any new cv objects * * @param format - * format for the text + * format for the text * @param args - * args to format into the text + * args to format into the text * */ public void putText(String format, Object... args) { @@ -1550,7 +1565,7 @@ public void startStreamer() { * key- input, filter, or display * * @param data - * data + * data */ public void record(OpenCVData data) { try { @@ -1572,8 +1587,7 @@ public void record(OpenCVData data) { */ FrameRecorder recorder = null; if (!recordingFrames) { - recordingFilename = String.format(getDataDir() + File.separator + "%s-%d.flv", recordingSource, - System.currentTimeMillis()); + recordingFilename = String.format(getDataDir() + File.separator + "%s-%d.flv", recordingSource, System.currentTimeMillis()); info("recording %s", recordingFilename); recorder = new FFmpegFrameRecorder(recordingFilename, frame.imageWidth, frame.imageHeight, 0); recorder.setFormat("flv"); @@ -1639,7 +1653,7 @@ public ImageData saveImage() { /** * @param name - * remove a filter by name + * remove a filter by name */ @Override synchronized public void removeFilter(String name) { @@ -1721,7 +1735,7 @@ public void setColor(String colorStr) { * enable() and setDisplayFilter() needed filter * * @param name - * name of the filter to set active + * name of the filter to set active * */ public void setActiveFilter(String name) { @@ -1760,15 +1774,12 @@ public void setDisplayFilter(String name) { /** * @param otherFilter - * - data from remote source + * - data from remote source * - * This updates the filter with all the non-transient data in - * a - * remote copy through a reflective field update. If your - * filter has - * JNI members or pointer references it will break, mark all - * of - * these. + * This updates the filter with all the non-transient data in a + * remote copy through a reflective field update. If your filter has + * JNI members or pointer references it will break, mark all of + * these. */ public void setFilterState(FilterWrapper otherFilter) { OpenCVFilter filter = getFilter(otherFilter.name); @@ -1786,9 +1797,9 @@ public void setFilterState(FilterWrapper otherFilter) { * filter * * @param name - * name of the filter + * name of the filter * @param data - * state date to set. + * state date to set. */ public void setFilterState(String name, String data) { OpenCVFilter filter = getFilter(name); @@ -1908,8 +1919,7 @@ public void recordFrames() { private boolean isSingleFrame() { if (inputSource.equals(INPUT_SOURCE_FILE) && inputFile != null) { String testExt = inputFile.toLowerCase(); - if (testExt.endsWith(".jpg") || testExt.endsWith(".jpeg") || testExt.endsWith(".png") || testExt.endsWith(".gif") - || testExt.endsWith(".tiff") || testExt.endsWith(".tif")) { + if (testExt.endsWith(".jpg") || testExt.endsWith(".jpeg") || testExt.endsWith(".png") || testExt.endsWith(".gif") || testExt.endsWith(".tiff") || testExt.endsWith(".tif")) { return true; } } @@ -1970,11 +1980,12 @@ public void enableFilter(String name) { /** * flip the video display vertically + * * @param toFlip */ public void flip(boolean toFlip) { config.flip = toFlip; - if (config.flip) { + if (config.flip) { addFilter("Flip"); } else { removeFilter("Flip"); @@ -2076,7 +2087,7 @@ public OpenCVConfig apply(OpenCVConfig c) { // TODO: better configuration of the filter when it's added. } } - + flip(c.flip); if (c.capturing) { @@ -2101,7 +2112,7 @@ public static void main(String[] args) throws Exception { // Runtime.start("python", "Python"); OpenCV cv = (OpenCV) Runtime.start("cv", "OpenCV"); cv.capture(); - + cv.addFilter(new OpenCVFilterYolo("yolo")); sleep(1000); cv.removeFilters(); From f155d0ae357a645dffe74a7beb72c341249f853f Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Thu, 22 Feb 2024 10:52:48 -0800 Subject: [PATCH 091/131] small config update --- src/main/java/org/myrobotlab/service/WebGui.java | 4 ++-- .../java/org/myrobotlab/service/config/InMoov2Config.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/WebGui.java b/src/main/java/org/myrobotlab/service/WebGui.java index 4367ae8c2a..e6efa1f28d 100644 --- a/src/main/java/org/myrobotlab/service/WebGui.java +++ b/src/main/java/org/myrobotlab/service/WebGui.java @@ -1177,8 +1177,8 @@ public static void main(String[] args) { try { - // Runtime.main(new String[] { "--log-level", "info", "-s", "log", "Log", "webgui", "WebGui", "intro", "Intro", "python", "Python" }); - Runtime.main(new String[] { "-c", "worky"}); + Runtime.main(new String[] { "--log-level", "warn", "-s", "log", "Log", "webgui", "WebGui", "intro", "Intro", "python", "Python" }); + // Runtime.main(new String[] { "-c", "worky"}); // Runtime.main(new String[] { "--install" }); boolean done = true; diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index d9cd38d947..1ae722a633 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -516,7 +516,7 @@ public Plan getDefault(Plan plan, String name) { listeners.add(new Listener("publishConfigFinished", name)); LogConfig log = (LogConfig) plan.get(getPeerName("log")); - log.level = "WARN"; + log.level = "warn"; log.listeners.add(new Listener("publishErrors", name)); // service --to--> InMoov2 From 1cdc49376865e19275609c538905d7107ee5e088 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Fri, 23 Feb 2024 21:55:07 -0800 Subject: [PATCH 092/131] remotespeech --- .../org/myrobotlab/service/LocalSpeech.java | 85 +++++++++---------- .../org/myrobotlab/service/RemoteSpeech.java | 77 +++++++++++++++++ .../service/config/RemoteSpeechConfig.java | 21 +++++ .../service/meta/RemoteSpeechMeta.java | 38 +++++++++ .../WebGui/app/service/js/RemoteSpeechGui.js | 40 +++++++++ .../WebGui/app/service/views/IntroGui.html | 1 - .../app/service/views/RemoteSpeechGui.html | 31 +++++++ 7 files changed, 247 insertions(+), 46 deletions(-) create mode 100644 src/main/java/org/myrobotlab/service/RemoteSpeech.java create mode 100644 src/main/java/org/myrobotlab/service/config/RemoteSpeechConfig.java create mode 100644 src/main/java/org/myrobotlab/service/meta/RemoteSpeechMeta.java create mode 100644 src/main/resources/resource/WebGui/app/service/js/RemoteSpeechGui.js create mode 100644 src/main/resources/resource/WebGui/app/service/views/RemoteSpeechGui.html diff --git a/src/main/java/org/myrobotlab/service/LocalSpeech.java b/src/main/java/org/myrobotlab/service/LocalSpeech.java index 3d2194c896..cb89016c82 100644 --- a/src/main/java/org/myrobotlab/service/LocalSpeech.java +++ b/src/main/java/org/myrobotlab/service/LocalSpeech.java @@ -77,8 +77,6 @@ public LocalSpeech(String n, String id) { @Override public AudioData generateAudioData(AudioData audioData, String toSpeak) throws IOException, InterruptedException { - LocalSpeechConfig c = (LocalSpeechConfig) config; - // the actual filename on the file system String localFileName = getLocalFileName(toSpeak); @@ -96,13 +94,13 @@ public AudioData generateAudioData(AudioData audioData, String toSpeak) throws I } // filter out breaking chars - if (c.replaceChars == null) { + if (config.replaceChars == null) { // if not user defined - escape double quotes to not affect templates - c.replaceChars = new HashMap<>(); - c.replaceChars.put("\'", "\'\'"); + config.replaceChars = new HashMap<>(); + config.replaceChars.put("\'", "\'\'"); } - for (String target : c.replaceChars.keySet()) { - toSpeak = toSpeak.replace(target, c.replaceChars.get(target)); + for (String target : config.replaceChars.keySet()) { + toSpeak = toSpeak.replace(target, config.replaceChars.get(target)); } Platform platform = Runtime.getPlatform(); @@ -209,7 +207,7 @@ public void loadVoices() { // FIXME this is not right - it should be based on speechType not OS // speechType should be "set" based on OS and user preference if (platform.isWindows()) { - + try { List<String> args = new ArrayList<>(); @@ -273,9 +271,9 @@ public void loadVoices() { } } // let apply config add and set the voices -// else if (platform.isLinux()) { -// addVoice("Linus", "male", "en-US", "festival"); -// } + // else if (platform.isLinux()) { + // addVoice("Linus", "male", "en-US", "festival"); + // } } public void removeExt(boolean b) { @@ -291,8 +289,7 @@ public boolean setEspeak() { return false; } - LocalSpeechConfig c = (LocalSpeechConfig) config; - c.speechType = "Espeak"; + config.speechType = "Espeak"; voices.clear(); addVoice("espeak", "male", "en-US", "espeak"); removeExt(false); @@ -310,10 +307,9 @@ public boolean setFestival() { return false; } - LocalSpeechConfig c = (LocalSpeechConfig) config; voices.clear(); addVoice("Linus", "male", "en-US", "festival"); - c.speechType = "Festival"; + config.speechType = "Festival"; removeExt(false); setTtsHack(false); setTtsCommand("echo \"{text}\" | text2wave -o {filename}"); @@ -330,9 +326,8 @@ public boolean setPico2Wav() { error("pico2wave only supported on Linux"); return false; } - - LocalSpeechConfig c = (LocalSpeechConfig) config; - c.speechType = "Pico2Wav"; + + config.speechType = "Pico2Wav"; removeExt(false); setTtsHack(false); @@ -343,13 +338,13 @@ public boolean setPico2Wav() { addVoice("es-ES", "female", "es-ES", "pico2wav"); addVoice("fr-FR", "female", "fr-FR", "pico2wav"); addVoice("it-IT", "female", "it-IT", "pico2wav"); - + if (voice == null) { setVoice(getLocale().getTag()); } setTtsCommand("pico2wave -l {voice_name} -w {filename} \"{text}\" "); - + broadcastState(); return true; } @@ -363,19 +358,19 @@ public boolean setPico2Wav() { * @param replace */ public void addFilter(String target, String replace) { - LocalSpeechConfig c = (LocalSpeechConfig) config; - if (c.replaceChars == null) { - c.replaceChars = new HashMap<>(); + + if (config.replaceChars == null) { + config.replaceChars = new HashMap<>(); } - c.replaceChars.put(target, replace); + config.replaceChars.put(target, replace); } /** * @return setMimic sets the Windows mimic template */ public boolean setMimic() { - LocalSpeechConfig c = (LocalSpeechConfig) config; - c.speechType = "Mimic"; + + config.speechType = "Mimic"; removeExt(false); setTtsHack(false); if (Runtime.getPlatform().isWindows()) { @@ -404,8 +399,8 @@ public String setSpeechType(String speechType) { } public String getSpeechType() { - LocalSpeechConfig c = (LocalSpeechConfig) config; - return c.speechType; + + return config.speechType; } /** @@ -418,8 +413,8 @@ public boolean setMsSpeech() { error("microsoft speech is only supported on Windows"); return false; } - LocalSpeechConfig c = (LocalSpeechConfig) config; - c.speechType = "MsSpeech"; + + config.speechType = "MsSpeech"; removeExt(false); setTtsHack(false); @@ -438,8 +433,8 @@ public boolean setMsSpeech() { * @return setSay sets the Mac say template */ public boolean setSay() { - LocalSpeechConfig c = (LocalSpeechConfig) config; - c.speechType = "Say"; + + config.speechType = "Say"; removeExt(false); setTtsHack(false); setTtsCommand("/usr/bin/say -v {voice_name} --data-format=LEF32@22050 -o {filename} \"{text}\""); @@ -455,8 +450,8 @@ public boolean setSay() { * */ public boolean setTts() { - LocalSpeechConfig c = (LocalSpeechConfig) config; - c.speechType = "Tts"; + + config.speechType = "Tts"; removeExt(false); setTtsHack(true); setTtsCommand("\"" + ttsPath + "\" -f 9 -v {voice} -o {filename} -t \"{text}\""); @@ -473,8 +468,8 @@ public boolean setTts() { * */ public void setTtsCommand(String ttsCommand) { - LocalSpeechConfig c = (LocalSpeechConfig) config; - info("LocalSpeech speechType %s template is now: %s", c.speechType, ttsCommand); + + info("LocalSpeech speechType %s template is now: %s", config.speechType, ttsCommand); this.ttsCommand = ttsCommand; } @@ -492,26 +487,26 @@ public void setTtsHack(boolean b) { public void setTtsPath(String ttsPath) { this.ttsPath = ttsPath; } - + public boolean isExecutableAvailable(String executableName) { ProcessBuilder processBuilder = new ProcessBuilder(); String command = ""; boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows"); if (isWindows) { - command = "where " + executableName; + command = "where " + executableName; } else { - command = "which " + executableName; + command = "which " + executableName; } processBuilder.command("sh", "-c", command); try { - Process process = processBuilder.start(); - process.waitFor(); - return process.exitValue() == 0; + Process process = processBuilder.start(); + process.waitFor(); + return process.exitValue() == 0; } catch (IOException | InterruptedException e) { - e.printStackTrace(); - return false; + e.printStackTrace(); + return false; } -} + } public LocalSpeechConfig apply(LocalSpeechConfig config) { super.apply(config); diff --git a/src/main/java/org/myrobotlab/service/RemoteSpeech.java b/src/main/java/org/myrobotlab/service/RemoteSpeech.java new file mode 100644 index 0000000000..6da50b45a9 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/RemoteSpeech.java @@ -0,0 +1,77 @@ +package org.myrobotlab.service; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.myrobotlab.io.FileIO; +import org.myrobotlab.logging.Level; +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; +import org.myrobotlab.service.config.HttpClientConfig; +import org.myrobotlab.service.config.RemoteSpeechConfig; +import org.myrobotlab.service.data.AudioData; +import org.slf4j.Logger; + +public class RemoteSpeech extends AbstractSpeechSynthesis<RemoteSpeechConfig> { + + private static final long serialVersionUID = 1L; + + public final static Logger log = LoggerFactory.getLogger(RemoteSpeech.class); + + public transient HttpClient<HttpClientConfig> http = null; + + protected Set<String> types = new HashSet<>(Arrays.asList("ModzillaTTS")); + + // http://localhost:5002/api/tts?text=Hello%20I%20am%20a%20speech%20synthesis%20system%20version%202 + public RemoteSpeech(String n, String id) { + super(n, id); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void startService() { + super.startService(); + http = (HttpClient)startPeer("http"); + } + + public static void main(String[] args) { + try { + + LoggingFactory.init(Level.INFO); + + Runtime.start("webgui", "WebGui"); + Runtime.start("python", "Python"); + Runtime.start("mouth", "RemoteSpeech"); + + } catch (Exception e) { + log.error("main threw", e); + } + } + + @Override + public AudioData generateAudioData(AudioData audioData, String toSpeak) throws Exception { + + try { + // IF GET must url encode .. use replace tags like {urlEncodedText} + String localFileName = getLocalFileName(toSpeak); + // merge template with text and/or config + String url = config.url.replace("{text}", URLEncoder.encode(toSpeak, StandardCharsets.UTF_8.toString())); + byte[] bytes = http.getBytes(url); + FileIO.toFile(localFileName, bytes); + return new AudioData(localFileName); + } catch (Exception e) { + error(e); + } + + return null; + } + + @Override + public void loadVoices() throws Exception { + addVoice("default", null, null, "remote"); + } +} diff --git a/src/main/java/org/myrobotlab/service/config/RemoteSpeechConfig.java b/src/main/java/org/myrobotlab/service/config/RemoteSpeechConfig.java new file mode 100644 index 0000000000..c7b48009b1 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/config/RemoteSpeechConfig.java @@ -0,0 +1,21 @@ +package org.myrobotlab.service.config; + +import org.myrobotlab.framework.Plan; + +public class RemoteSpeechConfig extends SpeechSynthesisConfig { + + public String verb = "GET"; + + public String url = "http://localhost:5002/api/tts?text={text}"; + + public String template = null; + + public String speechType = "ModzillaTTS"; + + public Plan getDefault(Plan plan, String name) { + super.getDefault(plan, name); + addDefaultPeerConfig(plan, name, "http", "HttpClient", true); + return plan; + } + +} diff --git a/src/main/java/org/myrobotlab/service/meta/RemoteSpeechMeta.java b/src/main/java/org/myrobotlab/service/meta/RemoteSpeechMeta.java new file mode 100644 index 0000000000..a2b2b7f007 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/meta/RemoteSpeechMeta.java @@ -0,0 +1,38 @@ +package org.myrobotlab.service.meta; + +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.meta.abstracts.MetaData; +import org.slf4j.Logger; + +public class RemoteSpeechMeta extends MetaData { + private static final long serialVersionUID = 1L; + public final static Logger log = LoggerFactory.getLogger(RemoteSpeechMeta.class); + + /** + * This class is contains all the meta data details of a service. It's peers, + * dependencies, and all other meta data related to the service. + * + */ + public RemoteSpeechMeta() { + + // add a cool description + addDescription("used as a general template"); + + // false will prevent it being seen in the ui + setAvailable(true); + + // add dependencies if necessary + // addDependency("com.twelvemonkeys.common", "common-lang", "3.1.1"); + + setAvailable(false); + + // add it to one or many categories + addCategory("general"); + + // add a sponsor to this service + // the person who will do maintenance + // setSponsor("GroG"); + + } + +} diff --git a/src/main/resources/resource/WebGui/app/service/js/RemoteSpeechGui.js b/src/main/resources/resource/WebGui/app/service/js/RemoteSpeechGui.js new file mode 100644 index 0000000000..18ed39bca5 --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/js/RemoteSpeechGui.js @@ -0,0 +1,40 @@ +angular.module("mrlapp.service.RemoteSpeechGui", []).controller("RemoteSpeechGuiCtrl", [ + "$scope", + "mrl", + function ($scope, mrl) { + console.info("RemoteSpeechGuiCtrl") + var _self = this + var msg = this.msg + + this.updateState = function (service) { + $scope.service = service + $scope.$apply() + } + + this.onMsg = function (inMsg) { + switch (inMsg.method) { + case "onState": + _self.updateState(inMsg.data[0]) + break + default: + console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) + break + } + } + + $scope.setType = function (type) { + msg.send("set" + type) + } + + $scope.speak = function (text) { + msg.send("speak", text) + } + + $scope.setVoice = function () { + console.log($scope.service.voice.name) + msg.send("setVoice", $scope.service.voice.name) + } + + msg.subscribe(this) + }, +]) diff --git a/src/main/resources/resource/WebGui/app/service/views/IntroGui.html b/src/main/resources/resource/WebGui/app/service/views/IntroGui.html index b85d70d473..0c9fd3d99a 100644 --- a/src/main/resources/resource/WebGui/app/service/views/IntroGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/IntroGui.html @@ -104,7 +104,6 @@ </tr> <tr> <td> - <img width="600" src="http://myrobotlab.org/sites/default/files/users/user3images/Serial_1.png"/> </td> </tr> </table> diff --git a/src/main/resources/resource/WebGui/app/service/views/RemoteSpeechGui.html b/src/main/resources/resource/WebGui/app/service/views/RemoteSpeechGui.html new file mode 100644 index 0000000000..3d8cbdae9b --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/views/RemoteSpeechGui.html @@ -0,0 +1,31 @@ +<div class="row"> + <div class="col-md-4"> + <br /> + <br /> + <b>type</b> + <br /> + {{service.config.speechType}} + + <select ng-model="service.config.speechType" ng-change="setType(service.config.speechType)" class="form-control" title="select a type of local voice control"> + <option ng-repeat="type in service.types" ng-value="{{type}}">{{type}}</option> + </select> + <div ng-show="service.config.speechType === 'Pico2Wav'"> + <b>Install pico2wave</b> + <br /> + https://doc.ubuntu-fr.org/svoxpico + <br /> + <pre>sudo apt install libttspico-utils</pre> + </div> + <br /> + <b>voice</b> + <br /> + <select ng-model="service.voice.name" ng-change="setVoice(voice)" class="form-control" title="select a voice"> + <option ng-repeat="(key, voice) in service.voices" ng-value="{{voice.name}}">{{voice.name}}</option> + </select> + <br /> + <textarea type="text" class="form-control" placeholder="type here" ng-model="text" /> + <br /> + <button class="btn btn-default" ng-click="speak(text)">speak</button> + <button class="btn btn-default" ng-click="text=''">clear</button> + </div> +</div> From 8924641d0874e037185a17853c6ae8dd70eaa763 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Fri, 23 Feb 2024 22:00:02 -0800 Subject: [PATCH 093/131] updates --- .../java/org/myrobotlab/service/InMoov2.java | 374 +++++++++++++----- .../java/org/myrobotlab/service/Vertx.java | 6 +- .../java/org/myrobotlab/service/WebGui.java | 34 +- 3 files changed, 297 insertions(+), 117 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index c496da1a61..2f2b513e87 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -14,7 +14,6 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; -import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.io.FilenameUtils; import org.myrobotlab.framework.Message; @@ -23,6 +22,7 @@ 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; @@ -37,6 +37,7 @@ import org.myrobotlab.service.Log.LogEntry; import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; import org.myrobotlab.service.config.InMoov2Config; +import org.myrobotlab.service.config.OpenCVConfig; import org.myrobotlab.service.data.JoystickData; import org.myrobotlab.service.data.Locale; import org.myrobotlab.service.interfaces.IKJointAngleListener; @@ -45,66 +46,14 @@ 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<InMoov2Config> - implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, IKJointAngleListener { - - 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 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"); - } - } - - 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(); - - public Heartbeat(InMoov2 inmoov) { - this.count = inmoov.heartbeatCount; - } - } +public class InMoov2 extends Service<InMoov2Config> implements ServiceLifeCycleListener, TextListener, TextPublisher, + JoystickListener, LocaleProvider, IKJointAngleListener { public final static Logger log = LoggerFactory.getLogger(InMoov2.class); @@ -114,7 +63,7 @@ public Heartbeat(InMoov2 inmoov) { * 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 */ @@ -172,6 +121,77 @@ 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); } @@ -246,7 +266,8 @@ public static void main(String[] args) { 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"); + 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 @@ -260,7 +281,8 @@ 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"); + 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); @@ -635,9 +657,9 @@ public void displayFullScreen(String src) { error("could not display picture %s", src); } } - + public void enableRandomHead() { - Random random = (Random)getPeer("random"); + Random random = (Random) getPeer("random"); if (random != null) { random.disableAll(); random.enable(String.format("%s.setHeadSpeed", getName())); @@ -645,12 +667,12 @@ public void enableRandomHead() { random.enable(); } } - + public void disableRandom() { - Random random = (Random)getPeer("random"); + Random random = (Random) getPeer("random"); if (random != null) { random.disable(); - } + } } public void enable() { @@ -683,7 +705,7 @@ public boolean exec(String 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) { @@ -718,7 +740,7 @@ public void execScript() { * a filesystem file :P * * @param someScriptName - * execute a resource script + * execute a resource script * @return success or failure */ public void execScript(String someScriptName) { @@ -796,11 +818,18 @@ public InMoov2Head getHead() { */ public Long getLastActivityTime() { 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 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; @@ -960,7 +989,7 @@ 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) { @@ -1059,7 +1088,8 @@ 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) { + 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); } @@ -1111,8 +1141,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) { @@ -1123,8 +1155,10 @@ 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) { @@ -1153,7 +1187,7 @@ public PredicateEvent onChangePredicate(PredicateEvent event) { * comes in from runtime which owns the config list * * @param configList - * list of configs + * list of configs */ public void onConfigList(List<String> configList) { this.configList = configList; @@ -1176,7 +1210,7 @@ public void onEndSpeaking(String utterance) { * including lower level logs that do not propegate as statuses * * @param log - * - flushed log from Log service + * - flushed log from Log service */ public void onErrors(List<LogEntry> log) { errors.addAll(log); @@ -1221,6 +1255,23 @@ public void onJoystickInput(JoystickData input) throws Exception { invoke("publishEvent", "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<LogEntry> log) { + // scan for warn or errors + for (LogEntry entry : log) { + if ("ERROR".equals(entry.level) && errors.size() < 100) { + errors.add(entry); + // invoke("publishError", entry); + } + } + } + public String onNewState(String state) { log.error("onNewState {}", state); @@ -1254,7 +1305,8 @@ public void onPeak(double volume) { public void onPirOff() { log.info("onPirOff"); - setPredicate(String.format("%s.pir_off", getName()), System.currentTimeMillis()); + setPredicate("pir", true); + setPredicate("pir.off", System.currentTimeMillis()); processMessage("onPirOff"); } @@ -1264,7 +1316,8 @@ public void onPirOff() { */ public void onPirOn() { log.info("onPirOn"); - setPredicate(String.format("%s.pir_on", getName()), System.currentTimeMillis()); + setPredicate("pir", false); + setPredicate("pir.on", System.currentTimeMillis()); processMessage("onPirOn"); } @@ -1415,7 +1468,7 @@ public void processMessage(String method) { * @param method * @param data */ - public void processMessage(String method, Object ... data) { + public void processMessage(String method, Object... data) { // User processing should not occur until after boot has completed if (!state.equals("boot")) { // FIXME - this needs to be in config @@ -1525,7 +1578,8 @@ public Heartbeat publishHeartbeat() { } // interval event firing - if (config.stateRandomInterval != null && System.currentTimeMillis() > stateLastRandomTime + (config.stateRandomInterval * 1000)) { + if (config.stateRandomInterval != null + && System.currentTimeMillis() > stateLastRandomTime + (config.stateRandomInterval * 1000)) { // fsm.fire("random"); stateLastRandomTime = System.currentTimeMillis(); } @@ -1577,7 +1631,8 @@ public Message publishMessage(Message msg) { return msg; } - public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double rotate, Double shoulder, Double omoplate) { + public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double rotate, Double shoulder, + Double omoplate) { HashMap<String, Double> map = new HashMap<>(); map.put("bicep", bicep); map.put("rotate", rotate); @@ -1591,7 +1646,8 @@ public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double return map; } - public HashMap<String, Object> publishMoveHand(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { + public HashMap<String, Object> publishMoveHand(String which, Double thumb, Double index, Double majeure, + Double ringFinger, Double pinky, Double wrist) { HashMap<String, Object> map = new HashMap<>(); map.put("which", which); map.put("thumb", thumb); @@ -1608,7 +1664,8 @@ public HashMap<String, Object> publishMoveHand(String which, Double thumb, Doubl return map; } - public HashMap<String, Double> publishMoveHead(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, Double rollNeck) { + public HashMap<String, Double> publishMoveHead(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, + Double rollNeck) { HashMap<String, Double> map = new HashMap<>(); map.put("neck", neck); map.put("rothead", rothead); @@ -1628,7 +1685,8 @@ public HashMap<String, Double> publishMoveLeftArm(Double bicep, Double rotate, D return map; } - public HashMap<String, Double> publishMoveLeftHand(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { + public HashMap<String, Double> publishMoveLeftHand(Double thumb, Double index, Double majeure, Double ringFinger, + Double pinky, Double wrist) { HashMap<String, Double> map = new HashMap<>(); map.put("thumb", thumb); map.put("index", index); @@ -1648,7 +1706,8 @@ public HashMap<String, Double> publishMoveRightArm(Double bicep, Double rotate, return map; } - public HashMap<String, Double> publishMoveRightHand(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { + public HashMap<String, Double> publishMoveRightHand(Double thumb, Double index, Double majeure, Double ringFinger, + Double pinky, Double wrist) { HashMap<String, Double> map = new HashMap<>(); map.put("thumb", thumb); map.put("index", index); @@ -1734,7 +1793,7 @@ public StateChange publishStateChange(StateChange stateChange) { lastState = state; state = stateChange.state; - + setPredicate(String.format("%s.end", lastState), System.currentTimeMillis()); setPredicate(String.format("%s.start", state), System.currentTimeMillis()); @@ -1830,7 +1889,8 @@ public void setAutoDisable(Boolean param) { } @Override - public void setConfigValue(String fieldname, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + public void setConfigValue(String fieldname, Object value) + throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { super.setConfigValue(fieldname, value); setPredicate(fieldname, value); } @@ -1839,7 +1899,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); @@ -1849,12 +1910,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); } @@ -1870,7 +1933,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); } @@ -1894,7 +1958,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); } @@ -1906,12 +1971,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 @@ -1966,12 +2034,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 boolean setSpeechType(String speechType) { @@ -2071,6 +2142,107 @@ 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"); } diff --git a/src/main/java/org/myrobotlab/service/Vertx.java b/src/main/java/org/myrobotlab/service/Vertx.java index d51deeffb1..a47d75a954 100644 --- a/src/main/java/org/myrobotlab/service/Vertx.java +++ b/src/main/java/org/myrobotlab/service/Vertx.java @@ -78,13 +78,15 @@ public void start() { * </pre> */ - // vertx = io.vertx.core.Vertx.vertx(new VertxOptions().setWorkerPoolSize(125).setBlockedThreadCheckInterval(100000)); + // vertx = io.vertx.core.Vertx.vertx(new + // VertxOptions().setWorkerPoolSize(125).setBlockedThreadCheckInterval(100000)); vertx = io.vertx.core.Vertx.vertx(new VertxOptions().setBlockedThreadCheckInterval(100000)); vertx.deployVerticle(new ApiVerticle(this)); if (config.autoStartBrowser) { log.info("auto starting default browser"); - String startUrl = (String.format((config.ssl) ? "https:" : "http:") + String.format("//localhost:%d/index.html", config.port)); + String startUrl = (String.format((config.ssl) ? "https:" : "http:") + + String.format("//localhost:%d/index.html", config.port)); BareBonesBrowserLaunch.openURL(startUrl); } listening = true; diff --git a/src/main/java/org/myrobotlab/service/WebGui.java b/src/main/java/org/myrobotlab/service/WebGui.java index e6efa1f28d..21971af537 100644 --- a/src/main/java/org/myrobotlab/service/WebGui.java +++ b/src/main/java/org/myrobotlab/service/WebGui.java @@ -61,7 +61,8 @@ * services are already APIs - perhaps a data API - same as service without the * message wrapper */ -public class WebGui extends Service<WebGuiConfig> implements AuthorizationProvider, Gateway, Handler, ServiceLifeCycleListener { +public class WebGui extends Service<WebGuiConfig> + implements AuthorizationProvider, Gateway, Handler, ServiceLifeCycleListener { public static class LiveVideoStreamHandler implements Handler { @@ -126,7 +127,7 @@ public Panel(String name, int x, int y, int z) { * needed to get the api key to select the appropriate api processor * * @param uri - * u + * u * @return api key * */ @@ -269,9 +270,9 @@ public boolean getAutoStartBrowser() { * String broadcast to specific client * * @param uuid - * u + * u * @param str - * s + * s * */ public void broadcast(String uuid, String str) { @@ -313,7 +314,9 @@ public Config.Builder getNettosphereConfig() { // cert.privateKey()).build(); SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); - SslContext context = SslContextBuilder.forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()).sslProvider(SslProvider.JDK) + SslContext context = SslContextBuilder + .forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey()) + .sslProvider(SslProvider.JDK) .clientAuth(ClientAuth.NONE).build(); configBuilder.sslContext(context); @@ -492,7 +495,8 @@ public void handle(AtmosphereResource r) { } else if ((bodyData != null) && log.isDebugEnabled()) { logData = bodyData; } - log.debug("-->{} {} {} - [{}] from connection {}", (newPersistentConnection) ? "new" : "", request.getMethod(), request.getRequestURI(), logData, uuid); + log.debug("-->{} {} {} - [{}] from connection {}", (newPersistentConnection) ? "new" : "", request.getMethod(), + request.getRequestURI(), logData, uuid); } // important persistent connections will have associated routes ... @@ -570,7 +574,8 @@ public void handle(AtmosphereResource r) { } if (msg.containsHop(getId())) { - log.error("{} dumping duplicate hop msg to avoid cyclical from {} --to--> {}.{}", getName(), msg.sender, msg.name, msg.method); + log.error("{} dumping duplicate hop msg to avoid cyclical from {} --to--> {}.{}", getName(), msg.sender, + msg.name, msg.method); return; } @@ -914,7 +919,7 @@ public void run() { * remotely control UI * * @param panel - * - the panel which has been moved or resized + * - the panel which has been moved or resized */ public void savePanel(Panel panel) { if (panel.name == null) { @@ -1101,7 +1106,7 @@ public void releaseService() { * Default (false) is to use the CDN * * @param useLocalResources - * - true uses local resources fals uses cdn + * - true uses local resources fals uses cdn */ public void useLocalResources(boolean useLocalResources) { this.useLocalResources = useLocalResources; @@ -1177,8 +1182,9 @@ public static void main(String[] args) { try { - Runtime.main(new String[] { "--log-level", "warn", "-s", "log", "Log", "webgui", "WebGui", "intro", "Intro", "python", "Python" }); - // Runtime.main(new String[] { "-c", "worky"}); + // Runtime.main(new String[] { "--log-level", "warn", "-s", "log", "Log", + // "webgui", "WebGui", "intro", "Intro", "python", "Python" }); + Runtime.main(new String[] { "-c", "worky" }); // Runtime.main(new String[] { "--install" }); boolean done = true; @@ -1187,8 +1193,7 @@ public static void main(String[] args) { } // Platform.setVirtual(true); - // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", - // "WebGui", + // Runtime.main(new String[] { "--log-level", "info", "-s", "webgui", "WebGui", // "intro", "Intro", "python", "Python", "-c", "dev" }); // Runtime.startConfig("dev"); @@ -1243,7 +1248,8 @@ public static void main(String[] args) { arduino.connect("/dev/ttyACM0"); for (int i = 0; i < 1000; ++i) { - webgui.display("https://i.kinja-img.com/gawker-media/image/upload/c_scale,f_auto,fl_progressive,q_80,w_800/pytutcxcrfjvuhz2jipa.jpg"); + webgui.display( + "https://i.kinja-img.com/gawker-media/image/upload/c_scale,f_auto,fl_progressive,q_80,w_800/pytutcxcrfjvuhz2jipa.jpg"); } // Runtime.setLogLevel("ERROR"); From 2a3584a96f0522b892398084d3e3719f2eb004ce Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 24 Feb 2024 06:37:54 -0800 Subject: [PATCH 094/131] randomtest update --- src/test/java/org/myrobotlab/service/RandomTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/myrobotlab/service/RandomTest.java b/src/test/java/org/myrobotlab/service/RandomTest.java index f089f2e453..9284a519b4 100644 --- a/src/test/java/org/myrobotlab/service/RandomTest.java +++ b/src/test/java/org/myrobotlab/service/RandomTest.java @@ -48,7 +48,7 @@ public void testService() throws Exception { assertTrue("should not have method", !random.getKeySet().contains("clock.setInterval")); random.addRandom(0, 200, "clock", "setInterval", 5000, 10000); - random.addRandom(0, 200, "clock", "startClock"); + random.addRandom(0, 100, "clock", "startClock"); sleep(500); assertTrue("clock should be started 1", clock.isClockRunning()); From 72add7baf5e4948457caeeeadb7db315dd862d9d Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 24 Feb 2024 06:39:03 -0800 Subject: [PATCH 095/131] missing service images --- src/main/resources/resource/Email.png | Bin 0 -> 4979 bytes src/main/resources/resource/Lloyd.png | Bin 0 -> 4819 bytes src/main/resources/resource/Maven.png | Bin 0 -> 7858 bytes src/main/resources/resource/RemoteSpeech.png | Bin 0 -> 3858 bytes src/main/resources/resource/RoboClaw.png | Bin 0 -> 4405 bytes src/main/resources/resource/Slack.png | Bin 0 -> 8951 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/resource/Email.png create mode 100644 src/main/resources/resource/Lloyd.png create mode 100644 src/main/resources/resource/Maven.png create mode 100644 src/main/resources/resource/RemoteSpeech.png create mode 100644 src/main/resources/resource/RoboClaw.png create mode 100644 src/main/resources/resource/Slack.png diff --git a/src/main/resources/resource/Email.png b/src/main/resources/resource/Email.png new file mode 100644 index 0000000000000000000000000000000000000000..595d625b2161ba6e159c9991784862658d365e1e GIT binary patch literal 4979 zcmV-(6O8PMP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00007bV*G`2jK|@ z3n(kZ1Lq+C000SaNLh0L01FcU01FcV0GgZ_00004XF*Lt006O%3;baP000vLNkl<Z zc-qBT2XI^0mHm?9?8aGpCZ4P}yY||<likGG5<8o+<8_NOa>j|X>YcVM%PN*++3JO& zs7^{OVs9c?#ZG`7B0z!!NCHG7QAkvR0NB7LcF~@7o3r<Q0F-3OQj$^ZKXc~;A0MCR zymRh-_ktXEkK^^<{qA9t#o;k@Sz8N>OIB>%y7fEUeGY*@@T=+B8K1)HLvC9ybgohK zdfbJ3cD?uA`+fI;A8)?(*5hWYbH>u+MsNQhynX#%wOSjQnJIW-`}XaReEI%@f`VU9 z%*@Vax46K?Yr!Sxz$NOCu5iQa8#N{+COq@C5;zfd@<;Yw@23`LFC4vIc)a}>@{M3@ zd;-HGV?(`eZ<EPv&d_z}FX;68$jPb6xen(Lk|Y+SNUeykw;)E`jqpk{uC}=_JU%fc z7K=k7A|w7SCN}mbv6o~2HSFZcC&NyLJ$~rWp~tpu+x9mPn!t|<_-9s9!0Gmq0s|Nr z^bv_sjE-H$^@%A=P0wO_b{4eon4FrX_cNHCrI?$=jT^I=pP%DnW^RV=o579wdCbkv ze{tiZ`OoJ+n*U^O{>BY@Jxyo&`m~iueD8q~I2j)PjMLNqncYPe^4uig8}<_zzdk`E zCV3*)C#Ntm$p}sJWQNBlVeT11o5@Szfx$6={*miMY>p2`#veqU$j;uF!$%)~jOdu? z|9W5qcJ1Et4>p%)&e7|E+tY_W%7ek7VV=Oq=s0gBKZz+mrU`uC@HhmuCTxsq#;II2 zu1dNgQrJ+`XhBk?0hx7X*u5i|Bf!%$v;GJWnI{55k?5@lMque{uYJdCvG=>&eZ2qs z2ZnAVFgkucKw^?#`A*vaUJR4sNQM$N_b_j#8QxyZR<oEQunzAi(rZkR>D_dFj=zW5 zkj0JpkDyYkqaGZArAxnUbvRvY^_hL{ATZ`9z^r3(4uboj4pBuqj58}uP2IFtASNcK zNu?PcbZN5{3WJA;%~9^m@G(6zhaOjN!viD0jxMWB&cFx#A0$9kXu(E9qt~%Hx(ORD z%HSLI=S46-rYN!b<ficF$P7<ng5GCcGoyE4j6R#;$*~w78SQ^<>2H=iFaq+H)+Dy_ z%>O|Gtds^-xIQs~+BQ45jY7z@J(whr!2};8Yc*<Jh$_}1hrssqkD`=vA-}$hC&g?g zQrLNN%!YyZtX9&nH7UDjRa$=e#*BjUb+M^}7mtR8fB(-SP+U^FW6(Fu+vm#!m<q#0 zVsD}xKR+slXK<7hAzKlFQBo#J*a7aHN{Ces0_#H}CGpRWORytOf$@N?@+Dmu8u43^ z5%-OZLu>EH1wlQY3Q5GzH^*Sbhbj0ls(3y_C@YD)obcm6i9kX^;!6zlmkF>Sx#9E< z;h*-`V1Kd#BLsj~g;rmxc7od^#DUZn0y~Lm$_ZYnO(KM5DtUV`wW{fw#WjR+|K><! z#*kKT!pXum2pigG>Ll_DiRpP!$%0bJzO#Z)PG*Qdy_}Z);vWV6L|EA0>2!LHn>H~9 zK1d)K!=!@I<^}g|1<vQHXa&c45I%}M33A?61q6oKjFD2CJ>WKra4xq!kl+)L>3aC< z<D?u@b&#S$X$5yoj6=CCFj$@S`#;?KErus7JpAz_L2-I)QIqq`g^0hsCwSV#p78mG zEi!F4+B+@q(Td+ng5S{ST8G6=HYvy1>^55cF`ks&J&1olSPO282u(UC4~~VV%TM4^ zf!d$s6fAF)#$K}BI0o6@plCBwFLho9?o<N~mzki{=+51WEwfaTTd(fU|4Q%|E=2sx zu+QggxAfsL+K>vRIs$f+Krn`gNSA{Y*dxLR2~9izAKhoN_u-MfRp541LfhrWC=p`- z<@z2{r5tf3+67Xg=582leLVTWp%L^C(lcFscp<I@+?i%v6zO5H*(JB)J9Y8$i(2nQ z|6Rc+B_}^SHaa%k+U3Ph;#IT~a-1$u(8GOv9t0~=U*L6b-yl|=5#yy}HR$mS@T45Q z19<*u4OvSFwW*iSmC>;=6iZFuR+k}FNPETjIEIGDkW2vk2K}X(Da&5>_7CALK|59_ z$lx9DF>>COt5*GW5aZF)5znf;lOuP4zZ@6$9Ie)b%;3fomkIo(Hi}j(kC&o{@}Qpq z=i6%kzTjb-+t3hV3YC<c#c0tx2!ub`Lo+A`)|WusYQg9TB|Rx~GK-$Ky#g1nw)6h4 zly{>_Vc`{GMS(9C%%b!<u}TAt$w4-oWSiWll#dJ5=S*DMGE%^J2Z8SH?wWQ>Ke%W= z@NY-Af{UU!CxOOj;|aJ4bT73PY<G6k*)uQzg`TQQND(gOw-SIsQf3g!PO3uN%kgfM z6gF2sDx1y7sq3V37hVaiMNEkr*|nWK*dX{|utjpXJiM(~X=OWfra<s|5NE!)B|_5P zdH4<jcDud3QfUJh*-X}<h*S^(^5W?Rl*@GR_79Lv`r+iGk6s6Yh@8l10=KmoDuV+a zx{uu}(^&C-d;>m6Y=od%N8|^2E3&*X**ts^V1SuTg5a4gojtv`HH_2LZZyhdJmA&q zH#}0O?#^AkX7xjN5NK1X5}1TWdoS`D4A_uL1kN^s3v0lILOEOnlm?tz!b7X;frIR& z>+Zn|heg<Qz7}SOo9^v{tFPZ5M3Sk}N9y@^^db0${DmOBQb&nSfPMbTW_R@P=UZD9 z*64LxEPU0;C>DhjR7>8wYXWCxWv}d~RdCblx#<tu2xNVG7mnmAusN=VR?fu(W&@7J z8H`>&>~vkOv*H)~%dt74j%vG;C*fDg%g+-YFKfvMFh~R}PA`>HB}7f8g|ck3J9%sJ zJN(GlLbJ%)4v*KP-no0vqfMQb_+`tN|J7XyyuWwv)2_aOkC;Uq)Izf{l1*Z4rBwzi zj1~vF>^*^S-4gz5R+3(2u;W021kdd+Lt;rQk+M*kbfKF_nyfCg>a7q|s&PD{8BJ>I zLUm&+&j>K<e7RX<P2kAr=x{-4*(*_r$@d<3%vOhyfxo%Zz36uMSDAq|+j?N^wxiQ* zg`vyFlQLPI3lSt&-)2H&P7`*;im^Ao9*0w;IG)~!Xe!Ayas!{FObu4esAGrG)V0u} z1PKHcxIQ__Pi-_y|CGDm(V|pcVFZHK2(B;#ZerjGq=7(pcH5vgS)ehPq3JY3XY57? z5$dEc5J9ueNyPo%>9jcMekaWIEE|D!_PEePYPp!gbk^!R7N))12?VA-D!Gh6xlp+2 zz61)2iZ?I<w*lut+tI;5cUhq|6L3>EA8KP4wHhY0={nJ>>3~XaAW~h>5xEXhDM*N& z+cbJ8+Emc!^e`CBl$bWC+BH0hTiX<WqevFw3HV!cHg;-t+WSvyp`j;!Xm@!(39fc0 z0oN1oAZRs7-A=2nBGAeXBiab?t<gdt<thyn?K&uR23{Q&*Ba%h5lhiQ#Jb4q7Q2Hd z!d5>xZwDv71p<Mwb^Q8u8b_yLu~=&FOW@^~UtX*?c9{)U$L+vrrBwt(*+HwVH=#vq zghJB^xw-?*YCVOHKP!kpi?)NV(|e^FO>OO{lgf}^T*h0DsmWBekiG1*^34AhtDQfy zx!eD<&G79uA_5arlXPvkXW6o4-@Y#im7!bCz~2U3Z$_)mL;#Hh)Bu^P1C2^O8rpP_ zwrU}1)ljJEy@sx9&_pUQ66{=9UcnOyTFPI|oUjrJC!Y%*J}+zzD!pz`fCN)vglt5O z;B_|n5sAsk`2z<I{Np{rr<Y34g{Mns<*AJ(FEijRS`*|1ys5pD2Q6vU;aZCZV!4`v zR=8P(x+WG%i0QRhu0nl_3Jt9)l-FECH7TGXyQyg*m<lW>Or&r(k+9Rs_qg0}Q{FKv z(NGU31*=d#o&Yt1<72#ySRSONrT+RW!Dp6=xBvEFDSr7@DgkCzG4sG>1iXQOUnAgk z3Jt28)u<%E6*46%8rvXhXoX1Hf(oeuRTMRit*DW;qP$Lm0%7HX8Z5!Jv?xpo6XjO7 z!vRN64}o{XLmOT2*AEuGq=eh+r6$nF6JRP#&&;5vwsy~5!N+Ijzq&J8`UUq&9Jn_U zP*$&koK{|{>LlQL)GD;7BH$H`N|Z}mQ6_1jd*mp+)<hvgskjkkVi|<=x}si&^4fX` z3JP(pNlx}rLZMbeMG32+<kuK2Xs6~-(P@XI-HxJ0E2>)U2>$*QY&TeA=;`a@&#WD1 ziLB9TGVcJt|HzTYPUR}S+|m>*S(Xa!)dWOm*Q0^FUe}`Ifr|)u838Z3ra)m`6Ry@Y zBC(<su|g$c%i99cikNaGqDqwr6G(CTY6~KTdYmom#EB9k_7|A3EvE~s)694=-iRMW zc2c8Aga2^@R-{;{9r3pttP$iZl8<s>c$ja|`>E??c5=FURCf?at8CBZHWq+ek&Z>n z)4;u!gstJlsA<uXP1*@~8;ZmV<kvJKr>YTI6;h;@OYqZ_E^rCdO2^X-d)Wdm#!PLv z0bB%uI;W?JN(=5J%@rqTUN}xI`mxqP(Cg54Y7*)5Q*`esBdV2Dg*+bI{1J^*AmBqj zUm%%Ti^1lFf#HylkVhB5N2O-Jy6z$kAAh)lC95*Am`HH1Cu7~=0tjVl6pCArSKSOj zMI$nV5~P-0Lvl$il8S4w<)V<Lt~~1dDYoTO<OG6VZ_fv}vk=^_QgH8!z<ody+WvZQ zhp4+fN`Eg@iA7-=I_q#SuZvGySKv2T4|TA8i~w1Oso)zKX1x%f$b3r?5%Hfl36yDN z-1;Isv?dEnD85A`xTUGcDr;8MwrKm(MU6-)mm;C09&trt#1_;dI=>3hc@>Dw7h&UR z0#8ZLy-mt(A@W-?XfB{<yqn`E@m?{_%;iLYX5@Wha0jK}j?nCIoaT(<?RfI64y_$j zJ_7`L-TwbW^bHII2n_M7PbHMCzN)IaYRwzJf8*v}uqL+s|F|`ISiCM5OV(zCTP47+ zwp~T?mHc%DRT5EbaXq50)*>>m8W(aZaXw3g^8z8xXO`ijpakaxW%%U*0=g*?+?z>6 zCWXilp?9wMN$j{vDwOzD_%NWt;YKVvMk*XqBCXm4CzVo1pqwrcphRZ=rwa7B&0()7 zEGk@j*Xr|q_8{i}jrlwYZkYhP&R2hY<oNN&Q*#QBoX@L8_!S{erk5cswFF@)g$PeA zz_FAvni1;p+*vsmeNai?kMqZ-1R|0QZZji6N^B#Ab{2u#T}CQY^C~Pl*g#4&BP5}n zI$ImH(LDhIy<{Ppy$AW0V%A_ZUWti~`Hy>VyC*2g-X@=hkY6{@N-oRBi3G8meH3=) z{PX8>YOyb_00-jo>70-4u_FBbk_=Crp=8`wOF+}Gcugdhth<CI>tnEJ!!0D3rQRh~ zw+C`zFA@E)j#QB1p<_*uw3%VE*<rQ$cVw2llmi1Atv34XxpPn7w`E|*-$xqhf#k_8 zWqu`==OQ$&#=<^JN=bY=rcgS);(Qr?8zIFr=ct;5l6Ut|@@~3}B`Z%;;-A6d)#tHz zZIDDPJ%<Q{B>VGXYZeip=f201xx!E2{dyz`HPoD03A}@EtLbd2s;b_xYuD~a|0v+W zBf7F%!(A}*3AN~ejHJGXjnOUh$+Z^MnR46U|6J02!G%)U+fNDjP8Jrey@(|%P7(Ms z1paIw&QSrlKsj{rCIP0v+oZtOY(IgWC0M+N2<)oGj)Z0ysY$d`?_)A|D=I3h)~#5v za`B%8Jb3Iou<y~#>h}1pS+>u)vsBB^nf$f+sG2N7%IvPdqIU#juQ&o3MG@f#yz=bL zp!bW}^|es}0xY3fGE*XN&EYMyh$*nM0xyS4slV#}M5R&{CnYC8|7U@|+YytL_KUbm zi}dBBK5*eS+I(muI;<cPR5jkCGPos+uQW^6#bC*5vdgOTH-o)j8%>|ZV)2G}zkS{$ z`-D&uZ^^~t9hAg7%JJ;6YsjyX=Ut4v@S_I?{L4p4ZSy;)%H0DGNA{B6DL;;p5{DH0 zzmK?WWQCAaEM8A)tc~%5W@iSRu5la4I_s#Eu2023@5sX5XdzaHQ~BINd%@mnq?NZ6 z|HXjcdK?cu`n}vbee}+}zE62Ij*uEhY3n~oC3iP%NbgX&4IzJTNcIC>OR+8)Pi{@e z+R$8F$f%%(P7ZmSQZEuWo_Y6j(<hgUmAXCe?|S%aArL&0GO~V_BeCWk%6E=GbyAB* zj*}WkD4`F^@PoY-cy@0A{%|A@yCVt^kyeV_vRc&3<Y;V?O9ffk|GRwo@<r@9$(eb- zITCvEsjn6Mtw&-?@=I3=X$MZu$LY8n#AS<+lv@r#VL7grRX|u>hx*3mj~isorSXZ0 z|NV^t{i<WdiWT225{d?s(y!2LTZ+O`A&Pk5Vn}52smhwV`0!JwfA~$~yyqdv&9BHQ xC_%BX3KcZ0)HgP{3W`eh?LTnf@oy5>{{y*7kSlp(l9K=c002ovPDHLkV1h$!jG+Jk literal 0 HcmV?d00001 diff --git a/src/main/resources/resource/Lloyd.png b/src/main/resources/resource/Lloyd.png new file mode 100644 index 0000000000000000000000000000000000000000..b1af6eab7f0b36c8bbc73ef282dbb4168b96ec4c GIT binary patch literal 4819 zcmV;^5-jbBP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00009a7bBm000XU z000XU0RWnu7ytkOAY({UO#lFGbpQZ!pa1~;-2ecCWB>qg=l}rhXaE2(J^%m^kmT%{ z>Hq)|SxH1eRCwB?nrV|9*;(&@XUWW3dy&+weH%$5Y3$iOGt3wpjIo&^!q^0cun7YJ zpMj47FK)zr4>t(l#WnbbfdJyhHQ>M+?6L6dBTZ}6EvXmvUR~W)mCHHj;YDUGt(hSR zZgpf-R_4h(&%XShRvi9(j@I0_;$tVSW2_Yc5VTgK1qua9Rp+48_#R3z@e8HO`%3BZ z998{Rqvy0%<@MpWD$n)s-AbXgA&G6+S-t<k*1|h~0sM5hhn?8(8^kkzJbV7J$F5vB zhtdWY!k9=kCSFz44>ug2580}Cl=7!B7Olo>^#l`I=`kZ}@?gYxWG#`=ycb8WZrxx0 zI@#_Y0^b|&24Ca&W8eSMSH8;UpL>ewsV2?^f=ETQlrKHRfEryJOS;mLQbXDSAp{|g z<Dufl8kx|Zn$YT4n`4F}qv`JV`QcCB<o|s4e^ab3{2K5#3TV?jcK6qQ?+=ds=4)TZ zyMkVSfQU>;UyaU3>K`!xO6%%dO_-Q)>iBU^o;uCy>ME-b9}>q=WlEY!TT>_mIb?n^ zlz^0qM1-W4(5Tn>yMOu-fBHZF$JW8x;;R<;)tL*IkA3--7qCXNyStBbek3iFLJw_J z=~C^(bFI)yL6Ficf<|kQAb97f*Xzt5n?q~E+Qxb*6m1Uq%g}2Mdk>&w$lYj#a}EJs z{e_o#=dCx7{mb9)|BgL*<+*FePM_q&{4u(FUGlsjNT}>ogDPdCCrm{Iv<4*#(UTe} zH<BPYS1{-g=yW=yS%v~a2qTKLVnXJJWPj*eCN!Y6Cd)GBjveFL<xBkN$cZcZcYpso zbsLwe$@2^uJu!ao*f}RRl*9WD9a;dbFxG++W@l${-jQY*D1{J8vk(vw%D*siNO@f# z4A+Pt67a#}oI@K!T&p44#CEFHY+O2j77@V*IYba5pfo}0QX5*MD9uKc1_WPtP>Nb? z321a>V*NqR!<`Oymsh#9@DVCXaA=f()*A0U0$5{^L$*_LrvZ(OH9^VIFu3g<9N_aJ zSZl3`qo{Nm4ohB?LbM^MsMNVq7^U$_;k-vF#nGelSR1jjwoV8_v!0M7mO@7?+*@O1 zYmX?h)arEvi-@MkJ@r}x368xqC5~dWE(J1-W>yIb!4Oa)h|*|N-s|iiu(P)hMS(4x z^I4uBwmkvSil8iBMW9Wo{vc%Dp|oOVYMK-C^UTlB<6K~QW0&pD4qh3qo;t<(c8h+0 zKoZB)Y6%Cu0~Q}VBwz^Mapue!P9C3Qb#;Z6)m3a{u~AIn9L{@!_o(18p+JW~5RU@- zSx$2%#fJb|V<iOfp|blbc7@;(Wx!ZOl^Yh$Q%e%sttQ4OW@o3FYERMa9<bN#pu{oV zs&nDYJV|Vbql7H-4Dy1RnOWM+I)i?XgEY|H>#(}If;EQuQ|G9)XZi8XH(1-)#6~fq zG+E&&oJ0GJ*k|B!M8L)|*IQE<T_!*~a+=C)ob%Y&;<Y7p9;M4t;>^ht9G^eIk=a>% zk@M!;Z?nJOM<lS{+s7Kk)$5m$5I}?wJlk8_9P|#DYE5%=?kJ7+EWz}+eQ%G=yZ5;9 z?rrLgHnn=fsZ%FOa=3T@KApV|&1MUw6-GRbkQ0R*qcwISA(o|tfJi_Doc9>3dF;YD zqI!#k2M<YV4W4-XapEYVaAh<Zqyx~JPG<+D6;Tvnv?kAT_78eYPt7njTVw9@Ir6o2 zdZIDTaC>Eo`zuQ<t*&FWA<Ht-^$iGt(<e{yr7wJm-A;#pd*gK$?ktcb2}*0c2(i*- zIFvH851rNqL4pY0c}}00r`>8GO4A#pG?E05rn9%l*7g=zo|6{^gEYljL!87U))Ih0 zUU2)~3WF@;_P@Tt!qNlMBH*0Ext!;oeU|5+d6K2&HIy<arKmUR)ao^Y_x$AMO*S^S zxO(L(U-{xMarxpU{_W;ZNsM3%MR#X|W)f3NYKW8pw<_jJj<i<fLFf-MiU7UBGsttg z`@3vzJ}fh`(imf?B{5NC$y{LjV8HG{kJX(H`@M{`NLgB3qMkHJlq1hl8ubQ6ma^aL z<D92nk3a(At4yyo{dB-PAH2uny+zKRK25zjLoKeO^DeE(a{lov+*@9za0Mb^6mzV| zuxN(0^$n(54T31P4*KL+thJTIG;tJ_F+R)L-S4rrx6i|!4w-W(6p^(Q-t)p|uJPp4 z&+(JDZ*k}DNBrj3UgM?DJ<s?4_HWAwGnsFyWERCSHUxUvfcNiwNSYQ*HJhj+<;eUD z*DhUT?&uMgmRCu$yvk7}0|Y4*(nw;mt#!6{wwangg>5uReV}Fy#%TI!&coe3*0wwN zgFWso+$tS)zst<*EK^fcEG;h5YSihhK48$8!_?Z`eEWTBv1Vgyi#UqQY(GllVU(x@ zh)jfNA!@}04OS<Jjagb=VgH~<mSv-Aci0bzgtE>+gpU?J<iW}c7p{6vKY1MQh0Hm& zb~-Gqth2G(Wu{rfZEo=98*e}e9GRPAb!C~wg#lAD)2uvLMnrh_7k`Cb98+&i(=P-Y zCFIUm{uj#RR26qhX@U=gP?SZV(FB3q72I83W_!z06gjmhI+VRj4FnmLuVqK1sW<Ax zk)?Cc<)ix#xWBqi2!W&1Gc=k_x^V=q;QY1geDPPm&PVUP&DzpkUi`{yEZ+WrcYl0? zshZ`*S6*Osb)Cfz-lynn(@1Jal#n`4Qv>42vy39osW%$<C?YEy4GD+{sZtoNiSq#s zqwvP!Mo~y%jW#NZY}vdhO}$=cW_p_T)HDkp-r;-S{~LBTH>uSk2tpj!c;@9-I5K+# z(`;g+h+G*=Tqe1oE%j!T6Q|E$5MKX>?{o9@8}!o*5Nb({=U@2(C(fSZ{eORhdmnxX z#-IoUf#+ZOdFD=>;>HjDFN1!c=YH{*nQAq-`B&dXe8CsK{ria0BMoR2A<uKHF&JY& z!`j1jZoc&{@7=o1();hy-Rp4W>CbWfg_l`gzR!&x{(#<o7h?=AcNp&oMb2Q5;?o{R z1fva`>+8IJ<8?M~EpYX-Pw~y~{2oOR-h2CJbT`)c@b+ys@7?3f<tx1SOTWTFf56Xv z@?+Ay4iA=BSX)`a2aiEmTv}wO)4>{Bc}vNH-rU+w@7!M|D+;X9+<N~5{_!9Gfu#q_ zG+S+StIhFKCz&~Wk(sj>IDhFfgMLb$=c67$hVoDf8(Fl`IOj-`m{zL=(^I_s3t!@E z-~JAtefk-K59Ea-Nov%N&vW64Cz(EXiL)0jkY*VHq1A5Do|z`8H#peaV`h4qr=R;A zK|%0=Vd<d%<WK)H%pX0<;_5oL?k@4>n{Sb)DM=DzOxXi?=O_jP@-!p(a^on94wu1I zqf?6_YH@_pia1V2jeKMcX<9OFqlkLFj@B9pLOMuE2R*!VBuRpeqB2q`g)x?_-^06t zsUx#EAIbnQbmIQbu4*@%tZZ(xvUs1eGA*M*^^Pj+m5$?_A2%5YRPAMvWpwvCXsw8o z8cJ!r52Lj2eLyLL%k#249zM^ol#xdo;<95)tYUX>mn_SO8*QAg0?yEcsMm1bgGeQ{ z2mqt2CT3E?N@29A`r@*=QB@6<BsJ!a9ie+Lpw}N@jmBD2wxLl(5ny|-S9Vg9{kGO9 zZSYdUFCt;IrPi88t;XisDp_8j>kV@6#|_R97m^`(C6EqM0MabO6*<8NB!p3wS-66t zD9H1iAVS*jll2d1oIJzPv*%gc+GX$FUHXFoS)P$+rA8NSeZWSySJp;_XVC9a<T=h2 zL|QT3Y)}+AS(cF(InFtP2vX#1K3u~Xi;WTnK2*Kt$AB$G2!Vd@pk$!m!+VFg0`Glk zuM)_zv}9QVMV^zU19b4DVi`DSx0~$wf;`I*?@5vv>GgT*$2Vv+-{St#BA<EcNt&%T zNgT7g*X8C9e?WU`ilv1Gu3UeD*jl0}Vs~wg@BZ2UqI+k7mtJ}v9Yy%y5B2RM2Aub3 zV|eN1mswd`<LrftB<*P~KmIuFRtt|Jc+d6g*9j=*=1*|>(ql|dPvf=4r$uQJk>T{& zvz$J2hPgB6c%pDb*0Q;=PG@tS6Z7+2y7n2&^ie+h)KfHSHFkG)*jj$TnR93P>~qhf zn{A%B{shad78@%Mxc>ChJoR(0u#*;;C}!+ELlY7(3VLZybovSx&tGFtBGT@FbI-g8 zDE4~;(2A)GS5d%v9=P=4&m%x*kdO4(?e{r-{vyvl{VZ?ZS|F{rdF@-j&6%?oS-5kD z#e0kNZH=AIKFwpNxOVIm&O0OoOcb#fpy>8!ox8-f^Ov~p9ZlugC<YWRqy0$#ZYTSA z59}Tc2-*;0AsE9TR84k;HFX9>1@;PQ7y@5{u8OlP%ZZz9j-5Wk;O+P6^atpAlk-=v zb9;G}-GQT}JqlqzFHw)-A$b7tAkfVnD2+jpdyjRFSc6uAsTzJn#tcZ6maSF<B?P7L z6+}3~KT&8k3}3(~Vkks`QHnvDGt+Ldv$?^>`Z~S{>}+k)>#VbK|30-SVpvNm35TMN zI&>oC`z4^o5fKO&4xC{lg+WkYEp`$rALEm1MARdIUs)-Rz*_++*CB$oWr+QM|9pdX z`yH@`W~<3hZob3H+9uupfOe}@hV7vhi|}b5HDXdo#gIut;bYW+XFph|H_D!PRFy0E zwAYOY9sypHiv>}NqHxsf2_cF(e)2Tk-X6h~*IMlv=1!bs>E04?6c6#j|AhF`Q5}j* zVsa8hG_h2Gm(rkZX}Y6#$s>rNe4L3#mQF|#(ljNpmf!xZ-{7@Zze=9wq=Nxp{QQf2 z=iA@p)X5X1gLDE?eDq!86jg|(Czg&R97uw%Fl<#>iIRYlaA?565E++Gkj;=urG{<b zgnfyilvLlUL@tUQoOhrUd0w!)vx9d9N}ETa*AciLP&hQH7>W{;Lx~XK4;lC*efDFD z6A=9=rh_0sQC1j_Ao|0^VlBo-JXl>LO;e&cVlYS%QKV@K5=KruB*cp*$55cld{c&q zO4<P?NEeu>o7AHWNEKejizm%VM&qOj2y)~*!>!6$!RFRx3C)YJ-|rE`v)>z3=1~$^ zpm8X?XhKEq&`fy5T!Vmlg!l;!3^SV=IjIf-W#to)<FP!-kUBX=QD_zD?eDOE(8ZXD zfM&0sbMN66%R2}3ZG%GMk;%wN(nC!@>Ud)>k;-95nOLb37m^Y7UsjhI58@E-@U~1- zS{-IYp+@L}R6w{>hF&n-eYnX^27TQiAVeW%;o%NDgMwg6u#5@9KQUqqE7HnY$8^dN z$T)05YQi+6avl{N1cNsQH_8pV5{xEPrXy03Vl<tDl=Zh3s5e`9T{bq|e#ZN^@02Rn z>X3;%;%tW^h8B+&S27?IK5)1eQp!|$U|%I1M5>x=Xq46jF@&J-%9JyLppk0qBr=|E zDODPU%X5qZ9UKM+zI<$EdWQX8k2KA()_$_YWjyp#63}?Gj3+w7?oO(_Q|5u-F~-s- zqDw-m>trS&(=izqC#veIO1xf4fesEU1(6g)A;(IA@i}RCmzO?sk#BtUizt^vks(D& zO?y87%vH{xI7&V^DA$WCkKo4V!{4F2uR<8l$kf;ewKhcM2uoX(<jWhIGzS42MR->r zVZx;q74d-5Of*xIevJrRICX?Gb8WUZ*6DS-G#hoiK-wSB?QHYRwTtZS?y$bOLoJCu zZaSaf4l-$0TA{QlLAb#IvcFqs2Ax~$yIq1!Na7f4%LQ7O02gXJV$~D>J}`<{6(=H| zQ^)71HCik^Sj9vZr4+_!d=OS2ZZh3&bL_}0-n&Phf8<c(Lst`0Z8X{#Oq9^DhKEZF zu)g@ACEfq~e0y!Xmc&QToIB6*{rhBDib(kQBwfaH+eb%gh4b8B+hJ!vXTP7(Znj2} z)yA;C-Q`C=eVg^o9op@7i9!7rKEu7VF+_2V%a<RcUG&*oyuC%<`?Dd1zkd9MFaPy( zuYQB1IX%j>q55BmNgWTz{}cR-uIJ9-y`vV#RmCbJ=DioPEF*~{tTm$|w2FD3YE1yf znsQALLT``P|N75ae(T29f$vYQ|0eSn|LFL&XU?BJdlpkoWYok4d`iq#e9F99jR)?B z)hB%73*-~t3D`Iy<T*=sKVt3OpDodS_=mt>jn`=d!2`TV{m8dCcJi7s)|AdXk!}CK tKJwpRrW6Dh*j)X9Z0|3DH-@+VV*oV%{5<qm3_}0_002ovPDHLkV1gNrNSgov literal 0 HcmV?d00001 diff --git a/src/main/resources/resource/Maven.png b/src/main/resources/resource/Maven.png new file mode 100644 index 0000000000000000000000000000000000000000..355edb4d2cf36ebb991b3cc2a6ed30af1edb0d66 GIT binary patch literal 7858 zcmV;j9!=qiP)<h;3K|Lk000e1NJLTq001xm001xu0ssI2*kEqZ000-8dQ@0+Qek%> zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=TImLoZGMgOr1Edlee9KZmaqaA4Zx({S#RaRF| zQh!#)i6E17x&t=6!JO{=&wpR{U;L}FMVCwKb=2zlmq#9P@}&FEzw!PCpWom2FMZ$Q z|G&KMe!dZTDgAb2o8RLnub1CG@Z%a`{rbA=-*;mFo+$i0@n;3PvYzkXFOt{e=k<`k zCmQGTq>kQxztr_Tk$n$*OZoohpUe6@=>P7Q7aCF8g$tz^PYTJuzx}g<q~Dz8dz}2T zD4utom(<;RDSS^xKz`ZYk4Nu+AD~}?{NtJXWAuMM`$O>K{Jx(45X<_F5l{Z~jZpsZ zdir^p_=j@%t|<QcMxLSl&33-O_wIi0`Rs1yN<{0oqF%6nL`VlNPL<xrDnE_i!t?fi z20z_TZ=`&C^NSw~BSf-tUC3dF8E!c5(SotW91m6RV~j>7^jc3Xj%Z{lkU4(BjV+C| z$y!Vnk4uS{<IlB(cir)>XQ6ZD9XK=wP8Rsc-~Dm_=*@rU$K7iRQSkj|tXNl6U9${j zPJcOygoOLeTX_Ng@$*f;|6Tp4R5OPu%$*HRzdol}CHz)f>E$_bT;cK4Lcz8DJpm!& z+J(i0L<W2fse~GQi?M}396Je`teifHiwuNP;%1RCr&ObBu}58-&$RH4jU@`HI$4?| zqNJJ{xfyAategw=({iVVW=SQNQfg_XyE4kGspeX0t*r{=W=k!%(rRn1x6x)#J@?XU zZ@u@?=ZGVLfo0UuMjvC$8QtVUlXFdupS-f-N-M9j>T0X6v8GWzmaSN|X5EI(9e3Km z#IC#TzQ>*?xC2t0e9Eb(oqoodms-2&=38#P?e;tF{F$|HR{!Sb-^g0{W-Y!<>A3bY zYrN`O`*DgOI4Q~*8H+iP@umz=&`~+_J>(pfIpxd`kS&$SBFkfu9h5Orm`{l1hM&3n zEpz`;-dySbQr_Y}%bZi{{u7yVr0#F?_9t1};yHU6`*fjV>J#0^F*A0@ulM}<|33b8 zqQ6e`e?LJ#d2IU2g7dVa-H=bEE2@Oxx>nXzubkFnGkS8(dIk+MTZZmdrO#F>X7$*n zht@pFozkfGUc#Q;r`_Ax3-)Q{-WW=Z`7E>&0JK}M-E56}pMFax0#?)0Ry;O$Djzv% zlml;jx-Nz@_lfiRTddG<W$lq$wAShdlYC6Qn9n(FBt^POqUsjczxxgI9C#jVxE4X9 zwffG-_j+xv5pVCDeXMi+0$I4!omS&&BbE`*s16=$K->axPFqksAB@XtE9LfcPwW87 zEd8W~3@{E0>i}A;G-;E0vx;<vOB|L7IC}SnIY#NpTW4bcNJ}@g#MT2j==DUackYyT z=lo}bV4ypR^Lu!AdRl7`o0`hmD9XJJ%&Yyz3`hsmPr1Zsd0k)e*ne6L?mtkEtZptB zr=>cl8SFzcSX!?H!Gny6_cjS#v5I%XtX9dzom2$)8VehQW&K9hREG-~2>`W=VEdfG zF5-)l@Kk7(<RK8L>jb0-b1`;lE0Wwocsp`!wLBfNY^0_lRt#ED#^%~Nov^ko<B{Sm z#m^kps>$wMeXzb;SQKg=)-aBDtN2lOJfWtJ=qJ7Yvd=rmm9M$OuBx*mAaChZ(S}|2 zvX<teny4}OlLBy}%?26nx+s!`LSg^stO6MoTCE>+5N&ke@8}PHrL}i-d@WVd!Pihs zj;cF%6nsg$E5W#?Ep8MK5gV6+h_%nXDKJjkMlz&XX)F)2PT6`z5$|#wWGMFCP;Ib5 zyQW>^t@F-wz=d2F41TDzO3m&}6Tq5NR0$l^k)#Hsj2%TMf7Pw}ak$!CEU6q|BoTtq znhi9e5JZ>M#agFi?c{#!q}L$39)JxZU3sv>hDcu*=phj+Z1j51abL(fR<wEebXMAD ztp$qcqLW}#FodJJTPx{{N@zf!v#@ZqvZ^bEIm4(2tyBF}p&(_9_~sf(S0E(@7W7p5 zavi<Ea>zY~0l%o;_HF0O;z|Ra5J(cUrw!f;v?dzk6tv1f-vn>AV`BHtpD`#D5N4+} z`YK$T*zGU!g2!MkrHT02fLoXdB$=9v-afN!)CF>818mrmn{pQ(Pn0u@4XN@<DUvn- z!7TKgJTR$?V<VlAY1Ts_VLn$5Sos_`PX@`5HfE~``(MJqV`54PjS)5g5A={IV8(Y- zq#6!4r|`+!^2Y#w7Sth(>5*qj7W)Q^hnQkhq~O@Z;hn1*@&G;Curm|ANqjJCAV)1L zx=v>g7P{@OU2l<-C&-H^Z+dy<Lah?LRcRLRe{@1eAB@mr=(onVq-pN8-`JL8*$3cY z#k6m(s+#lqX(7m#xG&wl-s`_Rc}1@8SwnCv4vX?VeGLa*s-i|~o{!Q8ZDIrY+{`NN z`bv9d*~dsW#@SRL8`qE}fI01kUS|+6Gyn@0CGWj2KZXzBOBaDm;2j*A#2D>-W**kc zJCjk_M(r7W(<~K$$p~J_k0EUWtm^?DSVG}6On{E1R8Nnkg9xxyg}6p{P)-}reO+)K z$)x^7+&xT#%?1U@uCV3nrXDj0$$wC}R6RMH+67AkuiOGtP}k6@0?m&p*{ALEaEt5_ zQs8dFmuzFULElT`Uzy4r*&e$|+kvWig7!OsjlJ60gB^v+A!}3^jrR#Aba;76OyG<V z;Bw%9%zW?geYO^yB9QD9VcEdH6AiZSCy@jQlWiDbA3O)8DmU8kJeewni|K&{pi{Fq zo4cWIs147F;4sj!gU)eMZ}3_UdoQq#<r>t!@~vVwj?4pmfyNYPa+bV%{R?zVoql%P z&<B~=!00Fe&tfkY3SJun&GQXk!RAxJbLS<|c3d4G4G|!Go0{<*)ua9_h=ANC{e@=B zi$0{((q4d1iFsp=PP!Y)x<ne+1+c*iXLC-f{lGO9B%|Dg5U?EL;Z?h|O2LFtR`eoZ z2=x>10+(GxQ}#~fjW~NPTxe4q8X5C!7jgv4&xPk|nV6>K3%F0c#wqItUMQ&qi2lCV ztcBoAp$_~d#nxXPCc4|;TJ|gYrXE3G#MhVrSs|}yTO@XzB;UqjfLc5?Ti;8_U2~d5 zxt9u;#c=8FiuxRQNgxW<S}}IyEYZqu_0TGPdRD8f2x;7PZR8+?K@4J&ss+<J?Rkxc z^Cem0BgjhXl!&jr3mp(7J8{o>2}Wq4aCQ)}k#E92Xats)StW3plALM1QK2KBbg+5S zNul;e%d-=~w)0dd`keZnxeVTc_nydGADQ!%z>rwpwTZyVF^k5*qO(X0tS&appA{h_ zbdk0b@>61xDsD)aGNn|p_`={8k&&Dksy#xz{O$Y+D#87)^_TCT*-}#BKloOQL5BEy z6u2n(Zc8Yq(jb`BhL7;9@cv#!_`172J6wSpL)**>vZ>ylUw}k{CymhN6^|kM55U*> zaFaqw0o_FMxVBa_w3C~~Mu=l0Ev+6%zf%}S10Arxp7pf%zK}f|&iJCK;`S6r4wS8n zY=d0?34bDL)@>OAMT5ZA!)8lRXWflhXAy}HL=Vg2Z^UL{W}5ics4I|J3QsB-k&;13 z%aUmjJj;0V7z-x=w(moIS)hNAPsvTNOUr>hIgzH0^sFYfU=o@T&fwevq;d*NT353m z_%Dr9wjHEkfmX_Lty3NfK)tAOx^bWS#)cgr%yR)inVd(J5=cc3sZuzG4)N%NkfArk z!g5I03;?O>;Bwd>DQ0N!CFM@T@r=E&go{ae<w<B|#o7hPI6|%tr|B+U2OJ%5i_(Xf z#y+&iH*Wpz@%WF$gF4`4o)K~sU_xR-=^i9@l2j8}8_6rV2!$>Lh$UEsd{@PeR{`YN z7_~@s$Pp&uvG=><0A`~dU3>h9Y}?Y$A<ykssB$Qp8`EFn@RdoqjE@%xky7MR2UrGV zk9aengJ2^Gr;r{^4*G44=>nCny8~(#b>L-dbos(UN37?>2x12)tP?nSQ&XU76Y;u~ zMbGdc{C7(pRm2X7nSbtq&IV)KRbNxb^skP@C7|7!<O0UeAh@@R4wJ=0M4mFHKe_@j z5Jj?WB_}$Kt^cqXs2Y3}u3_=)#O<KK0EBz01q_GfC%zg*in9ht0Evk^_YfhJCc6@1 zsT{!tL76UZf&1!fxHVAZAwWla>Y~IsX}o>ueXT9BWB?bP!?_b>R;Y|DHx4^L@H09( zG*`$fa2(@L<g0Y*`iXrd8bVMxT=VITxoZ&O6#0cp(E)44GFjUaxoWCT^DNIPsUPXt zS)cQ}pgvA9gPmn7pR59kXh)=nJh@q~IB8n^KhzrDAqAw;BVbEh-qNXcf3>wqreBkM zB*-Qo3tfFZSERD345;FsB{L_$FG?HqcoNnD;33G6bMb~SK<wOiGQ_rkfm*061ARns zWb`UZQgeC~0y+iKX~>h+z&tuXkl&Nlr~wNmH^IK74xd!UxlKyKv0n{!LorD^%4`n{ z0nkD+rmvw!V_e+}sGKY<;V|$GYiAaB(4D{qicKb};@%PJ2T)XB7nVmLS&u{{jt3pG z0TX<vR^z9<y`cMz2_O@e>A)x)HIk>}YI}PDe^Ltbh#G!S2HwC95TI47h+hy8;XjL} z#uOE6^R!4>xSR$o(!x~hGM3ulNA$y~O#>p2YoZ-8*{gEwl_J+<0Q}mbZLD@G4gt-f z(7guSNPWa6aE9dY?bSA(opwvF6R-?dk`Di8v>5sbcrZ)?VCmr7X98eRV6h;v(xV1g z*eGY5UM}GWw8WFY&;sSs12h$Ig<FBLBUTNF$54Nug<f6ZNyDQVHfnD)zyvpJ$SMpu z0D;^C=tkv=Hw&DJn?ukLHMquGF!>K`0+e*NgdtK(o31G65|P!>t1MVb(u6U1VCH&U z#|p&_)HpUo+b~a!SKVqrKaWn|zKL5ElUHKAlMW-09)Cg#hz)RzU=`pwJ1Sku2z&S@ zi~|m62M6$JPM_}yuPLyK`Au+^QV;`?50=p@rb(0=6Yh(J%_q<R1SsGVq>8SUUn#wz zjpme&r+at1Wv@}x(u)(I`-+!0T@JVb9Rneb&(m!daOB-Kmjr}i!5MI!*vq=mjnt6# z&P)E*=U_A-x*mO`krYAz!=%9uJeV}#dr%Zm{Z#3GBPal<1-VQxZFdt}C*5>ilIEyd zM8E)QfO<3)m<fKO7qO(fvUcMFIC5ly?{JCGOA}o!o=&-a!=v~+kIUBWr02R=v``5? zm+U|lqksY+rJ1dy0>F#wubN7Bnx0^^k3uYh0|%eubh=Duq-fb4)ls1pBCKik2L<?i z3#4j;)}S)1H@Z-nTz-ap!%Z|I#Vxyx+iMFkWU}fnGC1#z)DlS|pK^G1oig25g|I>H zK{fh}<8v9Jq-#M9BYR-;O#(*t8Q3rCk5<7If(5nrNvf;^1XMnZvc&PM8WmIB0Ey=G zy~AH7F^b&9^-)WQD{5v+=aRd`X|Yi=LV-=BzK{NU78Q&sIJ!-rGzxwquV`GoJFb&n z9lCESDS-yS+&_7uNjz#yLkE%o@Y0MGF_%%bP&K-BP;BIb4nyX&XJc!SOH%Toj;z8& z;^Kh$qnh@*3Fir-?x^?X`p_%_XNQ?tL1RzF7Q#xpIX)!Qffo~5AB{qg8eJE9=!+wj zWjw^$Nl7AKV$N#RB?ZiBzd~|o=tT7ljUu7cfFnjO(KFI}AIuQjlsFs|RbqO=vS?_I zuMJX~7CRi2c%nL~njVS*b)%u>U*CRj!fT)RrRd;sOc35)PRlNV6O|sh0ffcvU_e!V zxfV1?bTt{2Z=+WnA`uDGd!yAeVWr;`N@QS&m;gvPXyS!=PfCFSe`!*5nj_)>FuTA~ zT8pM#QZG5nxS^^RFx$!fOkXp&0Z37E6>DNK5%-W=u7;j}JSLpz*8voArYmWRVieTT zbY6EmE0u1Ta+DP5{Y<KNUwBa@w(*!@TCBT*G0gSP6u;<>g@&6|Qz1lDCKDA2(jo19 zK18RQW$T;*P5?sD5u#nN#PpRI-JFqXzIe0HUi79|6jcw`(53=swqy-Ifh*lS2cD89 z5C~!f5=|Z*{<N~iAlG4=-zK>(k?2iXni8#oZ_1ulvivdF5=Oc+G~jZ8U{04g^_C!d z@?j#HwzTv_F|qmq$_I#KNcmBa__@;1rXVv>qSnt-D9BYO5T~99ZX^Ib@>u$eufqcC zNTSIYrMim-GCAeaYoz20oruKQL)~DmB2qaqd!_4G?H<TlpYgeIu!lP1`J|&Ek6_bg zLBlNIC`QtOOv;{?!Hb}BP5avZ4bKZ_Yx*VzU_B6ctOG}PaSHj3mO%;wE(<=qfGOj- zLW;zB4#rl*%W|a>Hs!N!PEXPj`K*Z(2L25tP)8Y6d^K<*5ZED9VLYg&?%-0n$D^Fl z^CV+|teQGe2wOM&5D6GAK@4a~KBH(~^uM?NdB-og{OlaKVEw|A4MZ-8Ei#GB?DYrh zr_UV@0kuq`Z~~<T+H2|$du<0K;$%{hQt(l8`!F|XxunSsEj}<aZ?=;(3XF2v@yBFG zbKFPCdpSg_w4CJKq-H;E%HyUUU(MQZL%MUdm+nWPFtPTho2Z%|WMhOCO>!OWLz@Am zEdQ0Qp@r!WXn%y6?MbGV@p=Je(uAF{0$`AbacMpzeTy0q3xMKwVP`dStu05EQL;u1 z>LUCEdN;JPfFnG!q&sWQ&w1;1mGt^`Y<r8M&~h-~fw2;W@VVRe?#wlPeF8cP-x|cq zMz~{N>Ox3McxySyK?()!-}*=xtxEME7O|Z8rA@nFt4GkcDt=8jX@=8a?Dlq6uDPKb zKQf%U#Y9i&&gr|y6wZ0~Dx^O;iUTr}Gf*ePzbIkM>6>e5gmj^7HOAOI9j~#Q_|k8B zU{O#_8U@|}VWvDEb{j(=-DG}IN{c?A-qFd#X!>uO<$^SQOh}fLH5iG{anlks3<ZWW zi|`WK>o#NrOsTJ@5GQFWJgpQ1a-eFrO)5qA85X+22TPxprXBU2It`3C4f-q!0SQ*J zUG>G9fIh;gV5mQNFTc;b&36wDWTCbYxlxKuIzna`*XRMSs@agWUhw_kHpSv6Tw4UK z#OdhYMjoTsx<&$kB1vO8Fx#Yco-n0*tYl5GI)eE)GI(|KXzTj|$i-O@0+=uy97Tm) zltiiMbWN&I8ktzPDouM4xRhRj-!OeH>xMLZ7SnmG3=+UsZ=AXDRko^b-fcEZm%Vu= z36%r++`6TOP&=c_9CL0lb4E<QFB=v_7Ch0SXevVmpg@$)#?or$hxXFcS9id4pRk75 zlSlc(kbuFb9nD~VI0AJW8`TT7<|tgo#}`Tphi)1uy?GOWj^Ak5Q`V=u5WeUpXz4*f zNuQ~%AdSM3PV|dT4|LYlEku1I3O$05tVfg08&{}Mcg-k&1O^MDC82h_K52^@@sF7` z+SACMOaG{Gl+TuAqsXuFYpB5yiSl)+q)0pAsWsj{O_QTzkGbjkPDw`m7mi+o8nlJ7 zcDn900D%;rN^ih2PJoPX#Ao3QB0ffl9cc!TMBhQ7*dn+&<l9mi=y@~`-AJ9;*H>h^ zG&&#|=w9!p$$84HW1frvpIA6K8$?7VHkNebV!YuD#6>(e5Qy)W;IGGFplktj#6v0% zZq6DMbv9Q3>9Yk-vHhTc2mt@&005*1`nk;vi)1QFgJo*rP4`hq>*;E*FV@M*L(q_H z(fvlOZitDG>t?;aAOpBTyBIY`3)XBbUsSfj2Nq~3n2|$BGl)*(a(?)&Dsb{sFepwe z8knh}ujS+#DP~3eeR~GJ(YI%E@8}Gi16np|)}UG2q8*@`qwiG&Jj)ApEr@n~(Slw> zKkxLp`PC*^sXOr4bLwd8{=3imiqL~$SxE3q61QvIcKZD7%OmdJ|JF%GQGk?GRGfqY zJw|&L*TFf|9n^?UNxJ2R>t6uF=gSxGo8<L<B}m^Y|ND>bpB?b8KK^y0zfSZYKM|^Z zxc>za%m*8wog5Yb00D$)LqkwWLqi~Na&Km7Y-Iodc$|HaJxIeq9K~N#r9~<V78G&F zP@OD@is+_QC_;r$E41oha{YuR4M~cNqu^R_@ME#+;Nq;SgR3A2et<YTI4QbFiT_Iq zEn+-4?#H`(kGpq(P%ksp>=*-7%`%eln3&G2ih)-M=tnPR5R{pz&q-1Op5yBtKEB>X zd6xINKSy^qXEMMi63;T-u!uK^r#3B}^FDEy73D1PIq|4L7bJe<y5jO1=Yqom&kP%> z<UDbhSS+-$(#EW4sKk@Rk*umwzL0h~%Xy2lTCTF@J^2emIc;T`>okWD$0C*>L4<-T zN+`oZlva%t6G_^SdH4q%zeFyDTqQ7aET94vvf~H;gWuhn`H68iDHH=bUu^qh7zpeF zjhbzLAKP~01n@rtS6a(osRPrWq}N(n<Ot~51}?5!nz9F6?f?T%x@^df<fkd*^T7KV zeNzVLy9K(|+}@h|IDG(8)K%gJI5-4G3Y5L>@$O)IZ~vZY_V)vQNph=3<Nuog000Sa zNLh0L01FcU01FcV0GgZ_00007bV*G`2j~J95IPo2md8;500kOJL_t(o!|j-BOjBnV z$NxR4RxTwF1kzGtD{+FsL@h3$8D0`ESppv@PDn;#vdlo79~Lr_d6ziUWT;=s5@(kX z%K{6^ilT{;lu)(;GT<nh&`ZDywuj3prC0jw!>cD|$-ZpHxaB-w&htKf-}m|TeJ<ys zAR>H7!r=oG8At|_fn*>VNCuLD{_j9ab`b<29*+Y^Pfu5=RMO9ji;LlK7=T)>PEJmK zA4EjNTr#oQZ1hG#p-^#gF|+sf_R@P14uH?+lYnMtXC-}LU|?Wifay6oIqxrrUcP)O zji;xlnX<C7($dl**|W2=|Nf8@arfiD8dqXSto%cusi`SxEWwSAj#?}hrWY0#W@Kcr zlxl5lEiW(EYPHqX)pzdPA)-V;h}`=;?t39^k_ZPQzkVB=9A+C{y?Rxr(;YZ)z-qO! zNBE_srHdCYS}c}l&z{xR)@n4GbLY;9qWC(8E?>S3;MA#80Cw!y!D3loUk~8Ql`8;F zpFT}Qfk2?Vyd1!pGiREbnwanL@$smC*85FKaP)V!FcFXYe{7m>E{?j!CMG8G^752Q z<&h&t*!;e}J|g0I9)M1#OG!zQ{ziLyI}w41%F4<BJbwK8*u&xQ<m4oP3l}bQbaVh{ zZEYo@?(S{?w{G2HjvE>p05}|u$g5e`mt{U{C$qn@cDasM&V2usxG=wG&z{oKQm@xb zM1zBa08XAfNklz8Jpgv^-aR!nl}IEmUAhF|#*G_9#07)FuC6Wsd3ky3*RKb#u&~hC z*$LqA;loa+69BUxI&_GLs;a7DvDnk6Ppwuf0F6eYP_7QXPzb+`0a$W-X3al~HkZ2M z>%M;bqeqXbtE*YyIF18w|NeadZnqmiLqmg3r&A~t27`fh762S8vAuius?}<f$pm0z zWTd{n{=|tB1qB8D{r$`<E5??VmWqlBl}fdF^Jbps6$(XGR@SPF^r%uT_Bs-gsQZVX z;^CDwm8N99;lYCk0P5=Mv|6oJtKGhRJ4;6ZV`F0gHg4R=Vlp!`17O{{bpXIzHa0dA z(e>-srHBp<4F!Wiri-E|2tr9oiBhR-Z*PC{<jK&`5P+JR8WyHMzPt9Y@Kfi#hkaGY zZTe3FlP>A~v!|zL@QpYe4wuUnkH?del30nb<wuVmWyeNDoX6t<uxZmKmSt?0nwpxe zTerS_cy_zpZnvAwW|PTOP*5;8HwU1osEEy9mH$!Ppm#qUUQQ@e>OM=|pa;MbcH6dX zMx)VaG^VAciK3XDo$dGgL!r>FUAxlK(f~xGQM1`Bbrx=7VnWIityaq%)YjH=9LHL~ zzJ2?o<|zom{QNx6^JmYV1)$gKU$@PVje*<^{s2b?L*el+nDPAi^8jw%yg58PeEat8 z($dnqckilHDi)yq`}ebHKA(?>Dl045v0>-Vod7%@4-t)zkF(^BMx#W;`Vgx|o6ROg zv#6-(;K74Z4HHo;8f$B`{Bo_s!@H!L!1VOAbmswZ?AS3DBUTM<ZEb8CtG?#uW;Rqj zfBu}67{_t3Sj_2ku3ft}Gc%L13xbfFo6GW_=Xtl=otc@rWy_XODCGD1bvj*YYAW;h z!a1YS=Wv|j?Y0{X21iClmY0_`8clwFzEY_K;P?CAUMq^C*Xzy7%3@9x@8s(T5zPsU zDalD|l2!k5t9Jtwi9}VZ)zL_7wQAJ|0VF@d%0M!Z3?u`|Kr)aF^xp;g6EY+&W5Q}f QCIA2c07*qoM6N<$g7bp=rvLx| literal 0 HcmV?d00001 diff --git a/src/main/resources/resource/RemoteSpeech.png b/src/main/resources/resource/RemoteSpeech.png new file mode 100644 index 0000000000000000000000000000000000000000..450cd2900b1e96edd4b4863494297d2d9138d06e GIT binary patch literal 3858 zcmV+t5AE=YP)<h;3K|Lk000e1NJLTq001xm001xu0ssI2*kEqZ00007bV*G`2jK_> z4<;vj>Zk?)000SaNLh0L01m?d01m?e$8V@)00004XF*Lt006O%3;baP000i5Nkl<Z zc-pO*2Ut{B7RL_^Gs6s1nM$330W=1aWz|HnAe!B^?P@IR8jXq3jYgxZvF>JLK?qpL zViGlKEHqI>Bp4zJs34tz0mOzSYDBS7EkvX2f8K+a5p<*3-0yzxJ9FPHzjw~L_nrgP z`kR)#v!`QIQ=+$SP1>1|fAV;Tw|84xlm9$vm%aO8N!L9>uXXyD6Fv!Tc(M=uCLFot zWt&F79ZF6INLfx=3+c*8TSAHqQe9c~71u4t&R9yWWqe;V-v`ffo~eTL@gyzE%m2-Q zPFl)i3_bHmY9aS>o=+*?rvz2tYpoD?rb1F5A&=-kj@wT<Ga1@m+0-(gDU<kBx%vMu zP)loTR{u8(NL)$=v-P8WOKmFKQo?6e<$0Hpr<rGZ0;Rnt1hbXN=}zn8Uk`NsyTdso zH<KGQZ{hnD3Ve<Vy$(7WzH{_UbM!di=$Ywg%oCW(_<p4vmJJGq6bMY{hW^C@AD|bL zTj`*`{Ys$f(bEf|7@p5zp;wB?J>E$l<D`pq(s!tA<F_J@V!n@sAwy*m_lex2+lN2^ zI+$`v`0#$q3j<j+Jqk$kjmRDA#f$Zz*(T9#m*{tiT@u7DJH;+>PSz(@+G+i%k7JhH zD{{+lG-fy&Fff)GVtwmmt?^8UVu5cF=`V$^e*vI}^-Tq&OC>GFV<pTOCD*Q0sza6P zl}z0%)g_8uwo7z?!;-tjE-_X;Sy|)S)dm6FTO_)Kb|97sKg-Bi6*T^NftpX8O(9{l zK!q8x>Q_=72*Q-=PnGI$r3Mv32*b2ZVqF-lEGQbIWZGTa@(kP-sSOaLPSkXPM}@A} za{*QE|86tUR<Q=N;JZJQYgXEzaP?EACX`WFs0}NG^)f9+?P3gykBO99=Sgs~lEpKP zYvmfejTUQ*#4r6a(7_Fxz9NE+b};&nP*~UKfbv9Ds3T;~Yvigm5=FR38YYo%<jFSi zWS<LUadz@Ar0Or^nq*j3tVacCxI(j9p@9%D((TgtJrn5BW9zA~dC9-F5G63*2d#!Y zO))G)xRX3gAPyIZSMo&*T|LGK#p7Q2-SS~0KJxNe_twPE#*GjC)6lS)GvARa=80v? zMe2=WZL~!HDWl6(4A4%#ddKU*9f2M`eAv*?aP8W)`>m~?jGYiic6*rZvr(q)Y^DgM zX0==uE_7N#^wG=RS55!mqtRnF#l&P47KTSfC8egGx^MyW+<W{8r)X_M{pRi4mxQe7 z+Nbv)^lobvs!(P^>zoQTfD%Z$`{QLl12s1{U%h(uhaY~pR$GhlZw2-ZA$#@<p739Z zB8(>tA^u8oSZXi$o1MebzmCtfSnjp9-fn7Yx_7Uxz8;@+;J^XQdHeS5TkWN>vGKx% z3*q76-Clh)*wYv)S4Fh%1^^|AT|$Y@*4?rK)z{a9<I0sQH8nN2>*{XYx$}m*X9UlY z?agUr6`aFfxTH(BsECNfuea_?+B<Xc;<}cWru+Brx3pmR)%H?bTYK{4Nz4q6n>TOb z39^e9FM{dNp+lywU4p%RX2?`)869K(NQGu6In1B_0frDDSQJcjucf8nL{%Sq-e)4I zt$(ahYQ9uzmN{#d&YlyUoP6T+>H5Y->tLd?va-55P_+R8<;IO0KmGL6%$YOM;lYCk zkXZ|yJ$v^2`SV#>S-vm5G}z=jRHj;_P%ng@WX}I28b4$h+l^S18#iylhnIzg{ekFn zne%Gs1NaI}xKz1L>O5Pj^mgErKrm#)h`hYK4?p~H<j9c@4i3Z=I2<Md)8ogFA;T`{ zfcEj@$LHtgPo6wktyXvG(uM7;(&+*Q^dIcvzC^CdQfg9U%20R1_N<J{S8LjUK-2i( zK}luBNH5b|qDZA?zTA1TRQ?aS@?)7Yvgd0<NB)JLQS9yQS;5cGZ`iP5-Me@1{E90h zMvO3+Oh%%y&e~~m)nTPNMX8B*(k1g$gJmktm8tH>A9wzJ`aGgFVtJ%Qvr42{FVe<{ z^k0i~;e5qcufKu0^xe94@%QIeva_>eWjm(WuG+Z;WyXvd^XJd6s;VlftQ;kl4U?<3 zC^czHHLjpTLSrS*?^{wVTDG(es3PT?|5#5N>ltOOPA<Eg+K$moN5e@wQx$pT4jh*e z9Y25dr}XT?SrDaX&z^wTxN&1hNXV8gTP|O|eDvtiqN1XVoSacA-4MBIk5U~YQzwhu zEe^hBf!}#;pDCg?B~j`tf$@Abt~ai-w;fKxa=stJ7qh?@Ay_%7bALZ{--PJ{Nw_v7 zG&?IR^<erTbJ6MzUvAyD?a`x0@L%}Q-Me>VV`Jgwr%#`TN=}_Rb@=dMi^YOxOiaw7 z?Ch~F9s?xu^-^V$*cCBJKDpoO@m874e<M*H3lwdyh~ukp^u;q;ff`YFIO+C_JWBaL zhg8`@FEbg-$@8+jM=@#5q$nmuB8fgRbWb{(S#_@3Tv}RKSXf<M9UB*S@@#c!MR{4d z1$*$|!Gi$G$;sKfcW=VJ<louxJ{PI?i`)^}f##C$u*QDV3du8vq*a%`w*uX-Z-^GC zi%47P;0I(>geJ!X-iL)oE(V2wSj-yNIhabM{+9yAHuQP#n*Zo4rlB>eK8d6apFDNn z(ah3I=PfmtHhjJ1NWz{2yZ0m?N;iLhF00IZ{POpwuGOsAw0=xr{}j>|3cO40y$kFN zHQh!v2hBKV7+h%Yl~3~8s#C38#F##9Rx0sM*qO|XKDdY&aES5p1ttWVY<s4_P(+4v zo`aeP%xLU8ts!vg-N0!-2E2XFB{*B`pH1o#k{6S+nN&sO9803ld9Ld{1GekC$B=U= zxk1gvJnw3^0k>a&=lW}t8Uv>m+8Ys4=98ki;$*9B2%Bp$zwf9iB25)shu>x?M7{|2 zO1aRO@0%m^#uKxY2>fbZezU3n`}Mu2)V=;rL+^K+`n=cN@9(v*{`IW;z;f9uP-ih> zd-5nHcMH$ww6e!}!(hC<-De6oP|v(F^0+*pi06&9@=4Zs`ATbtnEJ=o);SC2FC;oo zh|jIdm-D-9chcgX&k`Ep<IqqMqbf{M%r_N~r$yp_)qgawpely!T|685PDRU|pbzc_ z&#LSFE`(csr`!$t;AZd1&Hdg#_44pL{ioNxHrXuoKgKh#-<eCI2dq=u0^w4_IbU?{ zOpsiCkd!AJtj4m&&P>F<TkMLT7EXUwCLgE)SdgpTgFtq>*W2}drrhc^3I1`$wZGZ1 zOFnrNlA%!gQngFJD);^uz5jUHt$((}4~~qnh0N!{hy`S5?F@w2<NEdMxb~ZWdic(J zQ<f9$6=<;OxTFHR4^esvlRVfaHy|tGdBINsmrtH}7T6il0xeq8MQl=m7go)7FcjHa zg$#kGx!o(Q2^x8%4;k|mAQT)SxSye_hC9t0lXgd`x}G98C?->AOl2}2Fu{o5I(nEp zVLh|LB0_KU&l8$hf^5bS*|+e7eIz}+?#rhG;c|wOzyvac4Jie=>qoy`O3q~tNRzDz z9^jDOL1)_}TAWqbePxVq{cj*_4(nFno7kV6BVt0Nm+Ocw*4I592nFF48^nMKaZFlX z&d#+_*nMU@FD?bfL`Q^Ph|jl5wAhCDbj$z5DrWbA?N5FiO&s6(WAAQ1U#?FLs97bk zffdYmFef`{?_M|{jLm(txg86UKa7|pcFPmAWBrmH*|Di2538n$w<|gM7l7{FyN5W7 zi$3Ay#|jH~XB|4~6<9_(o4;@PvYrInfeG%FD0amqW3f56I-9SIN$eAttRclgJznc> zyYw@&4lerSn2w(~u{-z3{t44dNoltC?yv^b!=4W8F#$g-U=&u?p4D@SJ?NvEKKn%a z_lb^Qt9dRUT;QDH*aF`|G`IiQ(cNEfJL2eTA$^I1PX{Y}EAoJz;@ba;faE_#<eu*6 ziNqf6lEVB&fJyuHsUnxzL|gVGc3Af*K!}c60kK3yMa3<f0+&LdUQ%w^m7SF`b#5VP zaXpndm~2&rZHV<^+k=@no4<!Jsj41%Rhol(IMKTJ*v_jzW!sM$HHwpm4<Bx;Brc&r z?0hIcH!&kUbJDC_aw{j7a`LLM0{OCkF-mi6vkQ~GTcfKaCzlkl{u_Jee-QOBdYjKy zKh-iFffg@btkdaiWq5aICa4Ntb>ZUo2aaVY9Ld<a;9p;udaWgK66tct?Ian`lko(3 zrrE1g?49@6DgQ&FX+*t<-UuGF^FSKAZ+;mF5tc|Ka&&ZDzI^$-dGlC#>C&a4p`pmL z5)u;7!4?Y#6|hoLQYK8Axbm~L%OcnG9uyo9)IVtKo4tn*2^=`k)62`nXb2qor+-JT ziQ2sBqXi2xGBfYoxdZQfmhV0R<l*7*)U*LuoNP22ZJCmjli8>q{Smx&>(&kOoH=uj zS5=K0Hx6>Ye*JJoB9Y~~;RE2E&jqw($r71NCKL+i&Ye4O;6PTMJ$p8f$HQC$1`L1& zH5!dA5E3Acb>hT{XU?2KAIO6S4Z;&&h@7}{x7KHujy|MdN(Z@b-@cegAP~Tmm6eqo z4Nd{u#KX<a4UMeb@lzs^Ae!#n4U{hc1g&sDoD8c&PplEgj2QzTPfScqOG`t3!#0i` zI~I8lyp06{3K}t-1>(A4EUXz35%B^*QmGUpvA?3CqBwcTkRi|wYXz7LR)8s4efTp8 zq{(Djv}jRSSQzpiS65fG1P2GR4hI^po3F1g=E2M_0JM4YW;W$>c6R1u+p=N92JBb> zA!CQCpeLA;eWg??0|Ejdz!w2AVZsE+K|w*awYB^A@8`OyRH_#MLOFNt9KItxJ)M(b zpxoTtq@*M)!}h`3%*;&oZh<^&)+}gf$BrF%Mn*=m9)`CoR;*aKaAAIaJ~kRmxnRKp z%!WB%4Cwg_QdOMVSeQLLI}|js0r3mX`P=+AsHLT)si}z#GT8$Yp!e_J{}ogH7nVN6 Uw};7xPyhe`07*qoM6N<$f^@on#{d8T literal 0 HcmV?d00001 diff --git a/src/main/resources/resource/RoboClaw.png b/src/main/resources/resource/RoboClaw.png new file mode 100644 index 0000000000000000000000000000000000000000..a7dbee1a55f46d2c861804cced61298d623a4b9c GIT binary patch literal 4405 zcmV-55z6j~P)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf5XwnJK~!i%)tLuW zR@a(_olJ5kncU=R;<Y3iqsG`v)MzYNf+(PXs7UWf@4ZQvE@GpIMg$d65D`SNcd-{N z5l|6O2oXgog6)~j;b&<kajvo4m3h}<{b%p<?egyZ)k8b}pW~eqcqaR;Z>W<!y<PlU zZeUa98aAe-^6=?19zSWL^dkPK@73^52{b;eCm~@mbJS*X;OH^xAKs(*>P6YxHneR1 zFGk=&-972|a?w@h`-kwLsga7?H<+!eNP2oYPsO{t_i7}WH^n<4@N?lQYO9N7E!eYv zC(e!z;x<-D?Z^l}P8A%bNgVv+Cr_ll*Tq{V(5ebm<)tjJ*QM<05vl!1=>3Va=fvB` zER0$pZbF5dw<;*FD53h7N=fE*@lFWbzHx&P2V>k#W^?+$Myam_8C$aOo9|CuV?Ebz z{EXrxIV!8Fq<1wnGL?Gv=j`FFt57pfI972CKL=Bsj5V<}Q)A7>Ao1o=tAN1P+zcFS z><JF?m)g-nrOI!XQg!zZj~@Rym3lh_WQW$W#oXdNeAha1x#2uvu14(5OJMuH6~txv z@bKYnN$y%{8JSsIr03}9aMTqQIe+mScklm7Y3Vho|5fpZ1fE}aHMN*0=y9?69A_V# zU~#@L`*&^S+}>4e$&Mvttt+u>9VowcST+I{FNww4%8uhFkJ0e(J~NdSs1&yGOKlDJ z?mv)8{oi;40?#+Ur7(kCRXJR5DrfigOq^G0;pOj0$?axJiY~BvWgr@1x>&~=6X31I z&g^Bfkx*V$$(2iIu`sv9!pM-yJCz(dn9tQqh0+QwF1Hmet><gD;f-#frui;s8qSb= zKAU}4vsqOTjl)VE9Kto(9Un#f@&xuAzsTMCCj_TNG1*;(@Gxu4ji-^k+(XRJEX}iX z&mKHHy~OsOuy<cRN)zQcfBp;)9zNhkX{jXBinmDM0T0<(vV)ZDXpYw(A}q%l+f~|V zhpFP=q)z#^)f`EVAuiOPHR(B23yru~bP`i9J**tG2y)iJ*I_QTH8*54Lh|Y)Vcd2E z_<9K{j|dC$;n?9rG(B#juHm6f>bH0af!|-(vlrThwT0L&p3QB_I8?D6_Y@P{*O_7x zKZlWeL+RGLBhx1g=E><Cit|#~v^0VhN$Xf06DT~BBAOQJ*jsDhVy%ypi5kU)`=ojA z)z+~lb&d2K6%~f^BzcY;K0wo>22O~;_eXf?4Yau9rluxYs};?HL}0c%r|SwRuD!_0 zgQ3_a&Bbq%H5Q55O!1h&WP|VdvR6mixBojvs^fTeVF%R*GelrBW6bycFq$(SZ&zLB z>ZqbJa|$N<>bRQC;^?ka*(#8ll8lX|HSx<9iPlr}v}cnmyjEj#1GRN88uzo`6DTb{ zPx8_bS?jkXwv?ukye*0IcMsv0Zbj<ubOKU5&|5T}dCN6Xv75-Cnf>Y9zazc6ccSBG zpE7CWw=@e9DieoOdnq4x8#NXN7!wiVf{m3f8gr(J4XWbjXuz(Gv0|0S(pu-wo#o!W zYFzB?(AUspM_xAdjrU1dwpe=KhTjm#NsA}YMi)=hxs+TvFY6U2-`r|!(MNsQRJ@XT z%VyydY|i?$os`u)!f(X_)B+}<XfcjX13p1VeH_zfjHN@z_H^y^8GXL&M2Mq4htguO zG*iXVaW;;&v&mZOO0bI-8amT4x0r*6trn@VE;Kc?M(1nSZs6tYOxpT2LX8?xR#0GB zOpLgb#}pM^k!0FE6FGWic@eRG_9Fb75aa7iLtPE|nX8HLu%!86Yaw!{z8a5wJHik7 zpy4%{2Zfutv_FMi+cr{C4Qcs#d^)N<&ISrh9?+Thdv%~|@6L1+1iE$Zgzk)qtPHfI zuKbcT-?}&t+^kiI7JVLGrug_<5$bKuhIk+Lr-gI3qEM1+!L@6}SkE)V(#V(%>yoLj zyUXNJ!=-oao-{Y%VmOmX4}13HWYPHGS7Ll@gw7hUXudNwRb|xG-jRfhsxRTb!vVjA zYVb=IO+~rnCq@zx7|gknI~=)ioseKVKJN25_O|NC4ev##FFvPxmyUGp@G<*0CrI<O z;A%lS?mqfxYs;}JF_2{|Ls%H&#*X!2TochdFV2rkCvs%ckgz<S{O!5ix^<mFy?UTH zVZ78YsgQ@MrtlD<TsXd$P$yITtqs_^VKq(lH7pi4<7cVEoyrPn!0m=g46KK8G0RlA zjU7BH+t0bpOQ|@M%Hr7NnCqx8MM;6t@`LIBT`wk&8OZQ{J$UcK5BMp}o}z-yY}*(u zHsnEKN)TyTORzMV#@6)<*|8&utc_7*C-{&N=f%NIkrbWYENrzkkIGC>Ww6-%*kMD^ zQ&*MxBm&L`vk7&!B*4y?C@)75?u)p5@&J*}rb4X@*^-h-Rar4tFPxGFRaaLqPqROJ zQ?x~~yH)7delBcHpj(#?#D==kM;P!djVUCCIbx+Tk$$85@M*UWBntw$tNn?Oup(0! z?6OF2GIy;aci$SEY*g7V$dnygO+oHb4s2P(={-s8-5Ez>tQQ`3`V8;emkA?Bh`;Z! zG}M#!A`!6HR$;!42=jI(q{S^{%bMjxdDsx{YR-)-7rAliGy&GS_?T%@d84&ZX%@l7 zOm`R?BBlsewS`z;10ww$$XmMzwdwLi1-mh6<~XKmj78T}nbDJm;Ham_>71n`MA@-6 zIhsD>2eL-!eC73noW6gQgyaBn5`8(fdlhF-ZsN?zGzv~{AUMzlRmBO69Wn%kaX&Er zyCJyQT1)$o2w2Wm!qHFze_K-`TrG+6v?V1rjHhCgnMtt(+ZqsTqsO^phqxxHtpi&# zq`@tir!$%)pE2YuQDaAD68ka}m}jQN`ut3SqM}44X2;lR!-Rb;MOSq^T|WPSLH#?E zkm$p<UF$fsGl!EEJ8=v);7EBk>oY>hO$nsvTsGG(<#G1JMuPmTP@N_S3>5^%j}^8x zlmJgRX?=-+`K)PZO`C+5g)U*v=7cy~uyI)gO9R|l;A$ZZ+?K}swH(gf!~#15yp3mb ztK?^Cf)==2C=qEnLg;k>KCXsD#3zxRe~{$l4WuV6BWYn6E<QTs3jNMIev-XEm$Cmy z0hfQiNYGjjmhABtj<K1DE%Qmr31Q!!L~>I6s5rlkXkQBvv9(Z;`<~H*2O&S^d&UeM zNQA$yw6;WG?vyc%>emZ*Va$FuhWOc<2o<s-!rg`qiA$xdA~D2^a7Sapos7lii#WA! z7aNwxNRza{UUw#;Hku-<)Z<7|8T#7ltk2rb#*I06y1H@r=m{#SZ&7vcK3A$5$roAb z*3wPfs4wQ|rCgj=nV{%3p15p3O3Mqla&|L0>*kAG$^jE?B~%o~F>262#);D((EUrU zTsSAKD-m!q*5U52Rj5rEfswi*p60qNa55p(-A-)Wn+4)VTF9)55EYb&8@X$e2oY}9 zcb+Dtm!JFVjJRkr7K;-syu{{h>A2bnC*bSKxswNF5-kbcO}3XD=VbW-QOvlD`r#CM z5o*i{Qbadeg>?lnTso7>4<o-OGsTzfn*%V{S7y?v@A$57Z~FD<%FP?sWwQwa&t9lO zhj#l6jVW?C>dzFeR1bf9Qx<yL5$0yiwzWU;tobR+0zFwMZYRdwlG^HDDA>83CB7}G zaVrjN-9(I^565qov3P9)zF|HT9?caQLOndG=gh4W%&;59O|DQ-eS}&5KcW#lP2^Hq z#N>LTU_O?@B9S1v=&?C9i2R*ls7)EpfF9lG`&BpU>%}}TK)Qi8g!;4^#*`5QFq^4> zo2iy$6A_;F#09t!Xm3ummjg**fs)*&gvA8f>fvcTOZe-PQeO)yORp2_?@vKVF}rX5 z!oj?aBrLEXCexnvS5|PRx`gcWsm$<^V{Vu-Ug;KuWH{0O>;Fd1;0I1r9O2a2EOzBZ ziYTpyftDt!BDVYyenX%IPai*`>qqb5Hc#a3TGNI5)E6$+h;SDR!rbl2Too_bM7XQS z+imp;vp3;E^{<>gbdUf`QxS7ogHB^z9SISU95{ZQNFN852Abiw&YT4YT!`7@%dGhd zqDoU{!8SK+BX#(%?tjC1xgH*g4(vU>kIfr`(Qq73Sh$}w-^=g<fmT&|_Us86Yei1} zq?vI8dts_R6(`|h1MLi@AQ$Okji0p%-d2V}y<DX+n^&%2zPXl2Nz^#8Ylqa=f(Q5R zk`|x9;Ql>v(N`lN!;X-B9{6NgqaHMssGVNSvY*QLa|bX+cPNfaj0h8PAul5ab)}&k z+4nq^co}|o18ulhUBzer*^VV4zUZsSiwso*PfH{GtPJq7G-OF&s~d~<bj4E`v#*&p zzm}I#eC{HK)0Jts*ZO&}rm`Ac&1rnvvpqrEUGd)Ti1$_-7Vn*pwvRIHK5U2WQca9P z=3?QlL+TP|ROCgfa;o*C*uTOb5NN%LriYDOy?BB8yVVTu^(DqCQ?S-m$IWOCF7xz- z?ziMj#&`=-{<db)n4Gm~=qOD_Q*k13AuZp6o+CEO6~}ZlT(Yc)KORJ%F<&u!c0WSW z98nUtuqf7z1@2lHs86P}s5N7K6}*%{8_Gq%`t1F7q^(?rrtrl!nlrH0n}d&~9$v!d zuMBI|(QtPU4Ag{PPnBbTuJAg-6!a98xp}2E;2k`=hf&@?;=RotrzAt<bcdtnHVLP2 zGrar^h3lS<lc+;#s#=rA|Gkfw;iW3nhB^^}Ywy-@=k`s8eBA|&AAi6~TMZ{Ybv&(& zM4>Vd)0wJht4<XJCr~BaXX)i4;f^Pwp(rPkIdy5CHHoYEQKZ<KF?u+zFd#6+3N_p5 zEQ+wje6Ab;&L$$2|82SWD)^TK+Hn2nLOOr+KD)MTKt*mOrn3~K{K?s9Hk!)vXedmS z<Z{<<V&<eV%#k0@LRq-in?4JbSu<Fho=9r4HxnGE;=jm+)Hru$$`2$;c&QeZeI2|q zfyVkeQJpnVbGwXwU;Yz?QA4nttBU#TY0OcQXU4>_A`KCzDldof_>pW&PnX8Y4H?Nd z-MV0EVL{3DtK2-kiRkcoqF}d0d0an|7l*#$S^qI!nLry#t`ze5-`Y{IHy<^TVCgAM zMCHfvOc*f)9aRMs$Bm|>@S;$on|%94HwN_iT7>^@=oyHBw{AVlmw1vNs?F4K{n`A} za@nk}k3UVIQH1j5rbeN!_ZZl%6C?YG@^1VnMh^HEt?5&lGI|6{{O0p@m#z#Exy#UR zd(*f37tFJ<A@102VxkOC7n{%9kSv?=&G4rQw4th^lz;q3JMwcf7%3%L-O*6`kuk#t zqcmX@{d@Oh;5R+#_e~F>LS3;HX?bkC9lBFTad>;SY?e32YZG|%sG0jB(l$2K@zv-5 zpl7EJs7)Truzue#UG4`4^b)Eg>eqffd(ivKu2>l<<6=9PGlvhzW_T0Le~#BC(1yxe zWqjDK9s73VGImfOz7_gDZR`lX>G>6X#b2ME-RUWAVBpu?*}G$V>mX_Ge-Q#Ls3>dw zg8Z%UPMtsfJ5zrU1O$=(y}zQj(EktL69o6}kz}6ZFGQdP4I=yOC~~L7WwFSAKaAeu z)O&R8%x9l`#I<WBGO4$Zw??1^>r;M`euwnx&Oh6KBGl(Ao;`j3FN$}8w@RQ5T|fQ+ vCHcv+*1N?!ArRo}DSLYNcqat@eSrS~gvRSV-HDkW00000NkvXXu0mjfTO67M literal 0 HcmV?d00001 diff --git a/src/main/resources/resource/Slack.png b/src/main/resources/resource/Slack.png new file mode 100644 index 0000000000000000000000000000000000000000..3544ef16f42aec55166b46872681c659522cecac GIT binary patch literal 8951 zcmV<TA_(1yP)<h;3K|Lk000e1NJLTq001xm001xu0ssI2*kEqZ000}WdQ@0+Qek%> zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3=Eaa_BSg#W{eF#-Ex4%Ue0!I(c^;xwBQCF<(F z7%|8u&kEF%m05ty{@?!`^FRD6-bWKtsk!BB`4?MkzVl19&%btmIvelL`!Bps@%K;1 z&HIGsQ24yE^w;&B<LUE;k9WB7^KnyOXMBA<$h{x@j6iSJ@9W1wa@;@1U3@*L?dO-= zYWZ=f<Mp8Vy6_I=^&Q{CdOq~G`RPExN-OcA;QbCR*!#0SBMADAq2JHWH-qB$c8?Up zhYRiXFbwk3^gceV{n0=_8TsQk^VRzA-@X{%_t)|8#g_SHBYyeq375Vc5AWCbFX8YS zk^lLGbvX9xbY9=*ta{FVX4hlJqWKk3*VyjkF<i()Vz{?uz6#%o-^=}Kd{x`+#O2FQ zJNTNeIgeM4anUW;-EsSVPB$51^xGFcy`MhZV>J}t{KP*F&++AsFL-Q)ivOnHCIk-q z_gLJ0Z@cfCuJY#PxYRoy=D6kCFY~99|K`h_Gp~hhecOt0dBruukmvN1t8j=rFTbU0 z;E%7D`|S_$qfm`pCNozi*zI^8F_Qa?t#JJuxGwSeq>wXZy&iyxcz0qjF5m&1T}UoF z3)mFruw%tRosp9#e&7L@l3xtc#}KkN#aFL)b5C*iTAzIOIt(`9h!k=t_=cRqSurN! zr^HGO^%PP}DdkjBHL0bZLykG+oJ$tS^%6=fspL{hEv@t#YOJZ|T57GW_U2mv15?Yb zwAxzh-J8yfI?w7{-#Ie;2qTU(@+hN@Hu@xdW}Io}S!SJW_T^Vtz{JX{th(Cj+nEhg z?6}j;yX?B#?uS@A;lz_pKIPQYPJest<<(!l{)N}v%WM96O4pUQ*EniQ`FMmAoFw@S zkNN2Ec##JPXfL1H>SFZroP1^*$QF`kkV(EV%gJN7FmC75ac|#!dhW0MW>WntzxjXi zoRRDPKRjn}-LHQ8jn|gy+`JxpvQRd)f$TducV7+P*7D=`pZye9hFfD+=T@4s@U*?8 zIVaebU5lhhn<bbu7enYR&VhZbpXGptIqY869#onG@}=fxs&l431!A5lj*yS%NhDmd zK%ChTgmt|$i-qTBP37<-^H<ty<{Et%7Y=7hxs5U8-xaf~ivjktxQS3TJEWfS=!drz z*K7{hc9+WZc`cStDz?3~O#p>8m#;f>PaBKDhSVsL)6QkW9-werv1i`abKQ|ifd28! zZcneLbPNrcj2ZffYXyAXPFl5)O?_G3T1PT&%syNrRLjANEmW80(O5=21X{;Dxw)2t ztNFC*P73?%l`1y%y|0ww4s)7`W6t(@_(BTU+KGizN^-l(+IH`vds>fn5{0;u$v^Y< z6gG}01`)bpV7=~64(#kl#tk{omEyvrDKyUucGP{CwKIvlyseetmOx4koLJ?oZSt<v z%tA~s+JL{Y);uXyPoM=&%L4|OZ)c68C|Iy>-?k_&GI2H!VQGA1W|&lw6POF;412rH zePkLyQk4~hXu|nxo}MQ8oFI&A4l>q}v~Fc*?yn+V|LzlPs5sc2)c`^qWmc|dN4%7B z81j9IWBYJUvxO*U&}lJfAIOSS#=xn$M^0@bJ0Is)Ve()$Ni~Kg+HZQ1nd9_0sX4?H z;Y_|%{K!RpF0e)h9?rEhHs;oZ3HaJ(G<%)B&q)qI$d&BN`8?wwbv$X3cuv4Fr~BD@ z-m~-E*m}Ef%jv#tT2f@!JZb<NWA%7ll|0D)gzQ$hmzC${AsigUy6jMcj%8w#V-4Hn zY{o1NAc~s+0W<nh-Mq8J5WA*pT>!;yyGkA#uqMih#Hd`rKtPoxSxqG*6jpm<<_Rms z!bJC(jl?fKG<*Ocv;byam>Q9()enolKh4(z-+a!x%3f=iVS#^`X=O-4RQw&`>P)T3 zFL;CSCee=HWJ%k>_9-5&4BEM*HUi!oAc||_kOTt0xyb2?IdYn&PB3{cVhZpil?N~J z*D`7Mc0&FLL7oM%!`o-!=qDZ=SrNEdDS{^)p75iEKVJC0ZZ0mFz6|VECefRLj&=+z zG-7$e<_TZ7N79Y%)OO=(avvpiZC(ET)#m5RiI-6TLXf>CuzT+7spd}D?{rHmothbJ zbbt&Yka4mB?0x&~sS3m1XO2N><MkuGy+3~AT=RF{_f159g?>Z9jj>$DnYH>ZPXaaO z_;`-W4=*loi$FY1GG?F752jHd3Y525NGkpU0aFAa9jTCKC-$TK9Sq4%`pHg^e1b~9 z1>jVlgo0k;^gGkUn`fT*<6ANqGRVk!j86qFkf|?}9!vuvsAW&C4Xus5Y{uTrQWl#< z1=elloxV1pLZq+07ImgK9yHr&#JXoj#C<7LQcK=a;;9q_3H*mbwh-gft}w$GDu@g8 zAjaO9U|ygwIC|VCDsc7JOTW9a?ljsoROm9jE-MdYh{k-<5o*Q+o{qQcK5@L#Abve# zUZ;SJveTm8oaUJLO*WDu)o;rHq+k@851-p<BIS5{hDEL=V#1;CeH>Y(i-H?gqB!}T zps#i8Z8xPYp0NrK>yDZY64v7i*<Z8Uq2#n1>`u%jmQA7FpaoI`wt}CWrOe{{a1&}X zYMq!n0DS1V+Oq*t`miY~or)*Zxo3l7<amT+L$-hOh^YLJA>U(RZlcd9>^RP?V!DCw zT*#rrG+{%Z&$B;0@z>`Cb07EPv&37vN@!oT?TuY1MtT(VI4}HD+Pnp;%u62>;@%BI zrnODPoJEGeNE!q~10!}wr+7-Dyij9q8vQ(>_k5iBZrG4i+>D1dwfiW<3;dAi<2}?s zSUGVyz&dV(+yiVt5jBudAX4Y(UXTya$D6hgAGk|ZUiZ<m5*&sODHcDhVyH@Cm}D}M z#GwvQA)&szAZ1A0J)rfFHQ=XH6-xqZfWJ(g1WZw%ohsOzujqmi!fxFeBny=h74k<! z{%8C)-(TJ$@QwIb)M;HRczi1Pc~^4OY}s&=(~QmYxn2De*vIUjC%>E%b*F$T`zJ6b zhS2&XtKi7TAbc?MSUMAA7775#(@2(>RA)gY2#+P77(6f-juK391>xJ>ZGs3t7**Gf zs^c%HL?Z<^DX&oxKuEvlqlly%fI}DQm)b(t;b3GxOauy|kfhRDg=A1nDq8!ETC_@- zR5_H!$wWEj6)a}FA7`hR0+<~joynmP4M7(`DOB1`EU5fgKt&<!2^KX1C)fqp5>e<+ zI0?>1?SS5n_#~?1)fuK=M$9bz@r&x!sAV2BM~Q@`>lWm$kiRoz>02}-xPfvYTSOLn zM+2W=&5Pt%xO`>`Ll(Iz;z}RFKB8)?$SOJ7kaMsX;$=M%Ahdq?iuoH(P2EZehkn|E z4v8zq;vW8rIIBJ(kBsE=!ueE#j{OKmifKd#;2<&*nl&&@2_D}foi5@y(PCsU#G;?T zUvMIm@Un|2kP0`VuHF((B@Tc9f&)ExW99%SM^%LyiX@_Ze!1HG>2jz&M(8<1=-^cj zd8o;HlKai?A!Os=Swpl#=%3qFi}*+%Cx1PM)TS(#qY?%zC@qmBaQR`9D7v~1KEh8V zKOvaJ6{-o8XoL%w`%|Y!tl-)-3E*UJ_(q?5Z>XoS8bpg^plmER_v2AtpxdA69T(V! zn1QedG!L~<Y#l}nQ|>C+X+$`W8F^0BB><~}OO-8^a>?V-faxR;`wbV_>ddew(A;(M zY(rldYpi=h>_|H08ufHv)Grjzg86nUcA-N_ih%CTb#Mv*eWZm7>G`E;VZP~RAVgbo zt@+(5Y;D1ZUr`khVc1v`Q3mkTA~9kdQJbMyBsbuG;DBiIE)=daMy41^0+5m^A4&+) z3@UO!seVH_VA2hRS|+LGzB>ODCxlc`l1*?I{uW%u<NYc<tR)4Q;i05VgGpYM;V73t zUN!(V2J)dp){giN4-}jbjsaHEp?|qBDlC%+2%8>5HPro^^FryonX>MPumvEh#NLYP z<G2zWKB$<x&u|O+1X1xWtACUMW8kQxKH=unL9;wa7$}X-<~v_J2&RLGIRMUfshfbw zT4%W68QH}AC!CCOfIoR@RY%>}*5Qi;5vKu3TQ+s#sEQNC5;;_@UDcQnrz7h)u!XSK zy*=WZ`>VMwU*a;WfhCm6q~)XH1tOTUf=En1ONv`k0c6hYwhpNnRAzO5cOB87XA5R) z0W=)ye$_61FZ#+f{PJi55+reNx$PhcJN(~H2Hrb~3jD&TscSffbifQ$RM>nElX70+ zl;o6HNh5+#xljgZ6_&zw@M3@n<$(I}kVTZL86=7?Vg}+lDMpCEkT^n^2$rTFOFaZ> z64G_Y1-SubT%cSGNA+J@&ouDO%*2ii-mc>2bYA07onKGl{WFbxq`9!|WmFVf3Qg3h z+)75T;SF8KxS0$Q3AchMaFaqIqep*vO8?!fItUqc0t?^ah98IfZ$@i=94^`rcKjag zzZ$H0Kje4g{W9EIRe6vt1Z6#>!!06&L`ug{G(k)tL+_=W+{TGhZ)dk=V_pzdVhQYy zuUC%Xa3!)r#aziBQeBaxjqOrMbUmafR3!{~Vk@yg5hM$g0VjwbY-kunk}BXRpRV?T z`axA&NCZ&`&2rQ<<RJ0`LPoKHB`v6`-xUIJ^-uFoxJ|e{lPNt$)b^a!rP_d6CtQj^ z1O#rY`^UtqryNxAci$=X%rd1shi|hjh&vfVJz2hkIKv9=NC6YuYv%Csn)3n0z-hmR zIxK4XToFuupmdiHdBWi`K)#bW4~(mZctAIU=Brb@u|3x86g28q@U51G2&oNb193xd z#<sfT9pz_;Wm1tG7U&W%$xtjRVHaTP&`;^37kV*;<T3?VbRUHeMr$F0*en^!2B6@7 z2hS>n3OEcgjBIF_feO5->~~xT(9Uoj&=UqZW&l`&0&q1#O?ohwS6si2nAf54pmdEH z&5x8Kf-gB$@6TQ-CE6?6+OCp{*Au0sru%aoY9CUokjg<TAwW5G?Y0P7_X^ccg}`AE zM;3RrJGK{E?$jh`)RcjJRdYdT0pm+;&IYAma%i!3y-ztCfEntX@lWN62aT8YNFdY} z56iJqdg?Tyud@X8NANw#g4==?<j{H)*rYzQ)tXBqEual>3UZG}5c#gDPl=lvUYk-n zWW+#OQMbLLz~03h^n~i87E$l=0X3JKa8|D56Um$15UJZ}>*%`?QNRF=N@2ySu_BZ` zkC)s8O)l9kP0e76eWcP;c5Kpu3t9zIty1a@fdHvQNvB6j<n>62Q68}nM+*3Zkfo#m zMM{ta2CegmGYMk#-~l%Ep}!uMN}i}yH6b)eUcGoU0$M$!bX!V*p9X=XBiTVnkr}{( zL3hH-Qm^hy7eFPU3yGP<b(Ai|LuFMGeL!n4CRzoPh#>4?`eN_^G9JLlWcd=ik2b<7 zA(e1|u?iSK?ImrIEk)HXhXL?hTIa}c^3x<YFfM=etks38RyT6!$caTO<bWF)4@M&o zq?P6)4Yx`XHHl<K1EEF86n}L-VNirEOejN!A*&4S?HDFcMTR98)EULbu1oPBqggZ? z5@HMiB3xFU>2J>fBX?z`P2L<OBHL@h3icf->#!OvW)ivLg$5Tf94ze$IYoF7R^ah# z6aYD?jte1ls9|zl0py9ipn*oG#UEEj-X;_4Q}kX<3@0Fiy=%Qv7`W{Pe|Sa6_~~}e zBFtS&eh^(d+gwrF*Ady%G1G=&V3ZxCuUbz5+JqF0H7-Jx`2kzKsG*_=lXM&KC<q`( z2v1(!6fK*klJh1tI>nJ{tDf6Irlqsivup=24de~#n1>O;Lmh#&sOxGL0Z7Zl!i^9z z@|6QXs4MF895GI6^bgM-7MBWsAUH=t=%;6ckV_(&@oBjupZ|7TMvx4=kholi9KX$_ zAMOS}ponlQwKzH&>&Lq}sU{>oy$X`wGTQvsH!H#phP>|<zwO0S7y|_F`UUaM`Bv@& zxW9l&!9WP7O?v**?*O4UF@;Xwma6E1|J_hBxE1aTfeBP5fKNM%5N&F(#O!A<UP(k@ z0@Ha_gf%q8b_5DZY`~+WR9z7)QV|spXfLoIBcdCLdu+gyVIv!ucI1?5r#jOE(-Zp} zN&+^ckF<&efI1}O86N(eDXyZXJd4>N4-!ed%_y&x4=g@D6eze~W_T>*s^2O*(D18c z1Gts402E0^Mr?w?f=B8bYdA-zwV<@%!@Tn5c7pvca5p}!!Yvjc$wje1Q+tqRI(T+V zh+{Z_?b@})#c-b@9N{G!Kj~6XFIBZPz)J$j_82a<l;G6<-Giak8cNe*X17xX<>E@w z5_6O_5WfjP-slNrHsS$LD^3+^Qx!qesI37+Thg9{WH5^Qe{T_YqCYn6eze3msI0iF z1ZYzZkTV!vf?$x)LaK~98KPyVZv!wYpS=qsf>{wKTIOw~j-aPmMvjQo4sk2miQ$~~ z5J?cL?*dL}@hdyim=u+w5b$0rWI@Xxw^w_+c*K|5;kUUT_a<gy6w)k;KL&SmaGkn1 z2B2Lk4GDlLkXtatk=qlxCG`Zrq}NK{2Ex+(ZY{@rJ&<6?5U@bBPOrsk;AFh|6i|As zi!ht*?nj~2s2gi1a>)cW8JJ_O74qTd6{GlsEY|ip@i$Ofy(h4kq$sLtSeW8W-av+- zx}n%^q+|AES{69Vmq-F9IjQ6mVo-+l(N9Cv*3ZyClh;897_hc0m<@R+_MgZviVkrj z=txb3LB6ahbd-{x>LBL;HykH8SH-+GEI%z}D{<s+2HFz{F9Lw7y2H@E3{P%6)WX5B zDNpLY+<db|y*0vV6V?pF`(xHk#Z{hGJV8W6XKho1O2IXRyXj*=v-JJh2|V@5Kls#Z zF;ks~tW1S7CN1G~-n-R>f7XPj%G%RfMW;NCYFrSsY%1RKpr}f=n%kc(4AKE;sv^m^ zjSVJL?QkH@$L&H{bWm;WwR#GGq4aGqY8D`&2dSNVt<UPt6#ZX!O?)Px*B)qjgWYV0 z$^v9+e>NXbOB<(eXY_W`K00dOeMXcnfe?{`gq`+Qc+xXzzXY;@z^r$f-j7~vi(~bR zMWGMIRTo@ff$PN78DMVrIci~qN}~OrGux+J5Tf|bkocvD<f0LvBa*8P_j>K!K^_fi zU7awq(ZNIY(qn`!iwR2TY6X|FfdVxOq8ilB)9#tnGo;XJgAO%_;O)ypVziPL%7RJ+ zXtD@T?tO3}L85LBfNGA`I7MkWdAD=G@L*W6<+(7qwh%RRXo8Kv<Y%v5u%v|wT?_d} z{#5eNSOQ&&H8dVU1SDztheBN*ig*Q;EmU9Itk9REVh!+*u_4KI8ILR3z<d*-Pb5TC zFbL5T6)N5J9Fmb*n0pj4ZKK{cS002oaO)SsFd$A5d+1wpSExHmQ-(x4-t>AUmlo%5 zdi|1I;Fmwjt|zz1Ec2w6Qu(R=B1p)Tr>{w*esnk?V}DE(^JK~|iNXl?U3xzE@kQ<L zczd0IjpPHY!#@jneZQ6tMT!Ydt!-x|v|8Xp%PuOCTYIO_gI250S(_46NMWv8W6_HH z_Is;4X{5p-c`*7EF)-W|(om<i*l2Cf#a>ATH7TL)PftPnSmLyT^4YX4PAb%pc3`wG zy{d({GC5eZt4jO6#@Ydq+C`S8blX_BQ~A=@KOHfCv|FGd&aG(S49?%-%33(4Qnhw7 zN~&8yC@T2-V@5rD28Gnub{^Qy-wog%8IwpP(JB~kASDfK0Xk34(xDReL8-{pe{&>Z z2qrLQxUFr3ir1DI>YB9CiS5;h(*~S+L%V7FqSlu<N+IhrN{#V^sP+zNotGdcqwbLs z$CSGPVXm$n&JYx&iPpqR?Qmq6u%*00P$Ooa+P26$Ye1GD!cl?qkTR4uI3)G-0Yb_X zO5_`ibpMz<=KJh<C(rtu<T3wLX8tyL%=g*zP4Y}panXjFcc-RjWLvk+gfXxp6#}&e zt4~@cWInE5iF}vbv$}-wNQO4`HBrzVNX{!05qu|6LNh|qVkKts;aS4ZcVke&WFNAk z+^KLQm_<d=LL@T6YSU7H_Nod^!$`{BI7F2m4w0ynNpB@nv)1B~{8vhj<)(Ij!Qra5 ztii(6@X<a{1aC6l>Y=-~Io*G`UGKdHNu|&Z-=lF!8W7J(1OiQgmB$K%+_P-}1H87& zoo9nXx5Pn%#|E>p1`tXZt4Z|`$saf;p={4X<<E-%36#Jqut80p+s4syM$wXSx0I-+ zh17E%!t)E;(N^eBzN7y>LZTDpj=<vpcqCP$sc4|}NAfbYD#BA?1R9JcL?pddaM5s0 zYb%Ts3nNZ|TCIPAymsjQH|xkm>eic_)!mrCmXw0_W9*Hg1Y4teElmecKvr`*vVCh6 z*3P&f381qwJ#TP`@N4-lS{<~(!(2Fw;L9b1B=kW)Yz~+WGe9oC$bF3`ME{rD_I~;l zV`Em=zc24v_1Wd!$G1%mg5RQtB9J5bQM3v5MP9EQ11nATB%sA6RChAeBUNF$&5Lr6 zxv<Q&6;=N|(v{Jky=9bPw0CO@4mKYi#&eW1<h6Q#N`9rjpxUY{6*fev59hUb)D$6D z72d4A4Y@Y8bVz*!2jaOKKp#rW`>Cp1woOC5e4Kro_*&10@?@Xg-5^T(#(*R&F`A2N zm(tph5k!K7hxSfV7Mij{*;?_xy#%G_C9@Mj`nl-?+%}IWa18x?h$-&}uPsyq?Tu!d zD?dB4C>y|sdc%dtXfWNqB7yQO)D24CarVFRIiK1<zeZSQt#NmFCyR2X5HkLC+mwU3 zX~PFv&!J<r6U&PlK1kihWxNx{t5y6BJ<hAE9n7eY*Cs0>KXS)}(w%r)_FA=pxg1z( z&AnZdg{A(u%C+|2gOY~7k>&kv6oZC7tu^?iKd3GMK`y}<aUw0c)mHBl+TAJB{noZo z?W6X6n@uRx#kGiL{@%$03lpV1ptD+@9un!mZ1YiF1YAn~YUgwx+BS8j3siz-vUcnT z<Yv&iFiJ+dfQIyBS(S8m<cJedPzDiGQQiKXTY_&<b&lA4U$&WOVYnVA0~84S($xX% zXzQ+iY^qJW=a`Ik$h{i&6u1f^%;B`BoJ^}VW^-Fr5GAxS@h6_Gid(#<j}*B3@0}W# zaIXz|j2N2!14d+aReg(%-k}^C7)P3rBy~`b2OS5#meNqkUMsQa91ATC$N>GH0azJ% zhc}5oKftBoB)s5|+5}DA-7;<;!!EtRJFn)EmS>raTHR2mu`Qm$9hO{@vm#@z)(_&% zXpfJ&;V3eB^75k+?mj}BdMuWih#zUcM$`r}o<7l$+QsCk{$NPjlwT7;qE~4w<quVo zl*E##>Hh;SXnhLZ5|&E<00D$)LqkwWLqi~Na&Km7Y-Iodc$|HaJxIeq9K~N#r9~<V z78G&FP@OD@is+_QC_;r$E41oha{YuR4M~cNqu^R_@ME#+;Nq;SgR3A2et<YTI4QbF ziT_IqEn+-4?#H`(kGpq(P%ksp>=*-7%`%eln3&G2ih)-M=tnPR5R{pz&q-1Op5yBt zKEB>Xd6xINKSy^qXEMMi63;T-u!uK^r#3B}^FDEy73D1PIq|4L7bJe<y5jO1=Yqom z&kP%><UDbhSS+-$(#EW4sKk@Rk*umwzL0h~%Xy2lTCTF@J^2emIc;T`>okWD$0C*> zL4<-TN+`oZlva%t6G_^SdH4q%zeFyDTqQ7aET94vvf~H;gWuhn`H68iDHH=bUu^qh z7zpeFjhbzLAKP~01n@rtS6a(osRPrWq}N(n<Ot~51}?5!nz9F6?f?T%x@^df<fkd* z^T7KVeNzVLy9K(|+}@h|IDG(8)K%gJI5-4G3Y5L>@$O)IZ~vZY_V)vQNph=3<Nuog z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2j~J95eF5%wX|XY00m1)L_t(o!_Ajl zY?MV5$IqFWZ|~dNZo6B`r4$QDE0jPR5K#<9(FE{<iSfaZK*S3%^~DEbq8NiFm>^Gv z2Ly#gA0QAhhD3ZI;DwZ88=$o<g<ha7W!vrUcG=7K&5RG-r5AR$-(D!4r`?&!`F?Ze zocW)FeSLkPd$a}FKS6p=Lx%8_NvzFNEmTg~6XwxNp0>t7w<?8$S=JU-RuwGJ{|VFy zBmqu%j6aUjiIYx*eeQ6nQ9PI#2n~OA>+E&aKXKbOd&SQ3jf_w|A0j%zFODcvXaK+? zVc(!JEO6|vCisD_->1+3z=^=ka|2B}KcowYOZeWeBwXs^_-}xX{ciDfH%7e)w;l8} zhIuq+Kwg!2MAFsPaLh>0{h?%gR4MxJ59jjAQj9?S%0@*h9|Y9<IRmmWsW0$l_Q%r& zWFm6Ajl+x#y;ruWFj~XpECRApxu-O|!=_pB(E>63Tg_Cy>Wpr2M2FN#$58Mw0mWR3 zP;3x0;UP_eS0$>BI6(jfp~xVzd^18?B%p>6q0nG|AS^MUKYhv(pK>>ylP@50dx5q( zPt5=&sD*#)Z#*=7b?mR37~U<Zd#0p{AQAxhb;$6wH$55v=oYwlSUKl4q)QX#(JwoG zc|ZdI10wpxz{Q_>E&u@O4ADaYma*Fp`dEJ?y{kLhf2ARsKt_&?wEXAmC6}_G85_;P z^ep`BZArHDx3`1b<Jo3yd)z|^)0^^yeV*i!=TU<sdvz^}7maq3DxUHU)@k*Q0FoMW zwj#CE6ibH+n=9EK7cscaMbnyEVV_HC^Rzi6bCFuXWQ{$R`HS=BK4(hIw5z1fNR`y4 z+{C!nxs;fQ{M;2;NHbxoVY1#8Ve!bJNItIEw8rS1vh5@1%Jr`5)TZW9^ZrF!tjrWE z^1}kJ1XxR$1cj~`&m*$hENblP!$PJBes!d)+27;Ug7Zy|HAR(WmT^RHByhC%{3MAa zhH6{cYDcAs8q(-GW^53cWh4HkaUt4o$s0YuCn*ToxCZUk@gmjhm)F{**AIxeq?}Zi z117j);_oE>c91${j!Nr1Ua$-Zivh{eAm4vXp1G00^tu{|1Br&Ky#6?Z#-9oC+7ep- z_C#XTg6!5&;y;zHW2kYJR#7i3r35t2_a072GjWL4PQL4hi8KHpPqp!Hj)OKn$!;FW zfCd0C(#SeKLoGx^Q;YngXZl|8z{$j&#+eRry(8vM-whHtopJP_c9RbNMa1RS>HC{| z!*F>p>1uO(JapQ!O)*~sfq;(wE#=!J@5XBhsb&d;k!FIWj#N8r)^t+Vo+Xg5tRuL( zDk;s*S}>dRo8tx5*#Z?kO~}#;+`TTrV!4IBwJ9xg(&FOoPco;;d!0L0FaTihZ02r< z`1B0Kixu3un0~Mgi}KSos+GM6)J*%o12LE$jq`nV-c~@$000=HuRKdP)QR3ch(ys< zj4nsU2i4r`wc=VbdK-C2UnEmun=cjHLye-vM3z+KMv$mU<UcV(oomXd4xt#&d`T3b z=En)7+&)b_hbOzLj62@sb6${CZ?-<-@`K-@gWoExdr=L{qIw7;4XpR%>?+WR_c*Gf zv#Vmn;GNt7rEW=V=6G7A)H1?y284zAFyE4Nb=krgka<puGf9g(cOGJte3(Sn%B{3+ zc`T(0tEBQ|;%qD4Etxf!F+`uQ!yT)p&MvgFgYS)31k|W4+5^P>kZQi6&E2dw)Y9n7 zB#6dq;$pKr)`Ybs_|i(Up%x3L3NS|f^zM1$yMU3)()u!YZsd;TfO=ox{{hy@{Zc^e Rp-lh)002ovPDHLkV1gFt9c};s literal 0 HcmV?d00001 From 365f9d32327aca9a0145e0f59bc947da991539d4 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 24 Feb 2024 06:59:10 -0800 Subject: [PATCH 096/131] runtime update --- .../org/myrobotlab/framework/CmdOptions.java | 2 +- .../java/org/myrobotlab/service/Runtime.java | 21 ++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/myrobotlab/framework/CmdOptions.java b/src/main/java/org/myrobotlab/framework/CmdOptions.java index 2c357e8db6..3b3b18d7bb 100644 --- a/src/main/java/org/myrobotlab/framework/CmdOptions.java +++ b/src/main/java/org/myrobotlab/framework/CmdOptions.java @@ -39,7 +39,7 @@ static boolean contains(List<String> l, String flag) { // launcher @Option(names = { "-c", - "--config" }, fallbackValue = "default", description = "Specify a configuration set to start. The config set is a directory which has all the necessary configuration files. It loads runtime.yml first, and subsequent service configuration files will then load. \n example: --config data/config/my-config-dir") + "--config" }, fallbackValue = "default", description = "Specify a configuration set to start. The config set is a directory which has all the necessary configuration files. It loads runtime.yml first, and subsequent service configuration files will then load. \n example: --config my-config-dir to start the configuration stored in config data/config/my-config-dir") public String config = null; @Option(names = { "-h", "-?", "--help" }, description = "shows help") diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 9b76d3bf65..e203a65b81 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -433,11 +433,6 @@ private static Map<String, ServiceInterface> createServicesFromPlan(Plan plan, M RuntimeConfig currentConfig = Runtime.getInstance().config; for (String service : plansRtConfig.getRegistry()) { - // FIXME - determine if you want to return a complete merge of activated - // or just "recent" - if (Runtime.getService(service) != null) { - continue; - } ServiceConfig sc = plan.get(service); if (sc == null) { runtime.error("could not get %s from plan", service); @@ -915,12 +910,14 @@ public static Runtime getInstance() { runtime.apply(c); } - if (options.services != null) { + if (options.services != null && options.services.size() != 0) { log.info("command line override for services created"); createAndStartServices(options.services); } else { log.info("processing config.registry"); - if (startYml.enable) { + if (options.config != null) { + Runtime.startConfig(options.config); + } else if (startYml.enable) { Runtime.startConfig(startYml.config); } } @@ -1549,7 +1546,7 @@ static public void install(String serviceType, Boolean blocking) { if (blocking == null) { blocking = false; } - + if (installerThread != null) { log.error("another request to install dependencies, 1st request has not completed"); return; @@ -1576,7 +1573,7 @@ public void run() { } else { installerThread.start(); } - + installerThread = null; } } @@ -4918,16 +4915,16 @@ static public void releaseConfigPath(String configPath) { RuntimeConfig config = CodecUtils.fromYaml(releaseData, RuntimeConfig.class); List<String> registry = config.getRegistry(); Collections.reverse(Arrays.asList(registry)); - + // get starting services if any entered on the command line - // -s log Log webgui WebGui ... etc - these will be protected + // -s log Log webgui WebGui ... etc - these will be protected List<String> startingServices = new ArrayList<>(); if (options.services.size() % 2 == 0) { for (int i = 0; i < options.services.size(); i += 2) { startingServices.add(options.services.get(i)); } } - + for (String name : registry) { if (startingServices.contains(name)) { continue; From 000ed4d765f2b83fd1fc96ff99a7f692a64b3649 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 24 Feb 2024 07:46:43 -0800 Subject: [PATCH 097/131] reverting bad git merge --- .../java/org/myrobotlab/service/InMoov2.java | 374 +++++------------- 1 file changed, 101 insertions(+), 273 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 2f2b513e87..c496da1a61 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -14,6 +14,7 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.io.FilenameUtils; import org.myrobotlab.framework.Message; @@ -22,7 +23,6 @@ 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; @@ -37,7 +37,6 @@ import org.myrobotlab.service.Log.LogEntry; import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; import org.myrobotlab.service.config.InMoov2Config; -import org.myrobotlab.service.config.OpenCVConfig; import org.myrobotlab.service.data.JoystickData; import org.myrobotlab.service.data.Locale; import org.myrobotlab.service.interfaces.IKJointAngleListener; @@ -46,14 +45,66 @@ 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<InMoov2Config> implements ServiceLifeCycleListener, TextListener, TextPublisher, - JoystickListener, LocaleProvider, IKJointAngleListener { +public class InMoov2 extends Service<InMoov2Config> + implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, IKJointAngleListener { + + 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 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"); + } + } + + 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(); + + public Heartbeat(InMoov2 inmoov) { + this.count = inmoov.heartbeatCount; + } + } public final static Logger log = LoggerFactory.getLogger(InMoov2.class); @@ -63,7 +114,7 @@ public class InMoov2 extends Service<InMoov2Config> implements ServiceLifeCycleL * 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 */ @@ -121,77 +172,6 @@ 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); } @@ -266,8 +246,7 @@ public static void main(String[] args) { 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"); + 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 @@ -281,8 +260,7 @@ 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"); + 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); @@ -657,9 +635,9 @@ public void displayFullScreen(String src) { error("could not display picture %s", src); } } - + public void enableRandomHead() { - Random random = (Random) getPeer("random"); + Random random = (Random)getPeer("random"); if (random != null) { random.disableAll(); random.enable(String.format("%s.setHeadSpeed", getName())); @@ -667,12 +645,12 @@ public void enableRandomHead() { random.enable(); } } - + public void disableRandom() { - Random random = (Random) getPeer("random"); + Random random = (Random)getPeer("random"); if (random != null) { random.disable(); - } + } } public void enable() { @@ -705,7 +683,7 @@ public boolean exec(String 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) { @@ -740,7 +718,7 @@ public void execScript() { * a filesystem file :P * * @param someScriptName - * execute a resource script + * execute a resource script * @return success or failure */ public void execScript(String someScriptName) { @@ -818,18 +796,11 @@ public InMoov2Head getHead() { */ public Long getLastActivityTime() { 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 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; @@ -989,7 +960,7 @@ 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) { @@ -1088,8 +1059,7 @@ 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) { + 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); } @@ -1141,10 +1111,8 @@ 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) { @@ -1155,10 +1123,8 @@ 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) { @@ -1187,7 +1153,7 @@ public PredicateEvent onChangePredicate(PredicateEvent event) { * comes in from runtime which owns the config list * * @param configList - * list of configs + * list of configs */ public void onConfigList(List<String> configList) { this.configList = configList; @@ -1210,7 +1176,7 @@ public void onEndSpeaking(String utterance) { * including lower level logs that do not propegate as statuses * * @param log - * - flushed log from Log service + * - flushed log from Log service */ public void onErrors(List<LogEntry> log) { errors.addAll(log); @@ -1255,23 +1221,6 @@ public void onJoystickInput(JoystickData input) throws Exception { invoke("publishEvent", "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<LogEntry> log) { - // scan for warn or errors - for (LogEntry entry : log) { - if ("ERROR".equals(entry.level) && errors.size() < 100) { - errors.add(entry); - // invoke("publishError", entry); - } - } - } - public String onNewState(String state) { log.error("onNewState {}", state); @@ -1305,8 +1254,7 @@ public void onPeak(double volume) { public void onPirOff() { log.info("onPirOff"); - setPredicate("pir", true); - setPredicate("pir.off", System.currentTimeMillis()); + setPredicate(String.format("%s.pir_off", getName()), System.currentTimeMillis()); processMessage("onPirOff"); } @@ -1316,8 +1264,7 @@ public void onPirOff() { */ public void onPirOn() { log.info("onPirOn"); - setPredicate("pir", false); - setPredicate("pir.on", System.currentTimeMillis()); + setPredicate(String.format("%s.pir_on", getName()), System.currentTimeMillis()); processMessage("onPirOn"); } @@ -1468,7 +1415,7 @@ public void processMessage(String method) { * @param method * @param data */ - public void processMessage(String method, Object... data) { + public void processMessage(String method, Object ... data) { // User processing should not occur until after boot has completed if (!state.equals("boot")) { // FIXME - this needs to be in config @@ -1578,8 +1525,7 @@ public Heartbeat publishHeartbeat() { } // interval event firing - if (config.stateRandomInterval != null - && System.currentTimeMillis() > stateLastRandomTime + (config.stateRandomInterval * 1000)) { + if (config.stateRandomInterval != null && System.currentTimeMillis() > stateLastRandomTime + (config.stateRandomInterval * 1000)) { // fsm.fire("random"); stateLastRandomTime = System.currentTimeMillis(); } @@ -1631,8 +1577,7 @@ public Message publishMessage(Message msg) { return msg; } - public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double rotate, Double shoulder, - Double omoplate) { + public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double rotate, Double shoulder, Double omoplate) { HashMap<String, Double> map = new HashMap<>(); map.put("bicep", bicep); map.put("rotate", rotate); @@ -1646,8 +1591,7 @@ public HashMap<String, Double> publishMoveArm(String which, Double bicep, Double return map; } - public HashMap<String, Object> publishMoveHand(String which, Double thumb, Double index, Double majeure, - Double ringFinger, Double pinky, Double wrist) { + public HashMap<String, Object> publishMoveHand(String which, Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { HashMap<String, Object> map = new HashMap<>(); map.put("which", which); map.put("thumb", thumb); @@ -1664,8 +1608,7 @@ public HashMap<String, Object> publishMoveHand(String which, Double thumb, Doubl return map; } - public HashMap<String, Double> publishMoveHead(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, - Double rollNeck) { + public HashMap<String, Double> publishMoveHead(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, Double rollNeck) { HashMap<String, Double> map = new HashMap<>(); map.put("neck", neck); map.put("rothead", rothead); @@ -1685,8 +1628,7 @@ public HashMap<String, Double> publishMoveLeftArm(Double bicep, Double rotate, D return map; } - public HashMap<String, Double> publishMoveLeftHand(Double thumb, Double index, Double majeure, Double ringFinger, - Double pinky, Double wrist) { + public HashMap<String, Double> publishMoveLeftHand(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { HashMap<String, Double> map = new HashMap<>(); map.put("thumb", thumb); map.put("index", index); @@ -1706,8 +1648,7 @@ public HashMap<String, Double> publishMoveRightArm(Double bicep, Double rotate, return map; } - public HashMap<String, Double> publishMoveRightHand(Double thumb, Double index, Double majeure, Double ringFinger, - Double pinky, Double wrist) { + public HashMap<String, Double> publishMoveRightHand(Double thumb, Double index, Double majeure, Double ringFinger, Double pinky, Double wrist) { HashMap<String, Double> map = new HashMap<>(); map.put("thumb", thumb); map.put("index", index); @@ -1793,7 +1734,7 @@ public StateChange publishStateChange(StateChange stateChange) { lastState = state; state = stateChange.state; - + setPredicate(String.format("%s.end", lastState), System.currentTimeMillis()); setPredicate(String.format("%s.start", state), System.currentTimeMillis()); @@ -1889,8 +1830,7 @@ public void setAutoDisable(Boolean param) { } @Override - public void setConfigValue(String fieldname, Object value) - throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + public void setConfigValue(String fieldname, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { super.setConfigValue(fieldname, value); setPredicate(fieldname, value); } @@ -1899,8 +1839,7 @@ 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); @@ -1910,14 +1849,12 @@ 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); } @@ -1933,8 +1870,7 @@ 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); } @@ -1958,8 +1894,7 @@ 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); } @@ -1971,15 +1906,12 @@ 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 @@ -2034,15 +1966,12 @@ 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 boolean setSpeechType(String speechType) { @@ -2142,107 +2071,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"); } From 81c13b0bffe47de2492afd971021c905a7d10317 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 24 Feb 2024 11:06:23 -0800 Subject: [PATCH 098/131] sliding window logs for py4j and python --- .../java/org/myrobotlab/service/Py4j.java | 103 +++++++++++------- .../java/org/myrobotlab/service/Python.java | 23 +++- src/main/resources/resource/Py4j/Py4j.py | 70 ++++++------ .../resource/WebGui/app/service/js/Py4jGui.js | 85 ++++++++------- .../WebGui/app/service/js/PythonGui.js | 43 ++++---- .../WebGui/app/service/views/Py4jGui.html | 13 ++- .../WebGui/app/service/views/PythonGui.html | 16 ++- 7 files changed, 209 insertions(+), 144 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Py4j.java b/src/main/java/org/myrobotlab/service/Py4j.java index 9e768854b7..b056ceb162 100644 --- a/src/main/java/org/myrobotlab/service/Py4j.java +++ b/src/main/java/org/myrobotlab/service/Py4j.java @@ -15,6 +15,7 @@ import org.myrobotlab.framework.Message; import org.myrobotlab.framework.Platform; import org.myrobotlab.framework.Service; +import org.myrobotlab.generics.SlidingWindowList; import org.myrobotlab.io.FileIO; import org.myrobotlab.io.StreamGobbler; import org.myrobotlab.logging.Level; @@ -35,8 +36,9 @@ /** * * - * A bridge between a native proces of Python running and MRL. - * Should support any version of Python. + * A bridge between a native proces of Python running and MRL. Should support + * any version of Python. + * * <pre> * requirements: * @@ -137,6 +139,11 @@ public void run() { */ private transient Executor handler = null; + /** + * a sliding window of logs + */ + protected List<String> logs = new SlidingWindowList<>(300); + /** * Opened scripts are scripts opened in memory, from there they can be * executed or saved to the file system, or updatd in memory which the js @@ -170,7 +177,7 @@ public Py4j(String n, String id) { * - code block */ public void addScript(String scriptName, String code) { - Py4jConfig c = (Py4jConfig)config; + Py4jConfig c = (Py4jConfig) config; File script = new File(c.scriptRootDir + fs + scriptName); if (script.exists()) { @@ -181,10 +188,11 @@ public void addScript(String scriptName, String code) { openedScripts.put(scriptName, new Script(scriptName, code)); broadcastState(); } - + /** * If autostartPython is true, Py4j will start a process on starting and * connect the stdout/stdin streams to be redirected to the UI + * * @param b * @return */ @@ -192,14 +200,15 @@ public boolean autostartPython(boolean b) { config.autostartPython = b; if (config.autostartPython && pythonProcess == null) { startPythonProcess(); - } + } return b; } /** * removes script from memory of openScripts * - * @param scriptName The name of the script to close. + * @param scriptName + * The name of the script to close. */ public void closeScript(String scriptName) { openedScripts.remove(scriptName); @@ -234,7 +243,8 @@ public void connectionStopped(Py4JServerConnection gatewayConnection) { /** * One of 3 methods supported on the MessageHandler() callbacks * - * @param code The Python code to execute in the interpreter. + * @param code + * The Python code to execute in the interpreter. */ @Override public boolean exec(String code) { @@ -256,7 +266,6 @@ private String getClientKey(Py4JServerConnection gatewayConnection) { return String.format("%s:%d", gatewayConnection.getSocket().getInetAddress(), gatewayConnection.getSocket().getPort()); } - /** * get listing of filesystem files location will be data/Py4j/{serviceName} * @@ -266,7 +275,7 @@ private String getClientKey(Py4JServerConnection gatewayConnection) { public List<String> getScriptList() throws IOException { List<String> sorted = new ArrayList<>(); System.out.println(CodecUtils.toJson(config)); - Py4jConfig c = (Py4jConfig)config; + Py4jConfig c = (Py4jConfig) config; List<File> files = FileIO.getFileList(c.scriptRootDir, true); for (File file : files) { if (file.toString().endsWith(".py")) { @@ -278,13 +287,17 @@ public List<String> getScriptList() throws IOException { } /** - * Sink for standard output from Py4j-related subprocesses. - * This method immediately publishes the output on {@link #publishStdOut(String)}. + * Sink for standard output from Py4j-related subprocesses. This method + * immediately publishes the output on {@link #publishStdOut(String)}. * - * @param msg The output from a py4j related subprocess. + * @param msg + * The output from a py4j related subprocess. */ public void handleStdOut(String msg) { - invoke("publishStdOut", msg); + if (!"\n".equals(msg)) { + logs.add(msg); + invoke("publishStdOut", msg); + } } /** @@ -296,8 +309,7 @@ public void onPython(String code) { log.info("onPython {}", code); exec(code); } - - + public String onPythonMessage(Message msg) { // create wrapper to tunnel incoming message - include original sender? Message tunnelMsg = Message.createMessage(msg.sender, getName(), "onPythonMessage", msg); @@ -335,7 +347,7 @@ public void openExampleScript(String serviceType) throws IOException { * @throws IOException */ public void openScript(String scriptName) throws IOException { - Py4jConfig c = (Py4jConfig)config; + Py4jConfig c = (Py4jConfig) config; File script = new File(c.scriptRootDir + fs + scriptName); if (!script.exists()) { @@ -362,12 +374,15 @@ public boolean preProcessHook(Message msg) { // back to the Python process, but: // 1. its useless for users - no way to access the content ? // 2. you can't do anything with it - // So, I've chosen to json encode it here, and the Py4j.py MessageHandler will + // So, I've chosen to json encode it here, and the Py4j.py + // MessageHandler will // decode it into a Python dictionary \o/ - // we do single encoding including the parameter array - there is no header needed - // with method and other details, as the invoke here is invoking directly in the + // we do single encoding including the parameter array - there is no + // header needed + // with method and other details, as the invoke here is invoking + // directly in the // Py4j.py script - + String json = CodecUtils.toJson(msg); // handler.invoke(msg.method, json); log.info(String.format("handler %s", json)); @@ -396,7 +411,7 @@ public String publishStdOut(String data) { public void saveScript(String scriptName, String code) throws IOException { if (scriptName != null && !scriptName.toLowerCase().endsWith(".py")) { scriptName = scriptName + ".py"; - } + } FileIO.toFile(config.scriptRootDir + fs + scriptName, code); info("saved file %s", scriptName); } @@ -439,9 +454,10 @@ public void start() { info("server started listening on %s:%d", gateway.getAddress(), gateway.getListeningPort()); handler = (Executor) gateway.getPythonServerEntryPoint(new Class[] { Executor.class }); -// sleep(100); -// String[] services = Runtime.getServiceNames(); -// sendRemote(Message.createMessage(getName(), "runtime", "onServiceNames", services)); + // sleep(100); + // String[] services = Runtime.getServiceNames(); + // sendRemote(Message.createMessage(getName(), "runtime", + // "onServiceNames", services)); } else { log.info("Py4j gateway server already started"); @@ -462,7 +478,7 @@ public void startPythonProcess() { String pythonScript = new File(getResourceDir() + fs + "Py4j.py").getAbsolutePath(); // Script requires full name as first command line argument - String[] pythonArgs = {getFullName()}; + String[] pythonArgs = { getFullName() }; // Build the command to start the Python process ProcessBuilder processBuilder; @@ -475,8 +491,10 @@ public void startPythonProcess() { String python = Loader.load(org.bytedeco.cpython.python.class); String venvLib = new File(python).getParent() + fs + "lib" + fs + "venv" + fs + "scripts" + fs + "nt"; if (Platform.getLocalInstance().isWindows()) { - // Super hacky workaround, venv works differently on Windows and requires these two - // files, but they are not distributed in bare-bones Python or in any pip packages. + // Super hacky workaround, venv works differently on Windows and + // requires these two + // files, but they are not distributed in bare-bones Python or in + // any pip packages. // So we copy them where it expects, and it seems to work now FileIO.copy(getResourceDir() + fs + "python.exe", venvLib + fs + "python.exe"); FileIO.copy(getResourceDir() + fs + "pythonw.exe", venvLib + fs + "pythonw.exe"); @@ -515,13 +533,15 @@ public void startPythonProcess() { } /** - * Install a list of packages into the environment Py4j is running in. - * Py4j does not need to be running/connected to call this method as it - * spawns a new subprocess to invoke Pip. Output from pip is echoed - * via {@link #handleStdOut(String)}. + * Install a list of packages into the environment Py4j is running in. Py4j + * does not need to be running/connected to call this method as it spawns a + * new subprocess to invoke Pip. Output from pip is echoed via + * {@link #handleStdOut(String)}. * - * @param packages The list of packages to install. Must be findable by Pip - * @throws IOException If an I/O error occurs running Pip. + * @param packages + * The list of packages to install. Must be findable by Pip + * @throws IOException + * If an I/O error occurs running Pip. */ public void installPipPackages(List<String> packages) throws IOException { List<String> commandArgs = new ArrayList<>(List.of("-m", "pip", "install")); @@ -530,8 +550,7 @@ public void installPipPackages(List<String> packages) throws IOException { pipProcess.command().addAll(commandArgs); Process proc = pipProcess.redirectErrorStream(true).start(); new Thread(() -> { - BufferedReader stdOutput = new BufferedReader(new - InputStreamReader(proc.getInputStream())); + BufferedReader stdOutput = new BufferedReader(new InputStreamReader(proc.getInputStream())); String s; try { while ((s = stdOutput.readLine()) != null) { @@ -614,7 +633,7 @@ public void updateScript(String scriptName, String code) { error("cannot find script %s to update", scriptName); } } - + public static void main(String[] args) { try { @@ -624,7 +643,8 @@ public static void main(String[] args) { webgui.autoStartBrowser(false); webgui.startService(); // Runtime.start("servo", "Servo"); - Py4j py4j = (Py4j) Runtime.start("py4j", "Py4j"); + Runtime.start("py4j", "Py4j"); + // Runtime.start("python", "Python"); } catch (Exception e) { log.error("main threw", e); @@ -634,7 +654,7 @@ public static void main(String[] args) { @Override public void connect(String uri) throws Exception { // host:port of python process running py4j ??? - + } /** @@ -662,6 +682,9 @@ public List<String> getClientIds() { public Map<String, Connection> getClients() { return Runtime.getInstance().getConnections(getName()); } - -} + public void clear() { + logs = new SlidingWindowList<>(300); + } + +} diff --git a/src/main/java/org/myrobotlab/service/Python.java b/src/main/java/org/myrobotlab/service/Python.java index c3d18162ed..74c7c49fd1 100644 --- a/src/main/java/org/myrobotlab/service/Python.java +++ b/src/main/java/org/myrobotlab/service/Python.java @@ -18,11 +18,13 @@ import org.myrobotlab.framework.interfaces.MessageListener; import org.myrobotlab.framework.interfaces.ServiceInterface; import org.myrobotlab.framework.repo.ServiceData; +import org.myrobotlab.generics.SlidingWindowList; import org.myrobotlab.io.FileIO; import org.myrobotlab.io.FindFile; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.service.Log.LogEntry; import org.myrobotlab.service.config.PythonConfig; import org.myrobotlab.service.data.Script; import org.myrobotlab.service.interfaces.Processor; @@ -184,6 +186,12 @@ public void run() { private final transient HashMap<String, PyObject> objectCache = new HashMap<String, PyObject>(); private static final long serialVersionUID = 1L; + + /** + * a sliding window of logs + */ + protected List<String> logs = new SlidingWindowList<>(300); + protected int newScriptCnt = 0; @@ -339,6 +347,7 @@ public void closeScript(String file) { * the code to append * @return the resulting concatenation */ + @Deprecated /* wtf is this for? */ public String appendScript(String code) { invoke("publishAppend", code); return code; @@ -719,12 +728,14 @@ public String publishAppend(String code) { return code; } - public String publishStdOut(String data) { - return data; + public String publishStdOut(String msg) { + logs.add(msg); + return msg; } - public String publishStdError(String data) { - return data; + public String publishStdError(String msg) { + logs.add(msg); + return msg; } public void setLocalScriptDir(String path) { @@ -1068,4 +1079,8 @@ public PythonConfig getConfig() { return config; } + public void clear() { + logs = new SlidingWindowList<>(300); + } + } diff --git a/src/main/resources/resource/Py4j/Py4j.py b/src/main/resources/resource/Py4j/Py4j.py index 3c75cd94c5..ecd6271655 100644 --- a/src/main/resources/resource/Py4j/Py4j.py +++ b/src/main/resources/resource/Py4j/Py4j.py @@ -1,10 +1,10 @@ ################################ # Py4j.py # more info here: https://www.py4j.org/ -# Py4J enables Python programs running in a Python interpreter to dynamically access -# Java objects in a Java Virtual Machine. -# Methods are called as if the Java objects resided in the Python interpreter and -# Java collections can be accessed through standard Python collection methods. +# Py4J enables Python programs running in a Python interpreter to dynamically access +# Java objects in a Java Virtual Machine. +# Methods are called as if the Java objects resided in the Python interpreter and +# Java collections can be accessed through standard Python collection methods. # Py4J also enables Java programs to call back Python objects. Py4J is distributed under the BSD license # Python 2.7 -to- 3.x is supported # In your python 3.x project @@ -12,11 +12,12 @@ # you have full access to mrl instance that's running # the gateway -import sys import json +import sys from abc import ABC, abstractmethod -from py4j.java_collections import JavaObject, JavaClass -from py4j.java_gateway import JavaGateway, CallbackServerParameters, GatewayParameters + +from py4j.java_collections import JavaClass, JavaObject +from py4j.java_gateway import CallbackServerParameters, GatewayParameters, JavaGateway class Service(ABC): @@ -30,11 +31,11 @@ def __getattr__(self, attr): def __str__(self): # Delegate string representation to the underlying Java object return str(self.java_object) - + def subscribe(self, event): print("subscribe") self.java_object.subscribe(event) - + @abstractmethod def getType(self): pass @@ -42,8 +43,8 @@ def getType(self): class NeoPixel(Service): def __init__(self, name): - super().__init__(name) - + super().__init__(name) + def getType(self): return "NeoPixel" @@ -54,7 +55,7 @@ def onFlash(self): class InMoov2(Service): def __init__(self, name): super().__init__(name) - self.subscribe('onStateChange') + self.subscribe("onStateChange") def getType(self): return "InMoov2" @@ -62,9 +63,9 @@ def getType(self): def onOnStateChange(self, state): print("onOnStateChange") print(state) - print(state.get('last')) - print(state.get('current')) - print(state.get('event')) + print(state.get("last")) + print(state.get("current")) + print(state.get("event")) # TODO dynamically add classes that you don't bother to check in @@ -77,7 +78,8 @@ def onOnStateChange(self, state): # FIXME - REMOVE THIS - DO NOT SET ANY GLOBALS !!!! runtime = None -# TODO - rename to mrl_lib ? + +# TODO - rename to mrl_lib ? # e.g. # mrl = mrl_lib.connect("localhost", 1099) # i01 = InMoov("i01", mrl) @@ -102,9 +104,11 @@ def __init__(self): self.stderr = sys.stderr sys.stdout = self sys.stderr = self - self.gateway = JavaGateway(callback_server_parameters=CallbackServerParameters(), - python_server_entry_point=self, - gateway_parameters=GatewayParameters(auto_convert=True)) + self.gateway = JavaGateway( + callback_server_parameters=CallbackServerParameters(), + python_server_entry_point=self, + gateway_parameters=GatewayParameters(auto_convert=True), + ) self.runtime = self.gateway.jvm.org.myrobotlab.service.Runtime.getInstance() # FIXME - REMOVE THIS - DO NOT SET ANY GLOBALS !!!! runtime = self.runtime @@ -116,33 +120,33 @@ def construct_runtime(self): Constructs a new Runtime instance and returns it. """ jvm_runtime = self.gateway.jvm.org.myrobotlab.service.Runtime.getInstance() - + # Define class attributes and methods as dictionaries class_attributes = { - 'x': 0, - 'y': 0, - 'move': lambda self, dx, dy: setattr(self, 'x', self.x + dx) or setattr(self, 'y', self.y + dy), - 'get_position': lambda self: (self.x, self.y), + "x": 0, + "y": 0, + "move": lambda self, dx, dy: setattr(self, "x", self.x + dx) + or setattr(self, "y", self.y + dy), + "get_position": lambda self: (self.x, self.y), } # Create the class dynamically using the type() function - MyDynamicClass = type('MyDynamicClass', (object,), class_attributes) + MyDynamicClass = type("MyDynamicClass", (object,), class_attributes) # Create an instance of the dynamically created class obj = MyDynamicClass() - return self.runtime # Define the callback function def handle_connection_break(self): # Add your custom logic here to handle the connection break - print("Connection with Java gateway was lost or terminated.") + print("Connection with Java gateway was lost or terminated.") print("goodbye.") sys.exit(1) - def write(self,string): - if (self.py4j): + def write(self, string): + if self.py4j: self.py4j.handleStdOut(string) def flush(self): @@ -167,7 +171,7 @@ def setName(self, name): print("reference to runtime") # TODO print env vars PYTHONPATH etc return name - + def getRuntime(self): return self.runtime @@ -261,11 +265,13 @@ def convert_array(self, array): return result class Java: - implements = ['org.myrobotlab.framework.interfaces.Invoker'] + implements = ["org.myrobotlab.framework.interfaces.Invoker"] handler = MessageHandler() if len(sys.argv) > 1: handler.setName(sys.argv[1]) else: - raise RuntimeError("This script requires the full name of the Py4j service as its first command-line argument") + raise RuntimeError( + "This script requires the full name of the Py4j service as its first command-line argument" + ) diff --git a/src/main/resources/resource/WebGui/app/service/js/Py4jGui.js b/src/main/resources/resource/WebGui/app/service/js/Py4jGui.js index 9e646e47a3..5b5ff5d0e4 100644 --- a/src/main/resources/resource/WebGui/app/service/js/Py4jGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/Py4jGui.js @@ -10,7 +10,6 @@ angular.module('mrlapp.service.Py4jGui', []).controller('Py4jGuiCtrl', ['$scope' // filesystem list of scripts $scope.scriptList = [] - $scope.logs = [] // this UI's currently active script $scope.activeKey = null @@ -33,11 +32,11 @@ angular.module('mrlapp.service.Py4jGui', []).controller('Py4jGuiCtrl', ['$scope' $scope.$apply() break case 'onStdOut': - if (data !== "\n"){ - $scope.logs.unshift(data) - if ($scope.logs.length > 100) { - $scope.logs.pop() - } + if (data !== "\n") { + $scope.service.logs.unshift(data) + if ($scope.service.logs.length > 300) { + $scope.service.logs.pop() + } $scope.$apply() } break @@ -54,7 +53,7 @@ angular.module('mrlapp.service.Py4jGui', []).controller('Py4jGuiCtrl', ['$scope' break case 'onStatus': if (data.level == 'error') { - $scope.logs.unshift(data.detail) + $scope.service.logs.unshift(data.detail) } console.info("onStatus ", data) $scope.$apply() @@ -105,11 +104,11 @@ angular.module('mrlapp.service.Py4jGui', []).controller('Py4jGuiCtrl', ['$scope' templateUrl: 'addScript.html', controller: function($scope, $uibModalInstance) { $scope.ok = function() { - if (!$scope.filename){ + if (!$scope.filename) { console.error('filename cannot be null') return } - + msg.send('addScript', $scope.filename, '# new awesome robot script\n') $uibModalInstance.close($scope.filename) } @@ -138,47 +137,46 @@ angular.module('mrlapp.service.Py4jGui', []).controller('Py4jGuiCtrl', ['$scope' } $scope.installPackage = function() { - var modalInstance = $uibModal.open({ - templateUrl: 'installPackage.html', - controller: function($scope, $uibModalInstance) { - $scope.ok = function() { - if (!$scope.packageName){ - console.error('filename cannot be null') - return - } - - msg.send('installPipPackages', [$scope.packageName]) - $uibModalInstance.close($scope.packageName) + var modalInstance = $uibModal.open({ + templateUrl: 'installPackage.html', + controller: function($scope, $uibModalInstance) { + $scope.ok = function() { + if (!$scope.packageName) { + console.error('filename cannot be null') + return } - $scope.cancel = function() { - $uibModalInstance.dismiss('cancel') - } + msg.send('installPipPackages', [$scope.packageName]) + $uibModalInstance.close($scope.packageName) + } - $scope.checkEnterKey = function(event) { - if (event.keyCode === 13) { - $scope.ok() - } + $scope.cancel = function() { + $uibModalInstance.dismiss('cancel') + } + + $scope.checkEnterKey = function(event) { + if (event.keyCode === 13) { + $scope.ok() } + } - }, - size: 'sm' - }) - - modalInstance.result.then(function(filename) { - // Do something with the filename - console.log("Filename: ", filename) - }, function() { - // Modal dismissed - console.log("Modal dismissed") - }) - } + }, + size: 'sm' + }) + modalInstance.result.then(function(filename) { + // Do something with the filename + console.log("Filename: ", filename) + }, function() { + // Modal dismissed + console.log("Modal dismissed") + }) + } $scope.openScript = function() { - + msg.send('getScriptList') - + var modalInstance = $uibModal.open({ templateUrl: 'openScript.html', scope: $scope, @@ -210,7 +208,10 @@ angular.module('mrlapp.service.Py4jGui', []).controller('Py4jGuiCtrl', ['$scope' console.log("Modal dismissed") }) } - + + $scope.clear = function() { + msg.send('clear') + } msg.subscribe('publishStdOut') msg.subscribe('publishAppend') diff --git a/src/main/resources/resource/WebGui/app/service/js/PythonGui.js b/src/main/resources/resource/WebGui/app/service/js/PythonGui.js index c893ce5389..a6184f56a3 100644 --- a/src/main/resources/resource/WebGui/app/service/js/PythonGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/PythonGui.js @@ -14,7 +14,6 @@ angular.module("mrlapp.service.PythonGui", []).controller("PythonGuiCtrl", [ // filesystem list of scripts $scope.scriptList = [] - $scope.log = "" // this UI's currently active script $scope.activeKey = null @@ -36,25 +35,26 @@ angular.module("mrlapp.service.PythonGui", []).controller("PythonGuiCtrl", [ _self.updateState(data) $scope.$apply() break - case "onStdOut": - $scope.log = data + $scope.log - $scope.$apply() - break - case "onAppend": - $scope.log = data + $scope.log - $scope.$apply() - break + case 'onStdOut': + if (data !== "\n") { + $scope.service.logs.unshift(data) + if ($scope.service.logs.length > 300) { + $scope.service.logs.pop() + } + $scope.$apply() + } + break case "onScriptList": $scope.scriptList = data $scope.$apply() break case "onStatus": - if (data.level == "error") { - $scope.log = data.detail + "\n" + $scope.log - } - console.info("onStatus ", data) - $scope.$apply() - break + if (data.level == 'error') { + $scope.service.logs.unshift(data.detail) + } + console.info("onStatus ", data) + $scope.$apply() + break default: console.error("ERROR - unhandled method " + msg.method) break @@ -133,11 +133,11 @@ angular.module("mrlapp.service.PythonGui", []).controller("PythonGuiCtrl", [ modalInstance.result.then( function (filename) { // Do something with the filename - console.log("Filename: ", filename) + console.info("Filename: ", filename) }, function () { // Modal dismissed - console.log("Modal dismissed") + console.info("Modal dismissed") } ) } @@ -170,17 +170,20 @@ angular.module("mrlapp.service.PythonGui", []).controller("PythonGuiCtrl", [ modalInstance.result.then( function (filename) { // Do something with the filename - console.log("Filename: ", filename) + console.info("Filename: ", filename) }, function () { // Modal dismissed - console.log("Modal dismissed") + console.info("Modal dismissed") } ) } + $scope.clear = function() { + msg.send('clear') + } + msg.subscribe("publishStdOut") - msg.subscribe("publishAppend") msg.subscribe("getClients") msg.subscribe("getScriptList") msg.send("getScriptList") diff --git a/src/main/resources/resource/WebGui/app/service/views/Py4jGui.html b/src/main/resources/resource/WebGui/app/service/views/Py4jGui.html index d13bbce690..6e9e5fab5a 100644 --- a/src/main/resources/resource/WebGui/app/service/views/Py4jGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/Py4jGui.html @@ -90,13 +90,19 @@ <div class="row"> <span> console - <button ng-click="log = '';$apply()" class="btn btn-default"> + <button ng-click="clear();service.logs = [];$apply();" class="btn btn-default"> <span class="glyphicon glyphicon-remove"/> </button> </span> - <br/> - <pre>{{log}}</pre> </div> +<br/> +<table> + <tbody> + <tr ng-repeat="e in service.logs track by $index"> + <td><small>{{e}}</small></td> + </tr> + </tbody> +</table> <script type="text/ng-template" id="installPackage.html"> <div class="modal-header"> @@ -147,3 +153,4 @@ <button class="btn btn-default" ng-click="cancel()">Cancel</button> </div> </script> + diff --git a/src/main/resources/resource/WebGui/app/service/views/PythonGui.html b/src/main/resources/resource/WebGui/app/service/views/PythonGui.html index fb7f488954..5bfcecee5d 100644 --- a/src/main/resources/resource/WebGui/app/service/views/PythonGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/PythonGui.html @@ -74,16 +74,26 @@ </uib-tab> </uib-tabset> </div> + <div class="row"> <span> console - <button ng-click="log = '';$apply()" class="btn btn-default"> + <button ng-click="clear();service.logs = [];$apply();" class="btn btn-default"> <span class="glyphicon glyphicon-remove"/> </button> </span> - <br/> - <pre>{{log}}</pre> </div> + +<br/> +<table> + <tbody> + <tr ng-repeat="e in service.logs track by $index"> + <td><small>{{e}}</small></td> + </tr> + </tbody> +</table> + + <script type="text/ng-template" id="addPythonScript.html"> <div class="modal-header"> Enter Filename From f963e3fd2bfe73d1acdd253e747d55abe1e0dd9a Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Wed, 28 Feb 2024 11:56:37 -0800 Subject: [PATCH 099/131] test last workflow --- .github/{ => workflows}/build.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/build.yml (100%) diff --git a/.github/build.yml b/.github/workflows/build.yml similarity index 100% rename from .github/build.yml rename to .github/workflows/build.yml From 53591c0f78080e636a6d8c0446d4640d5e6e8998 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Wed, 28 Feb 2024 12:00:15 -0800 Subject: [PATCH 100/131] pull_request to push --- .github/workflows/build.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b510c17054..10fe55026e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,8 +3,8 @@ name: Java CI on: - # push: - pull_request: + push: + # pull_request: jobs: build: @@ -84,4 +84,3 @@ jobs: tag_name: ${{ steps.version.outputs.version }} generate_release_notes: true body_path: ./release-template.md - From 8376de1e040fca426264951be9162590b135b6e8 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Wed, 28 Feb 2024 17:02:11 -0800 Subject: [PATCH 101/131] excluding OpenCV tests from github build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 10fe55026e..1e61b3bb79 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: - 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 -DexcludeTests=*OpenCV* - name: Get next version uses: reecetech/version-increment@2023.9.3 From 7f5e1733244719efcbeebb41dafce341792599e7 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Wed, 28 Feb 2024 18:42:05 -0800 Subject: [PATCH 102/131] trying to filter out tests on the command line again --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1e61b3bb79..3129bbd4ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: - 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 -DexcludeTests=*OpenCV* + run: mvn clean verify -q -Dexcludes='**/*OpenCV*' - name: Get next version uses: reecetech/version-increment@2023.9.3 From a80364c6c841acdabeabbf67f0481032cb55e678 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Wed, 28 Feb 2024 19:05:15 -0800 Subject: [PATCH 103/131] try filtering at command line again --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3129bbd4ba..716117e803 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: - 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 -Dexcludes='**/*OpenCV*' + run: mvn clean verify -q -Dtest=!org.myrobotlab.opencv.* - name: Get next version uses: reecetech/version-increment@2023.9.3 From 3ebe9b185353e66638a72418189506259ca670e0 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Wed, 28 Feb 2024 19:42:08 -0800 Subject: [PATCH 104/131] libgtk2.0-0 added to build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 716117e803..dba0f2dadc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ 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 From df72ce359fcc44a09ee5aabcced6a164ac9d2f03 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Wed, 28 Feb 2024 21:12:23 -0800 Subject: [PATCH 105/131] java 18 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dba0f2dadc..b512495625 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,11 +13,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up java uses: actions/setup-java@v3 with: - java-version: "11" + java-version: "18" distribution: "adopt" # NEATO ! CACHE !!!! cache: "maven" From 3b63f256ecb74c122d4576d31490be6d739182f2 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 2 Mar 2024 05:58:20 -0800 Subject: [PATCH 106/131] worky terminal manager --- .../org/myrobotlab/process/NodeTerminal.java | 13 + .../myrobotlab/process/PythonTerminal.java | 108 ++++++ .../org/myrobotlab/process/Ros2Terminal.java | 13 + .../org/myrobotlab/process/RosTerminal.java | 13 + .../java/org/myrobotlab/process/Terminal.java | 343 ++++++++++++++++++ src/main/java/org/myrobotlab/service/Log.java | 6 + .../myrobotlab/service/TerminalManager.java | 149 ++++++++ .../service/config/TerminalManagerConfig.java | 12 + .../service/meta/TerminalManagerMeta.java | 23 ++ 9 files changed, 680 insertions(+) create mode 100644 src/main/java/org/myrobotlab/process/NodeTerminal.java create mode 100644 src/main/java/org/myrobotlab/process/PythonTerminal.java create mode 100644 src/main/java/org/myrobotlab/process/Ros2Terminal.java create mode 100644 src/main/java/org/myrobotlab/process/RosTerminal.java create mode 100644 src/main/java/org/myrobotlab/process/Terminal.java create mode 100644 src/main/java/org/myrobotlab/service/TerminalManager.java create mode 100644 src/main/java/org/myrobotlab/service/config/TerminalManagerConfig.java create mode 100644 src/main/java/org/myrobotlab/service/meta/TerminalManagerMeta.java diff --git a/src/main/java/org/myrobotlab/process/NodeTerminal.java b/src/main/java/org/myrobotlab/process/NodeTerminal.java new file mode 100644 index 0000000000..8efb3a6167 --- /dev/null +++ b/src/main/java/org/myrobotlab/process/NodeTerminal.java @@ -0,0 +1,13 @@ +package org.myrobotlab.process; + +import java.io.IOException; + +import org.myrobotlab.service.TerminalManager; + +public class NodeTerminal extends Terminal { + + public NodeTerminal(TerminalManager service, String name) throws IOException { + super(service, name); + } + +} diff --git a/src/main/java/org/myrobotlab/process/PythonTerminal.java b/src/main/java/org/myrobotlab/process/PythonTerminal.java new file mode 100644 index 0000000000..deb7858177 --- /dev/null +++ b/src/main/java/org/myrobotlab/process/PythonTerminal.java @@ -0,0 +1,108 @@ +package org.myrobotlab.process; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.myrobotlab.framework.Service; +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.TerminalManager; +import org.myrobotlab.service.Runtime; +import org.slf4j.Logger; + +public class PythonTerminal extends Terminal { + + /** + * name of the venv + */ + protected String venvName = "venv"; + + public final static Logger log = LoggerFactory.getLogger(PythonTerminal.class); + + public PythonTerminal(TerminalManager service, String name) throws IOException { + super(service, name); + } + + @Override + public String getVersion() { + try { + processCommand(getScriptCmd("python --version")); + Service.sleep(300); + return outputCapture.toString(); + } catch (Exception e) { + service.error(e); + } + return null; + } + + public void installPipPackages(List<String> packages) { + String packagesString = String.join(" ", packages); + String command = "pip install " + packagesString; + processCommand(command + "\n"); + } + + public void installPipPackage(String string) { + // TODO Auto-generated method stub + + } + + public void activateVirtualEnv() { + if (isWindows()) { + processCommand(venvName + "\\Scripts\\activate"); + } else { + // source is "bash" + // processCommand("source " + venvName + "/bin/activate"); + // the posix way + processCommand(". " + venvName + "/bin/activate"); + } + Service.sleep(300); + } + + public void installVirtualEnv() { + installVirtualEnv(venvName); + } + + public void installVirtualEnv(String venvName) { + this.venvName = venvName; + // processCommand(getScriptCmd("python -m venv " + venvName)); + processCommand("python -m venv " + venvName); + Service.sleep(300); + } + + public static void main(String[] args) { + try { + TerminalManager processor = (TerminalManager) Runtime.start("processor", "ManagedProcess"); + PythonTerminal shell = new PythonTerminal(processor, "python"); + // shell.setWorkspace(".." + File.separator + "webcam"); + shell.start(".." + File.separator + "webcam"); + shell.installVirtualEnv(); + shell.activateVirtualEnv(); + // shell.installPipPackage(""); + shell.installPipPackages(Arrays.asList("aiortc aiohttp")); + + shell.processCommand("python webcam.py"); + System.out.println(shell.getPids().toString()); + + shell.terminate(); + + // Example usage + String directory = "../webcam"; + String venvName = "venv"; + String packageName = "package_name"; + String pythonScript = "your_script.py"; + + // shell.setupAndRunPythonEnvironment(directory, venvName, packageName, + // pythonScript); + + // Wait for the completion or handle accordingly + // shell.waitForCompletion(); + + // Terminate the shell if necessary + // shell.terminate(); + + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/myrobotlab/process/Ros2Terminal.java b/src/main/java/org/myrobotlab/process/Ros2Terminal.java new file mode 100644 index 0000000000..d64d0fa53c --- /dev/null +++ b/src/main/java/org/myrobotlab/process/Ros2Terminal.java @@ -0,0 +1,13 @@ +package org.myrobotlab.process; + +import java.io.IOException; + +import org.myrobotlab.service.TerminalManager; + +public class Ros2Terminal extends Terminal { + + public Ros2Terminal(TerminalManager service, String name) throws IOException { + super(service, name); + } + +} diff --git a/src/main/java/org/myrobotlab/process/RosTerminal.java b/src/main/java/org/myrobotlab/process/RosTerminal.java new file mode 100644 index 0000000000..ea45f0d0ab --- /dev/null +++ b/src/main/java/org/myrobotlab/process/RosTerminal.java @@ -0,0 +1,13 @@ +package org.myrobotlab.process; + +import java.io.IOException; + +import org.myrobotlab.service.TerminalManager; + +public class RosTerminal extends Terminal { + + public RosTerminal(TerminalManager service, String name) throws IOException { + super(service, name); + } + +} diff --git a/src/main/java/org/myrobotlab/process/Terminal.java b/src/main/java/org/myrobotlab/process/Terminal.java new file mode 100644 index 0000000000..22a1827484 --- /dev/null +++ b/src/main/java/org/myrobotlab/process/Terminal.java @@ -0,0 +1,343 @@ +package org.myrobotlab.process; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.TerminalManager; +import org.myrobotlab.service.Runtime; +import org.slf4j.Logger; + +public class Terminal { + + public final static Logger log = LoggerFactory.getLogger(Terminal.class); + + public boolean isRunning = false; + + /** + * executor service for managing streams + */ + private transient ExecutorService executorService; + + /** + * lock for synchonizing + */ + protected transient Object lock = new Object(); + + /** + * name of this shell + */ + protected String name; + + /** + * output buffer + */ + protected StringBuilder outputCapture = new StringBuilder(); + + /** + * The pid of the sub process + */ + protected Long pid; + + /** + * list of pids for this shell + */ + protected Set<Long> pids = new HashSet<>(); + + /** + * process handler + */ + private transient Process process; + + /** + * reference to mrl service + */ + protected transient TerminalManager service; + + /** + * The initial command that started the shell + */ + protected String shellCommand = null; + + /** + * The directory where the interactive shell will do its work, where the + * process will start + */ + protected String workspace = "."; + + public Terminal(TerminalManager service, String name) { + // can increase to handle more input + this.executorService = Executors.newFixedThreadPool(3); + this.service = service; + this.name = name; + } + + public void clearOutput() { + outputCapture = new StringBuilder(); + } + + private String determineShellCommand() { + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.contains("win")) { + return "cmd"; + } else { + return "/bin/sh"; // Works for Unix/Linux/Mac + } + } + + public boolean doesExecutableExist(String name) { + return false; + } + + /** + * <pre> + * FIXME - finish ! + + public void processAndWait(String command) throws IOException { + String completionMarker = "Command completed -- unique marker " + System.currentTimeMillis(); + processCommand(command + "\n"); + processCommand("echo \"" + completionMarker + "\"\n"); + + StringBuilder commandOutput = new StringBuilder(); + String line; + while ((line = readLineWithTimeout()) != null) { // Implement readLineWithTimeout according to your input handling + if (line.contains(completionMarker)) { + break; + } + commandOutput.append(line).append("\n"); + } + // Now commandOutput contains the output from the command, and you know the command finished. + } + * </pre> + */ + + public String getCapturedOutput() { + synchronized (outputCapture) { + return outputCapture.toString(); + } + } + + public Set<Long> getPids() { + Set<Long> scanPids = new HashSet<>(); + if (process.isAlive()) { + process.descendants().forEach(processHandle -> { + scanPids.add(processHandle.pid()); + }); + } + pids = scanPids; + return pids; + } + + /** + * cmd for executing a script + * + * @param scriptPath + * @return + */ + public String getScriptCmd(String scriptPath) { + if (isWindows()) { + return ("cmd /c \"" + scriptPath + "\"\n"); + } else { + return ("/bin/sh \"" + scriptPath + "\"\n"); + } + } + + public String getTemplate(String templateName) { + try { + byte[] bytes = Files.readAllBytes(getTemplatePath(templateName)); + if (bytes != null) { + return new String(bytes); + } + } catch (IOException e) { + service.error(e); + } + return null; + } + + // private void startStreamGobbler(InputStream inputStream, String streamName) + // { + // executorService.submit(() -> { + // new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(line + // -> { + // System.out.println(line); // Print the line + // synchronized (outputCapture) { + // outputCapture.append(line).append("\n"); // Capture the line + // } + // }); + // }); + // } + + public Path getTemplatePath(String templateName) { + Path scriptPath = Paths.get(service.getResourceDir() + File.separator + "templates" + File.separator, templateName + (isWindows() ? ".bat" : ".sh")); + return scriptPath; + } + + public String getVersion() { + return "0.0.0"; + } + + public boolean isWindows() { + return System.getProperty("os.name").toLowerCase().contains("win"); + } + + public void processCommand(String input) { + try { + if (input == null) { + input = ""; + } + if (process == null) { + service.error("cannot process a command when the terminal isn't started"); + return; + } + OutputStream outputStream = process.getOutputStream(); + outputStream.write(String.format("%s\n", input).getBytes()); + outputStream.flush(); + } catch (Exception e) { + service.error(e); + } + } + + // New method to process a list of commands + public void processCommands(List<String> commands) throws IOException { + for (String command : commands) { + processCommand(command + "\n"); + } + } + + private void shutdownExecutor() { + executorService.shutdownNow(); + } + + public void start() { + start(workspace); + } + + /** + * Start an interactive shell in a workspace directory + * + * @param workspace + */ + public void start(String workspace) { + if (!isRunning) { + synchronized (lock) { + try { + shellCommand = determineShellCommand(); + ProcessBuilder processBuilder = new ProcessBuilder(shellCommand.split(" ")); + processBuilder.redirectErrorStream(true); // Merge stdout and stderr + + if (workspace != null && !workspace.isEmpty()) { + this.workspace = workspace; + processBuilder.directory(new File(workspace)); // Set the CWD for + // the + // process + } + + process = processBuilder.start(); + pid = process.pid(); + isRunning = true; + + startStreamGobbler(process.getInputStream(), "OUTPUT"); + // FIXME option to attach to stdIn + // should + // startUserInputForwarder(); + } catch (Exception e) { + isRunning = false; + service.error(e); + } + service.broadcastState(); + } + } else { + log.info("{} already started", name); + } + } + + private void startStreamGobbler(InputStream inputStream, String streamName) { + executorService.submit(() -> { + try { + byte[] buffer = new byte[1024]; // Adjust size as needed + int length; + while ((length = inputStream.read(buffer)) != -1) { + String text = new String(buffer, 0, length); + // Synchronize writing to the outputCapture to ensure thread safety + synchronized (outputCapture) { + System.out.print(text); // Print the text as it comes without + // waiting for a new line + outputCapture.append(text); // Append the text to the output capture + } + } + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + private void startUserInputForwarder() { + executorService.submit(() -> { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { + String inputLine; + while ((inputLine = reader.readLine()) != null) { + processCommand(inputLine); + } + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + public void terminate() throws IOException { + synchronized (lock) { + // Optionally send a quit command to the shell if it supports graceful + // exit. + // Example for Unix/Linux/Mac: sendInput("exit\n"); + // For Windows, it might be different or not necessary. + if (process != null) { + process.descendants().forEach(processHandle -> { + log.info("Terminating PID: " + processHandle.pid()); + processHandle.destroyForcibly(); // Attempts to terminate the process + }); + // destroying parent + process.destroyForcibly(); + process = null; + shutdownExecutor(); // Shutdown the executor service + } + isRunning = false; + } + service.broadcastState(); + } + + public int waitForCompletion() throws InterruptedException { + process.waitFor(); + shutdownExecutor(); + return process.exitValue(); + } + + public static void main(String[] args) { + try { + TerminalManager processor = (TerminalManager) Runtime.start("processor", "ManagedProcess"); + Terminal shell = new Terminal(processor, "basic tty"); + shell.start(); + // Example usage of the new method if you want to process a list of + // commands + List<String> commands = Arrays.asList("echo Hello", "ls"); + shell.processCommands(commands); + int exitCode = shell.waitForCompletion(); + System.out.println("Shell exited with code: " + exitCode); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/org/myrobotlab/service/Log.java b/src/main/java/org/myrobotlab/service/Log.java index 51e388a994..f0043af0ea 100644 --- a/src/main/java/org/myrobotlab/service/Log.java +++ b/src/main/java/org/myrobotlab/service/Log.java @@ -54,6 +54,7 @@ public static class LogEntry { public String threadName; public String className; public String body; + public String src; public LogEntry(ILoggingEvent event) { ts = event.getTimeStamp(); @@ -63,6 +64,11 @@ public LogEntry(ILoggingEvent event) { body = event.getFormattedMessage(); } + public LogEntry() { + ts = System.currentTimeMillis(); + threadName = Thread.currentThread().getName(); + } + @Override public String toString() { return String.format("%d %s %s %s %s", ts, level, threadName, className, body); diff --git a/src/main/java/org/myrobotlab/service/TerminalManager.java b/src/main/java/org/myrobotlab/service/TerminalManager.java new file mode 100644 index 0000000000..d44d362e35 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/TerminalManager.java @@ -0,0 +1,149 @@ +package org.myrobotlab.service; + +import java.util.Map; +import java.util.concurrent.ConcurrentSkipListMap; + +import org.myrobotlab.framework.Instantiator; +import org.myrobotlab.framework.Service; +import org.myrobotlab.logging.Level; +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.process.Terminal; +import org.myrobotlab.service.Log.LogEntry; +import org.myrobotlab.service.config.TerminalManagerConfig; +import org.slf4j.Logger; + +public class TerminalManager extends Service<TerminalManagerConfig> { + + public static class TerminalStartupConfig { + public String type = null; // Python Node Ros + + } + + public final static Logger log = LoggerFactory.getLogger(TerminalManager.class); + + private static final long serialVersionUID = 1L; + + /** + * Thread safe map of all the terminals + */ + protected Map<String, Terminal> terminals = new ConcurrentSkipListMap<>(); + + public TerminalManager(String n, String id) { + super(n, id); + } + + /** + * Process a command against a named terminal + * + * @param name + * @param command + */ + public void processCommand(String name, String command) { + if (!terminals.containsKey(name)) { + error("could not find terminal %s to process command %s", name, command); + return; + } + Terminal terminal = terminals.get(name); + terminal.processCommand(command); + } + + /** + * Start a generalized simple terminal + * + * @param name + * terminal name + */ + public void startTerminal(String name) { + startTerminal(name, null); + } + + /** + * Terminates the terminal + * + * @param name + * terminal name + */ + public void terminateTerminal(String name) { + log.info("terminating terminal {}", name); + if (terminals.containsKey(name)) { + try { + Terminal terminal = terminals.get(name); + terminal.terminate(); + } catch (Exception e) { + error(e); + } + } else { + info("%s terminal does not exist", name); + } + } + + /** + * Save configuration of the terminal including if its currently running + * + * @param name + * terminal name + */ + public void saveTerminal(String name) { + log.info("saving terminal {}", name); + // TODO - get terminal startup info and + // save it to config + } + + public void deleteTerminal(String name) { + log.info("deleting terminal {}", name); + if (terminals.containsKey(name)) { + terminals.remove(name); + } else { + info("%s terminal does not exist", name); + } + } + + public LogEntry publishStdOut(String name, String msg) { + LogEntry entry = new LogEntry(); + entry.src = name; + entry.level = "INFO"; + entry.className = this.getClass().getCanonicalName(); + entry.body = msg; + return entry; + } + + public void startTerminal(String name, String type) { + log.info("starting terminal {} {}", name, type); + + Terminal terminal = null; + String fullType = null; + + if (type == null) { + type = ""; + } + + if (!type.contains(".")) { + fullType = "org.myrobotlab.process." + type + "Terminal"; + } else { + fullType = type; + } + + if (terminals.containsKey(name)) { + terminal = terminals.get(name); + } else { + terminal = (Terminal) Instantiator.getNewInstance(fullType, this, name); + terminals.put(name, terminal); + } + terminal.start(); + } + + public static void main(String[] args) { + try { + + LoggingFactory.init(Level.INFO); + + TerminalManager manager = (TerminalManager) Runtime.start("manager", "TerminalManager"); + Runtime.start("webgui", "WebGui"); + manager.startTerminal("basic"); + + } catch (Exception e) { + log.error("main threw", e); + } + } +} diff --git a/src/main/java/org/myrobotlab/service/config/TerminalManagerConfig.java b/src/main/java/org/myrobotlab/service/config/TerminalManagerConfig.java new file mode 100644 index 0000000000..c4e39c6dcd --- /dev/null +++ b/src/main/java/org/myrobotlab/service/config/TerminalManagerConfig.java @@ -0,0 +1,12 @@ +package org.myrobotlab.service.config; + +import java.util.Map; +import java.util.TreeMap; + +import org.myrobotlab.service.TerminalManager.TerminalStartupConfig; + +public class TerminalManagerConfig extends ServiceConfig { + + Map<String, TerminalStartupConfig> terminals = new TreeMap<>(); + +} diff --git a/src/main/java/org/myrobotlab/service/meta/TerminalManagerMeta.java b/src/main/java/org/myrobotlab/service/meta/TerminalManagerMeta.java new file mode 100644 index 0000000000..28f448218c --- /dev/null +++ b/src/main/java/org/myrobotlab/service/meta/TerminalManagerMeta.java @@ -0,0 +1,23 @@ +package org.myrobotlab.service.meta; + +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.meta.abstracts.MetaData; +import org.slf4j.Logger; + +public class TerminalManagerMeta extends MetaData { + private static final long serialVersionUID = 1L; + public final static Logger log = LoggerFactory.getLogger(TerminalManagerMeta.class); + + /** + * This class is contains all the meta data details of a service. It's peers, + * dependencies, and all other meta data related to the service. + */ + public TerminalManagerMeta() { + + addDescription("Service that can manage subprocesses"); + addCategory("programming", "service"); + setAvailable(true); + + } + +} From ea1a1d462f9f2ff2d1c8fbb4fb6a3326f885f9d8 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 2 Mar 2024 05:59:40 -0800 Subject: [PATCH 107/131] added 2 more --- .../app/service/js/TerminalManagerGui.js | 60 +++++++++++++++++++ .../app/service/views/TerminalManagerGui.html | 29 +++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js create mode 100644 src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html diff --git a/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js b/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js new file mode 100644 index 0000000000..fbf30ccd8d --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js @@ -0,0 +1,60 @@ +angular.module("mrlapp.service.TerminalManagerGui", []).controller("TerminalManagerGuiCtrl", [ + "$scope", + "mrl", + function ($scope, mrl) { + console.info("TerminalManagerGuiCtrl") + var _self = this + var msg = this.msg + + // GOOD TEMPLATE TO FOLLOW + this.updateState = function (service) { + $scope.service = service + } + + // init scope variables + $scope.onTime = null + $scope.onEpoch = null + + this.onMsg = function (inMsg) { + let data = inMsg.data[0] + switch (inMsg.method) { + case "onState": + _self.updateState(data) + $scope.$apply() + break + case "onTime": + const date = new Date(data) + $scope.onTime = date.toLocaleString() + $scope.$apply() + break + case "onEpoch": + $scope.onEpoch = data + $scope.$apply() + break + default: + console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) + break + } + } + + // Assuming `service` is your service managing terminals + $scope.startTerminal = function (key) { + msg.send("startTerminal", key) + } + + $scope.terminateTerminal = function (key) { + msg.send("terminateTerminal", key) + } + + $scope.saveTerminal = function (key) { + msg.send("saveTerminal", key) + } + + $scope.deleteTerminal = function (key) { + msg.send("deleteTerminal", key) + } + + msg.subscribe("publishEpoch") + msg.subscribe(this) + }, +]) diff --git a/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html b/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html new file mode 100644 index 0000000000..acfa8daaab --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html @@ -0,0 +1,29 @@ +<table class="table table-striped"> + <thead> + <tr> + <th style="width: 10%"></th> + <th style="width: 10%"></th> + <th style="width: 10%"></th> + <th style="width: 10%">Name</th> + <th style="width: 10%">PID</th> + <th style="width: 10%">Shell</th> + <th style="width: 70%">Command</th> + <th style="width: 70%">Control</th> + </tr> + </thead> + <tr ng-repeat="(key, value) in service.terminals"> + <td style="width: 10%"><input type="radio" name="selectedTerminal" ng-model="ctrl.selectedTerminal" ng-value="terminal" /></td> + <td style="width: 10%"><img src="TerminalManager.png" width="16" /></td> + <td style="width: 10%"><img ng-src="{{value.connected ? 'connected.png' : 'disconnected.png'}}" alt="Connection Status" width="16" /></td> + <td style="width: 10%"><small>{{key}}</small></td> + <td><small>{{value.pid}}</small></td> + <td><small>{{value.shellCommand}}</small></td> + <td><small>{{value.lastInput}}</small></td> + <td> + <button ng-click="startTerminal(key)">Start</button> + <button ng-click="terminateTerminal(key)">Terminate</button> + <button ng-click="saveTerminal(key)">Save</button> + <button ng-click="deleteTerminal(key)">Delete</button> + </td> + </tr> +</table> From 11457e61152e662496231ed0d7e9736d8251ac69 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 2 Mar 2024 08:30:22 -0800 Subject: [PATCH 108/131] updated openblas 1.5.8 --- .../java/org/myrobotlab/service/meta/Deeplearning4jMeta.java | 2 +- src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java | 1 + .../java/org/myrobotlab/service/meta/TesseractOcrMeta.java | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/meta/Deeplearning4jMeta.java b/src/main/java/org/myrobotlab/service/meta/Deeplearning4jMeta.java index 23fc4aeff8..1387f42739 100644 --- a/src/main/java/org/myrobotlab/service/meta/Deeplearning4jMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/Deeplearning4jMeta.java @@ -26,7 +26,7 @@ public Deeplearning4jMeta() { addDependency("org.bytedeco", "javacpp", "1.5.8"); // REMOVED FOR COLLISION - // addDependency("org.bytedeco", "openblas", "0.3.17-" + "1.5.6"); + addDependency("org.bytedeco", "openblas", "0.3.21-" + "1.5.8"); // dl4j deps. addDependency("org.deeplearning4j", "deeplearning4j-core", dl4jVersion); diff --git a/src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java b/src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java index 73d284cdb6..03428446dd 100644 --- a/src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/OpenCVMeta.java @@ -20,6 +20,7 @@ public OpenCVMeta() { // addDependency("org.bytedeco", "javacv", javaCvVersion); addDependency("org.bytedeco", "javacv-platform", javaCvVersion); addDependency("org.bytedeco", "javacpp", javaCvVersion); + addDependency("org.bytedeco", "openblas", "0.3.21-" + javaCvVersion); // FIXME - finish with cmdLine flag -gpu vs cudaEnabled for DL4J ? boolean gpu = false; if (gpu) { diff --git a/src/main/java/org/myrobotlab/service/meta/TesseractOcrMeta.java b/src/main/java/org/myrobotlab/service/meta/TesseractOcrMeta.java index 8c8d9a2554..e8149447f7 100644 --- a/src/main/java/org/myrobotlab/service/meta/TesseractOcrMeta.java +++ b/src/main/java/org/myrobotlab/service/meta/TesseractOcrMeta.java @@ -13,13 +13,15 @@ public class TesseractOcrMeta extends MetaData { * dependencies, and all other meta data related to the service. */ public TesseractOcrMeta() { + String javaCvVersion = "1.5.8"; - String tesseractVersion = "5.2.0-1.5.8"; + String tesseractVersion = "5.2.0-" + javaCvVersion; addDescription("Optical character recognition - the ability to read"); addCategory("ai", "vision"); addDependency("org.bytedeco", "tesseract", tesseractVersion); addDependency("org.bytedeco", "tesseract-platform", tesseractVersion); addDependency("tesseract", "tessdata", "0.0.2", "zip"); + addDependency("org.bytedeco", "openblas", "0.3.21-" + javaCvVersion); } From a36eba5715acf621a0bf81bef9c811e8e8430000 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 2 Mar 2024 08:36:24 -0800 Subject: [PATCH 109/131] forgot img --- src/main/resources/resource/TerminalManager.png | Bin 0 -> 1937 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/resource/TerminalManager.png diff --git a/src/main/resources/resource/TerminalManager.png b/src/main/resources/resource/TerminalManager.png new file mode 100644 index 0000000000000000000000000000000000000000..e212bb9b2d00a7208a1173f2514387b218bee6de GIT binary patch literal 1937 zcmV;C2X6R@P)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf2PR2GK~!i%otQa` zRoxPXSGV(kqNs_nU~FjUKQI;yG*B=T%#B2IH8n61L{OXpQN%G2LopKs13^@rXZ!1C zU7vm5uHNU|&-+4k)v9?-d+$Co_wV1Io0*wuBIUWcxj)i>fNe~dsP=!0%&k0D52StO z*|TSJt5>g{T3F|llY`Wa;os)?Tg=?Od$;d^NfI@=vW3B<*lC!Q*PPSx>921;LS352 z=FOYivSrH_B(2CIV+E_d=9!ciXftjs9=v}2y8Za^qsue*?%kW4ot<s#)~##%_wR2{ zpFSNX(H-VFb`$<BvXL6Upt*TDTg9e5`P;W|+qG-g+Qy9=+pk~0{(FShuV3Hx?c3Kr zefrdvELpOkn8|9wX~4(E$Nbg({{6dik-o}u^3C7bcJ=Djwqe7DK7m;sFj`ROrAwD~ zVnXaf1-@A%3D8`Q;po&)YTg(rb@r)l=qep)mc}_}%a<>2Jf&8wSkaFbHWLVwPa<H` zV3G~W$yFVc{$Q3t1;$OAHnj^EF0}32xA*bNl`GrYwQJj|Rjc~s+$|^zOw*~iMvnCd z+N7$>+Oz!OEI~Q~R)5>JZSBmNGwuBO^KI9zT|JN;aS~o`V8SuV$$)9ij@dFk3H^eD z?aaXY_wU>8-MicE+qc`+ty|mCqet7VTesStJ$rid`J^HdFt!#1IhN87WFB>iF<svQ z4RDgU8*kpcX~&Ko>rHc<&>uW_u$?=1u6_CPWtafS9F?bBCzk}O-LV`{mtGk62>=ur zG67?VN9)U%FWb9!@47F?NS{i?Lm+dhPhU{S(-uWA#t+^nU_qggN$uUcw_Ut=u^l>e zsBPJ@rCqvosq47Z#yQc1MKk|2AN`Wlr;l(v>;SL;Ixgz`ORgUuF7)Zsr~5@de*Abp zS`^^@z+I5?osrEVTTrSE0%Ph#0lL&1H|zie9P`}g=o~q6r0v|fvz<MAwx4?@1Cb0L z-Vr=r*Q{C7$@c|z8?@PJ&{x>3{QLLsy&qt1@(!2SVC7e@UbP)NcC?!}Z+1P+fBg8d zbB-31z>gJY<$X6`V*=;}dW!;i!batQ8Z^xl@$1*GJ-0B&aD?ClyBh@4h%=}0Z{NQ4 zBqZdi^!V}P_VD4uHvdK29z1x^9zA;0o;-OnT=Hxtn?|2DfsVSLKY#8I44WSFB_FhC z2LKcJ`SWLQo~B7kB98(S<1$~re!X3}a;1MA@_}{v^5u5p#*H334Ox%o$jbt;Ff>p6 z*e8oH9-5hNV)z<8eE4vC@!~~qa1y+@1m_ElF_UD~&@*`&u~5RM@qv)lr*D<9vokCf zb%`6@fjUkQL}Mfa$PzXt=#FI${2}z7YjzhOZ3*>>+7sj9bJ|q^H6ivSnqcKvZML3m zhcnKV2j)Wm;lqdCJn?zSGq>a974#|%m)JttIKZAZ#t^|YBDo95=ETRd&E(7lQkIK4 zHeUsP{d3x8Kssj;;5dDZ7ud<gO7qy|IH9X~%ptG#G3W!g=V1rNFqRaK9<J;YgB|dg zP@u*;mZvUdVGW_|g5lpMP*<_S<XONm<oV_<*mxEOV>*DY628Fv0DXcw=obA;IR=Yo zazNvpZ_as7_Gc&b%Q!HJ1M?aK;OQe=Y;400=vWg78-iSME@s+Hj0}jc@>tG9+7mUF zT2Q~l7&O)b0+|b0pD2=JkogknBhqh@JPnI}E6Ii&dpUE(6VnPa*8hLSX|Fjz^@WBV z;BizRqnu}KJPNs-APdxH#F*ZK$`#c%Hc=?+(_5cF4W@BG(=^|m{^<0{MC!oUSY%Vx z58GJI>XUl<0(}A!;QNGg&atuaG2maDs$)<a8}ox~2xy06tI!wQf(g(x=R6zFVu?w~ zg>m}mU*l^2v?tFPpsshSu_jB@Bvr1BRT;{K(Jr@k#;F_YYskrVAjm~FB%hQhIY2hP z$`Hu>6_fPBUZG(JpxR&^>zBYs0@}?7DUZbh^b$}HQdebYmd7VE@?vCg1-48COe8Ws z^}u*g<3Pp&?CCF%-GxO@p_D<&fN>_B)Q~oH$<r^Pt@>1dn)lt+?*LC9cK}$p20E^K z;bcVmSDUCd<Q!)$_4W#1aESp<lM0R{);Gt>iPUGFvDh3>s>yd5yiWjP7f|RV#&i|L z*QOG+iD~(2o-toPd-@Ub600PrCUKm}st=gd*rYh14UR3x73%C4KI{O*cRs}D1aIUV zLn)7qCsF#5cgzM)TOF%&0uYm!Id$q(|6z+CW$)a%)1E(nzThX1{}*E`__wGz=#$U- z0|yQalbJbr@?`hoHP3IFa2(z0u|~83R>Dt&ziAp@>nIyPCa2DSVY~jsi4$|II1V%e z7Y2$PkJ&)#66p`ue-g&>*D(t>+yTxL%H+Vy0-+z19an#3DSdt7=`-eQ52;&h|JD8h X?w=+{ykB|700000NkvXXu0mjf3k<|N literal 0 HcmV?d00001 From 45d7f8b81e4d18fdee0045fc641bf8d0bdd79bac Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Wed, 6 Mar 2024 07:48:34 -0800 Subject: [PATCH 110/131] workyish tm --- .../myrobotlab/process/PythonTerminal.java | 4 +- .../java/org/myrobotlab/process/Terminal.java | 147 ++++++++++----- .../myrobotlab/service/TerminalManager.java | 169 +++++++++++++----- .../app/service/js/TerminalManagerGui.js | 79 ++++---- .../WebGui/app/service/tab-header.html | 4 +- .../app/service/views/TerminalManagerGui.html | 124 ++++++++++--- 6 files changed, 369 insertions(+), 158 deletions(-) diff --git a/src/main/java/org/myrobotlab/process/PythonTerminal.java b/src/main/java/org/myrobotlab/process/PythonTerminal.java index deb7858177..42be1648fd 100644 --- a/src/main/java/org/myrobotlab/process/PythonTerminal.java +++ b/src/main/java/org/myrobotlab/process/PythonTerminal.java @@ -27,9 +27,7 @@ public PythonTerminal(TerminalManager service, String name) throws IOException { @Override public String getVersion() { try { - processCommand(getScriptCmd("python --version")); - Service.sleep(300); - return outputCapture.toString(); + return processBlockingCommand(getScriptCmd("python --version")); } catch (Exception e) { service.error(e); } diff --git a/src/main/java/org/myrobotlab/process/Terminal.java b/src/main/java/org/myrobotlab/process/Terminal.java index 22a1827484..3b5000543b 100644 --- a/src/main/java/org/myrobotlab/process/Terminal.java +++ b/src/main/java/org/myrobotlab/process/Terminal.java @@ -9,16 +9,18 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import org.myrobotlab.generics.SlidingWindowList; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.TerminalManager; -import org.myrobotlab.service.Runtime; import org.slf4j.Logger; public class Terminal { @@ -27,6 +29,8 @@ public class Terminal { public boolean isRunning = false; + protected final String BOUNDARY_MARKER = "----------------terminal-cmd-boundary-7MA4YWxkTrZu0gW----------------"; + /** * executor service for managing streams */ @@ -45,7 +49,8 @@ public class Terminal { /** * output buffer */ - protected StringBuilder outputCapture = new StringBuilder(); + // protected List<String> output = new SlidingWindowList<>(300); + protected StringBuilder output = new StringBuilder(); /** * The pid of the sub process @@ -72,12 +77,31 @@ public class Terminal { */ protected String shellCommand = null; + /** + * For synchronous output + */ + private transient BlockingQueue<String> blockingOutputQueue = new LinkedBlockingQueue<>(); + /** * The directory where the interactive shell will do its work, where the * process will start */ protected String workspace = "."; + /** + * last command processed + */ + protected String lastCmd = null; + + + public static class TerminalCmd { + public long ts = System.currentTimeMillis(); + public String src; + public String terminal; + public String cmd; + } + + public Terminal(TerminalManager service, String name) { // can increase to handle more input this.executorService = Executors.newFixedThreadPool(3); @@ -86,7 +110,8 @@ public Terminal(TerminalManager service, String name) { } public void clearOutput() { - outputCapture = new StringBuilder(); + // output = new SlidingWindowList<>(300); + output = new StringBuilder(); } private String determineShellCommand() { @@ -94,7 +119,15 @@ private String determineShellCommand() { if (osName.contains("win")) { return "cmd"; } else { - return "/bin/sh"; // Works for Unix/Linux/Mac + // return "/bin/sh"; // Works for Unix/Linux/Mac + String bashPath = "/bin/bash"; + File bashFile = new File(bashPath); + if (bashFile.exists()) { + return bashPath; + } else { + // Fallback to sh if Bash is not found (less ideal) + return "/bin/sh"; + } } } @@ -124,12 +157,6 @@ public void processAndWait(String command) throws IOException { * </pre> */ - public String getCapturedOutput() { - synchronized (outputCapture) { - return outputCapture.toString(); - } - } - public Set<Long> getPids() { Set<Long> scanPids = new HashSet<>(); if (process.isAlive()) { @@ -194,19 +221,55 @@ public boolean isWindows() { } public void processCommand(String input) { - try { - if (input == null) { - input = ""; + processCommand(input, false); + } + + public void processCommand(String input, boolean addBoundary) { + synchronized (lock) { + try { + if (input == null) { + input = ""; + } + if (process == null) { + service.error("cannot process a command when the terminal isn't started"); + return; + } + String cmd = null; + if (addBoundary) { + // windows/mac echo vs linux + cmd = String.format("%s\necho %s\n", input, BOUNDARY_MARKER); + } else { + cmd = String.format("%s\n", input); + } + lastCmd = cmd; + TerminalCmd terminalCmd = new TerminalCmd(); + terminalCmd.src = service.getName(); + terminalCmd.terminal = name; + terminalCmd.cmd = cmd; + service.invoke("publishCmd", terminalCmd); + OutputStream outputStream = process.getOutputStream(); + outputStream.write(cmd.getBytes()); + outputStream.flush(); + } catch (Exception e) { + service.error(e); } - if (process == null) { - service.error("cannot process a command when the terminal isn't started"); - return; + } + } + + // FIXME - should be synchronized with + public String processBlockingCommand(String input) { + synchronized (lock) { + blockingOutputQueue.clear(); + processCommand(input, true); + String ret = null; + try { + while (isRunning && ret == null) { + ret = blockingOutputQueue.poll(100, TimeUnit.MILLISECONDS); + } + } catch (InterruptedException e) { + service.error(e); } - OutputStream outputStream = process.getOutputStream(); - outputStream.write(String.format("%s\n", input).getBytes()); - outputStream.flush(); - } catch (Exception e) { - service.error(e); + return ret; } } @@ -267,19 +330,29 @@ public void start(String workspace) { private void startStreamGobbler(InputStream inputStream, String streamName) { executorService.submit(() -> { try { - byte[] buffer = new byte[1024]; // Adjust size as needed + byte[] buffer = new byte[8192]; // Adjust size as needed int length; + StringBuilder dynamicBuffer = new StringBuilder(); while ((length = inputStream.read(buffer)) != -1) { String text = new String(buffer, 0, length); - // Synchronize writing to the outputCapture to ensure thread safety - synchronized (outputCapture) { - System.out.print(text); // Print the text as it comes without - // waiting for a new line - outputCapture.append(text); // Append the text to the output capture + // asynchronous publishing of all stdout + service.invoke("publishLog", name, text); + service.invoke("publishStdOut", text); + output.append(text); + dynamicBuffer.append(text); + System.out.print(text); + if (dynamicBuffer.toString().contains(BOUNDARY_MARKER)) { + // Boundary marker found, handle command completion here + System.out.println("Command execution completed."); + // Remove the boundary marker from the output buffer + int index = dynamicBuffer.indexOf(BOUNDARY_MARKER); + dynamicBuffer.delete(index, index + BOUNDARY_MARKER.length()); + blockingOutputQueue.add(dynamicBuffer.toString()); + dynamicBuffer = new StringBuilder(); } } } catch (IOException e) { - e.printStackTrace(); + service.error(e); } }); } @@ -324,20 +397,4 @@ public int waitForCompletion() throws InterruptedException { return process.exitValue(); } - public static void main(String[] args) { - try { - TerminalManager processor = (TerminalManager) Runtime.start("processor", "ManagedProcess"); - Terminal shell = new Terminal(processor, "basic tty"); - shell.start(); - // Example usage of the new method if you want to process a list of - // commands - List<String> commands = Arrays.asList("echo Hello", "ls"); - shell.processCommands(commands); - int exitCode = shell.waitForCompletion(); - System.out.println("Shell exited with code: " + exitCode); - } catch (IOException | InterruptedException e) { - e.printStackTrace(); - } - } - } diff --git a/src/main/java/org/myrobotlab/service/TerminalManager.java b/src/main/java/org/myrobotlab/service/TerminalManager.java index d44d362e35..69af4548b2 100644 --- a/src/main/java/org/myrobotlab/service/TerminalManager.java +++ b/src/main/java/org/myrobotlab/service/TerminalManager.java @@ -9,12 +9,21 @@ import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.process.Terminal; -import org.myrobotlab.service.Log.LogEntry; +import org.myrobotlab.process.Terminal.TerminalCmd; import org.myrobotlab.service.config.TerminalManagerConfig; import org.slf4j.Logger; public class TerminalManager extends Service<TerminalManagerConfig> { + public class TerminalLogEntry { + public String msg = null; + public String src = null; + // FIXME - STDERR at some point + public String stream = "stdout"; + public String terminal = null; + public long ts = System.currentTimeMillis(); + } + public static class TerminalStartupConfig { public String type = null; // Python Node Ros @@ -33,49 +42,97 @@ public TerminalManager(String n, String id) { super(n, id); } + public void deleteTerminal(String name) { + log.info("deleting terminal {}", name); + if (terminals.containsKey(name)) { + terminals.remove(name); + } else { + info("%s terminal does not exist", name); + } + } + + /** + * Process blocking command in default terminal + * + * @param cmd + * @return + */ + public String processBlockingCommand(String cmd) { + return processBlockingCommand("default", cmd); + } + + /** + * Publishes the current command from a terminal + * @param cmd + * @return + */ + public TerminalCmd publishCmd(TerminalCmd cmd) { + return cmd; + } + + /** + * Synchronously process a command in the terminal + * + * @param name + * @param cmd + */ + public String processBlockingCommand(String name, String cmd) { + if (!terminals.containsKey(name)) { + error("could not find terminal %s to process command %s", name, cmd); + return null; + } + Terminal terminal = terminals.get(name); + return terminal.processBlockingCommand(cmd); + } + + /** + * Asynchronously process command in default terminal + * + * @param cmd + */ + public void processCommand(String cmd) { + processCommand("default", cmd); + } + /** * Process a command against a named terminal * * @param name - * @param command + * @param cmd */ - public void processCommand(String name, String command) { + public void processCommand(String name, String cmd) { if (!terminals.containsKey(name)) { - error("could not find terminal %s to process command %s", name, command); + error("could not find terminal %s to process command %s", name, cmd); return; } Terminal terminal = terminals.get(name); - terminal.processCommand(command); + terminal.processCommand(cmd); } /** - * Start a generalized simple terminal + * Structured log publishing * * @param name - * terminal name + * @param msg + * @return */ - public void startTerminal(String name) { - startTerminal(name, null); + public TerminalLogEntry publishLog(String name, String msg) { + TerminalLogEntry entry = new TerminalLogEntry(); + entry.src = getName(); + entry.terminal = name; + entry.msg = msg; + entry.stream = "stdout"; + return entry; } /** - * Terminates the terminal + * All stdout/stderr from all terminals is published here * - * @param name - * terminal name + * @param msg + * @return */ - public void terminateTerminal(String name) { - log.info("terminating terminal {}", name); - if (terminals.containsKey(name)) { - try { - Terminal terminal = terminals.get(name); - terminal.terminate(); - } catch (Exception e) { - error(e); - } - } else { - info("%s terminal does not exist", name); - } + public String publishStdOut(String msg) { + return msg; } /** @@ -90,25 +147,21 @@ public void saveTerminal(String name) { // save it to config } - public void deleteTerminal(String name) { - log.info("deleting terminal {}", name); - if (terminals.containsKey(name)) { - terminals.remove(name); - } else { - info("%s terminal does not exist", name); - } + public void startTerminal() { + startTerminal("default"); } - public LogEntry publishStdOut(String name, String msg) { - LogEntry entry = new LogEntry(); - entry.src = name; - entry.level = "INFO"; - entry.className = this.getClass().getCanonicalName(); - entry.body = msg; - return entry; + /** + * Start a generalized simple terminal + * + * @param name + * terminal name + */ + public void startTerminal(String name) { + startTerminal(name, null, null); } - public void startTerminal(String name, String type) { + public void startTerminal(String name, String workspace, String type) { log.info("starting terminal {} {}", name, type); Terminal terminal = null; @@ -118,6 +171,10 @@ public void startTerminal(String name, String type) { type = ""; } + if (workspace == null) { + workspace = "."; + } + if (!type.contains(".")) { fullType = "org.myrobotlab.process." + type + "Terminal"; } else { @@ -130,7 +187,27 @@ public void startTerminal(String name, String type) { terminal = (Terminal) Instantiator.getNewInstance(fullType, this, name); terminals.put(name, terminal); } - terminal.start(); + terminal.start(workspace); + } + + /** + * Terminates the terminal + * + * @param name + * terminal name + */ + public void terminateTerminal(String name) { + log.info("terminating terminal {}", name); + if (terminals.containsKey(name)) { + try { + Terminal terminal = terminals.get(name); + terminal.terminate(); + } catch (Exception e) { + error(e); + } + } else { + info("%s terminal does not exist", name); + } } public static void main(String[] args) { @@ -140,10 +217,20 @@ public static void main(String[] args) { TerminalManager manager = (TerminalManager) Runtime.start("manager", "TerminalManager"); Runtime.start("webgui", "WebGui"); - manager.startTerminal("basic"); + manager.startTerminal(); + +// for (int i = 0; i < 100; ++i) { +// String ls = manager.processBlockingCommand("ls"); +// manager.processCommand("ls"); +// } + +// List<String> commands = Arrays.asList("echo Hello", "ls"); +// manager.processCommands(commands); + } catch (Exception e) { log.error("main threw", e); } } + } diff --git a/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js b/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js index fbf30ccd8d..e7f3480f81 100644 --- a/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js @@ -1,60 +1,59 @@ -angular.module("mrlapp.service.TerminalManagerGui", []).controller("TerminalManagerGuiCtrl", [ - "$scope", - "mrl", - function ($scope, mrl) { +angular.module("mrlapp.service.TerminalManagerGui", []).controller("TerminalManagerGuiCtrl", ["$scope", "mrl", function($scope, mrl) { console.info("TerminalManagerGuiCtrl") var _self = this var msg = this.msg - // GOOD TEMPLATE TO FOLLOW - this.updateState = function (service) { - $scope.service = service + $scope.processCommand = function(key, input) { + msg.send("processCommand", key, input) + $scope.service.inputValue = "" } - // init scope variables - $scope.onTime = null - $scope.onEpoch = null - - this.onMsg = function (inMsg) { - let data = inMsg.data[0] - switch (inMsg.method) { + this.onMsg = function(inMsg) { + let data = inMsg.data[0] + switch (inMsg.method) { case "onState": - _self.updateState(data) - $scope.$apply() - break - case "onTime": - const date = new Date(data) - $scope.onTime = date.toLocaleString() - $scope.$apply() - break - case "onEpoch": - $scope.onEpoch = data - $scope.$apply() - break + $scope.service = data + $scope.$apply() + break + case "onLog": + $scope.service.terminals[data.terminal].output = $scope.service.terminals[data.terminal].output + data.msg + let length = $scope.service.terminals[data.terminal].output.length + if (length > 1024) { + let overLength = length - 1024; + $scope.service.terminals[data.terminal].output = $scope.service.terminals[data.terminal].output.substring(overLength); + } + $scope.$apply() + break + case "onStdOut": + break + case "onCmd": + break default: - console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) - break - } + console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) + break + } } // Assuming `service` is your service managing terminals - $scope.startTerminal = function (key) { - msg.send("startTerminal", key) + $scope.startTerminal = function(key) { + msg.send("startTerminal", key) } - $scope.terminateTerminal = function (key) { - msg.send("terminateTerminal", key) + $scope.terminateTerminal = function(key) { + msg.send("terminateTerminal", key) } - $scope.saveTerminal = function (key) { - msg.send("saveTerminal", key) + $scope.saveTerminal = function(key) { + msg.send("saveTerminal", key) } - $scope.deleteTerminal = function (key) { - msg.send("deleteTerminal", key) + $scope.deleteTerminal = function(key) { + msg.send("deleteTerminal", key) } - msg.subscribe("publishEpoch") + msg.subscribe("publishLog") + // msg.subscribe("publishStdOut") + msg.subscribe("publishCmd") msg.subscribe(this) - }, -]) +} +, ]) diff --git a/src/main/resources/resource/WebGui/app/service/tab-header.html b/src/main/resources/resource/WebGui/app/service/tab-header.html index 8e8448e23d..792051d6bf 100644 --- a/src/main/resources/resource/WebGui/app/service/tab-header.html +++ b/src/main/resources/resource/WebGui/app/service/tab-header.html @@ -10,9 +10,7 @@ <div class="dropdown"> <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown"> <img ng-src="{{::service.simpleName}}.png" alt="" width="16" /> - <<<<<<< HEAD   {{::service.simpleName}} {{::service.name}}@{{::service.id}} {{::service.serviceVersion}} ======= -   {{::service.simpleName}} {{::service.name}} {{::service.serviceVersion}} >>>>>>> - 5a2c9e63ebe81f301c16e7627b72f72c73edb1f6 +   {{::service.simpleName}} {{::service.name}} {{::service.serviceVersion}} <span class="caret"></span> </button> <ul class="dropdown-menu"> diff --git a/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html b/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html index acfa8daaab..3475cb610d 100644 --- a/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html @@ -1,29 +1,101 @@ +<style> + .inline-buttons { + display: inline-block; + /* Or display: inline; */ + } + + .terminal2 { + background-color: black; + color: #33ff33; + font-family: 'Courier New', Courier, monospace; + font-size: 0.8em; + border-radius: 0; + margin: 0; + /* Removes default margin */ + padding: 10px; + /* Adjust based on your design needs */ + border: none; + /* Removes border */ + overflow: auto; + } + + .terminal-wrapper { + max-height: 800px; /* Adjust based on your needs */ + overflow-y: auto; + display: flex; + flex-direction: column; + align-items: stretch; + /* Ensures child elements fill the container */ + } + + .terminal-input { + background-color: black; + color: #33ff33; + border: none; + /* Removes border */ + outline: none; + /* Removes focus outline */ + font-family: 'Courier New', Courier, monospace; + font-size: 0.8em; + padding: 10px; + /* Should match the <pre> padding for alignment */ + width: 100%; + /* Ensures it takes up all available width */ + box-sizing: border-box; + /* Includes padding in the width calculation */ + margin: 0; + /* Removes default margin */ + } +</style> <table class="table table-striped"> - <thead> - <tr> - <th style="width: 10%"></th> - <th style="width: 10%"></th> - <th style="width: 10%"></th> - <th style="width: 10%">Name</th> - <th style="width: 10%">PID</th> - <th style="width: 10%">Shell</th> - <th style="width: 70%">Command</th> - <th style="width: 70%">Control</th> + <thead> + <tr> + <th></th> + <th></th> + <th></th> + <th>Name</th> + <th>PID</th> + <th>Shell</th> + <th>Command</th> + <th>Control</th> + </tr> + </thead> + <tr ng-repeat="(key, value) in service.terminals"> + <td> + <input type="radio" name="selectedTerminal" ng-model="ctrl.selectedTerminal" ng-value="terminal"/> + </td> + <td> + <img src="TerminalManager.png" width="16"/> + </td> + <td> + <img ng-src="{{value.isRunning ? 'connected.png' : 'disconnected.png'}}" alt="Connection Status" width="16"/> + </td> + <td> + <small>{{key}}</small> + </td> + <td> + <small>{{value.pid}}</small> + </td> + <td> + <small>{{value.shellCommand}}</small> + </td> + <td> + <small>{{value.lastInput}}</small> + </td> + <td> + <span class="inline-buttons"> + <button class="btn btn-sm" ng-click="startTerminal(key)">Start</button> + <button class="btn btn-sm" ng-click="terminateTerminal(key)">Terminate</button> + <button class="btn btn-sm" ng-click="saveTerminal(key)">Save</button> + <button class="btn btn-sm" ng-click="deleteTerminal(key)">Delete</button> + </span> + </td> </tr> - </thead> - <tr ng-repeat="(key, value) in service.terminals"> - <td style="width: 10%"><input type="radio" name="selectedTerminal" ng-model="ctrl.selectedTerminal" ng-value="terminal" /></td> - <td style="width: 10%"><img src="TerminalManager.png" width="16" /></td> - <td style="width: 10%"><img ng-src="{{value.connected ? 'connected.png' : 'disconnected.png'}}" alt="Connection Status" width="16" /></td> - <td style="width: 10%"><small>{{key}}</small></td> - <td><small>{{value.pid}}</small></td> - <td><small>{{value.shellCommand}}</small></td> - <td><small>{{value.lastInput}}</small></td> - <td> - <button ng-click="startTerminal(key)">Start</button> - <button ng-click="terminateTerminal(key)">Terminate</button> - <button ng-click="saveTerminal(key)">Save</button> - <button ng-click="deleteTerminal(key)">Delete</button> - </td> - </tr> </table> +<div class="terminal-wrapper" ng-repeat="(key, value) in service.terminals"> + {{key}}<button class="btn btn-sm" ng-click="deleteTerminal(key)">Clear</button> + + <pre class="terminal2">{{value.output}}</pre> + + <input class="terminal-input" type="text" class="form-control" id="inputField" ng-model="service.inputValue" ng-keyup="$event.keyCode == 13 ? processCommand(key, service.inputValue) : null" placeholder="type here..."> +</div> From 107c7422a81d1d2aec34e679f7bf2bcf5142fc7a Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 9 Mar 2024 07:24:20 -0800 Subject: [PATCH 111/131] bottom scroll --- .../WebGui/app/service/js/TerminalManagerGui.js | 15 ++++++++++++++- .../app/service/views/TerminalManagerGui.html | 9 +++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js b/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js index e7f3480f81..17a85e6e53 100644 --- a/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js @@ -22,11 +22,24 @@ angular.module("mrlapp.service.TerminalManagerGui", []).controller("TerminalMana let overLength = length - 1024; $scope.service.terminals[data.terminal].output = $scope.service.terminals[data.terminal].output.substring(overLength); } - $scope.$apply() + // $scope.$apply() + $scope.$apply(function() { + // Scroll logic here + // Assuming you can uniquely identify the <pre> for this terminal + let terminalElement = document.querySelector('.terminal-wrapper[data-terminal-id="' + data.terminal + '"] .terminal2'); + if (terminalElement) { + terminalElement.scrollTop = terminalElement.scrollHeight; + } + }); + break case "onStdOut": break case "onCmd": + // FIXME - keep a list of commands ... can support history and maybe more importantly + // script generation to make automated packages + $scope.service.terminals[data.terminal].output = $scope.service.terminals[data.terminal].output + '# ' + data.cmd + $scope.$apply() break default: console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) diff --git a/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html b/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html index 3475cb610d..fe42096ccb 100644 --- a/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html @@ -92,10 +92,11 @@ </td> </tr> </table> -<div class="terminal-wrapper" ng-repeat="(key, value) in service.terminals"> + +<div class="terminal-wrapper" ng-repeat="(key, value) in service.terminals" data-terminal-id="{{key}}"> {{key}}<button class="btn btn-sm" ng-click="deleteTerminal(key)">Clear</button> - <pre class="terminal2">{{value.output}}</pre> + <pre class="terminal2" id="terminal-{{key}}" >{{value.output}}</pre> - <input class="terminal-input" type="text" class="form-control" id="inputField" ng-model="service.inputValue" ng-keyup="$event.keyCode == 13 ? processCommand(key, service.inputValue) : null" placeholder="type here..."> -</div> + <input class="terminal-input" type="text" class="form-control" ng-model="service.inputValue" ng-keyup="$event.keyCode == 13 ? processCommand(key, service.inputValue) : null" placeholder="type here..."> +</div> \ No newline at end of file From 0e5a9c3ec7bc7c09202ef5c9212cc67bd3fa2e45 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Sat, 9 Mar 2024 08:03:49 -0800 Subject: [PATCH 112/131] locked in InMoov2.speakBlocking --- .../org/myrobotlab/service/InMoov2Test.java | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/test/java/org/myrobotlab/service/InMoov2Test.java b/src/test/java/org/myrobotlab/service/InMoov2Test.java index c9ae337b69..377d00a28a 100644 --- a/src/test/java/org/myrobotlab/service/InMoov2Test.java +++ b/src/test/java/org/myrobotlab/service/InMoov2Test.java @@ -1,29 +1,43 @@ package org.myrobotlab.service; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Test; import org.myrobotlab.framework.StaticType; import org.myrobotlab.service.config.OpenCVConfig; +import org.myrobotlab.test.AbstractTest; -public class InMoov2Test { +public class InMoov2Test extends AbstractTest { @Test public void testCvFilters() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { - InMoov2 i01 = (InMoov2)Runtime.start("i01", "InMoov2"); - - // flip - i01.setPeerConfigValue("opencv", "flip", true); - OpenCVConfig cvconfig = i01.getPeerConfig("opencv", new StaticType<>() {}); - assertTrue(cvconfig.flip); - - i01.setPeerConfigValue("opencv", "flip", false); - cvconfig = i01.getPeerConfig("opencv", new StaticType<>() {}); - assertFalse(cvconfig.flip); - + Runtime.setConfig("InMoov2Test"); + + InMoov2 i01 = (InMoov2) Runtime.start("i01", "InMoov2"); + + + // flip + i01.setPeerConfigValue("opencv", "flip", true); + OpenCVConfig cvconfig = i01.getPeerConfig("opencv", new StaticType<>() { + }); + assertTrue(cvconfig.flip); + + i01.setPeerConfigValue("opencv", "flip", false); + cvconfig = i01.getPeerConfig("opencv", new StaticType<>() { + }); + assertFalse(cvconfig.flip); + + i01.startPeer("mouth"); + + long start = System.currentTimeMillis(); + // i01.setSpeechType("LocalSpeech"); + i01.speakBlocking( + "Hello this is a way to test if speech is actually blocking, if it blocks it should take a little time to say this, if it doesn't work it will execute the next line immediately."); + System.out.println(String.format("speech blocking time taken %d", System.currentTimeMillis() - start)); + assertTrue(start > 2000); + } - } - From d3917588b5c4979f57dd99997fdb60ea2767bbe4 Mon Sep 17 00:00:00 2001 From: grog <grog@myrobotlab.org> Date: Tue, 12 Mar 2024 20:29:50 -0700 Subject: [PATCH 113/131] javadoc fixed --- src/main/java/org/myrobotlab/config/ConfigUtils.java | 2 +- src/main/java/org/myrobotlab/service/Runtime.java | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/myrobotlab/config/ConfigUtils.java b/src/main/java/org/myrobotlab/config/ConfigUtils.java index 7b793452ac..c8191320be 100644 --- a/src/main/java/org/myrobotlab/config/ConfigUtils.java +++ b/src/main/java/org/myrobotlab/config/ConfigUtils.java @@ -46,7 +46,7 @@ public static String getResourceRoot() { * data/config/{configName}/runtime.yml If one does exits, it is returned, if * one does not exist a default one is created and saved. * - * @param configName + * @param options * @return */ static public RuntimeConfig loadRuntimeConfig(CmdOptions options) { diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 3a4ebda900..9b09766aa9 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -5033,10 +5033,9 @@ public boolean isProcessingConfig() { * Sets the directory for the current config. This will be under configRoot + * fs + configName. Static wrapper around setConfigName - so it can be used in * the same way as all the other common static service methods - * - * @param configName - * - config dir name under data/config/{config} - * @return configName + * + * @param name - config dir name under data/config/{config} + * @return config dir name */ public static String setConfig(String name) { if (name == null) { From a94466b0967465629bd0ae827d42a0d9a768449c Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Thu, 21 Mar 2024 12:53:49 -0700 Subject: [PATCH 114/131] publish on change current session and synchronized --- src/main/java/org/myrobotlab/programab/Session.java | 4 ++-- src/main/java/org/myrobotlab/service/ProgramAB.java | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/programab/Session.java b/src/main/java/org/myrobotlab/programab/Session.java index a76fb94bec..d7205ba2e4 100644 --- a/src/main/java/org/myrobotlab/programab/Session.java +++ b/src/main/java/org/myrobotlab/programab/Session.java @@ -95,7 +95,7 @@ private synchronized Chat getChat() { return chat; } - public void savePredicates() { + synchronized public void savePredicates() { StringBuilder sb = new StringBuilder(); TreeSet<String> sort = new TreeSet<>(); sort.addAll(getChat().predicates.keySet()); @@ -122,7 +122,7 @@ public void savePredicates() { * Get all current predicate names and values * @return */ - public Map<String, String> getPredicates() { + synchronized public Map<String, String> getPredicates() { TreeMap<String, String> sort = new TreeMap<>(); // copy keys, making this sort thread safe Set<String> keys = new HashSet(getChat().predicates.keySet()); diff --git a/src/main/java/org/myrobotlab/service/ProgramAB.java b/src/main/java/org/myrobotlab/service/ProgramAB.java index c32a0ab437..4d8b547004 100644 --- a/src/main/java/org/myrobotlab/service/ProgramAB.java +++ b/src/main/java/org/myrobotlab/service/ProgramAB.java @@ -271,8 +271,15 @@ public Response getResponse(String userName, String botName, String text, boolea // update the current session if we want to change which bot is at // attention. if (updateCurrentSession) { + + boolean sessionChanged = (!userName.equals(config.currentUserName) || !botName.equals(config.currentBotName)); + setCurrentUserName(userName); setCurrentBotName(botName); + + if (sessionChanged) { + invoke("publishSession", getSessionKey(userName, botName)); + } } // Get the actual bots aiml based response for this session From ac4edd47d1a5db4eb865b4060c8189cf60eac9cd Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Fri, 22 Mar 2024 12:14:22 -0700 Subject: [PATCH 115/131] java 11 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b512495625..06d98292ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: uses: actions/setup-java@v3 with: - java-version: "18" + java-version: "11" distribution: "adopt" # NEATO ! CACHE !!!! cache: "maven" From f09bf0eb3af2f4173ec37940b338d9417b3652f3 Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Tue, 23 Apr 2024 05:59:45 -0700 Subject: [PATCH 116/131] removal of terminal stuff removed --- TODO.md | 6 + .../org/myrobotlab/process/NodeTerminal.java | 13 - .../myrobotlab/process/PythonTerminal.java | 106 ----- .../org/myrobotlab/process/Ros2Terminal.java | 13 - .../org/myrobotlab/process/RosTerminal.java | 13 - .../java/org/myrobotlab/process/Terminal.java | 400 ------------------ .../java/org/myrobotlab/service/InMoov2.java | 16 +- .../org/myrobotlab/service/InMoov2Arm.java | 51 ++- .../org/myrobotlab/service/InMoov2Hand.java | 14 + .../org/myrobotlab/service/InMoov2Head.java | 6 + .../org/myrobotlab/service/InMoov2Torso.java | 22 +- .../myrobotlab/service/TerminalManager.java | 236 ----------- .../java/org/myrobotlab/service/WebGui.java | 2 +- .../service/config/AudioFileConfig.java | 3 + .../service/config/InMoov2Config.java | 12 +- .../service/config/ProgramABConfig.java | 8 +- .../service/config/RemoteSpeechConfig.java | 5 +- .../service/config/RuntimeConfig.java | 2 +- .../service/config/TerminalManagerConfig.java | 12 - .../service/meta/TerminalManagerMeta.java | 23 - 20 files changed, 94 insertions(+), 869 deletions(-) create mode 100644 TODO.md delete mode 100644 src/main/java/org/myrobotlab/process/NodeTerminal.java delete mode 100644 src/main/java/org/myrobotlab/process/PythonTerminal.java delete mode 100644 src/main/java/org/myrobotlab/process/Ros2Terminal.java delete mode 100644 src/main/java/org/myrobotlab/process/RosTerminal.java delete mode 100644 src/main/java/org/myrobotlab/process/Terminal.java delete mode 100644 src/main/java/org/myrobotlab/service/TerminalManager.java delete mode 100644 src/main/java/org/myrobotlab/service/config/TerminalManagerConfig.java delete mode 100644 src/main/java/org/myrobotlab/service/meta/TerminalManagerMeta.java diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000000..93df2377f6 --- /dev/null +++ b/TODO.md @@ -0,0 +1,6 @@ +## TODO + +- 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 diff --git a/src/main/java/org/myrobotlab/process/NodeTerminal.java b/src/main/java/org/myrobotlab/process/NodeTerminal.java deleted file mode 100644 index 8efb3a6167..0000000000 --- a/src/main/java/org/myrobotlab/process/NodeTerminal.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.myrobotlab.process; - -import java.io.IOException; - -import org.myrobotlab.service.TerminalManager; - -public class NodeTerminal extends Terminal { - - public NodeTerminal(TerminalManager service, String name) throws IOException { - super(service, name); - } - -} diff --git a/src/main/java/org/myrobotlab/process/PythonTerminal.java b/src/main/java/org/myrobotlab/process/PythonTerminal.java deleted file mode 100644 index 42be1648fd..0000000000 --- a/src/main/java/org/myrobotlab/process/PythonTerminal.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.myrobotlab.process; - -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -import org.myrobotlab.framework.Service; -import org.myrobotlab.logging.LoggerFactory; -import org.myrobotlab.service.TerminalManager; -import org.myrobotlab.service.Runtime; -import org.slf4j.Logger; - -public class PythonTerminal extends Terminal { - - /** - * name of the venv - */ - protected String venvName = "venv"; - - public final static Logger log = LoggerFactory.getLogger(PythonTerminal.class); - - public PythonTerminal(TerminalManager service, String name) throws IOException { - super(service, name); - } - - @Override - public String getVersion() { - try { - return processBlockingCommand(getScriptCmd("python --version")); - } catch (Exception e) { - service.error(e); - } - return null; - } - - public void installPipPackages(List<String> packages) { - String packagesString = String.join(" ", packages); - String command = "pip install " + packagesString; - processCommand(command + "\n"); - } - - public void installPipPackage(String string) { - // TODO Auto-generated method stub - - } - - public void activateVirtualEnv() { - if (isWindows()) { - processCommand(venvName + "\\Scripts\\activate"); - } else { - // source is "bash" - // processCommand("source " + venvName + "/bin/activate"); - // the posix way - processCommand(". " + venvName + "/bin/activate"); - } - Service.sleep(300); - } - - public void installVirtualEnv() { - installVirtualEnv(venvName); - } - - public void installVirtualEnv(String venvName) { - this.venvName = venvName; - // processCommand(getScriptCmd("python -m venv " + venvName)); - processCommand("python -m venv " + venvName); - Service.sleep(300); - } - - public static void main(String[] args) { - try { - TerminalManager processor = (TerminalManager) Runtime.start("processor", "ManagedProcess"); - PythonTerminal shell = new PythonTerminal(processor, "python"); - // shell.setWorkspace(".." + File.separator + "webcam"); - shell.start(".." + File.separator + "webcam"); - shell.installVirtualEnv(); - shell.activateVirtualEnv(); - // shell.installPipPackage(""); - shell.installPipPackages(Arrays.asList("aiortc aiohttp")); - - shell.processCommand("python webcam.py"); - System.out.println(shell.getPids().toString()); - - shell.terminate(); - - // Example usage - String directory = "../webcam"; - String venvName = "venv"; - String packageName = "package_name"; - String pythonScript = "your_script.py"; - - // shell.setupAndRunPythonEnvironment(directory, venvName, packageName, - // pythonScript); - - // Wait for the completion or handle accordingly - // shell.waitForCompletion(); - - // Terminate the shell if necessary - // shell.terminate(); - - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/src/main/java/org/myrobotlab/process/Ros2Terminal.java b/src/main/java/org/myrobotlab/process/Ros2Terminal.java deleted file mode 100644 index d64d0fa53c..0000000000 --- a/src/main/java/org/myrobotlab/process/Ros2Terminal.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.myrobotlab.process; - -import java.io.IOException; - -import org.myrobotlab.service.TerminalManager; - -public class Ros2Terminal extends Terminal { - - public Ros2Terminal(TerminalManager service, String name) throws IOException { - super(service, name); - } - -} diff --git a/src/main/java/org/myrobotlab/process/RosTerminal.java b/src/main/java/org/myrobotlab/process/RosTerminal.java deleted file mode 100644 index ea45f0d0ab..0000000000 --- a/src/main/java/org/myrobotlab/process/RosTerminal.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.myrobotlab.process; - -import java.io.IOException; - -import org.myrobotlab.service.TerminalManager; - -public class RosTerminal extends Terminal { - - public RosTerminal(TerminalManager service, String name) throws IOException { - super(service, name); - } - -} diff --git a/src/main/java/org/myrobotlab/process/Terminal.java b/src/main/java/org/myrobotlab/process/Terminal.java deleted file mode 100644 index 3b5000543b..0000000000 --- a/src/main/java/org/myrobotlab/process/Terminal.java +++ /dev/null @@ -1,400 +0,0 @@ -package org.myrobotlab.process; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; - -import org.myrobotlab.generics.SlidingWindowList; -import org.myrobotlab.logging.LoggerFactory; -import org.myrobotlab.service.TerminalManager; -import org.slf4j.Logger; - -public class Terminal { - - public final static Logger log = LoggerFactory.getLogger(Terminal.class); - - public boolean isRunning = false; - - protected final String BOUNDARY_MARKER = "----------------terminal-cmd-boundary-7MA4YWxkTrZu0gW----------------"; - - /** - * executor service for managing streams - */ - private transient ExecutorService executorService; - - /** - * lock for synchonizing - */ - protected transient Object lock = new Object(); - - /** - * name of this shell - */ - protected String name; - - /** - * output buffer - */ - // protected List<String> output = new SlidingWindowList<>(300); - protected StringBuilder output = new StringBuilder(); - - /** - * The pid of the sub process - */ - protected Long pid; - - /** - * list of pids for this shell - */ - protected Set<Long> pids = new HashSet<>(); - - /** - * process handler - */ - private transient Process process; - - /** - * reference to mrl service - */ - protected transient TerminalManager service; - - /** - * The initial command that started the shell - */ - protected String shellCommand = null; - - /** - * For synchronous output - */ - private transient BlockingQueue<String> blockingOutputQueue = new LinkedBlockingQueue<>(); - - /** - * The directory where the interactive shell will do its work, where the - * process will start - */ - protected String workspace = "."; - - /** - * last command processed - */ - protected String lastCmd = null; - - - public static class TerminalCmd { - public long ts = System.currentTimeMillis(); - public String src; - public String terminal; - public String cmd; - } - - - public Terminal(TerminalManager service, String name) { - // can increase to handle more input - this.executorService = Executors.newFixedThreadPool(3); - this.service = service; - this.name = name; - } - - public void clearOutput() { - // output = new SlidingWindowList<>(300); - output = new StringBuilder(); - } - - private String determineShellCommand() { - String osName = System.getProperty("os.name").toLowerCase(); - if (osName.contains("win")) { - return "cmd"; - } else { - // return "/bin/sh"; // Works for Unix/Linux/Mac - String bashPath = "/bin/bash"; - File bashFile = new File(bashPath); - if (bashFile.exists()) { - return bashPath; - } else { - // Fallback to sh if Bash is not found (less ideal) - return "/bin/sh"; - } - } - } - - public boolean doesExecutableExist(String name) { - return false; - } - - /** - * <pre> - * FIXME - finish ! - - public void processAndWait(String command) throws IOException { - String completionMarker = "Command completed -- unique marker " + System.currentTimeMillis(); - processCommand(command + "\n"); - processCommand("echo \"" + completionMarker + "\"\n"); - - StringBuilder commandOutput = new StringBuilder(); - String line; - while ((line = readLineWithTimeout()) != null) { // Implement readLineWithTimeout according to your input handling - if (line.contains(completionMarker)) { - break; - } - commandOutput.append(line).append("\n"); - } - // Now commandOutput contains the output from the command, and you know the command finished. - } - * </pre> - */ - - public Set<Long> getPids() { - Set<Long> scanPids = new HashSet<>(); - if (process.isAlive()) { - process.descendants().forEach(processHandle -> { - scanPids.add(processHandle.pid()); - }); - } - pids = scanPids; - return pids; - } - - /** - * cmd for executing a script - * - * @param scriptPath - * @return - */ - public String getScriptCmd(String scriptPath) { - if (isWindows()) { - return ("cmd /c \"" + scriptPath + "\"\n"); - } else { - return ("/bin/sh \"" + scriptPath + "\"\n"); - } - } - - public String getTemplate(String templateName) { - try { - byte[] bytes = Files.readAllBytes(getTemplatePath(templateName)); - if (bytes != null) { - return new String(bytes); - } - } catch (IOException e) { - service.error(e); - } - return null; - } - - // private void startStreamGobbler(InputStream inputStream, String streamName) - // { - // executorService.submit(() -> { - // new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(line - // -> { - // System.out.println(line); // Print the line - // synchronized (outputCapture) { - // outputCapture.append(line).append("\n"); // Capture the line - // } - // }); - // }); - // } - - public Path getTemplatePath(String templateName) { - Path scriptPath = Paths.get(service.getResourceDir() + File.separator + "templates" + File.separator, templateName + (isWindows() ? ".bat" : ".sh")); - return scriptPath; - } - - public String getVersion() { - return "0.0.0"; - } - - public boolean isWindows() { - return System.getProperty("os.name").toLowerCase().contains("win"); - } - - public void processCommand(String input) { - processCommand(input, false); - } - - public void processCommand(String input, boolean addBoundary) { - synchronized (lock) { - try { - if (input == null) { - input = ""; - } - if (process == null) { - service.error("cannot process a command when the terminal isn't started"); - return; - } - String cmd = null; - if (addBoundary) { - // windows/mac echo vs linux - cmd = String.format("%s\necho %s\n", input, BOUNDARY_MARKER); - } else { - cmd = String.format("%s\n", input); - } - lastCmd = cmd; - TerminalCmd terminalCmd = new TerminalCmd(); - terminalCmd.src = service.getName(); - terminalCmd.terminal = name; - terminalCmd.cmd = cmd; - service.invoke("publishCmd", terminalCmd); - OutputStream outputStream = process.getOutputStream(); - outputStream.write(cmd.getBytes()); - outputStream.flush(); - } catch (Exception e) { - service.error(e); - } - } - } - - // FIXME - should be synchronized with - public String processBlockingCommand(String input) { - synchronized (lock) { - blockingOutputQueue.clear(); - processCommand(input, true); - String ret = null; - try { - while (isRunning && ret == null) { - ret = blockingOutputQueue.poll(100, TimeUnit.MILLISECONDS); - } - } catch (InterruptedException e) { - service.error(e); - } - return ret; - } - } - - // New method to process a list of commands - public void processCommands(List<String> commands) throws IOException { - for (String command : commands) { - processCommand(command + "\n"); - } - } - - private void shutdownExecutor() { - executorService.shutdownNow(); - } - - public void start() { - start(workspace); - } - - /** - * Start an interactive shell in a workspace directory - * - * @param workspace - */ - public void start(String workspace) { - if (!isRunning) { - synchronized (lock) { - try { - shellCommand = determineShellCommand(); - ProcessBuilder processBuilder = new ProcessBuilder(shellCommand.split(" ")); - processBuilder.redirectErrorStream(true); // Merge stdout and stderr - - if (workspace != null && !workspace.isEmpty()) { - this.workspace = workspace; - processBuilder.directory(new File(workspace)); // Set the CWD for - // the - // process - } - - process = processBuilder.start(); - pid = process.pid(); - isRunning = true; - - startStreamGobbler(process.getInputStream(), "OUTPUT"); - // FIXME option to attach to stdIn - // should - // startUserInputForwarder(); - } catch (Exception e) { - isRunning = false; - service.error(e); - } - service.broadcastState(); - } - } else { - log.info("{} already started", name); - } - } - - private void startStreamGobbler(InputStream inputStream, String streamName) { - executorService.submit(() -> { - try { - byte[] buffer = new byte[8192]; // Adjust size as needed - int length; - StringBuilder dynamicBuffer = new StringBuilder(); - while ((length = inputStream.read(buffer)) != -1) { - String text = new String(buffer, 0, length); - // asynchronous publishing of all stdout - service.invoke("publishLog", name, text); - service.invoke("publishStdOut", text); - output.append(text); - dynamicBuffer.append(text); - System.out.print(text); - if (dynamicBuffer.toString().contains(BOUNDARY_MARKER)) { - // Boundary marker found, handle command completion here - System.out.println("Command execution completed."); - // Remove the boundary marker from the output buffer - int index = dynamicBuffer.indexOf(BOUNDARY_MARKER); - dynamicBuffer.delete(index, index + BOUNDARY_MARKER.length()); - blockingOutputQueue.add(dynamicBuffer.toString()); - dynamicBuffer = new StringBuilder(); - } - } - } catch (IOException e) { - service.error(e); - } - }); - } - - private void startUserInputForwarder() { - executorService.submit(() -> { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { - String inputLine; - while ((inputLine = reader.readLine()) != null) { - processCommand(inputLine); - } - } catch (IOException e) { - e.printStackTrace(); - } - }); - } - - public void terminate() throws IOException { - synchronized (lock) { - // Optionally send a quit command to the shell if it supports graceful - // exit. - // Example for Unix/Linux/Mac: sendInput("exit\n"); - // For Windows, it might be different or not necessary. - if (process != null) { - process.descendants().forEach(processHandle -> { - log.info("Terminating PID: " + processHandle.pid()); - processHandle.destroyForcibly(); // Attempts to terminate the process - }); - // destroying parent - process.destroyForcibly(); - process = null; - shutdownExecutor(); // Shutdown the executor service - } - isRunning = false; - } - service.broadcastState(); - } - - public int waitForCompletion() throws InterruptedException { - process.waitFor(); - shutdownExecutor(); - return process.exitValue(); - } - -} diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index c971d94147..5c6aeb1139 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -56,12 +56,10 @@ public class InMoov2 extends Service<InMoov2Config> IKJointAngleListener { 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"); @@ -70,14 +68,12 @@ public void run() { } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } finally { - lock.unlock(); log.info("heart stopping"); thread = null; } - } } - public void start() { + synchronized public void start() { if (thread == null) { log.info("starting heart"); thread = new Thread(this, String.format("%s-heart", getName())); @@ -88,7 +84,7 @@ public void start() { } } - public void stop() { + synchronized public void stop() { if (thread != null) { thread.interrupt(); config.heartbeat = false; @@ -274,12 +270,12 @@ public InMoov2Config apply(InMoov2Config c) { execScript(); - loadAppsScripts(); +// loadAppsScripts(); - loadInitScripts(); +// loadInitScripts(); if (c.loadGestures) { - loadGestures(); +// loadGestures(); } if (c.heartbeat) { @@ -1551,7 +1547,7 @@ public String publishFlash(String flashName) { * onHeartbeat at a regular interval */ public Heartbeat publishHeartbeat() { - log.debug("publishHeartbeat"); + log.info("publishHeartbeat"); heartbeatCount++; Heartbeat heartbeat = new Heartbeat(this); try { diff --git a/src/main/java/org/myrobotlab/service/InMoov2Arm.java b/src/main/java/org/myrobotlab/service/InMoov2Arm.java index e676c52878..3b74a3bf20 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Arm.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Arm.java @@ -10,9 +10,7 @@ import org.myrobotlab.io.FileIO; import org.myrobotlab.kinematics.DHLink; import org.myrobotlab.kinematics.DHRobotArm; -import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; -import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.math.MathUtils; import org.myrobotlab.service.config.InMoov2ArmConfig; import org.myrobotlab.service.interfaces.IKJointAngleListener; @@ -90,7 +88,7 @@ public static DHRobotArm getDHRobotArm(String name, String side) { return arm; } - + @Deprecated /* use onMove(map) */ public void onMoveArm(HashMap<String, Double> map) { onMove(map); @@ -100,6 +98,7 @@ public void onMove(Map<String, Double> map) { moveTo(map.get("bicep"), map.get("rotate"), map.get("shoulder"), map.get("omoplate")); } + /** * peer services FIXME - framework should always - startPeers() unless * configured not to @@ -124,6 +123,15 @@ public void startService() { shoulder = (ServoControl) startPeer("shoulder"); omoplate = (ServoControl) startPeer("omoplate"); } + + @Override + public void stopService() { + super.stopService(); + releasePeer("bicep"); + releasePeer("rotate"); + releasePeer("shoulder"); + releasePeer("omoplate"); + } @Override public void broadcastState() { @@ -192,8 +200,8 @@ public ServoControl getRotate() { public String getScript(String service) { String side = getName().contains("left") ? "left" : "right"; - return String.format("%s.moveArm(\"%s\",%.0f,%.0f,%.0f,%.0f)\n", service, side, bicep.getCurrentInputPos(), rotate.getCurrentInputPos(), shoulder.getCurrentInputPos(), - omoplate.getCurrentInputPos()); + return String.format("%s.moveArm(\"%s\",%.0f,%.0f,%.0f,%.0f)\n", service, side, bicep.getCurrentInputPos(), rotate.getCurrentInputPos(), + shoulder.getCurrentInputPos(), omoplate.getCurrentInputPos()); } public ServoControl getShoulder() { @@ -283,6 +291,17 @@ public void onJointAngles(Map<String, Double> angleMap) { } } + // FIXME - framework should auto-release - unless configured not to + @Override + public void releaseService() { + try { + disable(); + super.releaseService(); + } catch (Exception e) { + error(e); + } + } + public void rest() { if (bicep != null) bicep.rest(); @@ -450,26 +469,4 @@ public void waitTargetPos() { omoplate.waitTargetPos(); } - public static void main(String[] args) { - LoggingFactory.init(Level.INFO); - - try { - - Runtime.main(new String[] { "--log-level", "info", "-s", "inmoov2arm", "InMoov2Arm" }); - // Runtime.main(new String[] {}); - // Runtime.main(new String[] { "--install" }); - InMoov2Arm arm = (InMoov2Arm) Runtime.start("inmoov2arm", "InMoov2Arm"); - arm.releaseService(); - - boolean done = true; - if (done) { - return; - } - log.info("leaving main"); - - } catch (Exception e) { - log.error("main threw", e); - } - } - } diff --git a/src/main/java/org/myrobotlab/service/InMoov2Hand.java b/src/main/java/org/myrobotlab/service/InMoov2Hand.java index f7fdadebbc..b30c2bd792 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Hand.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Hand.java @@ -567,6 +567,20 @@ public List<String> refreshControllers() { return controllers; } + public void release() { + disable(); + } + + @Override + public void releaseService() { + try { + disable(); + super.releaseService(); + } catch (Exception e) { + error(e); + } + } + public void rest() { if (thumb != null) thumb.rest(); diff --git a/src/main/java/org/myrobotlab/service/InMoov2Head.java b/src/main/java/org/myrobotlab/service/InMoov2Head.java index 1e98970f78..f3f5edf366 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Head.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Head.java @@ -360,6 +360,12 @@ public void release() { disable(); } + @Override + public void releaseService() { + disable(); + super.releaseService(); + } + public void rest() { // initial positions // setSpeed(1.0, 1.0, 1.0, 1.0, 1.0, 1.0); diff --git a/src/main/java/org/myrobotlab/service/InMoov2Torso.java b/src/main/java/org/myrobotlab/service/InMoov2Torso.java index efdb127957..75fa410ca2 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2Torso.java +++ b/src/main/java/org/myrobotlab/service/InMoov2Torso.java @@ -36,11 +36,27 @@ public InMoov2Torso(String n, String id) { @Override public void startService() { super.startService(); + topStom = (ServoControl) getPeer("topStom"); midStom = (ServoControl) getPeer("midStom"); lowStom = (ServoControl) getPeer("lowStom"); } + @Override + public void releaseService() { + try { + disable(); + + topStom = null; + midStom = null; + lowStom = null; + + super.releaseService(); + } catch (Exception e) { + error(e); + } + } + public void enable() { if (topStom != null) topStom.enable(); @@ -77,7 +93,7 @@ public void disable() { if (lowStom != null) lowStom.disable(); } - + @Deprecated /* use onMove(map) */ public void onMoveTorso(HashMap<String, Double> map) { onMove(map); @@ -87,6 +103,7 @@ public void onMove(Map<String, Double> map) { moveTo(map.get("topStom"), map.get("midStom"), map.get("lowStom")); } + public long getLastActivityTime() { long minLastActivity = Math.max(topStom.getLastActivityTime(), midStom.getLastActivityTime()); minLastActivity = Math.max(minLastActivity, lowStom.getLastActivityTime()); @@ -94,7 +111,8 @@ public long getLastActivityTime() { } public String getScript(String inMoovServiceName) { - return String.format("%s.moveTorso(%.0f,%.0f,%.0f)\n", inMoovServiceName, topStom.getCurrentInputPos(), midStom.getCurrentInputPos(), lowStom.getCurrentInputPos()); + return String.format("%s.moveTorso(%.0f,%.0f,%.0f)\n", inMoovServiceName, topStom.getCurrentInputPos(), midStom.getCurrentInputPos(), + lowStom.getCurrentInputPos()); } public void moveTo(Double topStomPos, Double midStomPos, Double lowStomPos) { diff --git a/src/main/java/org/myrobotlab/service/TerminalManager.java b/src/main/java/org/myrobotlab/service/TerminalManager.java deleted file mode 100644 index 69af4548b2..0000000000 --- a/src/main/java/org/myrobotlab/service/TerminalManager.java +++ /dev/null @@ -1,236 +0,0 @@ -package org.myrobotlab.service; - -import java.util.Map; -import java.util.concurrent.ConcurrentSkipListMap; - -import org.myrobotlab.framework.Instantiator; -import org.myrobotlab.framework.Service; -import org.myrobotlab.logging.Level; -import org.myrobotlab.logging.LoggerFactory; -import org.myrobotlab.logging.LoggingFactory; -import org.myrobotlab.process.Terminal; -import org.myrobotlab.process.Terminal.TerminalCmd; -import org.myrobotlab.service.config.TerminalManagerConfig; -import org.slf4j.Logger; - -public class TerminalManager extends Service<TerminalManagerConfig> { - - public class TerminalLogEntry { - public String msg = null; - public String src = null; - // FIXME - STDERR at some point - public String stream = "stdout"; - public String terminal = null; - public long ts = System.currentTimeMillis(); - } - - public static class TerminalStartupConfig { - public String type = null; // Python Node Ros - - } - - public final static Logger log = LoggerFactory.getLogger(TerminalManager.class); - - private static final long serialVersionUID = 1L; - - /** - * Thread safe map of all the terminals - */ - protected Map<String, Terminal> terminals = new ConcurrentSkipListMap<>(); - - public TerminalManager(String n, String id) { - super(n, id); - } - - public void deleteTerminal(String name) { - log.info("deleting terminal {}", name); - if (terminals.containsKey(name)) { - terminals.remove(name); - } else { - info("%s terminal does not exist", name); - } - } - - /** - * Process blocking command in default terminal - * - * @param cmd - * @return - */ - public String processBlockingCommand(String cmd) { - return processBlockingCommand("default", cmd); - } - - /** - * Publishes the current command from a terminal - * @param cmd - * @return - */ - public TerminalCmd publishCmd(TerminalCmd cmd) { - return cmd; - } - - /** - * Synchronously process a command in the terminal - * - * @param name - * @param cmd - */ - public String processBlockingCommand(String name, String cmd) { - if (!terminals.containsKey(name)) { - error("could not find terminal %s to process command %s", name, cmd); - return null; - } - Terminal terminal = terminals.get(name); - return terminal.processBlockingCommand(cmd); - } - - /** - * Asynchronously process command in default terminal - * - * @param cmd - */ - public void processCommand(String cmd) { - processCommand("default", cmd); - } - - /** - * Process a command against a named terminal - * - * @param name - * @param cmd - */ - public void processCommand(String name, String cmd) { - if (!terminals.containsKey(name)) { - error("could not find terminal %s to process command %s", name, cmd); - return; - } - Terminal terminal = terminals.get(name); - terminal.processCommand(cmd); - } - - /** - * Structured log publishing - * - * @param name - * @param msg - * @return - */ - public TerminalLogEntry publishLog(String name, String msg) { - TerminalLogEntry entry = new TerminalLogEntry(); - entry.src = getName(); - entry.terminal = name; - entry.msg = msg; - entry.stream = "stdout"; - return entry; - } - - /** - * All stdout/stderr from all terminals is published here - * - * @param msg - * @return - */ - public String publishStdOut(String msg) { - return msg; - } - - /** - * Save configuration of the terminal including if its currently running - * - * @param name - * terminal name - */ - public void saveTerminal(String name) { - log.info("saving terminal {}", name); - // TODO - get terminal startup info and - // save it to config - } - - public void startTerminal() { - startTerminal("default"); - } - - /** - * Start a generalized simple terminal - * - * @param name - * terminal name - */ - public void startTerminal(String name) { - startTerminal(name, null, null); - } - - public void startTerminal(String name, String workspace, String type) { - log.info("starting terminal {} {}", name, type); - - Terminal terminal = null; - String fullType = null; - - if (type == null) { - type = ""; - } - - if (workspace == null) { - workspace = "."; - } - - if (!type.contains(".")) { - fullType = "org.myrobotlab.process." + type + "Terminal"; - } else { - fullType = type; - } - - if (terminals.containsKey(name)) { - terminal = terminals.get(name); - } else { - terminal = (Terminal) Instantiator.getNewInstance(fullType, this, name); - terminals.put(name, terminal); - } - terminal.start(workspace); - } - - /** - * Terminates the terminal - * - * @param name - * terminal name - */ - public void terminateTerminal(String name) { - log.info("terminating terminal {}", name); - if (terminals.containsKey(name)) { - try { - Terminal terminal = terminals.get(name); - terminal.terminate(); - } catch (Exception e) { - error(e); - } - } else { - info("%s terminal does not exist", name); - } - } - - public static void main(String[] args) { - try { - - LoggingFactory.init(Level.INFO); - - TerminalManager manager = (TerminalManager) Runtime.start("manager", "TerminalManager"); - Runtime.start("webgui", "WebGui"); - manager.startTerminal(); - -// for (int i = 0; i < 100; ++i) { -// String ls = manager.processBlockingCommand("ls"); -// manager.processCommand("ls"); -// } - -// List<String> commands = Arrays.asList("echo Hello", "ls"); -// manager.processCommands(commands); - - - } catch (Exception e) { - log.error("main threw", e); - } - } - -} diff --git a/src/main/java/org/myrobotlab/service/WebGui.java b/src/main/java/org/myrobotlab/service/WebGui.java index 5c1dde8ec0..4188def30d 100644 --- a/src/main/java/org/myrobotlab/service/WebGui.java +++ b/src/main/java/org/myrobotlab/service/WebGui.java @@ -943,7 +943,7 @@ public void sendRemote(Message msg) { String json = CodecUtils.toJsonMsg(msg); if (json.length() > maxMsgSize) { - log.warn(String.format("sendRemote default msg size (%d) exceeded 65536 for msg %s", json.length(), msg)); + log.info(String.format("sendRemote default msg size (%d) exceeded 65536 for msg %s", json.length(), msg)); /* * debugging large msgs try { * FileIO.toFile(String.format("too-big-%s-%d.json", msg.method, diff --git a/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java b/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java index bcff490640..2e21ee2b67 100644 --- a/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java +++ b/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java @@ -11,6 +11,9 @@ public class AudioFileConfig extends ServiceConfig { public double volume = 1.0; public String currentPlaylist = "default"; + + @Deprecated /* use regular "listeners" from ServiceConfig parent */ + public String[] audioListeners; /** * Named map of lists of files diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index b043165cf6..bf611a1fd2 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -236,7 +236,7 @@ public Plan getDefault(Plan plan, String name) { } mouthControl.mouth = i01Name + ".mouth"; - + UltrasonicSensorConfig ultrasonicLeft = (UltrasonicSensorConfig) plan.get(getPeerName("ultrasonicLeft")); ultrasonicLeft.triggerPin = 64; ultrasonicLeft.echoPin = 63; @@ -244,7 +244,8 @@ public Plan getDefault(Plan plan, String name) { UltrasonicSensorConfig ultrasonicRight = (UltrasonicSensorConfig) plan.get(getPeerName("ultrasonicRight")); ultrasonicRight.triggerPin = 64; ultrasonicRight.echoPin = 63; - + + ProgramABConfig chatBot = (ProgramABConfig) plan.get(getPeerName("chatBot")); chatBot.bots.add("resource/ProgramAB/Alice"); @@ -265,7 +266,7 @@ public Plan getDefault(Plan plan, String name) { chatBot.bots.add("resource/ProgramAB/tr-TR"); Runtime runtime = Runtime.getInstance(); - String[] bots = new String[] { "cn-ZH", "en-US", "fi-FI", "hi-IN", "nl-NL", "pl-PL", "ru-RU", "de-DE", "es-ES", "fr-FR", "it-IT", "pt-PT", "tr-TR" }; + String[] bots = new String[] { "cn-ZH", "en-US", "fi-FI", "hi-IN", "nl-NL", "pl-PL","ru-RU", "de-DE", "es-ES", "fr-FR", "it-IT", "pt-PT", "tr-TR" }; String tag = runtime.getLocaleTag(); if (tag != null) { String[] tagparts = tag.split("-"); @@ -533,7 +534,7 @@ public Plan getDefault(Plan plan, String name) { // 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 @@ -545,7 +546,6 @@ public Plan getDefault(Plan plan, String name) { listeners.add(new Listener("publishMoveTorso", getPeerName("torso"), "onMove")); // service --to--> InMoov2 - AudioFileConfig mouth_audioFile = (AudioFileConfig) plan.get(getPeerName("mouth.audioFile")); mouth_audioFile.listeners.add(new Listener("publishPeak", name)); @@ -562,7 +562,7 @@ public Plan getDefault(Plan plan, String name) { // Needs upcoming pr fsm.listeners.add(new Listener("publishStateChange", name, "publishStateChange")); - + // peer --to--> peer mouth.listeners.add(new Listener("publishStartSpeaking", name)); mouth.listeners.add(new Listener("publishStartSpeaking", getPeerName("ear"))); diff --git a/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java b/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java index c1d82dbe29..d91131ae66 100644 --- a/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java +++ b/src/main/java/org/myrobotlab/service/config/ProgramABConfig.java @@ -9,6 +9,7 @@ public class ProgramABConfig extends ServiceConfig { @Deprecated /* unused text filters */ public String[] textFilters; + /** * explicit bot directories */ @@ -21,15 +22,16 @@ public class ProgramABConfig extends ServiceConfig { public String currentBotName = "Alice"; /** - * User name currently interacting with the bot. Setting it here will default - * it. + * User name currently interacting with the bot. Setting it here will + * default it. */ public String currentUserName = "human"; /** * sleep current state of the sleep if globalSession is used true : ProgramAB * is sleeping and wont respond false : ProgramAB is not sleeping and any - * response requested will be processed current sleep/wake value + * response requested will be processed + * current sleep/wake value */ public boolean sleep = false; diff --git a/src/main/java/org/myrobotlab/service/config/RemoteSpeechConfig.java b/src/main/java/org/myrobotlab/service/config/RemoteSpeechConfig.java index 1eca25bdd0..e79ec273cc 100644 --- a/src/main/java/org/myrobotlab/service/config/RemoteSpeechConfig.java +++ b/src/main/java/org/myrobotlab/service/config/RemoteSpeechConfig.java @@ -10,8 +10,7 @@ public class RemoteSpeechConfig extends SpeechSynthesisConfig { public String verb = "GET"; /** - * Speech url {text} will be url encoded text that will be transformed to audio - * speech + * Speech url {text} will be url encoded text that will be transformed to audio speech */ public String url = "http://localhost:5002/api/tts?text={text}"; @@ -19,7 +18,7 @@ public class RemoteSpeechConfig extends SpeechSynthesisConfig { * Template for POST body, usually JSON, not implemented yet */ public String template = null; - + /** * Default speech type */ diff --git a/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java b/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java index d7572cd118..99c31bd301 100644 --- a/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java +++ b/src/main/java/org/myrobotlab/service/config/RuntimeConfig.java @@ -24,7 +24,7 @@ public class RuntimeConfig extends ServiceConfig { /** * Log level debug, info, warn, error */ - public String logLevel = "warn"; + public String logLevel = "info"; /** * Locale setting for the instance, initial default will be set by the default jvm/os diff --git a/src/main/java/org/myrobotlab/service/config/TerminalManagerConfig.java b/src/main/java/org/myrobotlab/service/config/TerminalManagerConfig.java deleted file mode 100644 index c4e39c6dcd..0000000000 --- a/src/main/java/org/myrobotlab/service/config/TerminalManagerConfig.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.myrobotlab.service.config; - -import java.util.Map; -import java.util.TreeMap; - -import org.myrobotlab.service.TerminalManager.TerminalStartupConfig; - -public class TerminalManagerConfig extends ServiceConfig { - - Map<String, TerminalStartupConfig> terminals = new TreeMap<>(); - -} diff --git a/src/main/java/org/myrobotlab/service/meta/TerminalManagerMeta.java b/src/main/java/org/myrobotlab/service/meta/TerminalManagerMeta.java deleted file mode 100644 index 28f448218c..0000000000 --- a/src/main/java/org/myrobotlab/service/meta/TerminalManagerMeta.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.myrobotlab.service.meta; - -import org.myrobotlab.logging.LoggerFactory; -import org.myrobotlab.service.meta.abstracts.MetaData; -import org.slf4j.Logger; - -public class TerminalManagerMeta extends MetaData { - private static final long serialVersionUID = 1L; - public final static Logger log = LoggerFactory.getLogger(TerminalManagerMeta.class); - - /** - * This class is contains all the meta data details of a service. It's peers, - * dependencies, and all other meta data related to the service. - */ - public TerminalManagerMeta() { - - addDescription("Service that can manage subprocesses"); - addCategory("programming", "service"); - setAvailable(true); - - } - -} From 3716a8e9d1fa4a7aa24329c8f0ce7e0e90df7803 Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Thu, 25 Apr 2024 21:37:49 -0700 Subject: [PATCH 117/131] starts setup --- src/main/java/org/myrobotlab/io/FileIO.java | 32 + .../java/org/myrobotlab/service/InMoov2.java | 194 +- .../org/myrobotlab/service/RemoteSpeech.java | 15 +- .../java/org/myrobotlab/service/Runtime.java | 10534 ++++++++-------- .../service/config/InMoov2Config.java | 2 + .../resource/WebGui/app/views/tabsViewCtrl.js | 142 +- 6 files changed, 5536 insertions(+), 5383 deletions(-) diff --git a/src/main/java/org/myrobotlab/io/FileIO.java b/src/main/java/org/myrobotlab/io/FileIO.java index 6e061e8c33..e027aeacd4 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; @@ -1424,5 +1426,35 @@ public static String normalize(String dirPath) { return dirPath.replace("\\", "/"); } } + + public static boolean isExecutableAvailable(String command) { + try { + // Attempt to execute the command + Process process = java.lang.Runtime.getRuntime().exec(command); + + // Check the exit value of the process + // If the process has terminated correctly, the command is available + if (process.waitFor() == 0) { + return true; + } + + // Read any errors from the attempted command + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + } + + return false; + } catch (IOException e) { + log.info("IOException: " + e.getMessage()); + return false; + } catch (InterruptedException e) { + log.info("InterruptedException: " + e.getMessage()); + return false; + } +} + } diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 5c6aeb1139..269d9dc8db 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -14,7 +14,6 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; -import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.io.FilenameUtils; import org.myrobotlab.framework.Message; @@ -52,25 +51,24 @@ import org.slf4j.Logger; public class InMoov2 extends Service<InMoov2Config> - implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, - IKJointAngleListener { + implements ServiceLifeCycleListener, SpeechListener, TextListener, TextPublisher, JoystickListener, LocaleProvider, IKJointAngleListener { 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; + try { + while (!Thread.currentThread().isInterrupted()) { + invoke("publishHeartbeat"); + Thread.sleep(config.heartbeatInterval); } + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } finally { + log.info("heart stopping"); + thread = null; + } } synchronized public void start() { @@ -179,22 +177,12 @@ public static void main(String[] args) { */ protected int bootCount = 0; - protected transient ProgramAB chatBot; - protected List<String> configList; protected transient SpeechRecognizer ear; protected List<LogEntry> 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; - // waiting controable threaded gestures we warn user protected boolean gestureAlreadyStarted = false; @@ -243,8 +231,7 @@ public static void main(String[] args) { 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"); + 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 @@ -257,39 +244,16 @@ public void addTextListener(TextListener service) { 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())); } - // one way sync configuration into predicates - configToPredicates(); - - 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); - } + } return c; } @@ -350,15 +314,42 @@ synchronized public void boot() { log.warn("will not boot again"); return; } + + if (bootCount == 0) { + info("%s BOOTING ....", getName()); + } bootCount++; - log.info("boot count {}", bootCount); + log.info("BOOT count {}", bootCount); - // config has not finished processing yet.. if (runtime.isProcessingConfig()) { - log.warn("runtime still processing config set {}, waiting ....", runtime.getConfigName()); + 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 @@ -390,7 +381,9 @@ synchronized public void boot() { */ // load the InMoov2.py and publish it for Python/Jython or Py4j to consume - execScript(); + if (config.execScript) { + execScript(); + } // TODO - MAKE BOOT REPORT !!!! deliver it on a heartbeat runtime.invoke("publishConfigList"); @@ -582,6 +575,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(); @@ -686,10 +680,16 @@ public void enable() { * @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; @@ -763,9 +763,12 @@ public void finishedGesture(String nameOfGesture) { * @param event */ public void fire(String event) { - // Should this be sent to chatbot too ? - // invoke("publishEvent", event); - fsm.fire(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() { @@ -813,18 +816,11 @@ public InMoov2Head getHead() { */ public Long getLastActivityTime() { 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 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; @@ -865,11 +861,24 @@ public OpenCV getOpenCV() { } public String getPredicate(String key) { + ProgramAB chatBot = (ProgramAB)getPeer("chatBot"); + if (chatBot != null) { return getPredicate(chatBot.getConfig().currentUserName, key); + } else { + log.info("chatBot not ready"); + return null; + } } public String getPredicate(String user, String key) { + ProgramAB chatBot = (ProgramAB)getPeer("chatBot"); + if (chatBot != null) { + return chatBot.getPredicate(user, key); + } else { + log.info("chatBot not ready"); + return null; + } } /** @@ -879,8 +888,15 @@ public String getPredicate(String user, String key) { * @return */ 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"); + return null; + } } public InMoov2Arm getRightArm() { @@ -1058,7 +1074,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()); } @@ -1200,7 +1217,7 @@ public void onEndSpeaking(String utterance) { * including lower level logs that do not propegate as statuses * * @param log - * - flushed log from Log service + * - flushed log from Log service */ public void onErrors(List<LogEntry> log) { errors.addAll(log); @@ -1222,7 +1239,6 @@ public void onConfigStarted(String configName) { invoke("publishConfigStarted", configName); } - public void onGestureStatus(Status status) { if (!status.equals(Status.success()) && !status.equals(Status.warn("Python process killed !"))) { error("I cannot execute %s, please check logs", lastGestureExecuted); @@ -1426,6 +1442,7 @@ public void onText(String text) { } // TODO FIX/CHECK this, migrate from python land + @Deprecated /* these are fsm states and should be implemented in python callbacks */ public void powerDown() { rest(); @@ -1444,6 +1461,7 @@ 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(); @@ -1577,8 +1595,7 @@ public Heartbeat publishHeartbeat() { } // interval event firing - if (config.stateRandomInterval != null - && System.currentTimeMillis() > stateLastRandomTime + (config.stateRandomInterval * 1000)) { + if (config.stateRandomInterval != null && System.currentTimeMillis() > stateLastRandomTime + (config.stateRandomInterval * 1000)) { // fsm.fire("random"); stateLastRandomTime = System.currentTimeMillis(); } @@ -2008,6 +2025,7 @@ public boolean setPirPlaySounds(boolean b) { } public Object setPredicate(String key, Object data) { + ProgramAB chatBot = (ProgramAB)getPeer("chatBot"); if (data == null) { chatBot.setPredicate(key, null); // "unknown" "null" other sillyness ? } else { @@ -2072,7 +2090,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) { @@ -2160,22 +2183,6 @@ public ServiceInterface startPeer(String peer) { public void startService() { super.startService(); - // This is required the core of InMoov is - // a FSM ProgramAB and some form of Python/Jython - fsm = (FiniteStateMachine) startPeer("fsm"); - - // Chatbot is a required part of InMoov2 - chatBot = (ProgramAB) startPeer("chatBot"); - try { - chatBot.startSession(); - chatBot.setPredicate("robot", getName()); - } catch (IOException e) { - error(e); - } - - // A python process is required - should be defined as a peer - // of Type Python or Py4j - // just for comparing config with current "default" // debugging only Runtime runtime = Runtime.getInstance(); @@ -2192,7 +2199,6 @@ public void startService() { // "subscriptions" in config too ? subscribe("runtime", "shutdown"); subscribe("runtime", "publishConfigList"); - runtime.invoke("publishConfigList"); if (config.heartbeat) { diff --git a/src/main/java/org/myrobotlab/service/RemoteSpeech.java b/src/main/java/org/myrobotlab/service/RemoteSpeech.java index 3c41b292ca..343e7e2850 100644 --- a/src/main/java/org/myrobotlab/service/RemoteSpeech.java +++ b/src/main/java/org/myrobotlab/service/RemoteSpeech.java @@ -17,11 +17,10 @@ import org.slf4j.Logger; /** - * A generalized "remote" speech synthesis interface service. I can be used for - * potentially many remote TTS services, however, the first one will be - * MozillaTTS, which we will assume is working locally with docker. See - * https://github.com/synesthesiam/docker-mozillatts. Example GET: - * http://localhost:5002/api/tts?text=Hello%20I%20am%20a%20speech%20synthesis%20system%20version%202 + * A generalized "remote" speech synthesis interface service. I can be used for potentially many + * remote TTS services, however, the first one will be MozillaTTS, which we will assume is + * working locally with docker. See https://github.com/synesthesiam/docker-mozillatts. + * Example GET: http://localhost:5002/api/tts?text=Hello%20I%20am%20a%20speech%20synthesis%20system%20version%202 * * @author GroG * @@ -36,7 +35,7 @@ public class RemoteSpeech extends AbstractSpeechSynthesis<RemoteSpeechConfig> { * HttpClient peer for GETs and POSTs */ public transient HttpClient<HttpClientConfig> http = null; - + /** * Currently only support MozillaTTS */ @@ -45,12 +44,12 @@ public class RemoteSpeech extends AbstractSpeechSynthesis<RemoteSpeechConfig> { public RemoteSpeech(String n, String id) { super(n, id); } - + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public void startService() { super.startService(); - http = (HttpClient) startPeer("http"); + http = (HttpClient)startPeer("http"); } public static void main(String[] args) { diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 579f22d4cf..4b5f418a39 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -130,5224 +130,5320 @@ * VAR OF RUNTIME ! * */ -public class Runtime extends Service<RuntimeConfig> implements MessageListener, ServiceLifeCyclePublisher, - RemoteMessageHandler, ConnectionManager, Gateway, LocaleProvider { - - final static private long serialVersionUID = 1L; - - // FIXME - AVOID STATIC FIELDS !!! use .getInstance() to get the singleton - - /** - * a registry of all services regardless of which environment they came from - - * each must have a unique name - */ - static volatile private Map<String, ServiceInterface> registry = new TreeMap<>(); - - /** - * A plan is a request to runtime to change the system. Typically its to ask to - * start and configure new services. The master plan is an accumulation of all - * these requests. - */ - @Deprecated /* use the filesystem only no memory plan */ - protected final Plan masterPlan = new Plan("runtime"); - - /** - * thread for non-blocking install of services - */ - static private transient Thread installerThread = null; - - /** - * services which want to know if another service with an interface they are - * interested in registers or is released - * - * requestor type > interface > set of applicable service names - */ - protected final Map<String, Set<String>> interfaceToNames = new HashMap<>(); - - protected final Map<String, Set<String>> typeToNames = new HashMap<>(); - - protected final Map<String, Set<String>> interfaceToType = new HashMap<>(); - - protected final Map<String, Set<String>> typeToInterface = new HashMap<>(); - - private transient static final Object processLock = new Object(); - - /** - * FILTERED_INTERFACES are the set of low level interfaces which we are - * interested in filtering out if we want to maintain a data structure which has - * "interfaces of interest" - */ - protected final static Set<String> FILTERED_INTERFACES = new HashSet<>(Arrays.asList( - "org.myrobotlab.framework.interfaces.Broadcaster", "org.myrobotlab.service.interfaces.QueueReporter", - "org.myrobotlab.framework.interfaces.ServiceQueue", "org.myrobotlab.framework.interfaces.MessageSubscriber", - "org.myrobotlab.framework.interfaces.Invoker", "java.lang.Runnable", - "org.myrobotlab.framework.interfaces.ServiceStatus", "org.atmosphere.nettosphere.Handler", - "org.myrobotlab.framework.interfaces.NameProvider", "org.myrobotlab.framework.interfaces.NameTypeProvider", - "org.myrobotlab.framework.interfaces.ServiceInterface", "org.myrobotlab.framework.interfaces.TaskManager", - "org.myrobotlab.framework.interfaces.LoggingSink", "org.myrobotlab.framework.interfaces.StatusPublisher", - "org.myrobotlab.framework.interfaces.TypeProvider", "java.io.Serializable", - "org.myrobotlab.framework.interfaces.Attachable", "org.myrobotlab.framework.interfaces.StateSaver", - "org.myrobotlab.framework.interfaces.MessageSender", "java.lang.Comparable", - "org.myrobotlab.service.interfaces.ServiceLifeCycleListener", - "org.myrobotlab.framework.interfaces.StatePublisher")); - - protected final Set<String> serviceTypes = new HashSet<>(); - - /** - * The directory name currently being used for config. This is NOT full path - * name. It cannot be null, it cannot have "/" or "\" in the name - it has to be - * a valid file name for the OS. It's defaulted to "default". Changed often - */ - protected static String configName = "default"; - - /** - * The runtime config which Runtime was started with. This is the config which - * will be applied to Runtime when its created on startup. - */ - // protected static RuntimeConfig startConfig = null; - - /** - * State variable reporting if runtime is currently starting services from - * config. If true you can find which config from runtime.getConfigName() - */ - boolean processingConfig = false; - - /** - * <pre> - * The set of client connections to this mrl instance Some of the connections - * are outbound to other webguis, others may be inbound if a webgui is - * listening in this instance. These details and many others (such as from - * which connection a client is from) is in the Map <String, Object> information. - * Since different connections have different requirements, and details regarding - * clients the only "fixed" required info to add a client is : - * - * uuid - key unique identifier for the client - * connection - name of the connection currently managing the clients connection - * state - state of the client and/or connection - * (lots more attributes with the Map<String, Object> to provide necessary data for the connection) - * </pre> - */ - protected final Map<String, Connection> connections = new HashMap<>(); - - /** - * corrected route table with (soon to be regex ids) mapped to - * gateway/interfaces - */ - protected final RouteTable routeTable = new RouteTable(); - - static private final String RUNTIME_NAME = "runtime"; - - /** - * user's data directory - */ - static public final String DATA_DIR = "data"; - - /** - * default parent path of configPath static ! - */ - public final static String ROOT_CONFIG_DIR = DATA_DIR + fs + "config"; - - /** - * number of services created by this runtime - */ - protected Integer creationCount = 0; - - /** - * the local repo.json manifest of this machine, which is a list of all - * libraries ivy installed - */ - transient private IvyWrapper repo = null; // was transient abstract Repo - - transient private ServiceData serviceData = ServiceData.getLocalInstance(); - - /** - * command line options - */ - static CmdOptions options = new CmdOptions(); - - /** - * command line configuration - */ - static StartYml startYml = new StartYml(); - - /** - * the platform (local instance) for this runtime. It must be a non-static as - * multiple runtimes will have different platforms - */ - protected Platform platform = null; - - private static long uniqueID = new Random(System.currentTimeMillis()).nextLong(); - - public final static Logger log = LoggerFactory.getLogger(Runtime.class); - - /** - * Object used to synchronize initializing this singleton. - */ - transient private static final Object INSTANCE_LOCK = new Object(); - - /** - * The singleton of this class. - */ - transient private static Runtime runtime = null; - - private List<String> jvmArgs; - - /** - * set of known hosts - */ - private transient Map<String, Host> hosts = null; - - /** - * global startingArgs - whatever came into main each runtime will have its - * individual copy - */ - // FIXME - remove static !!! - static String[] globalArgs; - - static Set<String> networkPeers = null; - - /** - * The name of the folder used to store native library dependencies during - * installation and runtime. - */ - private static final String LIBRARIES = "libraries"; - - String stdCliUuid = null; - - InProcessCli cli = null; - - /** - * available Locales - */ - protected Map<String, Locale> locales; - - protected List<String> configList; - - /** - * Wraps {@link java.lang.Runtime#availableProcessors()}. - * - * @return the number of processors available to the Java virtual machine. - * @see java.lang.Runtime#availableProcessors() - * - */ - public static final int availableProcessors() { - return java.lang.Runtime.getRuntime().availableProcessors(); - } - - /** - * Function to test if internet connectivity is available. If it is, will return - * the public gateway address of this computer by sending a request to an - * external server. If there is no internet, returns null. - * - * @return The public IP address or null if no internet available - */ - static public String getPublicGateway() { - try { - - URL url = new URL("http://checkip.amazonaws.com/"); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setRequestMethod("GET"); - - int status = con.getResponseCode(); - log.info("status " + status); - - String gateway = FileIO.toString(con.getInputStream()); - return gateway; - - } catch (Exception e) { - log.warn("internet not available"); - } - return null; - } - - /** - * Create which only has name (no type). This is only possible, if there is an - * appropriately named service config in the Plan (in memory) or (more commonly) - * on the filesystem. Since ServiceConfig comes with type information, a name is - * all that is needed to start the service. - * - * @param name - * @return - */ - static public ServiceInterface create(String name) { - return create(name, null); - } - - /** - * Create create(name, type) goes through the full service lifecycle of: - * - * <pre> - * clear - clearing the plan for construction of service(s) needed - * load - loading the plan for desired services - * check - checking all planned service have met appropriate licensing and dependency checks create - - * </pre> - * - * @param name - Required, cannot be null - * @param type - Can be null if a service file exists for named service - * @return the service - */ - static public ServiceInterface create(String name, String type) { - - synchronized (processLock) { - - try { - ServiceInterface si = Runtime.getService(name); - if (si != null) { - return si; - } - - Plan plan = Runtime.load(name, type); - Runtime.check(name, type); - // at this point - the plan should be loaded, now its time to create the - // children peers - // and parent service - createServicesFromPlan(plan, null, name); - si = Runtime.getService(name); - if (si == null) { - Runtime.getInstance().error("coult not create %s of type %s", name, type); - } - return si; - } catch (Exception e) { - runtime.error(e); - } - return null; - } - } - - /** - * Creates all services necessary for this service - "all peers" and the parent - * service too. At this point all type information and configuration should be - * defined in the plan. - * - * FIXME - should Plan be passed in as param ? - * - * @param name - * @return - */ - private static Map<String, ServiceInterface> createServicesFromPlan(Plan plan, - Map<String, ServiceInterface> createdServices, String name) { - - synchronized (processLock) { - - if (createdServices == null) { - createdServices = new LinkedHashMap<>(); - } - - // Plan's config - RuntimeConfig plansRtConfig = (RuntimeConfig) plan.get("runtime"); - // current Runtime config - RuntimeConfig currentConfig = Runtime.getInstance().config; - - for (String service : plansRtConfig.getRegistry()) { - ServiceConfig sc = plan.get(service); - if (sc == null) { - runtime.error("could not get %s from plan", service); - continue; - } - ServiceInterface si = createService(service, sc.type, null); - // process the base listeners/subscription of ServiceConfig - si.addConfigListeners(sc); - if (si instanceof ConfigurableService) { - try { - ((ConfigurableService) si).apply(sc); - } catch (Exception e) { - Runtime.getInstance().error( - "could not apply config of type %s to service %s, using default config", sc.type, - si.getName(), sc.type); - } - } - createdServices.put(service, si); - currentConfig.add(service); - } - - return createdServices; - } - } - - public String getServiceExample(String serviceType) { - String url = "https://raw.githubusercontent.com/MyRobotLab/myrobotlab/develop/src/main/resources/resource/" - + serviceType + "/" + serviceType + ".py"; - byte[] bytes = Http.get(url); - if (bytes != null) { - return new String(bytes); - } - return ""; - } - - public static String getPeerName(String peerKey, ServiceConfig config, Map<String, ServiceReservation> peers, - String parentName) { - - if (peerKey == null || !peers.containsKey(peerKey)) { - return null; - } - - if (config != null) { - - // dynamically get config peer name - // e.g. tilt should be a String value in config.tilt - Field[] fs = config.getClass().getDeclaredFields(); - for (Field f : fs) { - if (peerKey.equals(f.getName())) { - if (f.canAccess(config)) { - Object o; - try { - o = f.get(config); - - if (o == null) { - // config "has" the field, just set to null at the moment - // peer actual name then will be default notation - if (parentName != null) { - return String.format("%s.%s", parentName, peerKey); - } - log.warn("config has field named {} but it's null", peerKey); - return null; - } - - if (o instanceof String) { - return (String) o; - } else { - log.error("config has field named {} but it is not a string", peerKey); - break; - } - } catch (Exception e) { - log.error("getting access to field threw", e); - } - - } else { - log.error("config with field name {} but cannot access it", peerKey); - } - } - } - } - // last ditch attempt at getting the name - will default it if parentName is - // supplied - if (parentName != null) { - return String.format("%s.%s", parentName, peerKey); - } - return null; - } - - public static void check(String name, String type) { - log.info("check - implement - dependencies and licensing"); - // iterate through plan - check dependencies and licensing - } - - /** - * Use {@link #start(String, String)} instead. - * - * @param name Name of service - * @param type Type of service - * @return Created service - */ - @Deprecated /* use start */ - static public ServiceInterface createAndStart(String name, String type) { - return start(name, type); - } - - /** - * creates and starts services from a cmd line object - * - * @param services - services to be created - */ - public final static void createAndStartServices(List<String> services) { - - if (services == null) { - log.error("createAndStartServices(null)"); - return; - } - - log.info("services {}", Arrays.toString(services.toArray())); - - if (services.size() % 2 == 0) { - - for (int i = 0; i < services.size(); i += 2) { - String name = services.get(i); - String type = services.get(i + 1); - - log.info("attempting to invoke : {} of type {}", name, type); - - ServiceInterface s = Runtime.create(name, type); - - if (s != null) { - try { - s.startService(); - } catch (Exception e) { - runtime.error(e.getMessage()); - Logging.logError(e); - } - } else { - runtime.error(String.format("could not create service %s %s", name, type)); - } - - } - return; - } - Runtime.mainHelp(); - shutdown(); - } - - /** - * Setting the runtime virtual will set the platform virtual too. All subsequent - * services will be virtual - */ - @Override - public boolean setVirtual(boolean b) { - boolean changed = config.virtual != b; - config.virtual = b; - isVirtual = b; - setAllVirtual(b); - if (changed) { - broadcastState(); - } - return b; - } - - /** - * Sets all services' virtual state to {@code b}. This allows a single call to - * enable or disable virtualization across all services. - * - * @param b Whether all services should be virtual or not - * @return b - */ - static public boolean setAllVirtual(boolean b) { - for (ServiceInterface si : getServices()) { - if (!si.isRuntime()) { - si.setVirtual(b); - } - } - Runtime.getInstance().config.virtual = b; - Runtime.getInstance().broadcastState(); - return b; - } - - /** - * Sets the enable value in start.yml. start.yml is a file which can control the - * automatic loading of config. In general when its on, and a config is selected - * and saved, the next time Runtime starts it will attempt to load the last - * saved config and get the user back to their last state. - * - * @param autoStart - * @throws IOException - thrown if cannot write file to filesystem - */ - public void setAutoStart(boolean autoStart) throws IOException { - log.debug("setAutoStart {}", autoStart); - startYml.enable = autoStart; - startYml.config = configName; - FileIO.toFile("start.yml", CodecUtils.toYaml(startYml)); - invoke("getStartYml"); - } - - /** - * Framework owned method - core of creating a new service. This method will - * create a service with the given name and of the given type. If the type does - * not contain any dots, it will be assumed to be in the - * {@code org.myrobotlab.service} package. This method can currently only - * instantiate Java services, but in the future it could be enhanced to call - * native service runtimes. - * <p> - * The name parameter must not contain '/' or '@'. Thus, a full name must be - * split into its first and second part, passing the first in as the name and - * the second as the inId. This method will log an error and return null if name - * contains either of those two characters. - * <p> - * The {@code inId} is used to determine whether the service is a local one or a - * remote proxy. It should equal the Runtime ID of the MyRobotLab instance the - * service was originally instantiated under. - * - * @param name May not contain '/' or '@', i.e. cannot be a full name - * @param type The type of the new service - * @param inId The ID of the runtime the service is linked to. - * @return An existing service if the requested name and type match, otherwise a - * newly created service. If the name is null, or it contains '@' or - * '/', or a service with the same name exists but has a different type, - * will return null instead. - */ - static private ServiceInterface createService(String name, String type, String inId) { - synchronized (processLock) { - log.info("Runtime.createService {}", name); - - if (name == null) { - runtime.error("service name cannot be null"); - - return null; - } - - if (name.contains("@") || name.contains("/")) { - runtime.error("service name cannot contain '@' or '/': {}", name); - - return null; - } - - String fullName; - if (inId == null || inId.equals("")) - fullName = getFullName(name); - else - fullName = String.format("%s@%s", name, inId); - - if (type == null) { - ServiceConfig sc; - try { - sc = CodecUtils.readServiceConfig(runtime.getConfigName() + fs + name + ".yml"); - } catch (IOException e) { - runtime.error("could not find type for service %s", name); - return null; - } - if (sc != null) { - log.info("found type for {} in plan", name); - type = sc.type; - } else { - runtime.error("createService type not specified and could not get type for {} from plan", name); - return null; - } - } - - if (type == null) { - runtime.error("cannot create service {} no type in plan or yml file", name); - return null; - } - - String fullTypeName = CodecUtils.makeFullTypeName(type); - - ServiceInterface si = Runtime.getService(fullName); - if (si != null) { - if (!si.getTypeKey().equals(fullTypeName)) { - runtime.error("Service with name {} already exists but is of type {} while requested type is ", - name, si.getTypeKey(), type); - return null; - } - return si; - } - - // DO NOT LOAD HERE !!! - doing so would violate the service life cycle ! - // only try to resolve type by the plan - if not then error out - - String id = (inId == null) ? Runtime.getInstance().getId() : inId; - if (name.length() == 0 || fullTypeName == null || fullTypeName.length() == 0) { - log.error("{} not a type or {} not defined ", fullTypeName, name); - return null; - } - - // TODO - test new create of existing service - ServiceInterface sw = Runtime.getService(String.format("%s@%s", name, id)); - if (sw != null) { - log.info("service {} already exists", name); - return sw; - } - - try { - - if (log.isDebugEnabled()) { - // TODO - determine if there have been new classes added from - // ivy --> Boot Classloader --> Ext ClassLoader --> System - // ClassLoader - // http://blog.jamesdbloom.com/JVMInternals.html - log.debug("ABOUT TO LOAD CLASS"); - log.debug("loader for this class " + Runtime.class.getClassLoader().getClass().getCanonicalName()); - log.debug("parent " + Runtime.class.getClassLoader().getParent().getClass().getCanonicalName()); - log.debug("system class loader " + ClassLoader.getSystemClassLoader()); - log.debug("parent should be null" - + ClassLoader.getSystemClassLoader().getParent().getClass().getCanonicalName()); - log.debug("thread context " - + Thread.currentThread().getContextClassLoader().getClass().getCanonicalName()); - log.debug("thread context parent " - + Thread.currentThread().getContextClassLoader().getParent().getClass().getCanonicalName()); - } - - // FIXME - error if deps are missing - prompt license - // require restart ! - // FIXME - this should happen after inspecting the "loaded" "plan" not - // during the create/start/apply ! - - // create an instance - Object newService = Instantiator.getThrowableNewInstance(null, fullTypeName, name, id); - log.debug("returning {}", fullTypeName); - si = (ServiceInterface) newService; - - // si.setId(id); - if (Runtime.getInstance().getId().equals(id)) { - si.setVirtual(Runtime.getInstance().isVirtual()); - Runtime.getInstance().creationCount++; - si.setOrder(Runtime.getInstance().creationCount); - } - - if (runtime != null) { - - runtime.invoke("created", getFullName(name)); - - // add all the service life cycle subscriptions - // runtime.addListener("registered", name); - // runtime.addListener("created", name); - // runtime.addListener("started", name); - // runtime.addListener("stopped", name); - // runtime.addListener("released", name); - } - - return (Service) newService; - } catch (Exception e) { - log.error("createService failed for {}@{} of type {}", name, id, fullTypeName, e); - } - return null; - } - } - - static public Map<String, Map<String, List<MRLListener>>> getNotifyEntries() { - return getNotifyEntries(null); - } - - static public Map<String, Map<String, List<MRLListener>>> getNotifyEntries(String service) { - Map<String, Map<String, List<MRLListener>>> ret = new TreeMap<String, Map<String, List<MRLListener>>>(); - Map<String, ServiceInterface> sorted = null; - if (service == null) { - sorted = getLocalServices(); - } else { - sorted = new HashMap<String, ServiceInterface>(); - ServiceInterface si = Runtime.getService(service); - if (si != null) { - sorted.put(service, si); - } - } - for (Map.Entry<String, ServiceInterface> entry : sorted.entrySet()) { - log.info(entry.getKey() + "/" + entry.getValue()); - List<String> flks = entry.getValue().getNotifyListKeySet(); - Map<String, List<MRLListener>> subret = new TreeMap<String, List<MRLListener>>(); - for (String sn : flks) { - List<MRLListener> mrllistners = entry.getValue().getNotifyList(sn); - subret.put(sn, mrllistners); - } - ret.put(entry.getKey(), subret); - } - return ret; - } - - /** - * Dumps {@link #registry} to a file called {@code registry.json} in JSON form. - * - * @return The registry in JSON form or null if an error occurred. - */ - public static String dump() { - try { - FileOutputStream dump = new FileOutputStream("registry.json"); - String reg = CodecUtils.toJson(registry); - dump.write(reg.getBytes()); - dump.close(); - return reg; - } catch (Exception e) { - log.error("dump threw", e); - } - return null; - } - - /** - * Wraps {@link java.lang.Runtime#gc()}. - * - * Runs the garbage collector. - */ - public static final void gc() { - java.lang.Runtime.getRuntime().gc(); - } - - /** - * Although "fragile" since it relies on a external source - its useful to find - * the external ip address of NAT'd systems - * - * @return external or routers ip - * @throws Exception e - */ - public static String getExternalIp() throws Exception { - URL whatismyip = new URL("http://checkip.amazonaws.com"); - BufferedReader in = null; - try { - in = new BufferedReader(new InputStreamReader(whatismyip.openStream())); - String ip = in.readLine(); - return ip; - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - - /** - * Wraps {@link java.lang.Runtime#freeMemory()}. - * - * @return the amount of free memory in the Java Virtual Machine. Calling the gc - * method may result in increasing the value returned by freeMemory. - * - * - */ - public static final long getFreeMemory() { - return java.lang.Runtime.getRuntime().freeMemory(); - } - - /** - * Get a handle to the Runtime singleton. - * - * @return the Runtime - */ - public static Runtime getInstance() { - if (runtime == null) { - synchronized (INSTANCE_LOCK) { - try { - - RuntimeConfig c = null; - if (runtime == null) { - c = ConfigUtils.loadRuntimeConfig(options); - runtime = (Runtime) createService(RUNTIME_NAME, "Runtime", c.id); - runtime.startService(); - // klunky - Runtime.register(new Registration(runtime)); - } - - runtime.locales = Locale.getDefaults(); - - runtime.getRepo().addStatusPublisher(runtime); - runtime.startService(); - // extract resources "if a jar" - FileIO.extractResources(); - runtime.startInteractiveMode(); - if (c != null) { - runtime.apply(c); - } - - if (options.services != null && options.services.size() != 0) { - log.info("command line services were specified"); - createAndStartServices(options.services); - } - - if (options.config != null) { - log.info("command line -c config was specified"); - Runtime.startConfig(options.config); - } - - if (startYml.enable && startYml.config != null) { - log.info("start.yml is enabled and config is {}", startYml.config); - Runtime.startConfig(startYml.config); - } - - } catch (Exception e) { - log.error("runtime getInstance threw", e); - } - } // synchronized lock - } - - return runtime; - } - - /** - * The jvm args which started this process - * - * @return all jvm args in a list - */ - static public List<String> getJvmArgs() { - RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); - return runtimeMxBean.getInputArguments(); - } - - /** - * gets all non-loopback, active, non-virtual ip addresses - * - * @return list of local ipv4 IP addresses - */ - static public List<String> getIpAddresses() { - log.debug("getLocalAddresses"); - ArrayList<String> ret = new ArrayList<String>(); - - try { - Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); - while (interfaces.hasMoreElements()) { - NetworkInterface current = interfaces.nextElement(); - // log.info(current); - if (!current.isUp() || current.isLoopback() || current.isVirtual()) { - log.debug("skipping interface is down, a loopback or virtual"); - continue; - } - Enumeration<InetAddress> addresses = current.getInetAddresses(); - while (addresses.hasMoreElements()) { - InetAddress currentAddress = addresses.nextElement(); - - if (!(currentAddress instanceof Inet4Address)) { - log.debug("not ipv4 skipping"); - continue; - } - - if (currentAddress.isLoopbackAddress()) { - log.debug("skipping loopback address"); - continue; - } - log.debug(currentAddress.getHostAddress()); - ret.add(currentAddress.getHostAddress()); - } - } - } catch (Exception e) { - Logging.logError(e); - } - - if (ret.size() == 0) { - // if we don't have a "real" ip address - we always have home - ret.add("127.0.0.1"); - } - return ret; - } - - // What's the purpose of this? It doesn't return anything - static public void getNetInfo() { - try { - List<String> local = getIpAddresses(); - String gateway = getPublicGateway(); - getNetworkPeers(); - } catch (Exception e) { - log.error("getNetInfo threw", e); - } - - } - - // TODO - add network to search - static public Set<String> getNetworkPeers() throws UnknownHostException { - networkPeers = new TreeSet<>(); - // String myip = InetAddress.getLocalHost().getHostAddress(); - List<String> myips = getIpAddresses(); // TODO - if nothing else - - // 127.0.0.1 - for (String myip : myips) { - if (myip.equals("127.0.0.1")) { - log.info("This PC is not connected to any network!"); - } else { - String testIp = null; - for (int i = myip.length() - 1; i >= 0; --i) { - if (myip.charAt(i) == '.') { - testIp = myip.substring(0, i + 1); - break; - } - } - - log.info("My Device IP: " + myip + "\n"); - log.info("Search log:"); - - for (int i = 1; i <= 254; ++i) { - try { - - InetAddress addr = InetAddress.getByName(testIp + new Integer(i).toString()); - - if (addr.isReachable(1000)) { - log.info("Available: " + addr.getHostAddress()); - networkPeers.add(addr.getHostAddress()); - } else { - log.info("Not available: " + addr.getHostAddress()); - } - - // TODO - check default port 8888 8887 - - } catch (IOException ioex) { - } - } - - log.info("found {} devices", networkPeers.size()); - - for (String device : networkPeers) { - log.info(device); - } - } - } - return networkPeers; - } - - static public List<ApiDescription> getApis() { - return CodecUtils.getApis(); - } - - // @TargetApi(9) - static public List<String> getLocalHardwareAddresses() { - log.info("getLocalHardwareAddresses"); - ArrayList<String> ret = new ArrayList<String>(); - try { - Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); - while (interfaces.hasMoreElements()) { - NetworkInterface current = interfaces.nextElement(); - byte[] mac = current.getHardwareAddress(); - - if (mac == null || mac.length == 0) { - continue; - } - - String m = StringUtil.bytesToHex(mac); - log.info("mac address : {}", m); - ret.add(m); - log.info("added mac"); - } - } catch (Exception e) { - log.error("getLocalHardwareAddresses threw", e); - } - - log.info("done"); - return ret; - } - - /** - * Gets a Map between service names and the service object of all services local - * to this MRL instance. - * - * @return A Map between service names and service objects - */ - public static Map<String, ServiceInterface> getLocalServices() { - Map<String, ServiceInterface> local = new HashMap<>(); - for (String serviceName : registry.keySet()) { - // FIXME @ should be a requirement of "all" entries for consistency - if (!serviceName.contains("@") - || serviceName.endsWith(String.format("@%s", Runtime.getInstance().getId()))) { - local.put(serviceName, registry.get(serviceName)); - } - } - return local; - } - - /** - * FIXME - return - * - * @return filtering/query requests - */ - public static Map<String, ServiceInterface> getLocalServicesForExport() { - return registry; - } - - /* - * FIXME - DEPRECATE - THIS IS NOT "instance" specific info - its Class - * definition info - Runtime should return based on ClassName - * - * FIXME - INPUT PARAMETER SHOULD BE TYPE NOT INSTANCE NAME !!!! - */ - public static Map<String, MethodEntry> getMethodMap(String inName) { - String serviceName = getFullName(inName); - if (!registry.containsKey(serviceName)) { - runtime.error(String.format("%1$s not in registry - can not return method map", serviceName)); - return null; - } - - ServiceInterface sw = registry.get(serviceName); - Class<?> c = sw.getClass(); - - MethodCache cache = MethodCache.getInstance(); - return cache.getRemoteMethods(c.getTypeName()); - - } - - /** - * getServiceList returns the most important identifiers for a service which are - * it's process id, it's name, and it's type. - * <p> - * This will be part of the getHelloRequest - and the first listing from a - * process of what services are available. - * <p> - * TODO - future work would be to supply a query to the getServiceList(query) - * such that interfaces, types, or processes ids, can selectively be queried out - * of it - * - * @return list of registrations - */ - public List<Registration> getServiceList() { - synchronized (processLock) { - return registry.values().stream().map(si -> new Registration(si.getId(), si.getName(), si.getTypeKey())) - .collect(Collectors.toList()); - } - } - - // FIXME - scary function - returns private data - public static Map<String, ServiceInterface> getRegistry() { - return registry;// FIXME should return copy - } - - public static ServiceInterface getService(String inName) { - return getService(inName, new StaticType<>() { - }); - } - - public static <C extends ServiceConfig, S extends ServiceInterface & ConfigurableService<C>> S getConfigurableService( - String inName, StaticType<S> serviceType) { - return getService(inName, serviceType); - } - - /** - * Gets a running service with the specified name. If the name is null or - * there's no such service with the specified name, returns null instead. - * - * @param inName The name of the service - * @return The service if it exists, or null - */ - @SuppressWarnings("unchecked") - public static <S extends ServiceInterface> S getService(String inName, StaticType<S> serviceType) { - if (inName == null) { - return null; - } - - String name = getFullName(inName); - - if (!registry.containsKey(name)) { - return null; - } else { - return (S) registry.get(name); - } - } - - /** - * @return all service names in an array form - * - * - */ - static public String[] getServiceNames() { - Set<String> ret = registry.keySet(); - String[] services = new String[ret.size()]; - if (ret.size() == 0) { - return services; - } - - // if there are more than 0 services we need runtime - // to filter to make sure they are "local" - // and this requires a runtime service - String localId = Runtime.getInstance().getId(); - int cnt = 0; - for (String fullname : ret) { - if (fullname.endsWith(String.format("@%s", localId))) { - services[cnt] = CodecUtils.getShortName(fullname); - } else { - services[cnt] = fullname; - } - ++cnt; - } - return services; - } - - // Is it a good idea to modify all regex inputs? For example, if the pattern - // already contains ".?" then the replacement will result in "..?" - // If POSIX-style globs are desired there are different - // pattern matching engines designed for that - public static boolean match(String text, String pattern) { - return text.matches(pattern.replace("?", ".?").replace("*", ".*?")); - } - - public static List<String> getServiceNames(String pattern) { - return getServices().stream().map(NameProvider::getName).filter(serviceName -> match(serviceName, pattern)) - .collect(Collectors.toList()); - } - - /** - * @param interfaze the interface - * @return a list of service names that implement the interface - * @throws ClassNotFoundException if the class for the requested interface is - * not found. - * - */ - public static List<String> getServiceNamesFromInterface(String interfaze) throws ClassNotFoundException { - if (!interfaze.contains(".")) { - interfaze = "org.myrobotlab.service.interfaces." + interfaze; - } - - return getServiceNamesFromInterface(Class.forName(interfaze)); - } - - /** - * @param interfaze interface - * @return list of service names - * - */ - public static List<String> getServiceNamesFromInterface(Class<?> interfaze) { - return getServicesFromInterface(interfaze).stream().map(ServiceInterface::getFullName) - .collect(Collectors.toList()); - } - - /** - * Get all currently-running services - * - * @return A list of all currently-running services - */ - public static List<ServiceInterface> getServices() { - return getServices(null); - } - - /** - * Get all services that belong to an MRL instance with the given ID. - * - * @param id The ID of the MRL instance - * @return A list of the services that belong to the given MRL instance - */ - public static List<ServiceInterface> getServices(String id) { - if (id == null) { - return new ArrayList<ServiceInterface>(registry.values()); - } - - List<ServiceInterface> list = new ArrayList<>(); - // otherwise we are getting services of an instance - - for (String serviceName : registry.keySet()) { - ServiceInterface si = registry.get(serviceName); - if (si.getId().equals(id)) { - list.add(registry.get(serviceName)); - } - } - return list; - } - - /** - * @param interfaze interface - * @return results - * - */ - public ServiceTypeNameResults getServiceTypeNamesFromInterface(String interfaze) { - ServiceTypeNameResults results = new ServiceTypeNameResults(interfaze); - try { - - if (!interfaze.contains(".")) { - interfaze = "org.myrobotlab.service.interfaces." + interfaze; - } - - ServiceData sd = ServiceData.getLocalInstance(); - - List<MetaData> sts = sd.getServiceTypes(); - - for (MetaData st : sts) { - - Set<Class<?>> ancestry = new HashSet<>(); - Class<?> targetClass = Class.forName(st.getType()); // this.getClass(); - - while (targetClass.getCanonicalName().startsWith("org.myrobotlab") - && !targetClass.getCanonicalName().startsWith("org.myrobotlab.framework")) { - ancestry.add(targetClass); - targetClass = targetClass.getSuperclass(); - } - - for (Class<?> c : ancestry) { - Class<?>[] interfaces = Class.forName(c.getName()).getInterfaces(); - for (Class<?> inter : interfaces) { - if (interfaze.equals(inter.getName())) { - results.serviceTypes.add(st.getType()); - break; - } - } - } - } - - } catch (Exception e) { - error("could not find interfaces for %s - %s %s", interfaze, e.getClass().getSimpleName(), e.getMessage()); - log.error("getting class", e); - } - - return results; - } - - /** - * return a list of services which are currently running and implement a - * specific interface - * - * @param interfaze class - * @return list of service interfaces - * - */ - // FIXME !!! - use single implementation that gets parents - @Deprecated /* - * no longer used or needed - change events are pushed no longer pulled <-- Over - * complicated solution - */ - public static List<ServiceInterface> getServicesFromInterface(Class<?> interfaze) { - synchronized (processLock) { - List<ServiceInterface> ret = new ArrayList<ServiceInterface>(); - - for (String service : getServiceNames()) { - Class<?> clazz = getService(service).getClass(); - while (clazz != null) { - for (Class<?> inter : clazz.getInterfaces()) { - if (inter.getName().equals(interfaze.getName())) { - ret.add(getService(service)); - continue; - } - } - clazz = clazz.getSuperclass(); - } - } - return ret; - } - } - - /** - * Because startYml is required to be a static variable, since it's needed - * "before" a runtime instance exists it will be null in json serialization. - * This method is needed so we can serialize the data appropriately. - * - * @return - */ - static public StartYml getStartYml() { - return startYml; - } - - /** - * Gets the set of all threads currently running. - * - * @return A set containing thread objects representing all running threads - */ - static public Set<Thread> getThreads() { - return Thread.getAllStackTraces().keySet(); - } - - /** - * Wraps {@link java.lang.Runtime#totalMemory()}. - * - * @return The amount of memory available to the JVM in bytes. - */ - public static final long getTotalMemory() { - - return java.lang.Runtime.getRuntime().totalMemory(); - } - - /** - * FIXME - terrible use a uuid - * - * unique id's are need for sendBlocking - to uniquely identify the message this - * is a method to support that - it is unique within a process, but not across - * processes - * - * @return a unique id - */ - public static final synchronized long getUniqueID() { - ++uniqueID; - return System.currentTimeMillis(); - } - - /** - * Get how long this MRL instance has been running in human-readable String - * form. - * - * @return The uptime of this instance. - */ - public static String getUptime() { - Date now = new Date(); - Platform platform = Platform.getLocalInstance(); - String uptime = getDiffTime(now.getTime() - platform.getStartTime().getTime()); - log.info("up for {}", uptime); - return uptime; - } - - public static String getPlatformInfo() { - Platform platform = Platform.getLocalInstance(); - StringBuilder sb = new StringBuilder(); - sb.append(platform.getHostname()); - sb.append(" "); - sb.append(platform.getOS()); - sb.append(" "); - sb.append(platform.getArch()); - sb.append("."); - sb.append(platform.getOsBitness()); - - sb.append(" Java "); - sb.append(platform.getVmVersion()); - sb.append(" "); - sb.append(platform.getVMName()); - - return sb.toString(); - } - - /** - * Get a human-readable String form of a difference in time in milliseconds. - * - * @param diff The difference of time in milliseconds - * @return The human-readable string form of the difference in time - */ - public static String getDiffTime(long diff) { - - long diffSeconds = diff / 1000 % 60; - long diffMinutes = diff / (60 * 1000) % 60; - long diffHours = diff / (60 * 60 * 1000) % 24; - long diffDays = diff / (24 * 60 * 60 * 1000); - - StringBuffer sb = new StringBuffer(); - sb.append(diffDays).append(" days ").append(diffHours).append(" hours ").append(diffMinutes).append(" minutes ") - .append(diffSeconds).append(" seconds"); - return sb.toString(); - - } - - /** - * Get version returns the current version of mrl. It must be done this way, - * because the version may be queried on the command line without the desire to - * start a "Runtime" - * - * @return the version of the running platform instance - * - */ - public static String getVersion() { - return Platform.getLocalInstance().getVersion(); - } - - /** - * Get the latest version number of MRL in String form by querying the public - * build server. If it cannot be contacted, this method returns the String - * {@code "unknown"}. - * - * @return The latest build version in String form - */ - public static String getLatestVersion() { - String latest = "https://build.myrobotlab.org:8443/job/myrobotlab/job/develop/lastSuccessfulBuild/buildNumber"; - byte[] b = Http.get(latest); - String version = (b == null) ? "unknown" : "1.1." + new String(b); - return version; - } - - // FIXME - shouldn't this be in platform ??? - - /** - * Get the branch that this installation was built from. - * - * @return The branch - * @see Platform#getBranch() - */ - public static String getBranch() { - return Platform.getLocalInstance().getBranch(); - } - - /** - * Install all services - * - * @throws ParseException Unknown - * @throws IOException Unknown - */ - // TODO: Check throws list to see if these are still thrown - static public void install() throws ParseException, IOException { - install(null, null); - } - - /** - * Install specified service. - * - * @param serviceType Service to install - */ - static public void install(String serviceType) { - install(serviceType, null); - } - - /** - * Maximum complexity install - allows for blocking and non-blocking install. - * During typically runtime install of services - non blocking is desired, - * otherwise status info from the install is blocked until installation is - * completed. For command line installation "blocking" mode would be desired - * - * FIXME - problematic in that Runtime.create calls this directly, and this - * should be stepped through, because: If we need to install new components, a - * restart is likely needed ... we don't do custom dynamic classloaders .... yet - * - * License - should be appropriately accepted or rejected by user - * - * @param serviceType the service tyype to install - * @param blocking if this should block until done. - * - */ - static public void install(String serviceType, Boolean blocking) { - synchronized (processLock) { - Runtime r = getInstance(); - - if (blocking == null) { - blocking = false; - } - - if (installerThread != null) { - log.error("another request to install dependencies, 1st request has not completed"); - return; - } - - installerThread = new Thread() { - @Override - public void run() { - try { - if (serviceType == null) { - r.getRepo().install(); - } else { - r.getRepo().install(serviceType); - } - } catch (Exception e) { - r.error("dependencies failed - install error", e); - throw new RuntimeException( - String.format("dependencies failed - install error %s", e.getMessage())); - } - } - }; - - if (blocking) { - installerThread.run(); - } else { - installerThread.start(); - } - - installerThread = null; - } - } - - /** - * Invoke a service method. The parameter must not be null and must have at - * least 2 elements. The first is the service name and the second is the service - * method. The rest of the elements are parameters to the specified method. - * - * @param invoke The array of service name, method, and parameters - */ - static public void invokeCommands(String[] invoke) { - - if (invoke.length < 2) { - log.error("invalid invoke request, minimally 2 parameters are required: --invoke service method ..."); - return; - } - - String name = invoke[0]; - String method = invoke[1]; - - // params - Object[] data = new Object[invoke.length - 2]; - for (int i = 2; i < invoke.length; ++i) { - data[i - 2] = invoke[i]; - } - - log.info("attempting to invoke : {}.{}({})\n", name, method, Arrays.toString(data)); - getInstance().send(name, method, data); - } - - /** - * Checks if a service is local to this MRL instance. The service must exist. - * - * @param serviceName The name of the service to check - * @return Whether the specified service is local or not - */ - public static boolean isLocal(String serviceName) { - ServiceInterface sw = getService(serviceName); - return Objects.equals(sw.getId(), Runtime.getInstance().getId()); - } - - /* - * check if class is a Runtime class - * - * @return true if class == Runtime.class - */ - public static boolean isRuntime(Service newService) { - return newService.getClass().equals(Runtime.class); - } - - /** - * Start interactive mode on {@link System#in} and {@link System#out}. - * - * @see #startInteractiveMode(InputStream, OutputStream) - */ - public void startInteractiveMode() { - startInteractiveMode(System.in, System.out); - } - - /** - * Starts an interactive CLI on the specified input and output streams. The CLI - * command processor runs in its own thread and takes commands according to the - * CLI API. - * - * FIXME - have another shell script which starts jar as ws client with cli - * interface Remove this std in/out - it is overly complex and different OSs - * handle it differently Windows Java updates have broken it several times - * - * @param in The input stream to take commands from - * @param out The output stream to print command output to - * @return The constructed CLI processor - */ - public InProcessCli startInteractiveMode(InputStream in, OutputStream out) { - if (cli != null) { - log.info("already in interactive mode"); - return cli; - } - - cli = new InProcessCli(this, "runtime", in, out); - Connection c = cli.getConnection(); - stdCliUuid = (String) c.get("uuid"); - - // addRoute(".*", getName(), 100); - addConnection(stdCliUuid, cli.getId(), c); - - return cli; - } - - /** - * Stops interactive mode if it's running. - */ - public void stopInteractiveMode() { - if (cli != null) { - cli.stop(); - cli = null; - } - if (stdCliUuid != null) { - removeConnection(stdCliUuid); - stdCliUuid = null; - } - } - - /** - * prints help to the console - */ - static void mainHelp() { - new CommandLine(new CmdOptions()).usage(System.out); - } - - /** - * Logs a string message and publishes the message. - * - * @param msg The message to log and publish - * @return msg - */ - public static String message(String msg) { - getInstance().invoke("publishMessage", msg); - log.info(msg); - return msg; - } - - /** - * Listener for state publishing, updates registry - * - * @param updatedService Updated service to put in the registry - */ - public void onState(ServiceInterface updatedService) { - log.info("runtime updating registry info for remote service {}", updatedService.getName()); - registry.put(String.format("%s@%s", updatedService.getName(), updatedService.getId()), updatedService); - } - - public static Registration register(String id, String name, String typeKey, ArrayList<String> interfaces) { - synchronized (processLock) { - Registration proxy = new Registration(id, name, typeKey, interfaces); - register(proxy); - return proxy; - } - } - - /** - * Registration is the process where a remote system sends detailed info related - * to its services. It will have details on each service type, state, id, and - * other info. The registration is serializable, with state information in a - * serialized for so that stateless processes or other non-Java instances can - * register or be registered. - * - * Registration might setup subscriptions to support a UI. - * - * Additional info which will be added in the future is a method map (a swagger - * concept) and a list of supported interfaces - * - * TODO - have rules on what registrations to accept - dependent on security, - * desire, re-broadcasting configuration etc. TODO - determine rules on - * re-broadcasting based on configuration - * - * @param registration registration - * @return registration - * - */ - public static Registration register(Registration registration) { - synchronized (processLock) { - try { - - // TODO - have rules on what registrations to accept - dependent on - // security, desire, re-broadcasting configuration etc. - - String fullname = String.format("%s@%s", registration.getName(), registration.getId()); - if (registry.containsKey(fullname)) { - log.info("{} already registered", fullname); - return registration; - } - - // if (!ForeignProcessUtils.isValidTypeKey(registration.getTypeKey())) { - // log.error("Invalid type key being registered: " + - // registration.getTypeKey()); - // return null; - // } - - log.info("{}@{} registering at {} of type {}", registration.getName(), registration.getId(), - ConfigUtils.getId(), registration.getTypeKey()); - - if (!registration.isLocal(ConfigUtils.getId())) { - - // Check if we're registering a java service - if (ForeignProcessUtils.isValidJavaClassName(registration.getTypeKey())) { - - String fullTypeName; - if (registration.getTypeKey().contains(".")) { - fullTypeName = registration.getTypeKey(); - } else { - fullTypeName = String.format("org.myrobotlab.service.%s", registration.getTypeKey()); - } - - try { - // de-serialize, class exists - registration.service = Runtime.createService(registration.getName(), fullTypeName, - registration.getId()); - if (registration.getState() != null) { - copyShallowFrom(registration.service, - CodecUtils.fromJson(registration.getState(), Class.forName(fullTypeName))); - } - } catch (ClassNotFoundException classNotFoundException) { - log.error(String.format("Unknown service class for %s@%s: %s", registration.getName(), - registration.getId(), registration.getTypeKey()), classNotFoundException); - return null; - } - } else { - // We're registering a foreign process service. We don't need to - // check - // ForeignProcessUtils.isForeignTypeKey() because the type key is - // valid - // but is not a java class name - - // Class does not exist, check if registration has empty interfaces - // Interfaces should always include ServiceInterface if coming from - // remote client - if (registration.interfaces == null || registration.interfaces.isEmpty()) { - log.error("Unknown service type being registered, registration does not contain any " - + "interfaces for proxy generation: " + registration.getTypeKey()); - return null; - } - - // FIXME - probably some more clear definition about the - // requirements - // of remote - // service registration - // In general, there should be very few requirements if any, besides - // providing a - // name, and the proxy - // interface should be responsible for creating a minimal - // interpretation - // (ServiceInterface) for the remote - // service - - // Class<?>[] interfaces = registration.interfaces.stream().map(i -> - // { - // try { - // return Class.forName(i); - // } catch (ClassNotFoundException e) { - // throw new RuntimeException("Unable to load interface " + i + " - // defined in remote registration " + registration, e); - // } - // }).toArray(Class<?>[]::new); - - // registration.service = (ServiceInterface) - // Proxy.newProxyInstance(Runtime.class.getClassLoader(), - // interfaces, - // new ProxyServiceInvocationHandler(registration.getName(), - // registration.getId())); - try { - registration.service = ProxyFactory.createProxyService(registration); - log.info("Created proxy: " + registration.service); - } catch (Exception e) { - // at the moment preventing throw - Runtime.getInstance().error(e); - } - } - } - - registry.put(fullname, registration.service); - - if (runtime != null) { - - String type = registration.getTypeKey(); - - // If type does not exist in typeToNames, make it an empty hash set - // and - // return it - Set<String> names = runtime.typeToNames.computeIfAbsent(type, k -> new HashSet<>()); - names.add(fullname); - - // FIXME - most of this could be static as it represents meta data of - // class and interfaces - - // FIXME - was false - setting now to true .. because - // 1 edge case - "can something fulfill my need of an interface - is - // not - // currently - // switching to true - boolean updatedServiceLists = false; - - // maintaining interface type relations - // see if this service type is new - // PROCESS INDEXES ! - FIXME - will need this in unregister - // ALL CLASS/TYPE PROCESSING only needs to happen once per type - if (!runtime.serviceTypes.contains(type)) { - // CHECK IF "CAN FULFILL" - // add the interfaces of the new service type - Set<String> interfaces = ClassUtil.getInterfaces(registration.service.getClass(), - FILTERED_INTERFACES); - for (String interfaze : interfaces) { - Set<String> types = runtime.interfaceToType.get(interfaze); - if (types == null) { - types = new HashSet<>(); - } - types.add(registration.getTypeKey()); - runtime.interfaceToType.put(interfaze, types); - } - - runtime.typeToInterface.put(type, interfaces); - runtime.serviceTypes.add(registration.getTypeKey()); - updatedServiceLists = true; - } - - // check to see if any of our interfaces can fulfill requested ones - Set<String> myInterfaces = runtime.typeToInterface.get(type); - for (String inter : myInterfaces) { - if (runtime.interfaceToNames.containsKey(inter)) { - runtime.interfaceToNames.get(inter).add(fullname); - updatedServiceLists = true; - } - } - - if (updatedServiceLists) { - runtime.invoke("publishInterfaceToNames"); - } - - // TODO - determine rules on re-broadcasting based on configuration - runtime.invoke("registered", registration); - } - - // TODO - remove ? already get state from registration - if (!registration.isLocal(ConfigUtils.getId())) { - runtime.subscribe(registration.getFullName(), "publishState"); - } - - } catch (Exception e) { - log.error("registration threw for {}@{}", registration.getName(), registration.getId(), e); - return null; - } - - return registration; - } - } - - /** - * releases a service - stops the service, its threads, releases its resources, - * and removes registry entries - * - * FIXME - clean up subscriptions from released - * - * @param inName name to release - * @return true/false - * - */ - public static boolean releaseService(String inName) { - ServiceInterface sc = getService(inName); - if (sc != null) { - sc.releaseService(); - return true; - } - return false; - } - - /** - * Called after any subclassed releaseService has been called, this cleans up - * the registry and removes peers - * - * @param inName - * @return - */ - public static boolean releaseServiceInternal(String inName) { - synchronized (processLock) { - if (inName == null) { - log.debug("release (null)"); - return false; - } - - String name = getFullName(inName); - - String id = CodecUtils.getId(name); - if (!id.equals(Runtime.getInstance().getId())) { - log.warn("will only release local services - %s is remote", name); - return false; - } - - log.info("releasing service {}", name); - - if (!registry.containsKey(name)) { - log.info("{} not registered", name); - return false; - } - - // get reference from registry - ServiceInterface si = registry.get(name); - if (si == null) { - log.warn("cannot release {} - not in registry"); - return false; - } - - // FIXME - TODO invoke and or blocking on preRelease - Future - - // send msg to service to self terminate - if (si.isLocal()) { - si.purgeTasks(); - si.stopService(); - } else { - if (runtime != null) { - runtime.send(name, "releaseService"); - } - } - - // recursive peer release - Map<String, Peer> peers = si.getPeers(); - if (peers != null) { - for (Peer peer : peers.values()) { - release(peer.name); - } - } - - // FOR remote this isn't correct - it should wait for - // a message from the other runtime to say that its released - unregister(name); - return true; - } - } - - /** - * Removes registration for a service. Removes the service from - * {@link #typeToInterface} and {@link #interfaceToNames}. - * - * @param inName Name of the service to unregister - */ - public static void unregister(String inName) { - synchronized (processLock) { - String name = getFullName(inName); - log.info("unregister {}", name); - - // get reference from registry - ServiceInterface sw = registry.get(name); - if (sw == null) { - log.debug("{} already unregistered", name); - return; - } - - // you have to send released before removing from registry - if (runtime != null) { - runtime.invoke("released", inName); // <- DO NOT CHANGE THIS IS CORRECT - // !! - // it should be FULLNAME ! - // runtime.broadcast("released", inName); - String type = sw.getTypeKey(); - - boolean updatedServiceLists = false; - - // check to see if any of our interfaces can fullfill requested ones - Set<String> myInterfaces = runtime.typeToInterface.get(type); - if (myInterfaces != null) { - for (String inter : myInterfaces) { - if (runtime.interfaceToNames.containsKey(inter)) { - runtime.interfaceToNames.get(inter).remove(name); - updatedServiceLists = true; - } - } - } - - if (updatedServiceLists) { - runtime.invoke("publishInterfaceToNames"); - } - - } - - // FIXME - release autostarted peers ? - - // last step - remove from registry by making new registry - // thread safe way - Map<String, ServiceInterface> removedService = new TreeMap<>(); - for (String key : registry.keySet()) { - if (!name.equals(key)) { - removedService.put(key, registry.get(key)); - } - } - registry = removedService; - - // and config - RuntimeConfig c = (RuntimeConfig) Runtime.getInstance().config; - if (c != null) { - c.remove(CodecUtils.getShortName(name)); - } - - log.info("released {}", name); - } - } - - /** - * Get all remote services. - * - * @return List of remote services as proxies - */ - public List<ServiceInterface> getRemoteServices() { - return getRemoteServices(null); - } - - /** - * Get remote services associated with the MRL instance that has the given ID. - * - * @param id The id of the target MRL instance - * @return A list of services running on the target instance - */ - public List<ServiceInterface> getRemoteServices(String id) { - List<ServiceInterface> list = new ArrayList<>(); - for (String serviceName : registry.keySet()) { - if (serviceName.contains("@")) { - String sid = serviceName.substring(serviceName.indexOf("@") + 1); - if (id == null || sid.equals(id)) { - list.add(registry.get(serviceName)); - } - } - } - return list; - } - - /** - * Releases all local services including Runtime asynchronously. - * - * @see #releaseAll(boolean, boolean) - */ - public static void releaseAll() { - releaseAll(true, false); - } - - /** - * This does not EXIT(1) !!! releasing just releases all services - * - * FIXME FIXME FIXME - just call release on each - possibly saving runtime for - * last .. send prepareForRelease before releasing - * - * release all local services - * - * FIXME - there "should" be an order to releasing the correct way would be to - * save the Runtime for last and broadcast all the services being released - * - * FIXME - send SHUTDOWN event to all running services with a timeout period - - * end with System.exit() FIXME normalize with releaseAllLocal and - * releaseAllExcept - * - * local only? YES !!! LOCAL ONLY !! - * - * @param releaseRuntime Whether the Runtime should also be released - */ - public static void releaseAll(boolean releaseRuntime, boolean block) { - // a command thread is issuing this command is most likely - // tied to one of the services being removed - // therefore this needs to happen asynchronously otherwise - // the thread that issued the command will try to destroy/release itself - // which almost always causes a deadlock - log.debug("releaseAll"); - - if (block) { - processRelease(releaseRuntime); - ConfigUtils.reset(); - } else { - - new Thread() { - @Override - public void run() { - processRelease(releaseRuntime); - ConfigUtils.reset(); - } - }.start(); - - } - } - - /** - * Releases all threads and can be executed in a separate thread. - * - * @param releaseRuntime Whether the Runtime should also be released - */ - static private void processRelease(boolean releaseRuntime) { - synchronized (processLock) { - // reverse release to order of creation - Collection<ServiceInterface> local = getLocalServices().values(); - List<ServiceInterface> ordered = new ArrayList<>(local); - ordered.removeIf(Objects::isNull); - Collections.sort(ordered); - Collections.reverse(ordered); - - for (ServiceInterface sw : ordered) { - - // no longer needed now - runtime "should be" guaranteed to be last - if (sw == Runtime.getInstance()) { - // skipping runtime - continue; - } - - log.info("releasing service {}", sw.getName()); - - try { - sw.releaseService(); - } catch (Exception e) { - if (runtime != null) { - runtime.error("%s threw while releasing", e); - } - log.error("release", e); - } - } - - // clean up remote ... the contract should - // probably be just remove their references - do not - // ask for them to be released remotely .. - // in thread safe way - - if (releaseRuntime) { - if (runtime != null) { - runtime.releaseService(); - } - synchronized (INSTANCE_LOCK) { - runtime = null; - } - } else { - // put runtime in new registry - Runtime.getInstance(); - registry = new TreeMap<>(); - registry.put(runtime.getFullName(), registry.get(runtime.getFullName())); - } - } - } - - /** - * Shuts down this instance after the given number of seconds. - * - * @param seconds sets task to shutdown in (n) seconds - */ - // Why is this using the wrapper type? Null can be passed in and cause NPE - public static void shutdown(Integer seconds) { - log.info("shutting down in {} seconds", seconds); - if (seconds > 0) { - runtime.addTaskOneShot(seconds * 1000, "shutdown", (Object[]) null); - runtime.invoke("publishShutdown", seconds); - } else { - shutdown(); - } - } - - /** - * shutdown terminates the currently running Java virtual machine by initiating - * its shutdown sequence. This method never returns normally. The argument - * serves as a status code; by convention, a nonzero status code indicates - * abnormal termination - * - */ - public static void shutdown() { - try { - log.info("myrobotlab shutting down"); - - if (runtime != null) { - log.info("stopping interactive mode"); - runtime.stopInteractiveMode(); - } - - log.info("pre shutdown on all services"); - for (ServiceInterface service : getServices()) { - service.preShutdown(); - } - - log.info("releasing all"); - - // release - releaseAll(); - } catch (Exception e) { - log.error("something threw - continuing to shutdown", e); - } - - // calling System.exit(0) before some specialized threads - // are completed will actually end up in a deadlock - Service.sleep(1000); - System.exit(0); - } - - public Integer publishShutdown(Integer seconds) { - return seconds; - } - - /** - * publish the folders of the parent directory of configPath if the configPath - * is null then publish directory names of data/config - * - * @return list of configs - */ - public List<String> publishConfigList() { - configList = new ArrayList<>(); - - File configDirFile = new File(ROOT_CONFIG_DIR); - if (!configDirFile.exists() || !configDirFile.isDirectory()) { - error("%s config root does not exist", configDirFile.getAbsolutePath()); - return configList; - } - - File[] files = configDirFile.listFiles(); - if (files == null) { - // We checked for if directory earlier, so can only be null for IO error - error("IO error occurred while listing config directory files"); - return configList; - } - for (File file : files) { - String n = file.getName(); - - if (!file.isDirectory() || file.isHidden()) { - log.info("ignoring {} expecting directory not file", n); - continue; - } - - configList.add(file.getName()); - } - Collections.sort(configList); - return configList; - } - - /** - * Releases all local services except the services whose names are in the given - * set - * - * @param saveMe The set of services that should not be released - */ - public static void releaseAllServicesExcept(HashSet<String> saveMe) { - log.info("releaseAllServicesExcept"); - List<ServiceInterface> list = Runtime.getServices(); - for (ServiceInterface si : list) { - if (saveMe != null && saveMe.contains(si.getName())) { - log.info("leaving {}", si.getName()); - } else { - si.releaseService(); - } - } - } - - /** - * Release a specific service. Releasing shuts down the service and removes it - * from registries. - * - * @param fullName full name The service to be released - * - */ - static public void release(String fullName) { - releaseService(fullName); - } - - /** - * Disconnect from remote process. FIXME - not implemented - * - * @throws IOException Unknown - */ - // FIXME - implement ! also implement the callback events .. onDisconnect - public void disconnect() throws IOException { - // connect("admin", "ws://localhost:8887/api/messages"); - log.info("disconnect"); - } - - /** - * FIXME - can this be renamed back to attach ? jump to another process using - * the cli - * - * @param id instance id. - * @return string - * - */ - // FIXME - remove - the way to 'jump' is just to change - // context to the correct mrl id e.g. cd /runtime@remote07 - public String jump(String id) { - Connection c = getRoute(stdCliUuid); - if (c != null && c.get("cli") != null) { - ((InProcessCli) c.get("cli")).setRemote(id); - } else { - log.error("connection or cli is null for uuid {}", stdCliUuid); - } - - return id; - } - - /** - * Reconnects {@link #cli} to this process. - * - * @return The id of this instance - */ - // FIXME - remove ?!?!!? - public String exit() { - Connection c = getConnection(stdCliUuid); - if (c != null && c.get("cli") != null) { - ((InProcessCli) c.get("cli")).setRemote(getId()); - } - return getId(); - } - - /** - * Send a command to the {@link InProcessCli}. - * - * @param srcFullName Unknown - * @param cmd The command to execute - */ - public void sendToCli(String srcFullName, String cmd) { - Connection c = getConnection(stdCliUuid); - if (c == null || c.get("cli") == null) { - log.info("starting interactive mode"); - startInteractiveMode(); - sleep(1000); - } - c = getConnection(stdCliUuid); - if (c != null && c.get("cli") != null) { - ((InProcessCli) c.get("cli")).process(srcFullName, cmd); - } else { - log.error("could not start interactive mode"); - } - } - - /** - * Connect to the MRL instance at the given URL, auto-reconnecting if specified - * and the connection drops. - * - * FIXME implement autoReconnect - * - * @param url The URL to connect to - * @param autoReconnect Whether the connection should be re-established if it is - * dropped - */ - // FIXME - implement - public void connect(String url, boolean autoReconnect) { - if (!autoReconnect) { - connect(url); - } else { - addTask(1000, "checkConnections"); - } - } - - // FIXME - implement - public void checkConnections() { - for (Connection connection : connections.values()) { - if (connection.containsKey("url")) { - /* - * FIXME - check on "STATE" ... means we support disconnected connections .. if - * (connection.get("url").toString().equals(url)) { // already connected - * continue; } - */ - } - } - // could not find our connection for this "id" - need to reconnect - // connect(url); - } - - // FIXME - - // step 1 - first bind the uuids (1 local and 1 remote) - // step 2 - Clients will contain attribute - // FIXME - RETRIES TIMEOUTS OTHER COMPLEXITIES - // blocking connect - consider a non-blocking thread connect ... e.g. - // autoConnect - - /** - * Connect to the MRL instance at the given URL - * - * @param url Where the MRL instance being connected to is located - */ - @Override - public void connect(String url) { - try { - - // TODO - do auth, ssl and unit tests for them - // TODO - get session id - // request default describe - on describe do registrations .. zzz - - // standardize request - TODO check for ws wss not http https - if (!url.contains("api/messages")) { - url += "/api/messages"; - } - - if (!url.contains("id=")) { - url += "?id=" + getId(); - } - - WsClient client2 = new WsClient(); - client2.connect(this, url); - - // URI uri = new URI(url); - // adding "id" as full url :P ... because we don't know it !!! - Connection connection = new Connection(client2.getId(), getId(), getFullName()); - - // connection specific - connection.put("c-type", "Runtime"); - // attributes.put("c-endpoint", endpoint); - connection.put("c-client", client2); - - // cli specific - connection.put("cwd", "/"); - connection.put("url", url); - connection.put("uri", url); // not really correct - connection.put("user", "root"); - connection.put("host", "local"); - - // addendum - connection.put("User-Agent", "runtime-client"); - - addConnection(client2.getId(), url, connection); - - // direct send - may not have and "id" so it will be too runtime vs - // runtime@{id} - // subscribe to "describe" - MRLListener listener = new MRLListener("describe", getFullName(), "onDescribe"); - Message msg = Message.createMessage(getFullName(), "runtime", "addListener", listener); - client2.send(CodecUtils.toJsonMsg(msg)); - - // send describe - client2.send(CodecUtils.toJsonMsg(getDescribeMsg(null))); - - } catch (Exception e) { - log.error("connect to {} giving up {}", url, e.getMessage()); - } - } - - /** - * FIXME - this is a gateway callback - probably should be in the gateway - * interface - this is a "specific" gateway that supports typeless json or - * websockets - * <p> - * FIXME - decoding should be done at the Connection ! - this should be - * onRemoteMessage(msg) ! - * <p> - * callback - from clientRemote - all client connections will recieve here TODO - * - get clients directional api - an api per direction incoming and outgoing - * - * @param uuid - connection for incoming data - * @param data Incoming message in JSON String form - */ - @Override // uuid - public void onRemoteMessage(String uuid, String data) { - try { - - // log.debug("connection {} responded with {}", uuid, data); - // get api - decode msg - process it - Connection connection = getConnection(uuid); - if (connection == null) { - error("no connection with uuid %s", uuid); - return; - } - - if (log.isDebugEnabled()) { - log.debug("data - [{}]", data); - } - - // decoding message envelope - Message msg = CodecUtils.fromJson(data, Message.class); - log.info("==> {} --> {}.{}", msg.sender, msg.name, msg.method); - msg.setProperty("uuid", uuid); // Properties ???? REMOVE ??? - - if (msg.containsHop(getId())) { - log.error("{} dumping duplicate hop msg to avoid cyclical from {} --to--> {}.{} | {}", getName(), - msg.sender, msg.name, msg.method, msg.getHops()); - return; - } - - addRoute(msg.getSrcId(), uuid, 10); - - // add our id - we don't want to see it again - msg.addHop(getId()); - - Object ret = null; - - // FIXME - see if same code block exists in WebGui .. normalize - if (isLocal(msg)) { - - // log.info("--> {}.{} from {}", msg.name, msg.method, msg.sender); - - String serviceName = msg.getName(); - // to decode fully we need class name, method name, and an array of json - // encoded parameters - MethodCache cache = MethodCache.getInstance(); - Class<?> clazz = Runtime.getClass(serviceName); - if (clazz == null) { - log.error("local msg but no Class for requested service {}", serviceName); - return; - } - Object[] params = cache.getDecodedJsonParameters(clazz, msg.method, msg.data); - - Method method = cache.getMethod(clazz, msg.method, params); - ServiceInterface si = Runtime.getService(serviceName); - if (method == null) { - log.error("cannot find {}", cache.makeKey(clazz, msg.method, cache.getParamTypes(params))); - return; - } - if (si == null) { - log.error("si null for serviceName {}", serviceName); - return; - } - - ret = method.invoke(si, params); - - // propagate return data to subscribers - si.out(msg.method, ret); - - } else { - log.info("GATEWAY {} RELAY {} --to--> {}.{}", getName(), msg.sender, msg.name, msg.method); - send(msg); - } - - } catch (Exception e) { - log.error("processing msg threw", e); - } - } - - /** - * Add a route to the route table - * - * @param remoteId Id of the remote instance - * @param uuid Unknown - * @param metric Unknown - * @see RouteTable#addRoute(String, String, int) - */ - public void addRoute(String remoteId, String uuid, int metric) { - routeTable.addRoute(remoteId, uuid, metric); - } - - /** - * Start Runtime with the specified config - * - * @param configName The name of the config file - */ - static public void startConfig(String configName) { - setConfig(configName); - Runtime runtime = Runtime.getInstance(); - runtime.processingConfig = true; // multiple inbox threads not available - runtime.invoke("publishConfigStarted", configName); - RuntimeConfig rtConfig = runtime.readServiceConfig(runtime.getConfigName(), "runtime", new StaticType<>() { - }); - if (rtConfig == null) { - runtime.error("cannot find %s%s%s", runtime.getConfigName(), fs, "runtime.yml"); - return; - } - - runtime.apply(rtConfig); - - Plan plan = new Plan("runtime"); - // for every service listed in runtime registry - load it - // FIXME - regex match on filesystem matches on *.yml - for (String service : rtConfig.getRegistry()) { - - if ("runtime".equals(service) || Runtime.isStarted(service)) { - continue; - } - - // has to be loaded - File file = new File(Runtime.ROOT_CONFIG_DIR + fs + runtime.getConfigName() + fs + service + ".yml"); - if (!file.exists()) { - runtime.error("cannot read file %s - skipping", file.getPath()); - continue; - } - - ServiceConfig sc = runtime.readServiceConfig(runtime.getConfigName(), service); - try { - if (sc == null) { - continue; - } - runtime.loadService(plan, service, sc.type, true, 0); - } catch (Exception e) { - runtime.error(e); - } - } - - // for all newly created services start them - Map<String, ServiceInterface> created = Runtime.createServicesFromPlan(plan, null, null); - for (ServiceInterface si : created.values()) { - si.startService(); - } - - runtime.processingConfig = false; // multiple inbox threads not available - runtime.invoke("publishConfigFinished", configName); - - } - - public String publishConfigStarted(String configName) { - log.info("publishConfigStarted {}", configName); - // Make Note: done inline, because the thread actually doing the config - // processing - // would need to be finished with it before this thread could be invoked - // if multiple inbox threads were available then this would be possible - // processingConfig = true; - return configName; - } - - public String publishConfigFinished(String configName) { - log.info("publishConfigFinished {}", configName); - // Make Note: done inline, because the thread actually doing the config - // processing - // would need to be finished with it before this thread could be invoked - // if multiple inbox threads were available then this would be possible - // processingConfig = false; - return configName; - } - - /** - * Start a service of the specified type as the specified name. - * - * @param name The name of the new service - * @param type The type of the new service - * @return The started service - */ - static public ServiceInterface start(String name, String type) { - synchronized (processLock) { - try { - - ServiceInterface requestedService = Runtime.getService(name); - if (requestedService != null) { - log.info("requested service already exists"); - if (requestedService.isRunning()) { - log.info("requested service already running"); - } else { - requestedService.startService(); - } - return requestedService; - } - - Plan plan = Runtime.load(name, type); - - Map<String, ServiceInterface> services = createServicesFromPlan(plan, null, name); - - if (services == null) { - Runtime.getInstance().error("cannot create instance of %s with type %s given current configuration", - name, type); - return null; - } - - requestedService = Runtime.getService(name); - - // FIXME - does some order need to be maintained e.g. all children - // before - // parent - // breadth first, depth first, external order ordinal ? - for (ServiceInterface service : services.values()) { - if (service.getName().equals(name)) { - continue; - } - if (!Runtime.isStarted(service.getName())) { - service.startService(); - } - } - - if (requestedService == null) { - Runtime.getInstance().error("could not start %s of type %s", name, type); - return null; - } - - // getConfig() was problematic here for JMonkeyEngine - ServiceConfig sc = requestedService.getConfig(); - // Map<String, Peer> peers = sc.getPeers(); - // if (peers != null) { - // for (String p : peers.keySet()) { - // Peer peer = peers.get(p); - // log.info("peer {}", peer); - // } - // } - // recursive - start peers of peers of peers ... - Map<String, Peer> subPeers = sc.getPeers(); - if (sc != null && subPeers != null) { - for (String subPeerKey : subPeers.keySet()) { - // IF AUTOSTART !!! - Peer subPeer = subPeers.get(subPeerKey); - if (subPeer.autoStart) { - Runtime.start(sc.getPeerName(subPeerKey), subPeer.type); - } - } - } - - requestedService.startService(); - return requestedService; - } catch (Exception e) { - runtime.error(e); - } - return null; - } - } - - /** - * single parameter name info supplied - potentially all information regarding - * this service could be found in on the filesystem or in the plan - * - * @param name - * @return - */ - static public ServiceInterface start(String name) { - synchronized (processLock) { - if (Runtime.getService(name) != null) { - // already exists - ServiceInterface si = Runtime.getService(name); - if (!si.isRunning()) { - si.startService(); - } - return si; - } - Plan plan = Runtime.load(name, null); - Map<String, ServiceInterface> services = createServicesFromPlan(plan, null, name); - // FIXME - order ? - for (ServiceInterface service : services.values()) { - service.startService(); - } - return Runtime.getService(name); - } - } - - public static Plan load(String name, String type) { - synchronized (processLock) { - try { - Runtime runtime = Runtime.getInstance(); - return runtime.loadService(new Plan("runtime"), name, type, true, 0); - } catch (IOException e) { - runtime.error(e); - } - return null; - } - } - - /** - * Construct a new Runtime with the given name and ID. The name should always be - * "runtime" as parts of interprocess communication assume it to be so. - * - * TODO Check if there's a way to remove the assumptions about Runtime's name - * - * @param n Name of the runtime. Should always be {@code "runtime"} - * @param id The ID of the instance this runtime belongs to. - */ - public Runtime(String n, String id) { - super(n, id); - - // because you need to start with something ... - config = new RuntimeConfig(); - - repo = (IvyWrapper) Repo.getInstance(LIBRARIES, "IvyWrapper"); - - /** - * This is used to run through all the possible services and determine if they - * have any missing dependencies. If they do not they become "installed". The - * installed flag makes the gui do a crossout when a service type is selected. - */ - for (MetaData metaData : serviceData.getServiceTypes()) { - Set<ServiceDependency> deps = repo.getUnfulfilledDependencies(metaData.getType()); - if (deps.size() == 0) { - metaData.installed = true; - } else { - log.info("{} not installed", metaData.getSimpleName()); - } - } - - setLocale(Locale.getDefault().getTag()); - locales = Locale.getDefaults(); - - if (runtime.platform == null) { - runtime.platform = Platform.getLocalInstance(); - } - - // setting the id and the platform - platform = Platform.getLocalInstance(); - - String libararyPath = System.getProperty("java.library.path"); - String userDir = System.getProperty("user.dir"); - String userHome = System.getProperty("user.home"); - - // initialize the config list - publishConfigList(); - - // TODO this should be a single log statement - // http://developer.android.com/reference/java/lang/System.html - - String format = "yyyy/MM/dd HH:mm:ss"; - SimpleDateFormat sdf = new SimpleDateFormat(format); - SimpleDateFormat gmtf = new SimpleDateFormat(format); - gmtf.setTimeZone(TimeZone.getTimeZone("UTC")); - log.info("============== args begin =============="); - StringBuffer sb = new StringBuffer(); - - jvmArgs = getJvmArgs(); - if (globalArgs != null) { - for (int i = 0; i < globalArgs.length; ++i) { - sb.append(globalArgs[i]); - } - } - if (jvmArgs != null) { - log.info("jvmArgs {}", Arrays.toString(jvmArgs.toArray())); - } - log.info("file.encoding {}", System.getProperty("file.encoding")); - log.info("args {}", Arrays.toString(globalArgs)); - - log.info("============== args end =============="); - - log.info("============== env begin =============="); - - Map<String, String> env = System.getenv(); - if (env.containsKey("PATH")) { - log.info("PATH={}", env.get("PATH")); - } else { - log.info("PATH not defined"); - } - if (env.containsKey("JAVA_HOME")) { - log.info("JAVA_HOME={}", env.get("JAVA_HOME")); - } else { - log.info("JAVA_HOME not defined"); - } - - // also look at bitness detection in framework.Platform - String procArch = env.get("PROCESSOR_ARCHITECTURE"); - String procArchWow64 = env.get("PROCESSOR_ARCHITEW6432"); - if (procArch != null) { - log.info("PROCESSOR_ARCHITECTURE={}", procArch); - } else { - log.info("PROCESSOR_ARCHITECTURE not defined"); - } - if (procArchWow64 != null) { - log.info("PROCESSOR_ARCHITEW6432={}", procArchWow64); - } else { - log.info("PROCESSOR_ARCHITEW6432 not defined"); - } - log.info("============== env end =============="); - - log.info("============== platform =============="); - long startTime = platform.getStartTime().getTime(); - log.info("{} - GMT - {}", sdf.format(startTime), gmtf.format(startTime)); - log.info("pid {}", platform.getPid()); - log.info("hostname {}", platform.getHostname()); - log.info("ivy [runtime,{}.{}.{}]", platform.getArch(), platform.getJvmBitness(), platform.getOS()); - log.info("version {} branch {} commit {} build {}", platform.getVersion(), platform.getBranch(), - platform.getCommit(), platform.getBuild()); - System.out.println(String.format("version %s branch %s commit %s build %s", platform.getVersion(), - platform.getBranch(), platform.getCommit(), platform.getBuild())); - log.info("platform manifest {}", Platform.getManifest()); - log.info("platform [{}}]", platform); - log.info("version [{}]", platform.getVersion()); - log.info("root [{}]", FileIO.getRoot()); - log.info("cfg dir [{}]", FileIO.getCfgDir()); - log.info("sun.arch.data.model [{}]", System.getProperty("sun.arch.data.model")); - - log.info("============== non-normalized =============="); - log.info("os.name [{}] getOS [{}]", System.getProperty("os.name"), platform.getOS()); - log.info("os.arch [{}] getArch [{}]", System.getProperty("os.arch"), platform.getArch()); - log.info("os.version [{}]", System.getProperty("os.version")); - - log.info("java.vm.name [{}]", System.getProperty("java.vm.name")); - log.info("java.vm.vendor [{}]", System.getProperty("java.vm.vendor")); - log.info("java.specification.version [{}]", System.getProperty("java.specification.version")); - - String vmVersion = System.getProperty("java.specification.version"); - vmVersion = "11"; - if ("1.8".equals(vmVersion)) { - error("Unsupported Java %s - please remove version and install Java 1.8", vmVersion); - } - - // test ( force encoding ) - // System.setProperty("file.encoding","UTF-8" ); - log.info("file.encoding [{}]", System.getProperty("file.encoding")); - log.info("Charset.defaultCharset() [{}]", Charset.defaultCharset()); - log.info("user.language [{}]", System.getProperty("user.language")); - log.info("user.country [{}]", System.getProperty("user.country")); - log.info("user.variant [{}]", System.getProperty("user.variant")); - - // System.getProperty("pi4j.armhf") - - log.info("java.home [{}]", System.getProperty("java.home")); - log.debug("java.class.path [{}]", System.getProperty("java.class.path")); - log.info("java.library.path [{}]", libararyPath); - log.info("user.dir [{}]", userDir); - - log.info("user.home [{}]", userHome); - log.info("total mem [{}] Mb", Runtime.getTotalMemory() / 1048576); - log.info("total free [{}] Mb", Runtime.getFreeMemory() / 1048576); - // Access restriction - log.info("total physical mem [{}] Mb", - // Runtime.getTotalPhysicalMemory() / 1048576); - - if (platform.isWindows()) { - log.info("guessed os bitness [{}]", platform.getOsBitness()); - // try to compare os bitness with jvm bitness - if (platform.getOsBitness() != platform.getJvmBitness()) { - log.warn("detected possible bitness mismatch between os & jvm"); - } - } - - log.info("getting local repo"); - - if (repo != null)/* transient */ { - repo.addStatusPublisher(this); - } - } - - /** - * Get the process ID of the current JVM. - * - * @return The process ID. - * @see Platform#getPid() - */ - public String getPid() { - return Platform.getLocalInstance().getPid(); - } - - public String publishDefaultRoute(String defaultRoute) { - return defaultRoute; - } - - /** - * Get the hostname of the computer this instance is running on. - * - * @return The computer's hostname - * @see Platform#getHostname() - */ - public String getHostname() { - return Platform.getLocalInstance().getHostname(); - } - - /** - * publishing event - since checkForUpdates may take a while - */ - public void checkingForUpdates() { - log.info("checking for updates"); - } - - /** - * Read an entire input stream as a string and return it. If the input stream - * does not have any more tokens, returns an empty string instead. - * - * @param is The input stream to read from - * @return The entire input stream read as a string - */ - static public String getInputAsString(InputStream is) { - try (java.util.Scanner s = new java.util.Scanner(is)) { - return s.useDelimiter("\\A").hasNext() ? s.next() : ""; - } - } - - /** - * list the contents of the current working directory - * - * @return object - */ - public Object ls() { - return ls(null, null); - } - - /** - * List the contents of an absolute path. - * - * @param path The path to list - * @return The contents of the directory - */ - public Object ls(String path) { - return ls(null, path); - } - - /** - * list the contents of a specific path - * <p> - * </p> - * TODO It looks like this only returns Object because it wants to return either - * a String array or a method entry list. It would probably be best to just - * convert the method entry list to a string array using streams and change the - * signature to match. - * - * @param contextPath c - * @param path p - * @return object - * - */ - public Object ls(String contextPath, String path) { - String absPath = null; - - if (contextPath != null) { - path = contextPath + path; - } - - if (path == null) { - path = "/"; - } - - // ALL SHOULD BE ABSOLUTE PATH AT THIS POINT - // IE STARTING WITH / - - if (!path.startsWith("/")) { - path = "/" + path; - } - - absPath = path; - - String[] parts = absPath.split("/"); - - String ret = null; - if (absPath.equals("/")) { - return Runtime.getServiceNames(); - } else if (parts.length == 2 && !absPath.endsWith("/")) { - return Runtime.getService(parts[1]); - } else if (parts.length == 2 && absPath.endsWith("/")) { - ServiceInterface si = Runtime.getService(parts[1]); - if (si == null) { - return null; - } - return si.getDeclaredMethodNames(); - /* - * } else if (parts.length == 3 && !absPath.endsWith("/")) { // execute 0 - * parameter function ??? return Runtime.getService(parts[1]); - */ - } else if (parts.length == 3) { - ServiceInterface si = Runtime.getService(parts[1]); - MethodCache cache = MethodCache.getInstance(); - List<MethodEntry> me = cache.query(si.getTypeKey(), parts[2]); - return me; // si.getMethodMap().get(parts[2]); - } - return ret; - } - - /** - * serviceName at id - * - * @return runtime name with instance id. - * - */ - public String whoami() { - return "runtime@" + getId(); - } - - // end cli commands ---- - - // ---------- Java Runtime wrapper functions begin -------- - /** - * Executes the specified command and arguments in a separate process. Returns - * the exit value for the subprocess. - * - * @param program The name of or path to an executable program. If given a name, - * the program must be on the system PATH. - * @return The exit value of the subprocess - */ - static public String exec(String program) { - return execute(program, null, null, null, null); - } - - /* - * FIXME - see if this is used anymore publishing point of Ivy sub system - - * sends event failedDependency when the retrieve report for a Service fails - */ - @Deprecated /* remove */ - public String failedDependency(String dep) { - return dep; - } - - public static Platform getPlatform() { - return getInstance().platform; - } - - // FIXME - should be removed - use Platform.getLocalInstance().is64bit() - @Deprecated - public boolean is64bit() { - return getInstance().platform.getJvmBitness() == 64; - } - - public Repo getRepo() { - return repo; - } - - /** - * Returns an array of all the simple type names of all the possible services. - * The data originates from the repo's serviceData.json file. - * <p> - * There is a local one distributed with the installation jar. When an "update" - * is forced, MRL will try to download the latest copy from the repo. - * <p> - * The serviceData.json lists all service types, dependencies, categories and - * other relevant information regarding service creation - * - * @return list of all service type names - */ - public String[] getServiceTypeNames() { - return getServiceTypeNames("all"); - } - - /** - * getServiceTypeNames will publish service names based on some filter criteria - * - * @param filter f - * @return array of service types - * - */ - public String[] getServiceTypeNames(String filter) { - return serviceData.getServiceTypeNames(filter); - } - - // FIXME THIS IS NOT NORMALIZED !!! - - /** - * Send the full log of the currently running MRL instance to the MyRobotLab - * developers for help. The userID is the name of the MyRobotLab.org user - * account - * - * @param userId Name of the MRL website account to link the log to - * @return Whether the log was sent successfully, info if yes and error if no. - */ - static public Status noWorky(String userId) { - Status status = null; - try { - String retStr = HttpRequest.postFile("http://noworky.myrobotlab.org/no-worky", userId, "file", - new File(LoggingFactory.getLogFileName())); - if (retStr.contains("Upload:")) { - log.info("noWorky successfully sent - our crack team of experts will check it out !"); - status = Status.info("no worky sent"); - } else { - status = Status.error("could not send"); - } - } catch (Exception e) { - log.error("the noWorky didn't worky !"); - status = Status.error(e); - } - - // this makes the 'static' of this method pointless - // perhaps the webgui should invoke rather than call directly .. :P - Runtime.getInstance().invoke("publishNoWorky", status); - return status; - } - - static public Status publishNoWorky(Status status) { - return status; - } - - // FIXME - create interface for this - public String publishMessage(String msg) { - return msg; - } - - @Override - @Deprecated /* use onResponse ??? */ - public void onMessage(Message msg) { - // TODO: what do we do when we get a message? - log.info("onMessage()"); - } - - /** - * Publishing point when a service was successfully registered locally - - * regardless if the service is local or not. - * - * TODO - more business logic can be created here to limit broadcasting or - * re-broadcasting published registrations - * - * @param registration - contains all the information need for a registration to - * process - */ - @Override - public Registration registered(Registration registration) { - return registration; - } - - /** - * released event - when a service is successfully released from the registry - * this event is triggered - * - */ - @Override - public String released(String name) { - return name; - } - - /** - * A function for runtime to "save" a service - or if the service does not - * exists save the "default" config of that type of service - * - * @param name name of service to export - * @return true/false - * @throws IOException boom - * - */ - @Deprecated /* use save(name) */ - public boolean export(String name /* , String type */) throws IOException { - return save(name); - } - - public boolean save(String name /* , String type */) throws IOException { - ServiceInterface si = getService(name); - if (si != null) { - return si.save(); - } - error("cannot save %s - does not exist", name); - return false; - } - - /** - * restart occurs after applying updates - user or config data needs to be - * examined and see if its an appropriate time to restart - if it is the - * spawnBootstrap method will be called and bootstrap.jar will go through its - * sequence to update myrobotlab.jar - */ - public void restart() { - // to avoid deadlock of shutting down from external messages - // we spawn a kill thread - new Thread("kill-thread") { - @Override - public void run() { - try { - - info("restarting"); - - // FIXME - should we save() load() ??? - // export("last-restart"); - - // shutdown all services process - send ready to shutdown - ask back - // release all services - for (ServiceInterface service : getServices()) { - service.preShutdown(); - } - - // check if ready ??? - - // release all local services - releaseAll(); - - if (runtime != null) { - runtime.releaseService(); - } - - // make sure python is included - // options.services.add("python"); - // options.services.add("Python"); - - // force invoke - // options.invoke = new String[] { "python", "execFile", - // "lastRestart.py" }; - - // create builder from Launcher daemonize ? - log.info("re launching with commands \n{}", CmdOptions.toString(options.getOutputCmd())); - ProcessBuilder pb = Launcher.createBuilder(options); - - // fire it off - Process restarted = pb.start(); - // it "better" not be a requirement that a process must consume its - // std streams - // "hopefully" - if the OS realizes the process is dead it moves the - // streams to /dev/null ? - // StreamGobbler gobbler = new - // StreamGobbler(String.format("%s-gobbler", getName()), - // restarted.getInputStream()); - // gobbler.start(); - - // dramatic pause - sleep(2000); - - // check if process exists - if (restarted.isAlive()) { - log.info("yay! we continue to live in future generations !"); - } else { - log.error("omg! ... I killed all the services and now there is no offspring ! :("); - } - log.error("goodbye ..."); - shutdown(); - } catch (Exception e) { - log.error("shutdown threw", e); - } - } - }.start(); - } - - /** - * Get the META-INF/MANIFEST.MF file from the myrobotlab.jar as String key-value - * pairs. - * - * @return key-value pairs contained in the manifest file - * @see Platform#getManifest() - */ - static public Map<String, String> getManifest() { - return Platform.getManifest(); - } - - /** - * Runtime's setLogLevel will set the root log level if its called from a - * service - it will only set that Service type's log level - * - * @param level - DEBUG | INFO | WARN | ERROR - * @return the level which was set - */ - static public String setLogLevel(String level) { - log.info("setLogLevel {}", level); - Logging logging = LoggingFactory.getInstance(); - logging.setLevel(level); - log.info("setLogLevel {}", level); - return level; - } - - /** - * Get the log level of this MRL instance - * - * @return The log level as a String. - * @see Logging#getLevel() - */ - static public String getLogLevel() { - Logging logging = LoggingFactory.getInstance(); - return logging.getLevel(); - } - - /** - * Set the file to output logs to. This will remove all previously-applied - * appenders from the logging system. - * - * @param file The file to output logs to - * @return file - * @see Logging#removeAllAppenders() - */ - static public String setLogFile(String file) { - log.info("setLogFile {}", file); - Logging logging = LoggingFactory.getInstance(); - logging.removeAllAppenders(); - LoggingFactory.setLogFile(file); - logging.addAppender(AppenderType.FILE); - return file; - } - - /** - * Disables logging by removing all appenders. To re-enable call - * {@link #setLogFile(String)} or add appenders. - * - * @see Logging#addAppender(String) - */ - static public void disableLogging() { - Logging logging = LoggingFactory.getInstance(); - logging.removeAllAppenders(); - } - - /** - * Stops all service-related running items. This releases the singleton - * referenced by this class, but it does not guarantee that the old service will - * be GC'd. FYI - if stopServices does not remove INSTANCE - it is not - * re-entrant in junit tests - */ - @Override - public void releaseService() { - if (runtime != null) { - runtime.purgeTasks(); - runtime.stopService(); - runtime.stopInteractiveMode(); - runtime.getRepo().removeStatusPublishers(); - if (cli != null) { - cli.stop(); - } - registry = new TreeMap<>(); - } - synchronized (INSTANCE_LOCK) { - runtime = null; - } - } - - /** - * Close all connections using this runtime as the gateway. This includes both - * inbound and outbound connections. - */ - public void closeConnections() { - for (Connection c : connections.values()) { - String gateway = c.getGateway(); - if (getFullName().equals(gateway)) { - WsClient client = (WsClient) c.get("c-client"); - client.close(); - } - } - } - - // FYI - the way to call "all" service methods ! - - /** - * Clear all services' last error. - * - * @see ServiceInterface#clearLastError() - */ - public void clearErrors() { - for (String serviceName : registry.keySet()) { - send(serviceName, "clearLastError"); - } - } - - /** - * Check if any services have errors. - * - * @return Whether any service has an error - * @see ServiceInterface#hasError() - */ - public static boolean hasErrors() { - for (ServiceInterface si : registry.values()) { - if (si.hasError()) { - return true; - } - } - return false; - } - - /** - * remove all subscriptions from all local Services - */ - static public void removeAllSubscriptions() { - for (ServiceInterface si : getLocalServices().values()) { - List<String> nlks = si.getNotifyListKeySet(); - for (int i = 0; i < nlks.size(); ++i) { - si.getNotifyList().clear(); - } - } - } - - /** - * Get recent errors from all local services. - * - * @return A list of most recent service errors - * @see ServiceInterface#getLastError() - */ - public static List<Status> getErrors() { - ArrayList<Status> stati = new ArrayList<Status>(); - for (ServiceInterface si : getLocalServices().values()) { - Status status = si.getLastError(); - if (status != null && status.isError()) { - log.info(status.toString()); - stati.add(status); - } - } - return stati; - } - - /** - * Broadcast the states of all local services. - */ - public static void broadcastStates() { - for (ServiceInterface si : getLocalServices().values()) { - si.broadcastState(); - } - } - - /** - * Get the Runtime singleton instance. - * - * @return The singleton instance - * @see #getInstance() - */ - public static Runtime get() { - return Runtime.getInstance(); - } - - /** - * Execute an external program with arguments if specified. args must not be - * null and the length must be greater than zero, the first element is the - * program to be executed. If the program is just a name and not a path to the - * executable then it must be on the operating system PATH. - * - * @see <a href= - * "https://superuser.com/questions/284342/what-are-path-and-other-environment-variables-and-how-can-i-set-or-use-them"> - * What are PATH and other environment variables?</a> - * @param args The program to be executed as the first element and the args to - * the program as the rest, if any - * @return The program's stdout and stderr output - */ - static public String execute(String... args) { - if (args == null || args.length == 0) { - log.error("execute invalid number of args"); - return null; - } - String program = args[0]; - List<String> list = null; - - if (args.length > 1) { - list = new ArrayList<String>(); - for (int i = 1; i < args.length; ++i) { - list.add(args[i]); - } - } - - return execute(program, list, null, null, true); - } - - /** - * Execute an external program with a list of arguments, a specified working - * directory, any additional environment variables, and whether the execution - * blocks. - * - * TODO Implement workingDir and block - * - * @param program The program to be executed - * @param args Any arguments to the command - * @param workingDir The directory to execute the program in - * @param additionalEnv Any additional environment variables - * @param block Whether this method blocks for the program to execute - * @return The programs stderr and stdout output - */ - - static public String execute(String program, List<String> args, String workingDir, - Map<String, String> additionalEnv, boolean block) { - log.debug("execToString(\"{} {}\")", program, args); - - List<String> command = new ArrayList<>(); - command.add(program); - if (args != null) { - command.addAll(args); - } - - ProcessBuilder builder = new ProcessBuilder(command); - if (workingDir != null) { - builder.directory(new File(workingDir)); - } - - Map<String, String> environment = builder.environment(); - if (additionalEnv != null) { - environment.putAll(additionalEnv); - } - - StringBuilder outputBuilder = new StringBuilder(); - - try { - Process handle = builder.start(); - - InputStream stdErr = handle.getErrorStream(); - InputStream stdOut = handle.getInputStream(); - - // Read the output streams in separate threads to avoid potential blocking - Thread stdErrThread = new Thread(() -> readStream(stdErr, outputBuilder)); - stdErrThread.start(); - - Thread stdOutThread = new Thread(() -> readStream(stdOut, outputBuilder)); - stdOutThread.start(); - - if (block) { - int exitValue = handle.waitFor(); - outputBuilder.append("Exit Value: ").append(exitValue); - log.info("Command exited with exit value: {}", exitValue); - } else { - log.info("Command started"); - } - - return outputBuilder.toString(); - } catch (IOException e) { - log.error("Error executing command", e); - return e.getMessage(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.error("Command execution interrupted", e); - return e.getMessage(); - } - } - - private static void readStream(InputStream inputStream, StringBuilder outputBuilder) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { - String line; - while ((line = reader.readLine()) != null) { - outputBuilder.append(line).append(System.lineSeparator()); - } - } catch (IOException e) { - log.error("Error reading process output", e); - } - } - - /** - * Get the current battery level of the computer this MRL instance is running - * on. - * - * @return The battery level as a double from 0.0 to 100.0, expressed as a - * percentage. - */ - public static Double getBatteryLevel() { - Platform platform = Platform.getLocalInstance(); - Double r = 100.0; - try { - if (platform.isWindows()) { - // String ret = Runtime.execute("cmd.exe", "/C", "WMIC.exe", "PATH", - // "Win32_Battery", "Get", "EstimatedChargeRemaining"); - String ret = Runtime.execute("WMIC.exe", "PATH", "Win32_Battery", "Get", "EstimatedChargeRemaining"); - int pos0 = ret.indexOf("\n"); - if (pos0 != -1) { - pos0 = pos0 + 1; - int pos1 = ret.indexOf("\n", pos0); - String dble = ret.substring(pos0, pos1).trim(); - try { - r = Double.parseDouble(dble); - } catch (Exception e) { - log.error("no Battery detected by system"); - } - - return r; - } - - } 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"); - } - return r; - } - log.info(ret); - } else if (platform.isMac()) { - String ret = Runtime.execute("pmset -g batt"); - int pos0 = ret.indexOf("Battery-0"); - if (pos0 != -1) { - pos0 = pos0 + 10; - int pos1 = ret.indexOf("%", pos0); - String dble = ret.substring(pos0, pos1).trim(); - try { - r = Double.parseDouble(dble); - } catch (Exception e) { - log.error("no Battery detected by system"); - } - return r; - } - log.info(ret); - } - - } catch (Exception e) { - log.info("execToString threw", e); - } - return r; - } - - /** - * Get the local service data instance. - * - * @return The local service data - * @see ServiceData#getLocalInstance() - */ - public ServiceData getServiceData() { - return serviceData; - } - - /** - * Return supported system languages - * - * @return map of languages to locales - */ - public Map<String, Locale> getLanguages() { - return Locale.getAvailableLanguages(); - } - - /** - * Get a map between locale IDs and the associated {@link Locale} instance. - * - * @return A map between IDs and instances. - */ - @Override - public Map<String, Locale> getLocales() { - return locales; - } - - /** - * Set the locales by passing a list of locale IDs. - * - * @param codes A list of locale IDs - * @return A map between the IDs and the Locale instances. - */ - public Map<String, Locale> setLocales(String... codes) { - locales = Locale.getLocaleMap(codes); - return locales; - } - - /** - * @return get the Security singleton - * - * - */ - static public Security getSecurity() { - return Security.getInstance(); - } - - /** - * Execute a program with arguments, if any. Wraps - * {@link java.lang.Runtime#exec(String[])}. - * - * @param cmd A list with the program name as the first element and any - * arguments as the subsequent elements. - * @return The Process spawned by the execution - * @throws IOException if an I/O error occurs while spawning the process - */ - public static Process exec(String... cmd) throws IOException { - // FIXME - can't return a process - it will explode in serialization - // but we might want to keep it and put it on a transient map - log.info("Runtime exec {}", Arrays.toString(cmd)); - Process p = java.lang.Runtime.getRuntime().exec(cmd); - return p; - } - - /** - * Get all the options passed on the command line when MyRobotLab is executed. - * - * @return The options that were passed on the command line - */ - public static CmdOptions getOptions() { - return options; - } - - /** - * TODO Unimplemented - * - * @param sd ServiceData to use - * @return sd - */ - public ServiceData setServiceTypes(ServiceData sd) { - return sd; - } - - /** - * FIXME - describe will have the capability to describe many aspects of a - * running service. Default behavior will show a list of local names, but - * depending on input criteria it should be possible to show * interfaces * - * service data * service methods * details of a service method * help/javadoc - * of a service method * list of other known instances * levels of detail, or - * lists of fields to display * meaningful default - * - * FIXME - input parameters will need to change - at some point, a subscribe to - * describe, and appropriate input parameters should replace the current - * onRegistered system - * - * @param type t - * @param id i - * @param remoteUuid remote id - * @return describe results - * - */ - public DescribeResults describe(String type, String id, String remoteUuid) { - DescribeQuery query = new DescribeQuery(type, remoteUuid); - return describe(type, query); - } - - /** - * Get a default DescribeResults from this instance. - * - * @return A default description of this instance - */ - public DescribeResults describe() { - // default query - return describe("platform", null); - } - - /** - * Describe results returns the information of a "describe" which can be - * detailed information regarding services, theire methods and input or output - * types. - * <p> - * FIXME - describe(String[] filters) where filter can be name, type, local, - * state, etc - * <p> - * FIXME uuid and query are unused - * - * @param uuid u - * @param query q - * @return describe results - * - * - * - */ - public DescribeResults describe(String uuid, DescribeQuery query) { - - DescribeResults results = new DescribeResults(); - results.setStatus(Status.success("Ahoy!")); - - String fullname = null; - - try { - - results.setId(getId()); - results.setPlatform(Platform.getLocalInstance()); - - // broadcast completed connection information - invoke("getConnections"); // FIXME - why isn't this done before ??? - - Set<String> set = registry.keySet(); - String[] list = new String[set.size()]; - set.toArray(list); - - // TODO - filtering on what is broadcasted or re-broadcasted - for (int i = 0; i < list.length; ++i) { - fullname = list[i]; - ServiceInterface si = registry.get(fullname); - - Registration registration = new Registration(si); - - results.addRegistration(registration); - } - - } catch (Exception e) { - log.error("describe threw on {}", fullname, e); - } - - return results; - } - - /** - * Describe results from remote query to describe - * - * @param results describe results - * - * - */ - public void onDescribe(DescribeResults results) { - List<Registration> reservations = results.getReservations(); - if (reservations != null) { - for (Registration reservation : reservations) { - if ("runtime".equals(reservation.getName()) && !getId().equals(reservation.getId())) { - // If there's a reservation for a remote runtime, subscribe to its - // registered - // Maybe this should be done in register()? - subscribe(reservation.getFullName(), "registered"); - } - register(reservation); - } - } - - } - - /** - * IMPORTANT IMPORTANT IMPORTANT - Newly connected remote mrl processes blas a - * list of registrations through onRegistered messages, for each service they - * currently have in their registry. This process will send a list of - * registrations to the newly connected remote process. If the "registered" - * event is subscribed, any newly created service will be broadcasted thorough - * this publishing point as well. - * - * TODO - write filtering, configuration, or security which affects what can be - * registered - * - * Primarily, this is where new services are registered from remote systems - * - * - */ - public void onRegistered(Registration registration) { - try { - // check if registered ? - - // TODO - filtering - include/exclude - - String fullname = registration.getName() + "@" + registration.getId(); - if (!registry.containsKey(fullname)) { - register(registration); - if (fullname.startsWith("runtime@")) { - // We want to TELL remote runtime if we have new registrations - we'll - // send them - // to it's runtime - // subscribe(fullname, "registered"); - // subscribe(fullname, "released"); - // IMPORTANT w - addListener("registered", fullname); - addListener("released", fullname); - } - } else { - log.info("{} onRegistered already registered", fullname); - } - } catch (Exception e) { - log.error("onRegistered threw {}", registration, e); - } - } - - /** - * Listener for authentication. - * - * @param response The results from a foreign instance's - * {@link Runtime#describe(String, DescribeQuery)} - */ - public void onAuthenticate(DescribeResults response) { - log.info("onAuthenticate {}", response); - } - - /** - * Get a list of metadata about all services local to this instance. - * - * @return A list of metadata about local services - * @see ServiceData#getServiceTypes() - */ - public List<MetaData> getServiceTypes() { - List<MetaData> filteredTypes = new ArrayList<>(); - for (MetaData metaData : serviceData.getServiceTypes()) { - if (metaData.isAvailable()) { - filteredTypes.add(metaData); - } - } - return filteredTypes; - } - - /** - * Register a connection route from one instance to this one. - * - * @param uuid Unique ID for a connecting client - * @param id Name or ID of the connecting client - * @param connection Details of the connection - */ - @Override - public void addConnection(String uuid, String id, Connection connection) { - Connection attr = null; - if (!connections.containsKey(uuid)) { - attr = connection; - invoke("publishConnect", connection); - } else { - attr = connections.get(uuid); - attr.putAll(connection); - } - connections.put(uuid, attr); - // String id = (String)attr.get("id"); - - addRoute(id, uuid, 10); - } - - /** - * Unregister all connections that a specified client has made. - * - * @param uuid The ID of the client - */ - @Override - public void removeConnection(String uuid) { - - Connection conn = connections.remove(uuid); - - if (conn != null) { - invoke("publishDisconnect", uuid); - invoke("getConnections"); - - Set<String> remoteIds = routeTable.getAllIdsFor(uuid); - for (String id : remoteIds) { - unregisterId(id); - } - routeTable.removeRoute(uuid); - } - } - - /** - * Unregister all services originating from the instance with the given ID. - * - * @param id The ID of the instance that is being unregistered - */ - public void unregisterId(String id) { - Set<String> names = new HashSet<>(registry.keySet()); - for (String name : names) { - if (name.endsWith("@" + id)) { - unregister(name); - } - } - } - - public String publishDisconnect(String uuid) { - return uuid; - } - - // FIXME - filter only serializable objects ? - public Connection publishConnect(Connection attributes) { - return attributes; - } - - /** - * globally get all client - * - * @return connection map - */ - public Map<String, Connection> getConnections() { - return connections; - } - - /** - * separated by connection - send connection name and get filter results back - * for a specific connections connected clients - * - * @param gatwayName name - * @return map of connections - * - */ - public Map<String, Connection> getConnections(String gatwayName) { - Map<String, Connection> ret = new HashMap<>(); - for (String uuid : connections.keySet()) { - Connection c = connections.get(uuid); - String gateway = (String) c.get("gateway"); - if (gatwayName == null || gateway.equals(gatwayName)) { - ret.put(uuid, c); - } - } - return ret; - } - - /** - * @return list connections - current connection names to this mrl runtime - * - */ - public Map<String, Connection> lc() { - return getConnections(); - } - - /** - * get a specific clients data - * - * @param uuid uuid to get - * @return connection for uuid - * - */ - public Connection getConnection(String uuid) { - return connections.get(uuid); - } - - /** - * @return Globally get all connection uuids - * - */ - public List<String> getConnectionUuids() { - return getConnectionUuids(null); - } - - /** - * Get whether a connection to the given client exists. - * - * @param uuid Unique ID of the client to check for - * @return Whether a connection between this instance and the given client - * exists - */ - boolean connectionExists(String uuid) { - return connections.containsKey(uuid); - } - - /** - * Get connection ids that belong to a specific gateway - * - * @param name n - * @return list of uuids - * - */ - public List<String> getConnectionUuids(String name) { - List<String> ret = new ArrayList<>(); - for (String uuid : connections.keySet()) { - Connection c = connections.get(uuid); - String gateway = (String) c.get("gateway"); - if (name == null || gateway.equals(name)) { - ret.add(uuid); - } - } - return ret; - } - - /** - * Get the Class instance for a specific service. - * - * @param inName The name of the service - * @return The Class of the service. - * @see #getFullName(String) - */ - public static Class<?> getClass(String inName) { - String name = getFullName(inName); - ServiceInterface si = registry.get(name); - if (si == null) { - return null; - } - return si.getClass(); - } - - /** - * takes an id returns a connection uuid - * - * @param id id - * @return the connection - * - */ - public Connection getRoute(String id) { - return connections.get(routeTable.getRoute(id)); - } - - public RouteTable getRouteTable() { - return routeTable; - } - - /** - * get gateway based on remote address of a msg e.g. msg.getRemoteId() - * - * @param remoteId remote - * @return the gateway - * - */ - public Gateway getGatway(String remoteId) { - // get a connection from the route - Connection conn = getRoute(remoteId); - if (conn == null) { - log.debug("no connection for id {}", remoteId); - return null; - } - // find the gateway managing the connection - return (Gateway) getService((String) conn.get("gateway")); - } - - /** - * Get the full name of the service. A full name is defined as a "short name" - * plus the ID of the Runtime instance it is attached to. The two components are - * separated by an '@' character. If the given name is already a full name, it - * is returned immediately, otherwise a full name is constructed by assuming the - * service is local to this instance. Example: - * - * <pre> - * { - * @code - * String shortName = "python"; - * - * // Assume the local name is "bombastic-cherry" - * String fullName = getFullName(shortName); - * // fullName is now "python@bombastic-cherry" - * - * fullName = getFullName(fullName); - * // fullName is unchanged because it was already a full name - * - * } - * </pre> - * - * - * @param shortname The name to convert to a full name - * @return shortname if it is already a full name, or a newly constructed full - * name - */ - static public String getFullName(String shortname) { - if (shortname == null || shortname.contains("@")) { - // already long form - return shortname; - } - // if nothing is supplied assume local - return String.format("%s@%s", shortname, Runtime.getInstance().getId()); - } - - @Override - public List<String> getClientIds() { - return getConnectionUuids(getName()); - } - - @Override - public Map<String, Connection> getClients() { - return getConnections(getName()); - } - - public void pollHosts() { - runtime.addTask(20000, "getHosts"); - } - - // FIXME - remove if not using ... - @Override - public void sendRemote(Message msg) throws IOException { - if (isLocal(msg)) { - log.error("msg NOT REMOTE yet sendRemote is called {}", msg); - return; - } - - // get a connection from the route - Connection conn = getRoute(msg.getId()); - if (conn == null) { - log.error("could not get connection for {} from msg {}", msg.getId(), msg); - return; - } - - // two possible types of "remote" for this gateway cli & ws - if ("Cli".equals(conn.get("c-type"))) { - invoke("publishCli", msg); - - InProcessCli cli = ((InProcessCli) conn.get("cli")); - cli.onMsg(msg); - - } else { - // websocket Client ! - WsClient client = (WsClient) conn.get("c-client"); - if (client == null) { - log.error("could not get client for connection {}", msg.getId()); - return; - } - - /** - * ====================================================================== - * DYNAMIC ROUTE TABLE - outbound msg hop starts now - */ - - // add our id - we don't want to see it again - msg.addHop(getId()); - - log.info("<== {}.{} <-- {}", msg.name, msg.method, msg.sender); - - /** - * ====================================================================== - */ - - client.send(CodecUtils.toJsonMsg(msg)); - } - } - - public Object publishCli(Message msg) { - if (msg.data == null || msg.data.length == 0) { - return null; - } - return msg.data[0]; - } - - /** - * DONT MODIFY NAME - JUST work on is Local - and InvokeOn should handle it - * - * if the incoming Message's remote Id is the (same as ours) OR (it can't be - * found it our route table) - peel it off and treat it as local. - * - * if we have an @{id/connection} but do not have the connection - we'll peel - * off the @{id/connection} and treat it as local if id is ours - peel it off ! - */ - @Override - public boolean isLocal(Message msg) { - - if (msg.getId() == null || getId().equals(msg.getId())) { - return true; - } - - return false; - } - - public Object localizeDefault(String key) { - key = key.toUpperCase(); - return defaultLocalization.get(key); - } - - static public void setAllLocales(String code) { - for (ServiceInterface si : getLocalServices().values()) { - si.setLocale(code); - } - } - - @Override - public String created(String name) { - return name; - } - - @Override - public String started(String name) { - // if this is to be used as a callback in Python - // users typically would want simple name ... not "fullname" - - return name; - } - - @Override - public String stopped(String name) { - return name; - } - - /** - * Wrapper for {@link ServiceData#getMetaData(String, String)} - * - * @param serviceName The name of the service - * @param serviceType The type of the service - * @return The metadata of the service. - */ - public static MetaData getMetaData(String serviceName, String serviceType) { - return ServiceData.getMetaData(serviceName, serviceType); - } - - /** - * Wrapper for {@link ServiceData#getMetaData(String)} - * - * @param serviceType The type of the service - * @return The metadata of the service. - */ - public static MetaData getMetaData(String serviceType) { - return ServiceData.getMetaData(serviceType); - } - - /** - * Whether the singleton has been created - * - * @return Whether the singleton exists - */ - public static boolean exists() { - return runtime != null; - } - - /** - * Attempt to get the most likely valid address priority would be a lan address - * - possibly the smallest class - * - * @return string address - * - */ - public String getAddress() { - List<String> addresses = getIpAddresses(); - if (addresses.size() > 0) { - - // class priority - for (String ip : addresses) { - if (ip.startsWith("192.168")) { - return ip; - } - } - - for (String ip : addresses) { - if (ip.startsWith("172.")) { - return ip; - } - } - - for (String ip : addresses) { - if (ip.startsWith("10.")) { - return ip; - } - } - - // give up - return first :P - return addresses.get(0); - } - return null; - } - - public List<Host> getHosts() { - List<String> ips = getIpAddresses(); - String selectedIp = (ips.size() == 1) ? ips.get(0) : null; - if (selectedIp == null) { - for (String ip : ips) { - if ((selectedIp != null) && (ip.startsWith(("192.")))) { - selectedIp = ip; - } else if (selectedIp == null) { - selectedIp = ip; - } - } - } - String subnet = selectedIp.substring(0, selectedIp.lastIndexOf(".")); - return getHosts(subnet); - } - - public List<Host> getHosts(String subnet) { - - if (hosts == null) { - hosts = new HashMap<String, Host>(); - File check = new File(FileIO.gluePaths(getDataDir(), "hosts.json")); - if (check.exists()) { - try { - Host[] hf = CodecUtils.fromJson(FileIO.toString(check), Host[].class); - for (Host h : hf) { - hosts.put(h.ip, h); - } - info("found %d saved hosts", hosts.size()); - } catch (Exception e) { - error("could not load %s - %s", check, e.getMessage()); - } - } - } - - int timeout = 1500; - try { - for (int i = 1; i < 255; i++) { - Thread pinger = new Thread(new Pinger(this, hosts, subnet + "." + i, timeout), "pinger-" + i); - pinger.start(); - } - } catch (Exception e) { - log.error("getHosts threw", e); - } - List<Host> h = new ArrayList<>(); - for (Host hst : hosts.values()) { - if (hst.lastActiveTs != null) { - h.add(hst); - } - } - return h; - } - - public Host publishFoundHost(Host host) { - log.info("found host {}", host); - return host; - } - - public Host publishFoundNewHost(Host host) { - log.info("found new host {}", host); - return host; - } - - public Host publishLostHost(Host host) { - log.info("lost host {}", host); - return host; - } - - public void saveHosts() throws IOException { - FileOutputStream fos = new FileOutputStream(FileIO.gluePaths(getDataDir(), "hosts.json")); - List<Host> h = new ArrayList<>(hosts.values()); - String json = CodecUtils.toPrettyJson(h); - fos.write(json.getBytes()); - fos.close(); - } - - /** - * start python interactively at the command line - */ - public void python() { - if (cli == null) { - startInteractiveMode(); - } - start("python", "Python"); - // since we've suscribed to pythons st - cli.relay("python", "exec", "publishStdOut"); - cli.relay("python", "exec", "publishStdError"); - Logging logging = LoggingFactory.getInstance(); - logging.removeAllAppenders(); - } - - /** - * Main entry point for the MyRobotLab Runtime Check CmdOptions for list of - * options -h help -v version -list jvm args -Dhttp.proxyHost=webproxy - * f-Dhttp.proxyPort=80 -Dhttps.proxyHost=webproxy -Dhttps.proxyPort=80 - * - * @param args cmd line args from agent spawn - * - */ - public static void main(String[] args) { - - try { - - // loading args - globalArgs = args; - new CommandLine(options).parseArgs(args); - log.info("in args {}", Launcher.toString(args)); - log.info("options {}", CodecUtils.toJson(options)); - log.info("\n" + Launcher.banner); - - // creating initial data/config directory - File cfgRoot = new File(ROOT_CONFIG_DIR); - cfgRoot.mkdirs(); - - // initialize logging - initLog(); - - // extract if necessary - FileIO.extractResources(); - - // help and exit - if (options.help) { - mainHelp(); - return; - } - - // start.yml file is required, if not pre-existing - // is created immediately. It contains static information - // which needs to be available before a Runtime is created - Runtime.startYml = ConfigUtils.loadStartYml(); - - // resolve configName before starting getting runtime configuration - Runtime.configName = (startYml.enable) ? startYml.config : "default"; - if (options.config != null) { - // cmd line options has the highest priority - Runtime.configName = options.config; - } - - // start.yml is processed, config name is set, runtime config - // is resolved, now we can start instance - Runtime.getInstance(); - - if (options.install != null) { - // resetting log level to info - // for an install otherwise ivy - // info will not be shown in the terminal - // during install of dependencies - // which makes users panic and hit ctrl+C - setLogLevel("info"); - - // we start the runtime so there is a status publisher which will - // display status updates from the repo install - log.info("requesting install"); - Repo repo = getInstance().getRepo(); - if (options.install.length == 0) { - repo.install(LIBRARIES, (String) null); - } else { - for (String service : options.install) { - repo.install(LIBRARIES, service); - } - } - shutdown(); - return; - } - - } catch (Exception e) { - log.error("runtime exception", e); - Runtime.mainHelp(); - shutdown(); - log.error("main threw", e); - } - } - - public static void initLog() { - if (options != null) { - LoggingFactory.init(options.logLevel); - } else { - LoggingFactory.init("info"); - } - } - - public void test() { - for (int statusCnt = 0; statusCnt < 500; statusCnt++) { - statusCnt++; - invoke("publishStatus", Status.info("this is status %d", statusCnt)); - } - } - - public Connection getConnectionFromId(String remoteId) { - for (Connection c : connections.values()) { - if (c.getId().equals(remoteId)) { - return c; - } - } - return null; - } - - /** - * A gateway is responsible for creating a key to associate a unique - * "Connection". This key should be retrievable, when a msg arrives at the - * service which needs to be sent remotely. This key is used to get the - * "Connection" to send the msg remotely - * - * @param string s - * @param uuid u - * - */ - public void addLocalGatewayKey(String string, String uuid) { - routeTable.addLocalGatewayKey(string, uuid); - } - - public boolean containsRoute(String remoteId) { - return routeTable.contains(remoteId); - } - - public String getConnectionUuidFromGatewayKey(String gatewayKey) { - return routeTable.getConnectionUuid(gatewayKey); - } - - /** - * This helper method will create, load then start a service - * - * @param name - name of instance - * @param type - type - * @return returns the service in the form of a ServiceInterface - */ - static public ServiceInterface loadAndStart(String name, String type) { - ServiceInterface s = null; - try { - s = create(name, type); - s.load(); - s.startService(); - } catch (Exception e) { - log.error("loadAndStart threw", e); - } - return s; - } - - /** - * DEFAULT IF NOTHING EXISTS DO NOT DEFAULT SOMETHING THAT'S ALREADY IN PLAN - * OVERRIDE WITH FILE - * - * Load a single service entry into the plan through yml or default. This method - * is responsible for resolving the Type and ServiceConfig for a single service. - * Since some service Types are composites and require Peers, it can potentially - * be recursive. The level of overrides are from highest priority to lowest : - * - * <pre> - * if a Plan definition of {name} exists, use it - "current" plan definition ! - * /data/config/{configName}/{service}.yml - user's yml override - * /resource/config/{configName}/{service}.yml - system yml default - * {ServiceConfig}.java - system java type default - * - * - * </pre> - * - * @param plan - plan to load - * @param name - name of service - * @param type - type of service - * @param start - weather to specify in RuntimeConfig.registry to "start" this - * service when createFromPlan is run - * @param level - level of the depth, services may load peers which in turn will - * load more, this is the depth of recursion - * @return - * @throws IOException - */ - public Plan loadService(Plan plan, String name, String type, boolean start, int level) throws IOException { - synchronized (processLock) { - - if (plan == null) { - log.error("plan required to load a system"); - return null; - } - - log.info("loading - {} {} {}", name, type, level); - // from recursive memory definition - ServiceConfig sc = plan.get(name); - - // HIGHEST PRIORITY - OVERRIDE WITH FILE - String configPath = runtime.getConfigPath(); - String configFile = configPath + fs + name + ".yml"; - - // PRIORITY #1 - // find if a current yml config file exists - highest priority - log.debug("priority #1 user's yml override {} ", configFile); - ServiceConfig fileSc = readServiceConfig(Runtime.getInstance().getConfigName(), name); - if (fileSc != null) { - // if definition exists in file form, it overrides current memory one - sc = fileSc; - } else if (sc != null) { - // if memory config is available but not file - // we save it - String yml = CodecUtils.toYaml(sc); - FileIO.toFile(configFile, yml); - } - - // special conflict case - type is specified, but its not the same as - // file version - in that case specified parameter type wins and - // overwrites - // config. User can force type by supplying one as a parameter, however, - // the - // recursive - // call other peer types will have name/file.yml definition precedence - if ((type != null && sc != null && !type.equals(sc.type) && level == 0) || (sc == null)) { - if (sc != null) { - warn("type %s overwriting type %s specified in %s.yml file", type, sc.type, name); - } - ServiceConfig.getDefault(plan, name, type); - sc = plan.get(name); - - // create new file if it didn't exist or overwrite it if new type is - // required - String yml = CodecUtils.toYaml(sc); - FileIO.toFile(configFile, yml); - } - - if (sc == null && type == null) { - log.error("no local config and unknown type"); - return plan; - } - - // finalize - if (sc != null) { - plan.put(name, sc); - // RECURSIVE load peers - Map<String, Peer> peers = sc.getPeers(); - for (String peerKey : peers.keySet()) { - Peer peer = peers.get(peerKey); - // recursive depth load - parent and child need to be started - runtime.loadService(plan, peer.name, peer.type, start && peer.autoStart, level + 1); - } - - // valid service config at this point - now determine if its supposed to - // start or not - // if its level 0 then it was requested by user or config - so it needs - // to - // start - // if its not level 0 then it was loaded because peers were defined and - // appropriate config loaded - // peer.autoStart should determine if the peer starts if not explicitly - // requested by the - // user or config - if (level == 0 || start) { - plan.addRegistry(name); - } - - } else { - log.info("could not load {} {} {}", name, type, level); - } - - return plan; - } - } - - /** - * read a service's configuration, in the context of current config set name or - * default - * - * @param name - * @return - */ - public ServiceConfig readServiceConfig(String name) { - return readServiceConfig(name, new StaticType<>() { - }); - } - - /** - * read a service's configuration, in the context of current config set name or - * default - * - * @param name - * @return - */ - public <C extends ServiceConfig> C readServiceConfig(String name, StaticType<C> configType) { - return readServiceConfig(null, name, configType); - } - - public ServiceConfig readServiceConfig(String configName, String name) { - return readServiceConfig(configName, name, new StaticType<>() { - }); - } - - /** - * - * @param configName - filename or dir of config set - * @param name - name of config file within that dir e.g. {name}.yml - * @return - */ - public <C extends ServiceConfig> C readServiceConfig(String configName, String name, StaticType<C> configType) { - // if config path set and yaml file exists - it takes precedence - - if (configName == null) { - configName = runtime.getConfigName(); - } - - if (configName == null) { - log.info("config name is null cannot load {} file system", name); - return null; - } - - String filename = ROOT_CONFIG_DIR + fs + configName + fs + name + ".yml"; - File check = new File(filename); - C sc = null; - if (check.exists()) { - try { - sc = CodecUtils.readServiceConfig(filename, configType); - } catch (ConstructorException e) { - error("config %s invalid %s %s. Please remove it from the file.", name, filename, - e.getCause().getMessage()); - } catch (Exception e) { - error("config could not load %s file is invalid", filename); - } - } - return sc; - } - - public String publishConfigLoaded(String name) { - return name; - } - - @Override - public RuntimeConfig apply(RuntimeConfig config) { - super.apply(config); - - setLocale(config.locale); - - if (config.id == null) { - config.id = NameGenerator.getName(); - } - - if (config.logLevel != null) { - setLogLevel(config.logLevel); - } - - if (config.virtual != null) { - info("setting virtual to %b", config.virtual); - setAllVirtual(config.virtual); - } - - // APPLYING A RUNTIME CONFIG DOES NOT PROCESS THE REGISTRY - // USE startConfig(name) - - broadcastState(); - return config; - } - - /** - * release the current config - */ - static public void releaseConfig() { - String currentConfigPath = Runtime.getInstance().getConfigName(); - if (currentConfigPath != null) { - releaseConfigPath(currentConfigPath); - } - } - - /** - * wrapper - * - * @param configName - */ - static public void releaseConfig(String configName) { - setConfig(configName); - releaseConfigPath(Runtime.getInstance().getConfigName()); - } - - /** - * Release a configuration set - this depends on a runtime file - and it will - * release all the services defined in it, with the exception of the originally - * started services - * - * @param configPath config set to release - * - */ - static public void releaseConfigPath(String configPath) { - try { - String filename = ROOT_CONFIG_DIR + fs + Runtime.getInstance().getConfigName() + fs + "runtime.yml"; - String releaseData = FileIO.toString(new File(filename)); - RuntimeConfig config = CodecUtils.fromYaml(releaseData, RuntimeConfig.class); - List<String> registry = config.getRegistry(); - Collections.reverse(Arrays.asList(registry)); - - // get starting services if any entered on the command line - // -s log Log webgui WebGui ... etc - these will be protected - List<String> startingServices = new ArrayList<>(); - if (options.services.size() % 2 == 0) { - for (int i = 0; i < options.services.size(); i += 2) { - startingServices.add(options.services.get(i)); - } - } - - for (String name : registry) { - if (startingServices.contains(name)) { - continue; - } - release(name); - } - } catch (Exception e) { - Runtime.getInstance().error("could not release %s", configPath); - } - } - - public static String getConfigRoot() { - return ROOT_CONFIG_DIR; - } - - /** - * wrapper for saveConfigPath with default prefix path supplied - * - * @param configName - * @return - */ - static public boolean saveConfig(String configName) { - Runtime runtime = Runtime.getInstance(); - if (configName == null) { - runtime.error("saveConfig require a name cannot be null"); - return false; - } - boolean ret = runtime.saveService(configName, null, null); - runtime.broadcastState(); - return ret; - } - - /** - * - * Saves the current runtime, all services and all configuration for each - * service in the current "config path", if the config path does not exist will - * error - * - * @param configName - config set name if null defaults to default - * @param serviceName - service name if null defaults to saveAll - * @param filename - if not explicitly set - will be standard yml filename - * @return - true if all goes well - */ - public boolean saveService(String configName, String serviceName, String filename) { - try { - - if (configName == null) { - error("config name cannot be null"); - return false; - } - - setConfig(configName); - - String configPath = ROOT_CONFIG_DIR + fs + configName; - - // save running services - Set<String> servicesToSave = new HashSet<>(); - - // conditional boolean to flip and save a config name to start.yml ? - if (startYml.enable) { - startYml.config = configName; - FileIO.toFile("start.yml", CodecUtils.toYaml(startYml)); - } - - if (serviceName == null) { - // all services - servicesToSave = getLocalServices().keySet(); - } else { - // single service - servicesToSave.add(serviceName); - } - - for (String s : servicesToSave) { - ServiceInterface si = getService(s); - // TODO - switch to save "NON FILTERED" config !!!! - // get filtered clone of config for saving - ServiceConfig config = si.getFilteredConfig(); - String data = CodecUtils.toYaml(config); - String ymlFileName = configPath + fs + CodecUtils.getShortName(s) + ".yml"; - FileIO.toFile(ymlFileName, data.getBytes()); - info("saved %s", ymlFileName); - } - - invoke("publishConfigList"); - return true; - - } catch (Exception e) { - error(e); - } - return false; - } - - public String getConfigName() { - return configName; - } - - public boolean isProcessingConfig() { - return processingConfig; - } - - /** - * Sets the directory for the current config. This will be under configRoot + fs - * + configName. Static wrapper around setConfigName - so it can be used in the - * same way as all the other common static service methods - * - * @param name - config dir name under data/config/{config} - * @return config dir name - */ - public static String setConfig(String name) { - if (name == null) { - log.error("config cannot be null"); - if (runtime != null) { - runtime.error("config cannot be null"); - } - return null; - } - - if (name.contains(fs)) { - log.error("invalid character " + fs + " in configuration name"); - if (runtime != null) { - runtime.error("invalid character " + fs + " in configuration name"); - } - return name; - } - - configName = name.trim(); - - File configDir = new File(ROOT_CONFIG_DIR + fs + name); - if (!configDir.exists()) { - configDir.mkdirs(); - } - - if (runtime != null) { - runtime.invoke("publishConfigList"); - runtime.invoke("getConfigName"); - } - - return configName; - } - - public String deleteConfig(String configName) { - - File trashDir = new File(DATA_DIR + fs + "trash"); - if (!trashDir.exists()) { - trashDir.mkdirs(); - } - - File configDir = new File(ROOT_CONFIG_DIR + fs + configName); - // Create a new directory in the trash with a timestamp to avoid name conflicts - File trashTargetDir = new File(trashDir, configName + "_" + System.currentTimeMillis()); - try { - // Use Files.move to move the directory atomically - Files.move(configDir.toPath(), trashTargetDir.toPath(), StandardCopyOption.REPLACE_EXISTING); - log.info("Config moved to trash: " + trashTargetDir.getAbsolutePath()); - invoke("publishConfigList"); - } catch (IOException e) { - error("Failed to move config directory to trash: " + e.getMessage()); - return null; // Return null or throw a custom exception to indicate failure - } - - return configName; - } - - // FIXME - move this to service and add default (no servicename) method - // signature - @Deprecated /* - * I don't think this was a good solution - to handle interface lists in the js - * client - the js runtime should register for lifecycle events, the individiual - * services within that js runtime should only have local event handling to - * change attach lists - */ - public void registerForInterfaceChange(String requestor, Class<?> interestedInterface) { - registerForInterfaceChange(interestedInterface.getCanonicalName()); - } - - /** - * Builds the requestedAttachMatrix which is a mapping between new types and - * their requested interfaces - interfaces they are interested in. - * - * This data should be published whenever new "Type" definitions are found - * - * @param targetedInterface - interface this add new interface to requested - * interfaces - add current names of services which - * fulfill that interface "IS ASKING" - * - */ - public void registerForInterfaceChange(String targetedInterface) { - // boolean changed - Set<String> namesForRequestedInterface = interfaceToNames.get(targetedInterface); - if (namesForRequestedInterface == null) { - namesForRequestedInterface = new HashSet<>(); - interfaceToNames.put(targetedInterface, namesForRequestedInterface); - } - - // search through interfaceToType to find all types that implement this - // interface - - if (interfaceToType.containsKey(targetedInterface)) { - Set<String> types = interfaceToType.get(targetedInterface); - if (types != null) { - for (String type : types) { - Set<String> names = typeToNames.get(type); - namesForRequestedInterface.addAll(names); - } - } - } - invoke("publishInterfaceToNames"); - } - - /** - * Published whenever a new service type definition if found - * - * @return - */ - public Map<String, Set<String>> publishInterfaceTypeMatrix() { - return interfaceToType; - } - - public Map<String, Set<String>> publishInterfaceToNames() { - return interfaceToNames; - } - - static public Plan saveDefault(String className) { - try { - Runtime runtime = Runtime.getInstance(); - return runtime.saveDefault(className.toLowerCase(), className); - } catch (Exception e) { - log.error("saving default config failed", e); - } - return null; - } - - /** - * Helper method - returns if a service is started - * - * @param name - name of service - * @return - true if started - */ - static public boolean isStarted(String name) { - String fullname = null; - if (name == null) { - return false; - } - if (!name.contains("@")) { - fullname = name + "@" + Runtime.getInstance().getId(); - } else { - fullname = name; - } - if (registry.containsKey(fullname)) { - ServiceInterface si = registry.get(fullname); - return si.isRunning(); - } - - return false; - } - - /** - * Load all configuration files from a given directory. - * - * @param configPath The directory to load from - */ - public static void loadConfigPath(String configPath) { - - Runtime.setConfig(configPath); - Runtime runtime = Runtime.getInstance(); - - String configSetDir = runtime.getConfigName() + fs + runtime.getConfigName(); - File check = new File(configSetDir); - if (configPath == null || configPath.isEmpty() || !check.exists() || !check.isDirectory()) { - runtime.error("config set %s does not exist or is not a directory", check.getAbsolutePath()); - return; - } - - File[] configFiles = check.listFiles(); - runtime.info("%d config files found", configFiles.length); - for (File f : configFiles) { - if (!f.getName().toLowerCase().endsWith(".yml")) { - log.info("{} - none yml file found in config set", f.getAbsolutePath()); - } else { - runtime.loadFile(f.getAbsolutePath()); - } - } - } - - /** - * Load a service from a file - * - * @param path The full path of the file to load - this DOES NOT set the - * configPath - */ - public void loadFile(String path) { - try { - File f = new File(path); - if (!f.exists() || f.isDirectory()) { - error("loadFile cannot load %s - it does not exist", path); - return; - } - String name = f.getName().substring(0, f.getName().length() - 4); - ServiceConfig sc = CodecUtils.readServiceConfig(path); - loadService(new Plan("runtime"), name, sc.type, true, 0); - } catch (Exception e) { - error("loadFile requirese"); - } - } - - final public Plan getDefault(String name, String type) { - return ServiceConfig.getDefault(new Plan("runtime"), name, type); - } - - final public Plan saveDefault(String name, String type) { - return saveDefault(name, name, type, false); - } - - final public Plan saveDefault(String name, String type, boolean fullPlan) { - return saveDefault(name, name, type, fullPlan); - } - - final public Plan saveDefault(String configName, String name, String type, boolean fullPlan) { - - Plan plan = ServiceConfig.getDefault(new Plan(name), name, type); - String configPath = ROOT_CONFIG_DIR + fs + configName; - - if (!fullPlan) { - try { - String filename = configPath + fs + name + ".yml"; - ServiceConfig sc = plan.get(name); - String yaml = CodecUtils.toYaml(sc); - FileIO.toFile(filename, yaml); - info("saved %s", filename); - } catch (IOException e) { - error(e); - } - } else { - for (String service : plan.keySet()) { - try { - String filename = configPath + fs + service + ".yml"; - ServiceConfig sc = plan.get(service); - String yaml = CodecUtils.toYaml(sc); - FileIO.toFile(filename, yaml); - info("saved %s", filename); - } catch (IOException e) { - error(e); - } - } - } - return plan; - } - - public void savePlan(String name, String type) { - saveDefault(name, type, true); - } - - public void saveAllDefaults() { - saveAllDefaults(new File(getResourceDir()).getParent(), false); - } - - public void saveAllDefaults(String configPath, boolean fullPlan) { - List<MetaData> types = serviceData.getAvailableServiceTypes(); - for (MetaData meta : types) { - saveDefault(configPath + fs + meta.getSimpleName(), meta.getSimpleName().toLowerCase(), - meta.getSimpleName(), fullPlan); - } - } - - /** - * Get current runtime's config path - * - * @return - */ - public String getConfigPath() { - return ROOT_CONFIG_DIR + fs + configName; - } - - /** - * Gets a {serviceName}.yml file config from configName directory - * - * @param configName - * @param serviceName - * @return ServiceConfig - */ - public ServiceConfig getConfig(String configName, String serviceName) { - return readServiceConfig(configName, serviceName); - } - - /** - * Get a {serviceName}.yml file in the current config directory - * - * @param serviceName - * @return - */ - public ServiceConfig getConfig(String serviceName) { - return readServiceConfig(serviceName); - } - - /** - * Save a config with a new Config - * - * @param name - * @param serviceConfig - * @throws IOException - */ - public static void saveConfig(String name, ServiceConfig serviceConfig) throws IOException { - String file = Runtime.ROOT_CONFIG_DIR + fs + runtime.getConfigName() + fs + name + ".yml"; - FileIO.toFile(file, CodecUtils.toYaml(serviceConfig)); - } - - /** - * get the service's peer config - * - * @param serviceName - * @param peerKey - * @return - */ - public ServiceConfig getPeerConfig(String serviceName, String peerKey) { - ServiceConfig sc = runtime.getConfig(serviceName); - if (sc == null) { - return null; - } - Peer peer = sc.getPeer(peerKey); - return runtime.getConfig(peer.name); - } - - /** - * Switches a service's .yml type definition while replacing the set of - * listeners to preserver subscriptions. Useful when switching services that - * support the same interface like SpeechSynthesis services etc. - * - * @param serviceName - * @param type - * @return - */ - public boolean changeType(String serviceName, String type) { - try { - ServiceConfig sc = getConfig(serviceName); - if (sc == null) { - error("could not find %s config", serviceName); - return false; - } - // get target - Plan targetPlan = getDefault(serviceName, type); - if (targetPlan == null || targetPlan.get(serviceName) == null) { - error("%s null", type); - return false; - } - ServiceConfig target = targetPlan.get(serviceName); - // replacing listeners - target.listeners = sc.listeners; - saveConfig(serviceName, target); - return true; - } catch (Exception e) { - error("could not save %s of type %s", serviceName, type); - return false; - } - } - - /** - * Get a peer's config - * - * @param sericeName - * @param peerKey - * @return - */ - public ServiceConfig getPeer(String sericeName, String peerKey) { - ServiceConfig sc = getConfig(sericeName); - if (sc == null) { - return null; - } - Peer peer = sc.getPeer(peerKey); - if (peer == null) { - return null; - } - return getConfig(peer.name); - } - - /** - * Removes a config set and all its files - * - * @param configName - name of config - */ - public static void removeConfig(String configName) { - try { - log.info("removing config"); - - File check = new File(ROOT_CONFIG_DIR + fs + configName); - - if (check.exists()) { - Path pathToBeDeleted = Paths.get(check.getAbsolutePath()); - Files.walk(pathToBeDeleted).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); - } - } catch (Exception e) { - log.error("removeConfig threw", e); - } - } +public class Runtime extends Service<RuntimeConfig> implements MessageListener, ServiceLifeCyclePublisher, RemoteMessageHandler, ConnectionManager, Gateway, LocaleProvider { + + final static private long serialVersionUID = 1L; + + // FIXME - AVOID STATIC FIELDS !!! use .getInstance() to get the singleton + + /** + * a registry of all services regardless of which environment they came from - + * each must have a unique name + */ + static volatile private Map<String, ServiceInterface> registry = new TreeMap<>(); + + /** + * A plan is a request to runtime to change the system. Typically its to ask + * to start and configure new services. The master plan is an accumulation of + * all these requests. + */ + @Deprecated /* use the filesystem only no memory plan */ + protected final Plan masterPlan = new Plan("runtime"); + + /** + * thread for non-blocking install of services + */ + static private transient Thread installerThread = null; + + /** + * services which want to know if another service with an interface they are + * interested in registers or is released + * + * requestor type > interface > set of applicable service names + */ + protected final Map<String, Set<String>> interfaceToNames = new HashMap<>(); + + protected final Map<String, Set<String>> typeToNames = new HashMap<>(); + + protected final Map<String, Set<String>> interfaceToType = new HashMap<>(); + + protected final Map<String, Set<String>> typeToInterface = new HashMap<>(); + + private transient static final Object processLock = new Object(); + + /** + * FILTERED_INTERFACES are the set of low level interfaces which we are + * interested in filtering out if we want to maintain a data structure which + * has "interfaces of interest" + */ + protected final static Set<String> FILTERED_INTERFACES = new HashSet<>(Arrays.asList("org.myrobotlab.framework.interfaces.Broadcaster", + "org.myrobotlab.service.interfaces.QueueReporter", "org.myrobotlab.framework.interfaces.ServiceQueue", "org.myrobotlab.framework.interfaces.MessageSubscriber", + "org.myrobotlab.framework.interfaces.Invoker", "java.lang.Runnable", "org.myrobotlab.framework.interfaces.ServiceStatus", "org.atmosphere.nettosphere.Handler", + "org.myrobotlab.framework.interfaces.NameProvider", "org.myrobotlab.framework.interfaces.NameTypeProvider", "org.myrobotlab.framework.interfaces.ServiceInterface", + "org.myrobotlab.framework.interfaces.TaskManager", "org.myrobotlab.framework.interfaces.LoggingSink", "org.myrobotlab.framework.interfaces.StatusPublisher", + "org.myrobotlab.framework.interfaces.TypeProvider", "java.io.Serializable", "org.myrobotlab.framework.interfaces.Attachable", + "org.myrobotlab.framework.interfaces.StateSaver", "org.myrobotlab.framework.interfaces.MessageSender", "java.lang.Comparable", + "org.myrobotlab.service.interfaces.ServiceLifeCycleListener", "org.myrobotlab.framework.interfaces.StatePublisher")); + + protected final Set<String> serviceTypes = new HashSet<>(); + + /** + * The directory name currently being used for config. This is NOT full path + * name. It cannot be null, it cannot have "/" or "\" in the name - it has to + * be a valid file name for the OS. It's defaulted to "default". Changed often + */ + protected static String configName = "default"; + + /** + * The runtime config which Runtime was started with. This is the config which + * will be applied to Runtime when its created on startup. + */ + // protected static RuntimeConfig startConfig = null; + + /** + * State variable reporting if runtime is currently starting services from + * config. If true you can find which config from runtime.getConfigName() + */ + boolean processingConfig = false; + + /** + * <pre> + * The set of client connections to this mrl instance Some of the connections + * are outbound to other webguis, others may be inbound if a webgui is + * listening in this instance. These details and many others (such as from + * which connection a client is from) is in the Map <String, Object> information. + * Since different connections have different requirements, and details regarding + * clients the only "fixed" required info to add a client is : + * + * uuid - key unique identifier for the client + * connection - name of the connection currently managing the clients connection + * state - state of the client and/or connection + * (lots more attributes with the Map<String, Object> to provide necessary data for the connection) + * </pre> + */ + protected final Map<String, Connection> connections = new HashMap<>(); + + /** + * corrected route table with (soon to be regex ids) mapped to + * gateway/interfaces + */ + protected final RouteTable routeTable = new RouteTable(); + + static private final String RUNTIME_NAME = "runtime"; + + /** + * user's data directory + */ + static public final String DATA_DIR = "data"; + + /** + * default parent path of configPath static ! + */ + public final static String ROOT_CONFIG_DIR = DATA_DIR + fs + "config"; + + /** + * number of services created by this runtime + */ + protected Integer creationCount = 0; + + /** + * the local repo.json manifest of this machine, which is a list of all + * libraries ivy installed + */ + transient private IvyWrapper repo = null; // was transient abstract Repo + + transient private ServiceData serviceData = ServiceData.getLocalInstance(); + + /** + * command line options + */ + static CmdOptions options = new CmdOptions(); + + /** + * command line configuration + */ + static StartYml startYml = new StartYml(); + + /** + * the platform (local instance) for this runtime. It must be a non-static as + * multiple runtimes will have different platforms + */ + protected Platform platform = null; + + private static long uniqueID = new Random(System.currentTimeMillis()).nextLong(); + + public final static Logger log = LoggerFactory.getLogger(Runtime.class); + + /** + * Object used to synchronize initializing this singleton. + */ + transient private static final Object INSTANCE_LOCK = new Object(); + + /** + * The singleton of this class. + */ + transient private static Runtime runtime = null; + + private List<String> jvmArgs; + + /** + * set of known hosts + */ + private transient Map<String, Host> hosts = null; + + /** + * global startingArgs - whatever came into main each runtime will have its + * individual copy + */ + // FIXME - remove static !!! + static String[] globalArgs; + + static Set<String> networkPeers = null; + + /** + * The name of the folder used to store native library dependencies during + * installation and runtime. + */ + private static final String LIBRARIES = "libraries"; + + String stdCliUuid = null; + + InProcessCli cli = null; + + /** + * available Locales + */ + protected Map<String, Locale> locales; + + protected List<String> configList; + + /** + * Wraps {@link java.lang.Runtime#availableProcessors()}. + * + * @return the number of processors available to the Java virtual machine. + * @see java.lang.Runtime#availableProcessors() + * + */ + public static final int availableProcessors() { + return java.lang.Runtime.getRuntime().availableProcessors(); + } + + /** + * Function to test if internet connectivity is available. If it is, will + * return the public gateway address of this computer by sending a request to + * an external server. If there is no internet, returns null. + * + * @return The public IP address or null if no internet available + */ + static public String getPublicGateway() { + try { + + URL url = new URL("http://checkip.amazonaws.com/"); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + + int status = con.getResponseCode(); + log.info("status " + status); + + String gateway = FileIO.toString(con.getInputStream()); + return gateway; + + } catch (Exception e) { + log.warn("internet not available"); + } + return null; + } + + /** + * Create which only has name (no type). This is only possible, if there is an + * appropriately named service config in the Plan (in memory) or (more + * commonly) on the filesystem. Since ServiceConfig comes with type + * information, a name is all that is needed to start the service. + * + * @param name + * @return + */ + static public ServiceInterface create(String name) { + return create(name, null); + } + + /** + * Create create(name, type) goes through the full service lifecycle of: + * + * <pre> + * clear - clearing the plan for construction of service(s) needed + * load - loading the plan for desired services + * check - checking all planned service have met appropriate licensing and dependency checks create - + * </pre> + * + * @param name + * - Required, cannot be null + * @param type + * - Can be null if a service file exists for named service + * @return the service + */ + static public ServiceInterface create(String name, String type) { + + synchronized (processLock) { + + try { + ServiceInterface si = Runtime.getService(name); + if (si != null) { + return si; + } + + Plan plan = Runtime.load(name, type); + Runtime.check(name, type); + // at this point - the plan should be loaded, now its time to create the + // children peers + // and parent service + createServicesFromPlan(plan, null, name); + si = Runtime.getService(name); + if (si == null) { + Runtime.getInstance().error("coult not create %s of type %s", name, type); + } + return si; + } catch (Exception e) { + runtime.error(e); + } + return null; + } + } + + /** + * Creates all services necessary for this service - "all peers" and the + * parent service too. At this point all type information and configuration + * should be defined in the plan. + * + * FIXME - should Plan be passed in as param ? + * + * @param name + * @return + */ + private static Map<String, ServiceInterface> createServicesFromPlan(Plan plan, Map<String, ServiceInterface> createdServices, String name) { + + synchronized (processLock) { + + if (createdServices == null) { + createdServices = new LinkedHashMap<>(); + } + + // Plan's config + RuntimeConfig plansRtConfig = (RuntimeConfig) plan.get("runtime"); + // current Runtime config + RuntimeConfig currentConfig = Runtime.getInstance().config; + + for (String service : plansRtConfig.getRegistry()) { + ServiceConfig sc = plan.get(service); + if (sc == null) { + runtime.error("could not get %s from plan", service); + continue; + } + ServiceInterface si = createService(service, sc.type, null); + // process the base listeners/subscription of ServiceConfig + si.addConfigListeners(sc); + if (si instanceof ConfigurableService) { + try { + ((ConfigurableService) si).apply(sc); + } catch (Exception e) { + Runtime.getInstance().error("could not apply config of type %s to service %s, using default config", sc.type, si.getName(), sc.type); + } + } + createdServices.put(service, si); + currentConfig.add(service); + } + + return createdServices; + } + } + + public String getServiceExample(String serviceType) { + String url = "https://raw.githubusercontent.com/MyRobotLab/myrobotlab/develop/src/main/resources/resource/" + serviceType + "/" + serviceType + ".py"; + byte[] bytes = Http.get(url); + if (bytes != null) { + return new String(bytes); + } + return ""; + } + + public static String getPeerName(String peerKey, ServiceConfig config, Map<String, ServiceReservation> peers, String parentName) { + + if (peerKey == null || !peers.containsKey(peerKey)) { + return null; + } + + if (config != null) { + + // dynamically get config peer name + // e.g. tilt should be a String value in config.tilt + Field[] fs = config.getClass().getDeclaredFields(); + for (Field f : fs) { + if (peerKey.equals(f.getName())) { + if (f.canAccess(config)) { + Object o; + try { + o = f.get(config); + + if (o == null) { + // config "has" the field, just set to null at the moment + // peer actual name then will be default notation + if (parentName != null) { + return String.format("%s.%s", parentName, peerKey); + } + log.warn("config has field named {} but it's null", peerKey); + return null; + } + + if (o instanceof String) { + return (String) o; + } else { + log.error("config has field named {} but it is not a string", peerKey); + break; + } + } catch (Exception e) { + log.error("getting access to field threw", e); + } + + } else { + log.error("config with field name {} but cannot access it", peerKey); + } + } + } + } + // last ditch attempt at getting the name - will default it if parentName is + // supplied + if (parentName != null) { + return String.format("%s.%s", parentName, peerKey); + } + return null; + } + + public static void check(String name, String type) { + log.info("check - implement - dependencies and licensing"); + // iterate through plan - check dependencies and licensing + } + + /** + * Use {@link #start(String, String)} instead. + * + * @param name + * Name of service + * @param type + * Type of service + * @return Created service + */ + @Deprecated /* use start */ + static public ServiceInterface createAndStart(String name, String type) { + return start(name, type); + } + + /** + * creates and starts services from a cmd line object + * + * @param services + * - services to be created + */ + public final static void createAndStartServices(List<String> services) { + + if (services == null) { + log.error("createAndStartServices(null)"); + return; + } + + log.info("services {}", Arrays.toString(services.toArray())); + + if (services.size() % 2 == 0) { + + for (int i = 0; i < services.size(); i += 2) { + String name = services.get(i); + String type = services.get(i + 1); + + log.info("attempting to invoke : {} of type {}", name, type); + + ServiceInterface s = Runtime.create(name, type); + + if (s != null) { + try { + s.startService(); + } catch (Exception e) { + runtime.error(e.getMessage()); + Logging.logError(e); + } + } else { + runtime.error(String.format("could not create service %s %s", name, type)); + } + + } + return; + } + Runtime.mainHelp(); + shutdown(); + } + + /** + * Setting the runtime virtual will set the platform virtual too. All + * subsequent services will be virtual + */ + @Override + public boolean setVirtual(boolean b) { + boolean changed = config.virtual != b; + config.virtual = b; + isVirtual = b; + setAllVirtual(b); + if (changed) { + broadcastState(); + } + return b; + } + + /** + * Sets all services' virtual state to {@code b}. This allows a single call to + * enable or disable virtualization across all services. + * + * @param b + * Whether all services should be virtual or not + * @return b + */ + static public boolean setAllVirtual(boolean b) { + for (ServiceInterface si : getServices()) { + if (!si.isRuntime()) { + si.setVirtual(b); + } + } + Runtime.getInstance().config.virtual = b; + Runtime.getInstance().broadcastState(); + return b; + } + + /** + * Sets the enable value in start.yml. start.yml is a file which can control + * the automatic loading of config. In general when its on, and a config is + * selected and saved, the next time Runtime starts it will attempt to load + * the last saved config and get the user back to their last state. + * + * @param autoStart + * @throws IOException + * - thrown if cannot write file to filesystem + */ + public void setAutoStart(boolean autoStart) throws IOException { + log.debug("setAutoStart {}", autoStart); + startYml.enable = autoStart; + startYml.config = configName; + FileIO.toFile("start.yml", CodecUtils.toYaml(startYml)); + invoke("getStartYml"); + } + + /** + * Framework owned method - core of creating a new service. This method will + * create a service with the given name and of the given type. If the type + * does not contain any dots, it will be assumed to be in the + * {@code org.myrobotlab.service} package. This method can currently only + * instantiate Java services, but in the future it could be enhanced to call + * native service runtimes. + * <p> + * The name parameter must not contain '/' or '@'. Thus, a full name must be + * split into its first and second part, passing the first in as the name and + * the second as the inId. This method will log an error and return null if + * name contains either of those two characters. + * <p> + * The {@code inId} is used to determine whether the service is a local one or + * a remote proxy. It should equal the Runtime ID of the MyRobotLab instance + * the service was originally instantiated under. + * + * @param name + * May not contain '/' or '@', i.e. cannot be a full name + * @param type + * The type of the new service + * @param inId + * The ID of the runtime the service is linked to. + * @return An existing service if the requested name and type match, otherwise + * a newly created service. If the name is null, or it contains '@' or + * '/', or a service with the same name exists but has a different + * type, will return null instead. + */ + static private ServiceInterface createService(String name, String type, String inId) { + synchronized (processLock) { + log.info("Runtime.createService {}", name); + + if (name == null) { + runtime.error("service name cannot be null"); + + return null; + } + + if (name.contains("@") || name.contains("/")) { + runtime.error("service name cannot contain '@' or '/': {}", name); + + return null; + } + + String fullName; + if (inId == null || inId.equals("")) + fullName = getFullName(name); + else + fullName = String.format("%s@%s", name, inId); + + if (type == null) { + ServiceConfig sc; + try { + sc = CodecUtils.readServiceConfig(runtime.getConfigName() + fs + name + ".yml"); + } catch (IOException e) { + runtime.error("could not find type for service %s", name); + return null; + } + if (sc != null) { + log.info("found type for {} in plan", name); + type = sc.type; + } else { + runtime.error("createService type not specified and could not get type for {} from plan", name); + return null; + } + } + + if (type == null) { + runtime.error("cannot create service {} no type in plan or yml file", name); + return null; + } + + String fullTypeName = CodecUtils.makeFullTypeName(type); + + ServiceInterface si = Runtime.getService(fullName); + if (si != null) { + if (!si.getTypeKey().equals(fullTypeName)) { + runtime.error("Service with name {} already exists but is of type {} while requested type is ", name, si.getTypeKey(), type); + return null; + } + return si; + } + + // DO NOT LOAD HERE !!! - doing so would violate the service life cycle ! + // only try to resolve type by the plan - if not then error out + + String id = (inId == null) ? Runtime.getInstance().getId() : inId; + if (name.length() == 0 || fullTypeName == null || fullTypeName.length() == 0) { + log.error("{} not a type or {} not defined ", fullTypeName, name); + return null; + } + + // TODO - test new create of existing service + ServiceInterface sw = Runtime.getService(String.format("%s@%s", name, id)); + if (sw != null) { + log.info("service {} already exists", name); + return sw; + } + + try { + + if (log.isDebugEnabled()) { + // TODO - determine if there have been new classes added from + // ivy --> Boot Classloader --> Ext ClassLoader --> System + // ClassLoader + // http://blog.jamesdbloom.com/JVMInternals.html + log.debug("ABOUT TO LOAD CLASS"); + log.debug("loader for this class " + Runtime.class.getClassLoader().getClass().getCanonicalName()); + log.debug("parent " + Runtime.class.getClassLoader().getParent().getClass().getCanonicalName()); + log.debug("system class loader " + ClassLoader.getSystemClassLoader()); + log.debug("parent should be null" + ClassLoader.getSystemClassLoader().getParent().getClass().getCanonicalName()); + log.debug("thread context " + Thread.currentThread().getContextClassLoader().getClass().getCanonicalName()); + log.debug("thread context parent " + Thread.currentThread().getContextClassLoader().getParent().getClass().getCanonicalName()); + } + + // FIXME - error if deps are missing - prompt license + // require restart ! + // FIXME - this should happen after inspecting the "loaded" "plan" not + // during the create/start/apply ! + + // create an instance + Object newService = Instantiator.getThrowableNewInstance(null, fullTypeName, name, id); + log.debug("returning {}", fullTypeName); + si = (ServiceInterface) newService; + + // si.setId(id); + if (Runtime.getInstance().getId().equals(id)) { + si.setVirtual(Runtime.getInstance().isVirtual()); + Runtime.getInstance().creationCount++; + si.setOrder(Runtime.getInstance().creationCount); + } + + if (runtime != null) { + + runtime.invoke("created", getFullName(name)); + + // add all the service life cycle subscriptions + // runtime.addListener("registered", name); + // runtime.addListener("created", name); + // runtime.addListener("started", name); + // runtime.addListener("stopped", name); + // runtime.addListener("released", name); + } + + return (Service) newService; + } catch (Exception e) { + log.error("createService failed for {}@{} of type {}", name, id, fullTypeName, e); + } + return null; + } + } + + static public Map<String, Map<String, List<MRLListener>>> getNotifyEntries() { + return getNotifyEntries(null); + } + + static public Map<String, Map<String, List<MRLListener>>> getNotifyEntries(String service) { + Map<String, Map<String, List<MRLListener>>> ret = new TreeMap<String, Map<String, List<MRLListener>>>(); + Map<String, ServiceInterface> sorted = null; + if (service == null) { + sorted = getLocalServices(); + } else { + sorted = new HashMap<String, ServiceInterface>(); + ServiceInterface si = Runtime.getService(service); + if (si != null) { + sorted.put(service, si); + } + } + for (Map.Entry<String, ServiceInterface> entry : sorted.entrySet()) { + log.info(entry.getKey() + "/" + entry.getValue()); + List<String> flks = entry.getValue().getNotifyListKeySet(); + Map<String, List<MRLListener>> subret = new TreeMap<String, List<MRLListener>>(); + for (String sn : flks) { + List<MRLListener> mrllistners = entry.getValue().getNotifyList(sn); + subret.put(sn, mrllistners); + } + ret.put(entry.getKey(), subret); + } + return ret; + } + + /** + * Dumps {@link #registry} to a file called {@code registry.json} in JSON + * form. + * + * @return The registry in JSON form or null if an error occurred. + */ + public static String dump() { + try { + FileOutputStream dump = new FileOutputStream("registry.json"); + String reg = CodecUtils.toJson(registry); + dump.write(reg.getBytes()); + dump.close(); + return reg; + } catch (Exception e) { + log.error("dump threw", e); + } + return null; + } + + /** + * Wraps {@link java.lang.Runtime#gc()}. + * + * Runs the garbage collector. + */ + public static final void gc() { + java.lang.Runtime.getRuntime().gc(); + } + + /** + * Although "fragile" since it relies on a external source - its useful to + * find the external ip address of NAT'd systems + * + * @return external or routers ip + * @throws Exception + * e + */ + public static String getExternalIp() throws Exception { + URL whatismyip = new URL("http://checkip.amazonaws.com"); + BufferedReader in = null; + try { + in = new BufferedReader(new InputStreamReader(whatismyip.openStream())); + String ip = in.readLine(); + return ip; + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + /** + * Wraps {@link java.lang.Runtime#freeMemory()}. + * + * @return the amount of free memory in the Java Virtual Machine. Calling the + * gc method may result in increasing the value returned by + * freeMemory. + * + * + */ + public static final long getFreeMemory() { + return java.lang.Runtime.getRuntime().freeMemory(); + } + + /** + * Get a handle to the Runtime singleton. + * + * @return the Runtime + */ + public static Runtime getInstance() { + if (runtime == null) { + synchronized (INSTANCE_LOCK) { + try { + + RuntimeConfig c = null; + if (runtime == null) { + c = ConfigUtils.loadRuntimeConfig(options); + runtime = (Runtime) createService(RUNTIME_NAME, "Runtime", c.id); + runtime.startService(); + // klunky + Runtime.register(new Registration(runtime)); + } + + runtime.locales = Locale.getDefaults(); + + runtime.getRepo().addStatusPublisher(runtime); + runtime.startService(); + // extract resources "if a jar" + FileIO.extractResources(); + runtime.startInteractiveMode(); + if (c != null) { + runtime.apply(c); + } + + if (options.services != null && options.services.size() != 0) { + log.info("command line services were specified"); + createAndStartServices(options.services); + } + + if (options.config != null) { + log.info("command line -c config was specified"); + Runtime.startConfig(options.config); + } + + if (startYml.enable && startYml.config != null) { + log.info("start.yml is enabled and config is {}", startYml.config); + Runtime.startConfig(startYml.config); + } + + } catch (Exception e) { + log.error("runtime getInstance threw", e); + } + } // synchronized lock + } + + return runtime; + } + + /** + * The jvm args which started this process + * + * @return all jvm args in a list + */ + static public List<String> getJvmArgs() { + RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); + return runtimeMxBean.getInputArguments(); + } + + /** + * gets all non-loopback, active, non-virtual ip addresses + * + * @return list of local ipv4 IP addresses + */ + static public List<String> getIpAddresses() { + log.debug("getLocalAddresses"); + ArrayList<String> ret = new ArrayList<String>(); + + try { + Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface current = interfaces.nextElement(); + // log.info(current); + if (!current.isUp() || current.isLoopback() || current.isVirtual()) { + log.debug("skipping interface is down, a loopback or virtual"); + continue; + } + Enumeration<InetAddress> addresses = current.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress currentAddress = addresses.nextElement(); + + if (!(currentAddress instanceof Inet4Address)) { + log.debug("not ipv4 skipping"); + continue; + } + + if (currentAddress.isLoopbackAddress()) { + log.debug("skipping loopback address"); + continue; + } + log.debug(currentAddress.getHostAddress()); + ret.add(currentAddress.getHostAddress()); + } + } + } catch (Exception e) { + Logging.logError(e); + } + + if (ret.size() == 0) { + // if we don't have a "real" ip address - we always have home + ret.add("127.0.0.1"); + } + return ret; + } + + // What's the purpose of this? It doesn't return anything + static public void getNetInfo() { + try { + List<String> local = getIpAddresses(); + String gateway = getPublicGateway(); + getNetworkPeers(); + } catch (Exception e) { + log.error("getNetInfo threw", e); + } + + } + + // TODO - add network to search + static public Set<String> getNetworkPeers() throws UnknownHostException { + networkPeers = new TreeSet<>(); + // String myip = InetAddress.getLocalHost().getHostAddress(); + List<String> myips = getIpAddresses(); // TODO - if nothing else - + // 127.0.0.1 + for (String myip : myips) { + if (myip.equals("127.0.0.1")) { + log.info("This PC is not connected to any network!"); + } else { + String testIp = null; + for (int i = myip.length() - 1; i >= 0; --i) { + if (myip.charAt(i) == '.') { + testIp = myip.substring(0, i + 1); + break; + } + } + + log.info("My Device IP: " + myip + "\n"); + log.info("Search log:"); + + for (int i = 1; i <= 254; ++i) { + try { + + InetAddress addr = InetAddress.getByName(testIp + new Integer(i).toString()); + + if (addr.isReachable(1000)) { + log.info("Available: " + addr.getHostAddress()); + networkPeers.add(addr.getHostAddress()); + } else { + log.info("Not available: " + addr.getHostAddress()); + } + + // TODO - check default port 8888 8887 + + } catch (IOException ioex) { + } + } + + log.info("found {} devices", networkPeers.size()); + + for (String device : networkPeers) { + log.info(device); + } + } + } + return networkPeers; + } + + static public List<ApiDescription> getApis() { + return CodecUtils.getApis(); + } + + // @TargetApi(9) + static public List<String> getLocalHardwareAddresses() { + log.info("getLocalHardwareAddresses"); + ArrayList<String> ret = new ArrayList<String>(); + try { + Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface current = interfaces.nextElement(); + byte[] mac = current.getHardwareAddress(); + + if (mac == null || mac.length == 0) { + continue; + } + + String m = StringUtil.bytesToHex(mac); + log.info("mac address : {}", m); + ret.add(m); + log.info("added mac"); + } + } catch (Exception e) { + log.error("getLocalHardwareAddresses threw", e); + } + + log.info("done"); + return ret; + } + + /** + * Gets a Map between service names and the service object of all services + * local to this MRL instance. + * + * @return A Map between service names and service objects + */ + public static Map<String, ServiceInterface> getLocalServices() { + Map<String, ServiceInterface> local = new HashMap<>(); + for (String serviceName : registry.keySet()) { + // FIXME @ should be a requirement of "all" entries for consistency + if (!serviceName.contains("@") || serviceName.endsWith(String.format("@%s", Runtime.getInstance().getId()))) { + local.put(serviceName, registry.get(serviceName)); + } + } + return local; + } + + /** + * FIXME - return + * + * @return filtering/query requests + */ + public static Map<String, ServiceInterface> getLocalServicesForExport() { + return registry; + } + + /* + * FIXME - DEPRECATE - THIS IS NOT "instance" specific info - its Class + * definition info - Runtime should return based on ClassName + * + * FIXME - INPUT PARAMETER SHOULD BE TYPE NOT INSTANCE NAME !!!! + */ + public static Map<String, MethodEntry> getMethodMap(String inName) { + String serviceName = getFullName(inName); + if (!registry.containsKey(serviceName)) { + runtime.error(String.format("%1$s not in registry - can not return method map", serviceName)); + return null; + } + + ServiceInterface sw = registry.get(serviceName); + Class<?> c = sw.getClass(); + + MethodCache cache = MethodCache.getInstance(); + return cache.getRemoteMethods(c.getTypeName()); + + } + + /** + * getServiceList returns the most important identifiers for a service which + * are it's process id, it's name, and it's type. + * <p> + * This will be part of the getHelloRequest - and the first listing from a + * process of what services are available. + * <p> + * TODO - future work would be to supply a query to the getServiceList(query) + * such that interfaces, types, or processes ids, can selectively be queried + * out of it + * + * @return list of registrations + */ + public List<Registration> getServiceList() { + synchronized (processLock) { + return registry.values().stream().map(si -> new Registration(si.getId(), si.getName(), si.getTypeKey())).collect(Collectors.toList()); + } + } + + // FIXME - scary function - returns private data + public static Map<String, ServiceInterface> getRegistry() { + return registry;// FIXME should return copy + } + + public static ServiceInterface getService(String inName) { + return getService(inName, new StaticType<>() { + }); + } + + public static <C extends ServiceConfig, S extends ServiceInterface & ConfigurableService<C>> S getConfigurableService(String inName, StaticType<S> serviceType) { + return getService(inName, serviceType); + } + + /** + * Gets a running service with the specified name. If the name is null or + * there's no such service with the specified name, returns null instead. + * + * @param inName + * The name of the service + * @return The service if it exists, or null + */ + @SuppressWarnings("unchecked") + public static <S extends ServiceInterface> S getService(String inName, StaticType<S> serviceType) { + if (inName == null) { + return null; + } + + String name = getFullName(inName); + + if (!registry.containsKey(name)) { + return null; + } else { + return (S) registry.get(name); + } + } + + /** + * @return all service names in an array form + * + * + */ + static public String[] getServiceNames() { + Set<String> ret = registry.keySet(); + String[] services = new String[ret.size()]; + if (ret.size() == 0) { + return services; + } + + // if there are more than 0 services we need runtime + // to filter to make sure they are "local" + // and this requires a runtime service + String localId = Runtime.getInstance().getId(); + int cnt = 0; + for (String fullname : ret) { + if (fullname.endsWith(String.format("@%s", localId))) { + services[cnt] = CodecUtils.getShortName(fullname); + } else { + services[cnt] = fullname; + } + ++cnt; + } + return services; + } + + // Is it a good idea to modify all regex inputs? For example, if the pattern + // already contains ".?" then the replacement will result in "..?" + // If POSIX-style globs are desired there are different + // pattern matching engines designed for that + public static boolean match(String text, String pattern) { + return text.matches(pattern.replace("?", ".?").replace("*", ".*?")); + } + + public static List<String> getServiceNames(String pattern) { + return getServices().stream().map(NameProvider::getName).filter(serviceName -> match(serviceName, pattern)).collect(Collectors.toList()); + } + + /** + * @param interfaze + * the interface + * @return a list of service names that implement the interface + * @throws ClassNotFoundException + * if the class for the requested interface is not found. + * + */ + public static List<String> getServiceNamesFromInterface(String interfaze) throws ClassNotFoundException { + if (!interfaze.contains(".")) { + interfaze = "org.myrobotlab.service.interfaces." + interfaze; + } + + return getServiceNamesFromInterface(Class.forName(interfaze)); + } + + /** + * @param interfaze + * interface + * @return list of service names + * + */ + public static List<String> getServiceNamesFromInterface(Class<?> interfaze) { + return getServicesFromInterface(interfaze).stream().map(ServiceInterface::getFullName).collect(Collectors.toList()); + } + + /** + * Get all currently-running services + * + * @return A list of all currently-running services + */ + public static List<ServiceInterface> getServices() { + return getServices(null); + } + + /** + * Get all services that belong to an MRL instance with the given ID. + * + * @param id + * The ID of the MRL instance + * @return A list of the services that belong to the given MRL instance + */ + public static List<ServiceInterface> getServices(String id) { + if (id == null) { + return new ArrayList<ServiceInterface>(registry.values()); + } + + List<ServiceInterface> list = new ArrayList<>(); + // otherwise we are getting services of an instance + + for (String serviceName : registry.keySet()) { + ServiceInterface si = registry.get(serviceName); + if (si.getId().equals(id)) { + list.add(registry.get(serviceName)); + } + } + return list; + } + + /** + * @param interfaze + * interface + * @return results + * + */ + public ServiceTypeNameResults getServiceTypeNamesFromInterface(String interfaze) { + ServiceTypeNameResults results = new ServiceTypeNameResults(interfaze); + try { + + if (!interfaze.contains(".")) { + interfaze = "org.myrobotlab.service.interfaces." + interfaze; + } + + ServiceData sd = ServiceData.getLocalInstance(); + + List<MetaData> sts = sd.getServiceTypes(); + + for (MetaData st : sts) { + + Set<Class<?>> ancestry = new HashSet<>(); + Class<?> targetClass = Class.forName(st.getType()); // this.getClass(); + + while (targetClass.getCanonicalName().startsWith("org.myrobotlab") && !targetClass.getCanonicalName().startsWith("org.myrobotlab.framework")) { + ancestry.add(targetClass); + targetClass = targetClass.getSuperclass(); + } + + for (Class<?> c : ancestry) { + Class<?>[] interfaces = Class.forName(c.getName()).getInterfaces(); + for (Class<?> inter : interfaces) { + if (interfaze.equals(inter.getName())) { + results.serviceTypes.add(st.getType()); + break; + } + } + } + } + + } catch (Exception e) { + error("could not find interfaces for %s - %s %s", interfaze, e.getClass().getSimpleName(), e.getMessage()); + log.error("getting class", e); + } + + return results; + } + + /** + * return a list of services which are currently running and implement a + * specific interface + * + * @param interfaze + * class + * @return list of service interfaces + * + */ + // FIXME !!! - use single implementation that gets parents + @Deprecated /* + * no longer used or needed - change events are pushed no longer + * pulled <-- Over complicated solution + */ + public static List<ServiceInterface> getServicesFromInterface(Class<?> interfaze) { + synchronized (processLock) { + List<ServiceInterface> ret = new ArrayList<ServiceInterface>(); + + for (String service : getServiceNames()) { + Class<?> clazz = getService(service).getClass(); + while (clazz != null) { + for (Class<?> inter : clazz.getInterfaces()) { + if (inter.getName().equals(interfaze.getName())) { + ret.add(getService(service)); + continue; + } + } + clazz = clazz.getSuperclass(); + } + } + return ret; + } + } + + /** + * Because startYml is required to be a static variable, since it's needed + * "before" a runtime instance exists it will be null in json serialization. + * This method is needed so we can serialize the data appropriately. + * + * @return + */ + static public StartYml getStartYml() { + return startYml; + } + + /** + * Gets the set of all threads currently running. + * + * @return A set containing thread objects representing all running threads + */ + static public Set<Thread> getThreads() { + return Thread.getAllStackTraces().keySet(); + } + + /** + * Wraps {@link java.lang.Runtime#totalMemory()}. + * + * @return The amount of memory available to the JVM in bytes. + */ + public static final long getTotalMemory() { + + return java.lang.Runtime.getRuntime().totalMemory(); + } + + /** + * FIXME - terrible use a uuid + * + * unique id's are need for sendBlocking - to uniquely identify the message + * this is a method to support that - it is unique within a process, but not + * across processes + * + * @return a unique id + */ + public static final synchronized long getUniqueID() { + ++uniqueID; + return System.currentTimeMillis(); + } + + /** + * Get how long this MRL instance has been running in human-readable String + * form. + * + * @return The uptime of this instance. + */ + public static String getUptime() { + Date now = new Date(); + Platform platform = Platform.getLocalInstance(); + String uptime = getDiffTime(now.getTime() - platform.getStartTime().getTime()); + log.info("up for {}", uptime); + return uptime; + } + + public static String getPlatformInfo() { + Platform platform = Platform.getLocalInstance(); + StringBuilder sb = new StringBuilder(); + sb.append(platform.getHostname()); + sb.append(" "); + sb.append(platform.getOS()); + sb.append(" "); + sb.append(platform.getArch()); + sb.append("."); + sb.append(platform.getOsBitness()); + + sb.append(" Java "); + sb.append(platform.getVmVersion()); + sb.append(" "); + sb.append(platform.getVMName()); + + return sb.toString(); + } + + /** + * Get a human-readable String form of a difference in time in milliseconds. + * + * @param diff + * The difference of time in milliseconds + * @return The human-readable string form of the difference in time + */ + public static String getDiffTime(long diff) { + + long diffSeconds = diff / 1000 % 60; + long diffMinutes = diff / (60 * 1000) % 60; + long diffHours = diff / (60 * 60 * 1000) % 24; + long diffDays = diff / (24 * 60 * 60 * 1000); + + StringBuffer sb = new StringBuffer(); + sb.append(diffDays).append(" days ").append(diffHours).append(" hours ").append(diffMinutes).append(" minutes ").append(diffSeconds).append(" seconds"); + return sb.toString(); + + } + + /** + * Get version returns the current version of mrl. It must be done this way, + * because the version may be queried on the command line without the desire + * to start a "Runtime" + * + * @return the version of the running platform instance + * + */ + public static String getVersion() { + return Platform.getLocalInstance().getVersion(); + } + + /** + * Get the latest version number of MRL in String form by querying the public + * build server. If it cannot be contacted, this method returns the String + * {@code "unknown"}. + * + * @return The latest build version in String form + */ + public static String getLatestVersion() { + String latest = "https://build.myrobotlab.org:8443/job/myrobotlab/job/develop/lastSuccessfulBuild/buildNumber"; + byte[] b = Http.get(latest); + String version = (b == null) ? "unknown" : "1.1." + new String(b); + return version; + } + + // FIXME - shouldn't this be in platform ??? + + /** + * Get the branch that this installation was built from. + * + * @return The branch + * @see Platform#getBranch() + */ + public static String getBranch() { + return Platform.getLocalInstance().getBranch(); + } + + /** + * Install all services + * + * @throws ParseException + * Unknown + * @throws IOException + * Unknown + */ + // TODO: Check throws list to see if these are still thrown + static public void install() throws ParseException, IOException { + install(null, null); + } + + /** + * Install specified service. + * + * @param serviceType + * Service to install + */ + static public void install(String serviceType) { + install(serviceType, null); + } + + /** + * Maximum complexity install - allows for blocking and non-blocking install. + * During typically runtime install of services - non blocking is desired, + * otherwise status info from the install is blocked until installation is + * completed. For command line installation "blocking" mode would be desired + * + * FIXME - problematic in that Runtime.create calls this directly, and this + * should be stepped through, because: If we need to install new components, a + * restart is likely needed ... we don't do custom dynamic classloaders .... + * yet + * + * License - should be appropriately accepted or rejected by user + * + * @param serviceType + * the service tyype to install + * @param blocking + * if this should block until done. + * + */ + static public void install(String serviceType, Boolean blocking) { + synchronized (processLock) { + Runtime r = getInstance(); + + if (blocking == null) { + blocking = false; + } + + if (installerThread != null) { + log.error("another request to install dependencies, 1st request has not completed"); + return; + } + + installerThread = new Thread() { + @Override + public void run() { + try { + if (serviceType == null) { + r.getRepo().install(); + } else { + r.getRepo().install(serviceType); + } + } catch (Exception e) { + r.error("dependencies failed - install error", e); + throw new RuntimeException(String.format("dependencies failed - install error %s", e.getMessage())); + } + } + }; + + if (blocking) { + installerThread.run(); + } else { + installerThread.start(); + } + + installerThread = null; + } + } + + /** + * Invoke a service method. The parameter must not be null and must have at + * least 2 elements. The first is the service name and the second is the + * service method. The rest of the elements are parameters to the specified + * method. + * + * @param invoke + * The array of service name, method, and parameters + */ + static public void invokeCommands(String[] invoke) { + + if (invoke.length < 2) { + log.error("invalid invoke request, minimally 2 parameters are required: --invoke service method ..."); + return; + } + + String name = invoke[0]; + String method = invoke[1]; + + // params + Object[] data = new Object[invoke.length - 2]; + for (int i = 2; i < invoke.length; ++i) { + data[i - 2] = invoke[i]; + } + + log.info("attempting to invoke : {}.{}({})\n", name, method, Arrays.toString(data)); + getInstance().send(name, method, data); + } + + /** + * Checks if a service is local to this MRL instance. The service must exist. + * + * @param serviceName + * The name of the service to check + * @return Whether the specified service is local or not + */ + public static boolean isLocal(String serviceName) { + ServiceInterface sw = getService(serviceName); + return Objects.equals(sw.getId(), Runtime.getInstance().getId()); + } + + /* + * check if class is a Runtime class + * + * @return true if class == Runtime.class + */ + public static boolean isRuntime(Service newService) { + return newService.getClass().equals(Runtime.class); + } + + /** + * Start interactive mode on {@link System#in} and {@link System#out}. + * + * @see #startInteractiveMode(InputStream, OutputStream) + */ + public void startInteractiveMode() { + startInteractiveMode(System.in, System.out); + } + + /** + * Starts an interactive CLI on the specified input and output streams. The + * CLI command processor runs in its own thread and takes commands according + * to the CLI API. + * + * FIXME - have another shell script which starts jar as ws client with cli + * interface Remove this std in/out - it is overly complex and different OSs + * handle it differently Windows Java updates have broken it several times + * + * @param in + * The input stream to take commands from + * @param out + * The output stream to print command output to + * @return The constructed CLI processor + */ + public InProcessCli startInteractiveMode(InputStream in, OutputStream out) { + if (cli != null) { + log.info("already in interactive mode"); + return cli; + } + + cli = new InProcessCli(this, "runtime", in, out); + Connection c = cli.getConnection(); + stdCliUuid = (String) c.get("uuid"); + + // addRoute(".*", getName(), 100); + addConnection(stdCliUuid, cli.getId(), c); + + return cli; + } + + /** + * Stops interactive mode if it's running. + */ + public void stopInteractiveMode() { + if (cli != null) { + cli.stop(); + cli = null; + } + if (stdCliUuid != null) { + removeConnection(stdCliUuid); + stdCliUuid = null; + } + } + + /** + * prints help to the console + */ + static void mainHelp() { + new CommandLine(new CmdOptions()).usage(System.out); + } + + /** + * Logs a string message and publishes the message. + * + * @param msg + * The message to log and publish + * @return msg + */ + public static String message(String msg) { + getInstance().invoke("publishMessage", msg); + log.info(msg); + return msg; + } + + /** + * Listener for state publishing, updates registry + * + * @param updatedService + * Updated service to put in the registry + */ + public void onState(ServiceInterface updatedService) { + log.info("runtime updating registry info for remote service {}", updatedService.getName()); + registry.put(String.format("%s@%s", updatedService.getName(), updatedService.getId()), updatedService); + } + + public static Registration register(String id, String name, String typeKey, ArrayList<String> interfaces) { + synchronized (processLock) { + Registration proxy = new Registration(id, name, typeKey, interfaces); + register(proxy); + return proxy; + } + } + + /** + * Registration is the process where a remote system sends detailed info + * related to its services. It will have details on each service type, state, + * id, and other info. The registration is serializable, with state + * information in a serialized for so that stateless processes or other + * non-Java instances can register or be registered. + * + * Registration might setup subscriptions to support a UI. + * + * Additional info which will be added in the future is a method map (a + * swagger concept) and a list of supported interfaces + * + * TODO - have rules on what registrations to accept - dependent on security, + * desire, re-broadcasting configuration etc. TODO - determine rules on + * re-broadcasting based on configuration + * + * @param registration + * registration + * @return registration + * + */ + public static Registration register(Registration registration) { + synchronized (processLock) { + try { + + // TODO - have rules on what registrations to accept - dependent on + // security, desire, re-broadcasting configuration etc. + + String fullname = String.format("%s@%s", registration.getName(), registration.getId()); + if (registry.containsKey(fullname)) { + log.info("{} already registered", fullname); + return registration; + } + + // if (!ForeignProcessUtils.isValidTypeKey(registration.getTypeKey())) { + // log.error("Invalid type key being registered: " + + // registration.getTypeKey()); + // return null; + // } + + log.info("{}@{} registering at {} of type {}", registration.getName(), registration.getId(), ConfigUtils.getId(), registration.getTypeKey()); + + if (!registration.isLocal(ConfigUtils.getId())) { + + // Check if we're registering a java service + if (ForeignProcessUtils.isValidJavaClassName(registration.getTypeKey())) { + + String fullTypeName; + if (registration.getTypeKey().contains(".")) { + fullTypeName = registration.getTypeKey(); + } else { + fullTypeName = String.format("org.myrobotlab.service.%s", registration.getTypeKey()); + } + + try { + // de-serialize, class exists + registration.service = Runtime.createService(registration.getName(), fullTypeName, registration.getId()); + if (registration.getState() != null) { + copyShallowFrom(registration.service, CodecUtils.fromJson(registration.getState(), Class.forName(fullTypeName))); + } + } catch (ClassNotFoundException classNotFoundException) { + log.error(String.format("Unknown service class for %s@%s: %s", registration.getName(), registration.getId(), registration.getTypeKey()), classNotFoundException); + return null; + } + } else { + // We're registering a foreign process service. We don't need to + // check + // ForeignProcessUtils.isForeignTypeKey() because the type key is + // valid + // but is not a java class name + + // Class does not exist, check if registration has empty interfaces + // Interfaces should always include ServiceInterface if coming from + // remote client + if (registration.interfaces == null || registration.interfaces.isEmpty()) { + log.error("Unknown service type being registered, registration does not contain any " + "interfaces for proxy generation: " + registration.getTypeKey()); + return null; + } + + // FIXME - probably some more clear definition about the + // requirements + // of remote + // service registration + // In general, there should be very few requirements if any, besides + // providing a + // name, and the proxy + // interface should be responsible for creating a minimal + // interpretation + // (ServiceInterface) for the remote + // service + + // Class<?>[] interfaces = registration.interfaces.stream().map(i -> + // { + // try { + // return Class.forName(i); + // } catch (ClassNotFoundException e) { + // throw new RuntimeException("Unable to load interface " + i + " + // defined in remote registration " + registration, e); + // } + // }).toArray(Class<?>[]::new); + + // registration.service = (ServiceInterface) + // Proxy.newProxyInstance(Runtime.class.getClassLoader(), + // interfaces, + // new ProxyServiceInvocationHandler(registration.getName(), + // registration.getId())); + try { + registration.service = ProxyFactory.createProxyService(registration); + log.info("Created proxy: " + registration.service); + } catch (Exception e) { + // at the moment preventing throw + Runtime.getInstance().error(e); + } + } + } + + registry.put(fullname, registration.service); + + if (runtime != null) { + + String type = registration.getTypeKey(); + + // If type does not exist in typeToNames, make it an empty hash set + // and + // return it + Set<String> names = runtime.typeToNames.computeIfAbsent(type, k -> new HashSet<>()); + names.add(fullname); + + // FIXME - most of this could be static as it represents meta data of + // class and interfaces + + // FIXME - was false - setting now to true .. because + // 1 edge case - "can something fulfill my need of an interface - is + // not + // currently + // switching to true + boolean updatedServiceLists = false; + + // maintaining interface type relations + // see if this service type is new + // PROCESS INDEXES ! - FIXME - will need this in unregister + // ALL CLASS/TYPE PROCESSING only needs to happen once per type + if (!runtime.serviceTypes.contains(type)) { + // CHECK IF "CAN FULFILL" + // add the interfaces of the new service type + Set<String> interfaces = ClassUtil.getInterfaces(registration.service.getClass(), FILTERED_INTERFACES); + for (String interfaze : interfaces) { + Set<String> types = runtime.interfaceToType.get(interfaze); + if (types == null) { + types = new HashSet<>(); + } + types.add(registration.getTypeKey()); + runtime.interfaceToType.put(interfaze, types); + } + + runtime.typeToInterface.put(type, interfaces); + runtime.serviceTypes.add(registration.getTypeKey()); + updatedServiceLists = true; + } + + // check to see if any of our interfaces can fulfill requested ones + Set<String> myInterfaces = runtime.typeToInterface.get(type); + for (String inter : myInterfaces) { + if (runtime.interfaceToNames.containsKey(inter)) { + runtime.interfaceToNames.get(inter).add(fullname); + updatedServiceLists = true; + } + } + + if (updatedServiceLists) { + runtime.invoke("publishInterfaceToNames"); + } + + // TODO - determine rules on re-broadcasting based on configuration + runtime.invoke("registered", registration); + } + + // TODO - remove ? already get state from registration + if (!registration.isLocal(ConfigUtils.getId())) { + runtime.subscribe(registration.getFullName(), "publishState"); + } + + } catch (Exception e) { + log.error("registration threw for {}@{}", registration.getName(), registration.getId(), e); + return null; + } + + return registration; + } + } + + /** + * releases a service - stops the service, its threads, releases its + * resources, and removes registry entries + * + * FIXME - clean up subscriptions from released + * + * @param inName + * name to release + * @return true/false + * + */ + public static boolean releaseService(String inName) { + ServiceInterface sc = getService(inName); + if (sc != null) { + sc.releaseService(); + return true; + } + return false; + } + + /** + * Called after any subclassed releaseService has been called, this cleans up + * the registry and removes peers + * + * @param inName + * @return + */ + public static boolean releaseServiceInternal(String inName) { + synchronized (processLock) { + if (inName == null) { + log.debug("release (null)"); + return false; + } + + String name = getFullName(inName); + + String id = CodecUtils.getId(name); + if (!id.equals(Runtime.getInstance().getId())) { + log.warn("will only release local services - %s is remote", name); + return false; + } + + log.info("releasing service {}", name); + + if (!registry.containsKey(name)) { + log.info("{} not registered", name); + return false; + } + + // get reference from registry + ServiceInterface si = registry.get(name); + if (si == null) { + log.warn("cannot release {} - not in registry"); + return false; + } + + // FIXME - TODO invoke and or blocking on preRelease - Future + + // send msg to service to self terminate + if (si.isLocal()) { + si.purgeTasks(); + si.stopService(); + } else { + if (runtime != null) { + runtime.send(name, "releaseService"); + } + } + + // recursive peer release + Map<String, Peer> peers = si.getPeers(); + if (peers != null) { + for (Peer peer : peers.values()) { + release(peer.name); + } + } + + // FOR remote this isn't correct - it should wait for + // a message from the other runtime to say that its released + unregister(name); + return true; + } + } + + /** + * Removes registration for a service. Removes the service from + * {@link #typeToInterface} and {@link #interfaceToNames}. + * + * @param inName + * Name of the service to unregister + */ + public static void unregister(String inName) { + synchronized (processLock) { + String name = getFullName(inName); + log.info("unregister {}", name); + + // get reference from registry + ServiceInterface sw = registry.get(name); + if (sw == null) { + log.debug("{} already unregistered", name); + return; + } + + // you have to send released before removing from registry + if (runtime != null) { + runtime.invoke("released", inName); // <- DO NOT CHANGE THIS IS CORRECT + // !! + // it should be FULLNAME ! + // runtime.broadcast("released", inName); + String type = sw.getTypeKey(); + + boolean updatedServiceLists = false; + + // check to see if any of our interfaces can fullfill requested ones + Set<String> myInterfaces = runtime.typeToInterface.get(type); + if (myInterfaces != null) { + for (String inter : myInterfaces) { + if (runtime.interfaceToNames.containsKey(inter)) { + runtime.interfaceToNames.get(inter).remove(name); + updatedServiceLists = true; + } + } + } + + if (updatedServiceLists) { + runtime.invoke("publishInterfaceToNames"); + } + + } + + // FIXME - release autostarted peers ? + + // last step - remove from registry by making new registry + // thread safe way + Map<String, ServiceInterface> removedService = new TreeMap<>(); + for (String key : registry.keySet()) { + if (!name.equals(key)) { + removedService.put(key, registry.get(key)); + } + } + registry = removedService; + + // and config + RuntimeConfig c = (RuntimeConfig) Runtime.getInstance().config; + if (c != null) { + c.remove(CodecUtils.getShortName(name)); + } + + log.info("released {}", name); + } + } + + /** + * Get all remote services. + * + * @return List of remote services as proxies + */ + public List<ServiceInterface> getRemoteServices() { + return getRemoteServices(null); + } + + /** + * Get remote services associated with the MRL instance that has the given ID. + * + * @param id + * The id of the target MRL instance + * @return A list of services running on the target instance + */ + public List<ServiceInterface> getRemoteServices(String id) { + List<ServiceInterface> list = new ArrayList<>(); + for (String serviceName : registry.keySet()) { + if (serviceName.contains("@")) { + String sid = serviceName.substring(serviceName.indexOf("@") + 1); + if (id == null || sid.equals(id)) { + list.add(registry.get(serviceName)); + } + } + } + return list; + } + + /** + * Releases all local services including Runtime asynchronously. + * + * @see #releaseAll(boolean, boolean) + */ + public static void releaseAll() { + releaseAll(true, false); + } + + /** + * This does not EXIT(1) !!! releasing just releases all services + * + * FIXME FIXME FIXME - just call release on each - possibly saving runtime for + * last .. send prepareForRelease before releasing + * + * release all local services + * + * FIXME - there "should" be an order to releasing the correct way would be to + * save the Runtime for last and broadcast all the services being released + * + * FIXME - send SHUTDOWN event to all running services with a timeout period - + * end with System.exit() FIXME normalize with releaseAllLocal and + * releaseAllExcept + * + * local only? YES !!! LOCAL ONLY !! + * + * @param releaseRuntime + * Whether the Runtime should also be released + */ + public static void releaseAll(boolean releaseRuntime, boolean block) { + // a command thread is issuing this command is most likely + // tied to one of the services being removed + // therefore this needs to happen asynchronously otherwise + // the thread that issued the command will try to destroy/release itself + // which almost always causes a deadlock + log.debug("releaseAll"); + + if (block) { + processRelease(releaseRuntime); + ConfigUtils.reset(); + } else { + + new Thread() { + @Override + public void run() { + processRelease(releaseRuntime); + ConfigUtils.reset(); + } + }.start(); + + } + } + + /** + * Releases all threads and can be executed in a separate thread. + * + * @param releaseRuntime + * Whether the Runtime should also be released + */ + static private void processRelease(boolean releaseRuntime) { + synchronized (processLock) { + // reverse release to order of creation + Collection<ServiceInterface> local = getLocalServices().values(); + List<ServiceInterface> ordered = new ArrayList<>(local); + ordered.removeIf(Objects::isNull); + Collections.sort(ordered); + Collections.reverse(ordered); + + for (ServiceInterface sw : ordered) { + + // no longer needed now - runtime "should be" guaranteed to be last + if (sw == Runtime.getInstance()) { + // skipping runtime + continue; + } + + log.info("releasing service {}", sw.getName()); + + try { + sw.releaseService(); + } catch (Exception e) { + if (runtime != null) { + runtime.error("%s threw while releasing", e); + } + log.error("release", e); + } + } + + // clean up remote ... the contract should + // probably be just remove their references - do not + // ask for them to be released remotely .. + // in thread safe way + + if (releaseRuntime) { + if (runtime != null) { + runtime.releaseService(); + } + synchronized (INSTANCE_LOCK) { + runtime = null; + } + } else { + // put runtime in new registry + Runtime.getInstance(); + registry = new TreeMap<>(); + registry.put(runtime.getFullName(), registry.get(runtime.getFullName())); + } + } + } + + /** + * Shuts down this instance after the given number of seconds. + * + * @param seconds + * sets task to shutdown in (n) seconds + */ + // Why is this using the wrapper type? Null can be passed in and cause NPE + public static void shutdown(Integer seconds) { + log.info("shutting down in {} seconds", seconds); + if (seconds > 0) { + runtime.addTaskOneShot(seconds * 1000, "shutdown", (Object[]) null); + runtime.invoke("publishShutdown", seconds); + } else { + shutdown(); + } + } + + /** + * shutdown terminates the currently running Java virtual machine by + * initiating its shutdown sequence. This method never returns normally. The + * argument serves as a status code; by convention, a nonzero status code + * indicates abnormal termination + * + */ + public static void shutdown() { + try { + log.info("myrobotlab shutting down"); + + if (runtime != null) { + log.info("stopping interactive mode"); + runtime.stopInteractiveMode(); + } + + log.info("pre shutdown on all services"); + for (ServiceInterface service : getServices()) { + service.preShutdown(); + } + + log.info("releasing all"); + + // release + releaseAll(); + } catch (Exception e) { + log.error("something threw - continuing to shutdown", e); + } + + // calling System.exit(0) before some specialized threads + // are completed will actually end up in a deadlock + Service.sleep(1000); + System.exit(0); + } + + public Integer publishShutdown(Integer seconds) { + return seconds; + } + + /** + * publish the folders of the parent directory of configPath if the configPath + * is null then publish directory names of data/config + * + * @return list of configs + */ + public List<String> publishConfigList() { + configList = new ArrayList<>(); + + File configDirFile = new File(ROOT_CONFIG_DIR); + if (!configDirFile.exists() || !configDirFile.isDirectory()) { + error("%s config root does not exist", configDirFile.getAbsolutePath()); + return configList; + } + + File[] files = configDirFile.listFiles(); + if (files == null) { + // We checked for if directory earlier, so can only be null for IO error + error("IO error occurred while listing config directory files"); + return configList; + } + for (File file : files) { + String n = file.getName(); + + if (!file.isDirectory() || file.isHidden()) { + log.info("ignoring {} expecting directory not file", n); + continue; + } + + configList.add(file.getName()); + } + Collections.sort(configList); + return configList; + } + + /** + * Releases all local services except the services whose names are in the + * given set + * + * @param saveMe + * The set of services that should not be released + */ + public static void releaseAllServicesExcept(HashSet<String> saveMe) { + log.info("releaseAllServicesExcept"); + List<ServiceInterface> list = Runtime.getServices(); + for (ServiceInterface si : list) { + if (saveMe != null && saveMe.contains(si.getName())) { + log.info("leaving {}", si.getName()); + } else { + si.releaseService(); + } + } + } + + /** + * Release a specific service. Releasing shuts down the service and removes it + * from registries. + * + * @param fullName + * full name The service to be released + * + */ + static public void release(String fullName) { + releaseService(fullName); + } + + /** + * Disconnect from remote process. FIXME - not implemented + * + * @throws IOException + * Unknown + */ + // FIXME - implement ! also implement the callback events .. onDisconnect + public void disconnect() throws IOException { + // connect("admin", "ws://localhost:8887/api/messages"); + log.info("disconnect"); + } + + /** + * FIXME - can this be renamed back to attach ? jump to another process using + * the cli + * + * @param id + * instance id. + * @return string + * + */ + // FIXME - remove - the way to 'jump' is just to change + // context to the correct mrl id e.g. cd /runtime@remote07 + public String jump(String id) { + Connection c = getRoute(stdCliUuid); + if (c != null && c.get("cli") != null) { + ((InProcessCli) c.get("cli")).setRemote(id); + } else { + log.error("connection or cli is null for uuid {}", stdCliUuid); + } + + return id; + } + + /** + * Reconnects {@link #cli} to this process. + * + * @return The id of this instance + */ + // FIXME - remove ?!?!!? + public String exit() { + Connection c = getConnection(stdCliUuid); + if (c != null && c.get("cli") != null) { + ((InProcessCli) c.get("cli")).setRemote(getId()); + } + return getId(); + } + + /** + * Send a command to the {@link InProcessCli}. + * + * @param srcFullName + * Unknown + * @param cmd + * The command to execute + */ + public void sendToCli(String srcFullName, String cmd) { + Connection c = getConnection(stdCliUuid); + if (c == null || c.get("cli") == null) { + log.info("starting interactive mode"); + startInteractiveMode(); + sleep(1000); + } + c = getConnection(stdCliUuid); + if (c != null && c.get("cli") != null) { + ((InProcessCli) c.get("cli")).process(srcFullName, cmd); + } else { + log.error("could not start interactive mode"); + } + } + + /** + * Connect to the MRL instance at the given URL, auto-reconnecting if + * specified and the connection drops. + * + * FIXME implement autoReconnect + * + * @param url + * The URL to connect to + * @param autoReconnect + * Whether the connection should be re-established if it is dropped + */ + // FIXME - implement + public void connect(String url, boolean autoReconnect) { + if (!autoReconnect) { + connect(url); + } else { + addTask(1000, "checkConnections"); + } + } + + // FIXME - implement + public void checkConnections() { + for (Connection connection : connections.values()) { + if (connection.containsKey("url")) { + /* + * FIXME - check on "STATE" ... means we support disconnected + * connections .. if (connection.get("url").toString().equals(url)) { // + * already connected continue; } + */ + } + } + // could not find our connection for this "id" - need to reconnect + // connect(url); + } + + // FIXME - + // step 1 - first bind the uuids (1 local and 1 remote) + // step 2 - Clients will contain attribute + // FIXME - RETRIES TIMEOUTS OTHER COMPLEXITIES + // blocking connect - consider a non-blocking thread connect ... e.g. + // autoConnect + + /** + * Connect to the MRL instance at the given URL + * + * @param url + * Where the MRL instance being connected to is located + */ + @Override + public void connect(String url) { + try { + + // TODO - do auth, ssl and unit tests for them + // TODO - get session id + // request default describe - on describe do registrations .. zzz + + // standardize request - TODO check for ws wss not http https + if (!url.contains("api/messages")) { + url += "/api/messages"; + } + + if (!url.contains("id=")) { + url += "?id=" + getId(); + } + + WsClient client2 = new WsClient(); + client2.connect(this, url); + + // URI uri = new URI(url); + // adding "id" as full url :P ... because we don't know it !!! + Connection connection = new Connection(client2.getId(), getId(), getFullName()); + + // connection specific + connection.put("c-type", "Runtime"); + // attributes.put("c-endpoint", endpoint); + connection.put("c-client", client2); + + // cli specific + connection.put("cwd", "/"); + connection.put("url", url); + connection.put("uri", url); // not really correct + connection.put("user", "root"); + connection.put("host", "local"); + + // addendum + connection.put("User-Agent", "runtime-client"); + + addConnection(client2.getId(), url, connection); + + // direct send - may not have and "id" so it will be too runtime vs + // runtime@{id} + // subscribe to "describe" + MRLListener listener = new MRLListener("describe", getFullName(), "onDescribe"); + Message msg = Message.createMessage(getFullName(), "runtime", "addListener", listener); + client2.send(CodecUtils.toJsonMsg(msg)); + + // send describe + client2.send(CodecUtils.toJsonMsg(getDescribeMsg(null))); + + } catch (Exception e) { + log.error("connect to {} giving up {}", url, e.getMessage()); + } + } + + /** + * FIXME - this is a gateway callback - probably should be in the gateway + * interface - this is a "specific" gateway that supports typeless json or + * websockets + * <p> + * FIXME - decoding should be done at the Connection ! - this should be + * onRemoteMessage(msg) ! + * <p> + * callback - from clientRemote - all client connections will recieve here + * TODO - get clients directional api - an api per direction incoming and + * outgoing + * + * @param uuid + * - connection for incoming data + * @param data + * Incoming message in JSON String form + */ + @Override // uuid + public void onRemoteMessage(String uuid, String data) { + try { + + // log.debug("connection {} responded with {}", uuid, data); + // get api - decode msg - process it + Connection connection = getConnection(uuid); + if (connection == null) { + error("no connection with uuid %s", uuid); + return; + } + + if (log.isDebugEnabled()) { + log.debug("data - [{}]", data); + } + + // decoding message envelope + Message msg = CodecUtils.fromJson(data, Message.class); + log.info("==> {} --> {}.{}", msg.sender, msg.name, msg.method); + msg.setProperty("uuid", uuid); // Properties ???? REMOVE ??? + + if (msg.containsHop(getId())) { + log.error("{} dumping duplicate hop msg to avoid cyclical from {} --to--> {}.{} | {}", getName(), msg.sender, msg.name, msg.method, msg.getHops()); + return; + } + + addRoute(msg.getSrcId(), uuid, 10); + + // add our id - we don't want to see it again + msg.addHop(getId()); + + Object ret = null; + + // FIXME - see if same code block exists in WebGui .. normalize + if (isLocal(msg)) { + + // log.info("--> {}.{} from {}", msg.name, msg.method, msg.sender); + + String serviceName = msg.getName(); + // to decode fully we need class name, method name, and an array of json + // encoded parameters + MethodCache cache = MethodCache.getInstance(); + Class<?> clazz = Runtime.getClass(serviceName); + if (clazz == null) { + log.error("local msg but no Class for requested service {}", serviceName); + return; + } + Object[] params = cache.getDecodedJsonParameters(clazz, msg.method, msg.data); + + Method method = cache.getMethod(clazz, msg.method, params); + ServiceInterface si = Runtime.getService(serviceName); + if (method == null) { + log.error("cannot find {}", cache.makeKey(clazz, msg.method, cache.getParamTypes(params))); + return; + } + if (si == null) { + log.error("si null for serviceName {}", serviceName); + return; + } + + ret = method.invoke(si, params); + + // propagate return data to subscribers + si.out(msg.method, ret); + + } else { + log.info("GATEWAY {} RELAY {} --to--> {}.{}", getName(), msg.sender, msg.name, msg.method); + send(msg); + } + + } catch (Exception e) { + log.error("processing msg threw", e); + } + } + + /** + * Add a route to the route table + * + * @param remoteId + * Id of the remote instance + * @param uuid + * Unknown + * @param metric + * Unknown + * @see RouteTable#addRoute(String, String, int) + */ + public void addRoute(String remoteId, String uuid, int metric) { + routeTable.addRoute(remoteId, uuid, metric); + } + + /** + * Start Runtime with the specified config + * + * @param configName + * The name of the config file + */ + static public void startConfig(String configName) { + setConfig(configName); + Runtime runtime = Runtime.getInstance(); + runtime.processingConfig = true; // multiple inbox threads not available + runtime.invoke("publishConfigStarted", configName); + RuntimeConfig rtConfig = runtime.readServiceConfig(runtime.getConfigName(), "runtime", new StaticType<>() { + }); + if (rtConfig == null) { + runtime.error("cannot find %s%s%s", runtime.getConfigName(), fs, "runtime.yml"); + return; + } + + runtime.apply(rtConfig); + + Plan plan = new Plan("runtime"); + // for every service listed in runtime registry - load it + // FIXME - regex match on filesystem matches on *.yml + for (String service : rtConfig.getRegistry()) { + + if ("runtime".equals(service) || Runtime.isStarted(service)) { + continue; + } + + // has to be loaded + File file = new File(Runtime.ROOT_CONFIG_DIR + fs + runtime.getConfigName() + fs + service + ".yml"); + if (!file.exists()) { + runtime.error("cannot read file %s - skipping", file.getPath()); + continue; + } + + ServiceConfig sc = runtime.readServiceConfig(runtime.getConfigName(), service); + try { + if (sc == null) { + continue; + } + runtime.loadService(plan, service, sc.type, true, 0); + } catch (Exception e) { + runtime.error(e); + } + } + + // for all newly created services start them + Map<String, ServiceInterface> created = Runtime.createServicesFromPlan(plan, null, null); + for (ServiceInterface si : created.values()) { + si.startService(); + } + + runtime.processingConfig = false; // multiple inbox threads not available + runtime.invoke("publishConfigFinished", configName); + + } + + public String publishConfigStarted(String configName) { + log.info("publishConfigStarted {}", configName); + // Make Note: done inline, because the thread actually doing the config + // processing + // would need to be finished with it before this thread could be invoked + // if multiple inbox threads were available then this would be possible + // processingConfig = true; + return configName; + } + + public String publishConfigFinished(String configName) { + log.info("publishConfigFinished {}", configName); + // Make Note: done inline, because the thread actually doing the config + // processing + // would need to be finished with it before this thread could be invoked + // if multiple inbox threads were available then this would be possible + // processingConfig = false; + return configName; + } + + /** + * Start a service of the specified type as the specified name. + * + * @param name + * The name of the new service + * @param type + * The type of the new service + * @return The started service + */ + static public ServiceInterface start(String name, String type) { + synchronized (processLock) { + try { + + ServiceInterface requestedService = Runtime.getService(name); + if (requestedService != null) { + log.info("requested service already exists"); + if (requestedService.isRunning()) { + log.info("requested service already running"); + } else { + requestedService.startService(); + } + return requestedService; + } + + Plan plan = Runtime.load(name, type); + + Map<String, ServiceInterface> services = createServicesFromPlan(plan, null, name); + + if (services == null) { + Runtime.getInstance().error("cannot create instance of %s with type %s given current configuration", name, type); + return null; + } + + requestedService = Runtime.getService(name); + + // FIXME - does some order need to be maintained e.g. all children + // before + // parent + // breadth first, depth first, external order ordinal ? + for (ServiceInterface service : services.values()) { + if (service.getName().equals(name)) { + continue; + } + if (!Runtime.isStarted(service.getName())) { + service.startService(); + } + } + + if (requestedService == null) { + Runtime.getInstance().error("could not start %s of type %s", name, type); + return null; + } + + // getConfig() was problematic here for JMonkeyEngine + ServiceConfig sc = requestedService.getConfig(); + // Map<String, Peer> peers = sc.getPeers(); + // if (peers != null) { + // for (String p : peers.keySet()) { + // Peer peer = peers.get(p); + // log.info("peer {}", peer); + // } + // } + // recursive - start peers of peers of peers ... + Map<String, Peer> subPeers = sc.getPeers(); + if (sc != null && subPeers != null) { + for (String subPeerKey : subPeers.keySet()) { + // IF AUTOSTART !!! + Peer subPeer = subPeers.get(subPeerKey); + if (subPeer.autoStart) { + Runtime.start(sc.getPeerName(subPeerKey), subPeer.type); + } + } + } + + requestedService.startService(); + return requestedService; + } catch (Exception e) { + runtime.error(e); + } + return null; + } + } + + /** + * single parameter name info supplied - potentially all information regarding + * this service could be found in on the filesystem or in the plan + * + * @param name + * @return + */ + static public ServiceInterface start(String name) { + synchronized (processLock) { + if (Runtime.getService(name) != null) { + // already exists + ServiceInterface si = Runtime.getService(name); + if (!si.isRunning()) { + si.startService(); + } + return si; + } + Plan plan = Runtime.load(name, null); + Map<String, ServiceInterface> services = createServicesFromPlan(plan, null, name); + // FIXME - order ? + for (ServiceInterface service : services.values()) { + service.startService(); + } + return Runtime.getService(name); + } + } + + public static Plan load(String name, String type) { + synchronized (processLock) { + try { + Runtime runtime = Runtime.getInstance(); + return runtime.loadService(new Plan("runtime"), name, type, true, 0); + } catch (IOException e) { + runtime.error(e); + } + return null; + } + } + + /** + * Construct a new Runtime with the given name and ID. The name should always + * be "runtime" as parts of interprocess communication assume it to be so. + * + * TODO Check if there's a way to remove the assumptions about Runtime's name + * + * @param n + * Name of the runtime. Should always be {@code "runtime"} + * @param id + * The ID of the instance this runtime belongs to. + */ + public Runtime(String n, String id) { + super(n, id); + + // because you need to start with something ... + config = new RuntimeConfig(); + + repo = (IvyWrapper) Repo.getInstance(LIBRARIES, "IvyWrapper"); + + /** + * This is used to run through all the possible services and determine if + * they have any missing dependencies. If they do not they become + * "installed". The installed flag makes the gui do a crossout when a + * service type is selected. + */ + for (MetaData metaData : serviceData.getServiceTypes()) { + Set<ServiceDependency> deps = repo.getUnfulfilledDependencies(metaData.getType()); + if (deps.size() == 0) { + metaData.installed = true; + } else { + log.info("{} not installed", metaData.getSimpleName()); + } + } + + setLocale(Locale.getDefault().getTag()); + locales = Locale.getDefaults(); + + if (runtime.platform == null) { + runtime.platform = Platform.getLocalInstance(); + } + + // setting the id and the platform + platform = Platform.getLocalInstance(); + + String libararyPath = System.getProperty("java.library.path"); + String userDir = System.getProperty("user.dir"); + String userHome = System.getProperty("user.home"); + + // initialize the config list + publishConfigList(); + + // TODO this should be a single log statement + // http://developer.android.com/reference/java/lang/System.html + + String format = "yyyy/MM/dd HH:mm:ss"; + SimpleDateFormat sdf = new SimpleDateFormat(format); + SimpleDateFormat gmtf = new SimpleDateFormat(format); + gmtf.setTimeZone(TimeZone.getTimeZone("UTC")); + log.info("============== args begin =============="); + StringBuffer sb = new StringBuffer(); + + jvmArgs = getJvmArgs(); + if (globalArgs != null) { + for (int i = 0; i < globalArgs.length; ++i) { + sb.append(globalArgs[i]); + } + } + if (jvmArgs != null) { + log.info("jvmArgs {}", Arrays.toString(jvmArgs.toArray())); + } + log.info("file.encoding {}", System.getProperty("file.encoding")); + log.info("args {}", Arrays.toString(globalArgs)); + + log.info("============== args end =============="); + + log.info("============== env begin =============="); + + Map<String, String> env = System.getenv(); + if (env.containsKey("PATH")) { + log.info("PATH={}", env.get("PATH")); + } else { + log.info("PATH not defined"); + } + if (env.containsKey("JAVA_HOME")) { + log.info("JAVA_HOME={}", env.get("JAVA_HOME")); + } else { + log.info("JAVA_HOME not defined"); + } + + // also look at bitness detection in framework.Platform + String procArch = env.get("PROCESSOR_ARCHITECTURE"); + String procArchWow64 = env.get("PROCESSOR_ARCHITEW6432"); + if (procArch != null) { + log.info("PROCESSOR_ARCHITECTURE={}", procArch); + } else { + log.info("PROCESSOR_ARCHITECTURE not defined"); + } + if (procArchWow64 != null) { + log.info("PROCESSOR_ARCHITEW6432={}", procArchWow64); + } else { + log.info("PROCESSOR_ARCHITEW6432 not defined"); + } + log.info("============== env end =============="); + + log.info("============== platform =============="); + long startTime = platform.getStartTime().getTime(); + log.info("{} - GMT - {}", sdf.format(startTime), gmtf.format(startTime)); + log.info("pid {}", platform.getPid()); + log.info("hostname {}", platform.getHostname()); + log.info("ivy [runtime,{}.{}.{}]", platform.getArch(), platform.getJvmBitness(), platform.getOS()); + log.info("version {} branch {} commit {} build {}", platform.getVersion(), platform.getBranch(), platform.getCommit(), platform.getBuild()); + System.out.println(String.format("version %s branch %s commit %s build %s", platform.getVersion(), platform.getBranch(), platform.getCommit(), platform.getBuild())); + log.info("platform manifest {}", Platform.getManifest()); + log.info("platform [{}}]", platform); + log.info("version [{}]", platform.getVersion()); + log.info("root [{}]", FileIO.getRoot()); + log.info("cfg dir [{}]", FileIO.getCfgDir()); + log.info("sun.arch.data.model [{}]", System.getProperty("sun.arch.data.model")); + + log.info("============== non-normalized =============="); + log.info("os.name [{}] getOS [{}]", System.getProperty("os.name"), platform.getOS()); + log.info("os.arch [{}] getArch [{}]", System.getProperty("os.arch"), platform.getArch()); + log.info("os.version [{}]", System.getProperty("os.version")); + + log.info("java.vm.name [{}]", System.getProperty("java.vm.name")); + log.info("java.vm.vendor [{}]", System.getProperty("java.vm.vendor")); + log.info("java.specification.version [{}]", System.getProperty("java.specification.version")); + + String vmVersion = System.getProperty("java.specification.version"); + vmVersion = "11"; + if ("1.8".equals(vmVersion)) { + error("Unsupported Java %s - please remove version and install Java 1.8", vmVersion); + } + + // test ( force encoding ) + // System.setProperty("file.encoding","UTF-8" ); + log.info("file.encoding [{}]", System.getProperty("file.encoding")); + log.info("Charset.defaultCharset() [{}]", Charset.defaultCharset()); + log.info("user.language [{}]", System.getProperty("user.language")); + log.info("user.country [{}]", System.getProperty("user.country")); + log.info("user.variant [{}]", System.getProperty("user.variant")); + + // System.getProperty("pi4j.armhf") + + log.info("java.home [{}]", System.getProperty("java.home")); + log.debug("java.class.path [{}]", System.getProperty("java.class.path")); + log.info("java.library.path [{}]", libararyPath); + log.info("user.dir [{}]", userDir); + + log.info("user.home [{}]", userHome); + log.info("total mem [{}] Mb", Runtime.getTotalMemory() / 1048576); + log.info("total free [{}] Mb", Runtime.getFreeMemory() / 1048576); + // Access restriction - log.info("total physical mem [{}] Mb", + // Runtime.getTotalPhysicalMemory() / 1048576); + + if (platform.isWindows()) { + log.info("guessed os bitness [{}]", platform.getOsBitness()); + // try to compare os bitness with jvm bitness + if (platform.getOsBitness() != platform.getJvmBitness()) { + log.warn("detected possible bitness mismatch between os & jvm"); + } + } + + log.info("getting local repo"); + + if (repo != null)/* transient */ { + repo.addStatusPublisher(this); + } + } + + /** + * Get the process ID of the current JVM. + * + * @return The process ID. + * @see Platform#getPid() + */ + public String getPid() { + return Platform.getLocalInstance().getPid(); + } + + public String publishDefaultRoute(String defaultRoute) { + return defaultRoute; + } + + /** + * Get the hostname of the computer this instance is running on. + * + * @return The computer's hostname + * @see Platform#getHostname() + */ + public String getHostname() { + return Platform.getLocalInstance().getHostname(); + } + + /** + * publishing event - since checkForUpdates may take a while + */ + public void checkingForUpdates() { + log.info("checking for updates"); + } + + /** + * Read an entire input stream as a string and return it. If the input stream + * does not have any more tokens, returns an empty string instead. + * + * @param is + * The input stream to read from + * @return The entire input stream read as a string + */ + static public String getInputAsString(InputStream is) { + try (java.util.Scanner s = new java.util.Scanner(is)) { + return s.useDelimiter("\\A").hasNext() ? s.next() : ""; + } + } + + /** + * list the contents of the current working directory + * + * @return object + */ + public Object ls() { + return ls(null, null); + } + + /** + * List the contents of an absolute path. + * + * @param path + * The path to list + * @return The contents of the directory + */ + public Object ls(String path) { + return ls(null, path); + } + + /** + * list the contents of a specific path + * <p> + * </p> + * TODO It looks like this only returns Object because it wants to return + * either a String array or a method entry list. It would probably be best to + * just convert the method entry list to a string array using streams and + * change the signature to match. + * + * @param contextPath + * c + * @param path + * p + * @return object + * + */ + public Object ls(String contextPath, String path) { + String absPath = null; + + if (contextPath != null) { + path = contextPath + path; + } + + if (path == null) { + path = "/"; + } + + // ALL SHOULD BE ABSOLUTE PATH AT THIS POINT + // IE STARTING WITH / + + if (!path.startsWith("/")) { + path = "/" + path; + } + + absPath = path; + + String[] parts = absPath.split("/"); + + String ret = null; + if (absPath.equals("/")) { + return Runtime.getServiceNames(); + } else if (parts.length == 2 && !absPath.endsWith("/")) { + return Runtime.getService(parts[1]); + } else if (parts.length == 2 && absPath.endsWith("/")) { + ServiceInterface si = Runtime.getService(parts[1]); + if (si == null) { + return null; + } + return si.getDeclaredMethodNames(); + /* + * } else if (parts.length == 3 && !absPath.endsWith("/")) { // execute 0 + * parameter function ??? return Runtime.getService(parts[1]); + */ + } else if (parts.length == 3) { + ServiceInterface si = Runtime.getService(parts[1]); + MethodCache cache = MethodCache.getInstance(); + List<MethodEntry> me = cache.query(si.getTypeKey(), parts[2]); + return me; // si.getMethodMap().get(parts[2]); + } + return ret; + } + + /** + * serviceName at id + * + * @return runtime name with instance id. + * + */ + public String whoami() { + return "runtime@" + getId(); + } + + // end cli commands ---- + + // ---------- Java Runtime wrapper functions begin -------- + /** + * Executes the specified command and arguments in a separate process. Returns + * the exit value for the subprocess. + * + * @param program + * The name of or path to an executable program. If given a name, the + * program must be on the system PATH. + * @return The exit value of the subprocess + */ + static public String exec(String program) { + return execute(program, null, null, null, null); + } + + /* + * FIXME - see if this is used anymore publishing point of Ivy sub system - + * sends event failedDependency when the retrieve report for a Service fails + */ + @Deprecated /* remove */ + public String failedDependency(String dep) { + return dep; + } + + public static Platform getPlatform() { + return getInstance().platform; + } + + // FIXME - should be removed - use Platform.getLocalInstance().is64bit() + @Deprecated + public boolean is64bit() { + return getInstance().platform.getJvmBitness() == 64; + } + + public Repo getRepo() { + return repo; + } + + /** + * Returns an array of all the simple type names of all the possible services. + * The data originates from the repo's serviceData.json file. + * <p> + * There is a local one distributed with the installation jar. When an + * "update" is forced, MRL will try to download the latest copy from the repo. + * <p> + * The serviceData.json lists all service types, dependencies, categories and + * other relevant information regarding service creation + * + * @return list of all service type names + */ + public String[] getServiceTypeNames() { + return getServiceTypeNames("all"); + } + + /** + * getServiceTypeNames will publish service names based on some filter + * criteria + * + * @param filter + * f + * @return array of service types + * + */ + public String[] getServiceTypeNames(String filter) { + return serviceData.getServiceTypeNames(filter); + } + + // FIXME THIS IS NOT NORMALIZED !!! + + /** + * Send the full log of the currently running MRL instance to the MyRobotLab + * developers for help. The userID is the name of the MyRobotLab.org user + * account + * + * @param userId + * Name of the MRL website account to link the log to + * @return Whether the log was sent successfully, info if yes and error if no. + */ + static public Status noWorky(String userId) { + Status status = null; + try { + String retStr = HttpRequest.postFile("http://noworky.myrobotlab.org/no-worky", userId, "file", new File(LoggingFactory.getLogFileName())); + if (retStr.contains("Upload:")) { + log.info("noWorky successfully sent - our crack team of experts will check it out !"); + status = Status.info("no worky sent"); + } else { + status = Status.error("could not send"); + } + } catch (Exception e) { + log.error("the noWorky didn't worky !"); + status = Status.error(e); + } + + // this makes the 'static' of this method pointless + // perhaps the webgui should invoke rather than call directly .. :P + Runtime.getInstance().invoke("publishNoWorky", status); + return status; + } + + static public Status publishNoWorky(Status status) { + return status; + } + + // FIXME - create interface for this + public String publishMessage(String msg) { + return msg; + } + + @Override + @Deprecated /* use onResponse ??? */ + public void onMessage(Message msg) { + // TODO: what do we do when we get a message? + log.info("onMessage()"); + } + + /** + * Publishing point when a service was successfully registered locally - + * regardless if the service is local or not. + * + * TODO - more business logic can be created here to limit broadcasting or + * re-broadcasting published registrations + * + * @param registration + * - contains all the information need for a registration to process + */ + @Override + public Registration registered(Registration registration) { + return registration; + } + + /** + * released event - when a service is successfully released from the registry + * this event is triggered + * + */ + @Override + public String released(String name) { + return name; + } + + /** + * A function for runtime to "save" a service - or if the service does not + * exists save the "default" config of that type of service + * + * @param name + * name of service to export + * @return true/false + * @throws IOException + * boom + * + */ + @Deprecated /* use save(name) */ + public boolean export(String name /* , String type */) throws IOException { + return save(name); + } + + public boolean save(String name /* , String type */) throws IOException { + ServiceInterface si = getService(name); + if (si != null) { + return si.save(); + } + error("cannot save %s - does not exist", name); + return false; + } + + /** + * restart occurs after applying updates - user or config data needs to be + * examined and see if its an appropriate time to restart - if it is the + * spawnBootstrap method will be called and bootstrap.jar will go through its + * sequence to update myrobotlab.jar + */ + public void restart() { + // to avoid deadlock of shutting down from external messages + // we spawn a kill thread + new Thread("kill-thread") { + @Override + public void run() { + try { + + info("restarting"); + + // FIXME - should we save() load() ??? + // export("last-restart"); + + // shutdown all services process - send ready to shutdown - ask back + // release all services + for (ServiceInterface service : getServices()) { + service.preShutdown(); + } + + // check if ready ??? + + // release all local services + releaseAll(); + + if (runtime != null) { + runtime.releaseService(); + } + + // make sure python is included + // options.services.add("python"); + // options.services.add("Python"); + + // force invoke + // options.invoke = new String[] { "python", "execFile", + // "lastRestart.py" }; + + // create builder from Launcher daemonize ? + log.info("re launching with commands \n{}", CmdOptions.toString(options.getOutputCmd())); + ProcessBuilder pb = Launcher.createBuilder(options); + + // fire it off + Process restarted = pb.start(); + // it "better" not be a requirement that a process must consume its + // std streams + // "hopefully" - if the OS realizes the process is dead it moves the + // streams to /dev/null ? + // StreamGobbler gobbler = new + // StreamGobbler(String.format("%s-gobbler", getName()), + // restarted.getInputStream()); + // gobbler.start(); + + // dramatic pause + sleep(2000); + + // check if process exists + if (restarted.isAlive()) { + log.info("yay! we continue to live in future generations !"); + } else { + log.error("omg! ... I killed all the services and now there is no offspring ! :("); + } + log.error("goodbye ..."); + shutdown(); + } catch (Exception e) { + log.error("shutdown threw", e); + } + } + }.start(); + } + + /** + * Get the META-INF/MANIFEST.MF file from the myrobotlab.jar as String + * key-value pairs. + * + * @return key-value pairs contained in the manifest file + * @see Platform#getManifest() + */ + static public Map<String, String> getManifest() { + return Platform.getManifest(); + } + + /** + * Runtime's setLogLevel will set the root log level if its called from a + * service - it will only set that Service type's log level + * + * @param level + * - DEBUG | INFO | WARN | ERROR + * @return the level which was set + */ + static public String setLogLevel(String level) { + log.info("setLogLevel {}", level); + Logging logging = LoggingFactory.getInstance(); + logging.setLevel(level); + log.info("setLogLevel {}", level); + return level; + } + + /** + * Get the log level of this MRL instance + * + * @return The log level as a String. + * @see Logging#getLevel() + */ + static public String getLogLevel() { + Logging logging = LoggingFactory.getInstance(); + return logging.getLevel(); + } + + /** + * Set the file to output logs to. This will remove all previously-applied + * appenders from the logging system. + * + * @param file + * The file to output logs to + * @return file + * @see Logging#removeAllAppenders() + */ + static public String setLogFile(String file) { + log.info("setLogFile {}", file); + Logging logging = LoggingFactory.getInstance(); + logging.removeAllAppenders(); + LoggingFactory.setLogFile(file); + logging.addAppender(AppenderType.FILE); + return file; + } + + /** + * Disables logging by removing all appenders. To re-enable call + * {@link #setLogFile(String)} or add appenders. + * + * @see Logging#addAppender(String) + */ + static public void disableLogging() { + Logging logging = LoggingFactory.getInstance(); + logging.removeAllAppenders(); + } + + /** + * Stops all service-related running items. This releases the singleton + * referenced by this class, but it does not guarantee that the old service + * will be GC'd. FYI - if stopServices does not remove INSTANCE - it is not + * re-entrant in junit tests + */ + @Override + public void releaseService() { + if (runtime != null) { + runtime.purgeTasks(); + runtime.stopService(); + runtime.stopInteractiveMode(); + runtime.getRepo().removeStatusPublishers(); + if (cli != null) { + cli.stop(); + } + registry = new TreeMap<>(); + } + synchronized (INSTANCE_LOCK) { + runtime = null; + } + } + + /** + * Close all connections using this runtime as the gateway. This includes both + * inbound and outbound connections. + */ + public void closeConnections() { + for (Connection c : connections.values()) { + String gateway = c.getGateway(); + if (getFullName().equals(gateway)) { + WsClient client = (WsClient) c.get("c-client"); + client.close(); + } + } + } + + // FYI - the way to call "all" service methods ! + + /** + * Clear all services' last error. + * + * @see ServiceInterface#clearLastError() + */ + public void clearErrors() { + for (String serviceName : registry.keySet()) { + send(serviceName, "clearLastError"); + } + } + + /** + * Check if any services have errors. + * + * @return Whether any service has an error + * @see ServiceInterface#hasError() + */ + public static boolean hasErrors() { + for (ServiceInterface si : registry.values()) { + if (si.hasError()) { + return true; + } + } + return false; + } + + /** + * remove all subscriptions from all local Services + */ + static public void removeAllSubscriptions() { + for (ServiceInterface si : getLocalServices().values()) { + List<String> nlks = si.getNotifyListKeySet(); + for (int i = 0; i < nlks.size(); ++i) { + si.getNotifyList().clear(); + } + } + } + + /** + * Get recent errors from all local services. + * + * @return A list of most recent service errors + * @see ServiceInterface#getLastError() + */ + public static List<Status> getErrors() { + ArrayList<Status> stati = new ArrayList<Status>(); + for (ServiceInterface si : getLocalServices().values()) { + Status status = si.getLastError(); + if (status != null && status.isError()) { + log.info(status.toString()); + stati.add(status); + } + } + return stati; + } + + /** + * Broadcast the states of all local services. + */ + public static void broadcastStates() { + for (ServiceInterface si : getLocalServices().values()) { + si.broadcastState(); + } + } + + /** + * Get the Runtime singleton instance. + * + * @return The singleton instance + * @see #getInstance() + */ + public static Runtime get() { + return Runtime.getInstance(); + } + + /** + * Execute an external program with arguments if specified. args must not be + * null and the length must be greater than zero, the first element is the + * program to be executed. If the program is just a name and not a path to the + * executable then it must be on the operating system PATH. + * + * @see <a href= + * "https://superuser.com/questions/284342/what-are-path-and-other-environment-variables-and-how-can-i-set-or-use-them"> + * What are PATH and other environment variables?</a> + * @param args + * The program to be executed as the first element and the args to + * the program as the rest, if any + * @return The program's stdout and stderr output + */ + static public String execute(String... args) { + if (args == null || args.length == 0) { + log.error("execute invalid number of args"); + return null; + } + String program = args[0]; + List<String> list = null; + + if (args.length > 1) { + list = new ArrayList<String>(); + for (int i = 1; i < args.length; ++i) { + list.add(args[i]); + } + } + + return execute(program, list, null, null, true); + } + + /** + * Execute an external program with a list of arguments, a specified working + * directory, any additional environment variables, and whether the execution + * blocks. + * + * TODO Implement workingDir and block + * + * @param program + * The program to be executed + * @param args + * Any arguments to the command + * @param workingDir + * The directory to execute the program in + * @param additionalEnv + * Any additional environment variables + * @param block + * Whether this method blocks for the program to execute + * @return The programs stderr and stdout output + */ + + static public String execute(String program, List<String> args, String workingDir, Map<String, String> additionalEnv, boolean block) { + log.debug("execToString(\"{} {}\")", program, args); + + List<String> command = new ArrayList<>(); + command.add(program); + if (args != null) { + command.addAll(args); + } + + ProcessBuilder builder = new ProcessBuilder(command); + if (workingDir != null) { + builder.directory(new File(workingDir)); + } + + Map<String, String> environment = builder.environment(); + if (additionalEnv != null) { + environment.putAll(additionalEnv); + } + + StringBuilder outputBuilder = new StringBuilder(); + + try { + Process handle = builder.start(); + + InputStream stdErr = handle.getErrorStream(); + InputStream stdOut = handle.getInputStream(); + + // Read the output streams in separate threads to avoid potential blocking + Thread stdErrThread = new Thread(() -> readStream(stdErr, outputBuilder)); + stdErrThread.start(); + + Thread stdOutThread = new Thread(() -> readStream(stdOut, outputBuilder)); + stdOutThread.start(); + + if (block) { + int exitValue = handle.waitFor(); + outputBuilder.append("Exit Value: ").append(exitValue); + log.info("Command exited with exit value: {}", exitValue); + } else { + log.info("Command started"); + } + + return outputBuilder.toString(); + } catch (IOException e) { + log.error("Error executing command", e); + return e.getMessage(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Command execution interrupted", e); + return e.getMessage(); + } + } + + private static void readStream(InputStream inputStream, StringBuilder outputBuilder) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = reader.readLine()) != null) { + outputBuilder.append(line).append(System.lineSeparator()); + } + } catch (IOException e) { + log.error("Error reading process output", e); + } + } + + /** + * Get the current battery level of the computer this MRL instance is running + * on. + * + * @return The battery level as a double from 0.0 to 100.0, expressed as a + * percentage. + */ + public static Double getBatteryLevel() { + Platform platform = Platform.getLocalInstance(); + Double r = 100.0; + try { + if (platform.isWindows()) { + // String ret = Runtime.execute("cmd.exe", "/C", "WMIC.exe", "PATH", + // "Win32_Battery", "Get", "EstimatedChargeRemaining"); + String ret = Runtime.execute("WMIC.exe", "PATH", "Win32_Battery", "Get", "EstimatedChargeRemaining"); + int pos0 = ret.indexOf("\n"); + if (pos0 != -1) { + pos0 = pos0 + 1; + int pos1 = ret.indexOf("\n", pos0); + String dble = ret.substring(pos0, pos1).trim(); + try { + r = Double.parseDouble(dble); + } catch (Exception e) { + log.error("no Battery detected by system"); + } + + return r; + } + + } 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 + 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; + } + log.info(ret); + } + } else if (platform.isMac()) { + String ret = Runtime.execute("pmset -g batt"); + int pos0 = ret.indexOf("Battery-0"); + if (pos0 != -1) { + pos0 = pos0 + 10; + int pos1 = ret.indexOf("%", pos0); + String dble = ret.substring(pos0, pos1).trim(); + try { + r = Double.parseDouble(dble); + } catch (Exception e) { + log.error("no Battery detected by system"); + } + return r; + } + log.info(ret); + } + + } catch (Exception e) { + log.info("execToString threw", e); + } + return r; + } + + /** + * Get the local service data instance. + * + * @return The local service data + * @see ServiceData#getLocalInstance() + */ + public ServiceData getServiceData() { + return serviceData; + } + + /** + * Return supported system languages + * + * @return map of languages to locales + */ + public Map<String, Locale> getLanguages() { + return Locale.getAvailableLanguages(); + } + + /** + * Get a map between locale IDs and the associated {@link Locale} instance. + * + * @return A map between IDs and instances. + */ + @Override + public Map<String, Locale> getLocales() { + return locales; + } + + /** + * Set the locales by passing a list of locale IDs. + * + * @param codes + * A list of locale IDs + * @return A map between the IDs and the Locale instances. + */ + public Map<String, Locale> setLocales(String... codes) { + locales = Locale.getLocaleMap(codes); + return locales; + } + + /** + * @return get the Security singleton + * + * + */ + static public Security getSecurity() { + return Security.getInstance(); + } + + /** + * Execute a program with arguments, if any. Wraps + * {@link java.lang.Runtime#exec(String[])}. + * + * @param cmd + * A list with the program name as the first element and any + * arguments as the subsequent elements. + * @return The Process spawned by the execution + * @throws IOException + * if an I/O error occurs while spawning the process + */ + public static Process exec(String... cmd) throws IOException { + // FIXME - can't return a process - it will explode in serialization + // but we might want to keep it and put it on a transient map + log.info("Runtime exec {}", Arrays.toString(cmd)); + Process p = java.lang.Runtime.getRuntime().exec(cmd); + return p; + } + + /** + * Get all the options passed on the command line when MyRobotLab is executed. + * + * @return The options that were passed on the command line + */ + public static CmdOptions getOptions() { + return options; + } + + /** + * TODO Unimplemented + * + * @param sd + * ServiceData to use + * @return sd + */ + public ServiceData setServiceTypes(ServiceData sd) { + return sd; + } + + /** + * FIXME - describe will have the capability to describe many aspects of a + * running service. Default behavior will show a list of local names, but + * depending on input criteria it should be possible to show * interfaces * + * service data * service methods * details of a service method * help/javadoc + * of a service method * list of other known instances * levels of detail, or + * lists of fields to display * meaningful default + * + * FIXME - input parameters will need to change - at some point, a subscribe + * to describe, and appropriate input parameters should replace the current + * onRegistered system + * + * @param type + * t + * @param id + * i + * @param remoteUuid + * remote id + * @return describe results + * + */ + public DescribeResults describe(String type, String id, String remoteUuid) { + DescribeQuery query = new DescribeQuery(type, remoteUuid); + return describe(type, query); + } + + /** + * Get a default DescribeResults from this instance. + * + * @return A default description of this instance + */ + public DescribeResults describe() { + // default query + return describe("platform", null); + } + + /** + * Describe results returns the information of a "describe" which can be + * detailed information regarding services, theire methods and input or output + * types. + * <p> + * FIXME - describe(String[] filters) where filter can be name, type, local, + * state, etc + * <p> + * FIXME uuid and query are unused + * + * @param uuid + * u + * @param query + * q + * @return describe results + * + * + * + */ + public DescribeResults describe(String uuid, DescribeQuery query) { + + DescribeResults results = new DescribeResults(); + results.setStatus(Status.success("Ahoy!")); + + String fullname = null; + + try { + + results.setId(getId()); + results.setPlatform(Platform.getLocalInstance()); + + // broadcast completed connection information + invoke("getConnections"); // FIXME - why isn't this done before ??? + + Set<String> set = registry.keySet(); + String[] list = new String[set.size()]; + set.toArray(list); + + // TODO - filtering on what is broadcasted or re-broadcasted + for (int i = 0; i < list.length; ++i) { + fullname = list[i]; + ServiceInterface si = registry.get(fullname); + + Registration registration = new Registration(si); + + results.addRegistration(registration); + } + + } catch (Exception e) { + log.error("describe threw on {}", fullname, e); + } + + return results; + } + + /** + * Describe results from remote query to describe + * + * @param results + * describe results + * + * + */ + public void onDescribe(DescribeResults results) { + List<Registration> reservations = results.getReservations(); + if (reservations != null) { + for (Registration reservation : reservations) { + if ("runtime".equals(reservation.getName()) && !getId().equals(reservation.getId())) { + // If there's a reservation for a remote runtime, subscribe to its + // registered + // Maybe this should be done in register()? + subscribe(reservation.getFullName(), "registered"); + } + register(reservation); + } + } + + } + + /** + * IMPORTANT IMPORTANT IMPORTANT - Newly connected remote mrl processes blas a + * list of registrations through onRegistered messages, for each service they + * currently have in their registry. This process will send a list of + * registrations to the newly connected remote process. If the "registered" + * event is subscribed, any newly created service will be broadcasted thorough + * this publishing point as well. + * + * TODO - write filtering, configuration, or security which affects what can + * be registered + * + * Primarily, this is where new services are registered from remote systems + * + * + */ + public void onRegistered(Registration registration) { + try { + // check if registered ? + + // TODO - filtering - include/exclude + + String fullname = registration.getName() + "@" + registration.getId(); + if (!registry.containsKey(fullname)) { + register(registration); + if (fullname.startsWith("runtime@")) { + // We want to TELL remote runtime if we have new registrations - we'll + // send them + // to it's runtime + // subscribe(fullname, "registered"); + // subscribe(fullname, "released"); + // IMPORTANT w + addListener("registered", fullname); + addListener("released", fullname); + } + } else { + log.info("{} onRegistered already registered", fullname); + } + } catch (Exception e) { + log.error("onRegistered threw {}", registration, e); + } + } + + /** + * Listener for authentication. + * + * @param response + * The results from a foreign instance's + * {@link Runtime#describe(String, DescribeQuery)} + */ + public void onAuthenticate(DescribeResults response) { + log.info("onAuthenticate {}", response); + } + + /** + * Get a list of metadata about all services local to this instance. + * + * @return A list of metadata about local services + * @see ServiceData#getServiceTypes() + */ + public List<MetaData> getServiceTypes() { + List<MetaData> filteredTypes = new ArrayList<>(); + for (MetaData metaData : serviceData.getServiceTypes()) { + if (metaData.isAvailable()) { + filteredTypes.add(metaData); + } + } + return filteredTypes; + } + + /** + * Register a connection route from one instance to this one. + * + * @param uuid + * Unique ID for a connecting client + * @param id + * Name or ID of the connecting client + * @param connection + * Details of the connection + */ + @Override + public void addConnection(String uuid, String id, Connection connection) { + Connection attr = null; + if (!connections.containsKey(uuid)) { + attr = connection; + invoke("publishConnect", connection); + } else { + attr = connections.get(uuid); + attr.putAll(connection); + } + connections.put(uuid, attr); + // String id = (String)attr.get("id"); + + addRoute(id, uuid, 10); + } + + /** + * Unregister all connections that a specified client has made. + * + * @param uuid + * The ID of the client + */ + @Override + public void removeConnection(String uuid) { + + Connection conn = connections.remove(uuid); + + if (conn != null) { + invoke("publishDisconnect", uuid); + invoke("getConnections"); + + Set<String> remoteIds = routeTable.getAllIdsFor(uuid); + for (String id : remoteIds) { + unregisterId(id); + } + routeTable.removeRoute(uuid); + } + } + + /** + * Unregister all services originating from the instance with the given ID. + * + * @param id + * The ID of the instance that is being unregistered + */ + public void unregisterId(String id) { + Set<String> names = new HashSet<>(registry.keySet()); + for (String name : names) { + if (name.endsWith("@" + id)) { + unregister(name); + } + } + } + + public String publishDisconnect(String uuid) { + return uuid; + } + + // FIXME - filter only serializable objects ? + public Connection publishConnect(Connection attributes) { + return attributes; + } + + /** + * globally get all client + * + * @return connection map + */ + public Map<String, Connection> getConnections() { + return connections; + } + + /** + * separated by connection - send connection name and get filter results back + * for a specific connections connected clients + * + * @param gatwayName + * name + * @return map of connections + * + */ + public Map<String, Connection> getConnections(String gatwayName) { + Map<String, Connection> ret = new HashMap<>(); + for (String uuid : connections.keySet()) { + Connection c = connections.get(uuid); + String gateway = (String) c.get("gateway"); + if (gatwayName == null || gateway.equals(gatwayName)) { + ret.put(uuid, c); + } + } + return ret; + } + + /** + * @return list connections - current connection names to this mrl runtime + * + */ + public Map<String, Connection> lc() { + return getConnections(); + } + + /** + * get a specific clients data + * + * @param uuid + * uuid to get + * @return connection for uuid + * + */ + public Connection getConnection(String uuid) { + return connections.get(uuid); + } + + /** + * @return Globally get all connection uuids + * + */ + public List<String> getConnectionUuids() { + return getConnectionUuids(null); + } + + /** + * Get whether a connection to the given client exists. + * + * @param uuid + * Unique ID of the client to check for + * @return Whether a connection between this instance and the given client + * exists + */ + boolean connectionExists(String uuid) { + return connections.containsKey(uuid); + } + + /** + * Get connection ids that belong to a specific gateway + * + * @param name + * n + * @return list of uuids + * + */ + public List<String> getConnectionUuids(String name) { + List<String> ret = new ArrayList<>(); + for (String uuid : connections.keySet()) { + Connection c = connections.get(uuid); + String gateway = (String) c.get("gateway"); + if (name == null || gateway.equals(name)) { + ret.add(uuid); + } + } + return ret; + } + + /** + * Get the Class instance for a specific service. + * + * @param inName + * The name of the service + * @return The Class of the service. + * @see #getFullName(String) + */ + public static Class<?> getClass(String inName) { + String name = getFullName(inName); + ServiceInterface si = registry.get(name); + if (si == null) { + return null; + } + return si.getClass(); + } + + /** + * takes an id returns a connection uuid + * + * @param id + * id + * @return the connection + * + */ + public Connection getRoute(String id) { + return connections.get(routeTable.getRoute(id)); + } + + public RouteTable getRouteTable() { + return routeTable; + } + + /** + * get gateway based on remote address of a msg e.g. msg.getRemoteId() + * + * @param remoteId + * remote + * @return the gateway + * + */ + public Gateway getGatway(String remoteId) { + // get a connection from the route + Connection conn = getRoute(remoteId); + if (conn == null) { + log.debug("no connection for id {}", remoteId); + return null; + } + // find the gateway managing the connection + return (Gateway) getService((String) conn.get("gateway")); + } + + /** + * Get the full name of the service. A full name is defined as a "short name" + * plus the ID of the Runtime instance it is attached to. The two components + * are separated by an '@' character. If the given name is already a full + * name, it is returned immediately, otherwise a full name is constructed by + * assuming the service is local to this instance. Example: + * + * <pre> + * { + * @code + * String shortName = "python"; + * + * // Assume the local name is "bombastic-cherry" + * String fullName = getFullName(shortName); + * // fullName is now "python@bombastic-cherry" + * + * fullName = getFullName(fullName); + * // fullName is unchanged because it was already a full name + * + * } + * </pre> + * + * + * @param shortname + * The name to convert to a full name + * @return shortname if it is already a full name, or a newly constructed full + * name + */ + static public String getFullName(String shortname) { + if (shortname == null || shortname.contains("@")) { + // already long form + return shortname; + } + // if nothing is supplied assume local + return String.format("%s@%s", shortname, Runtime.getInstance().getId()); + } + + @Override + public List<String> getClientIds() { + return getConnectionUuids(getName()); + } + + @Override + public Map<String, Connection> getClients() { + return getConnections(getName()); + } + + public void pollHosts() { + runtime.addTask(20000, "getHosts"); + } + + // FIXME - remove if not using ... + @Override + public void sendRemote(Message msg) throws IOException { + if (isLocal(msg)) { + log.error("msg NOT REMOTE yet sendRemote is called {}", msg); + return; + } + + // get a connection from the route + Connection conn = getRoute(msg.getId()); + if (conn == null) { + log.error("could not get connection for {} from msg {}", msg.getId(), msg); + return; + } + + // two possible types of "remote" for this gateway cli & ws + if ("Cli".equals(conn.get("c-type"))) { + invoke("publishCli", msg); + + InProcessCli cli = ((InProcessCli) conn.get("cli")); + cli.onMsg(msg); + + } else { + // websocket Client ! + WsClient client = (WsClient) conn.get("c-client"); + if (client == null) { + log.error("could not get client for connection {}", msg.getId()); + return; + } + + /** + * ====================================================================== + * DYNAMIC ROUTE TABLE - outbound msg hop starts now + */ + + // add our id - we don't want to see it again + msg.addHop(getId()); + + log.info("<== {}.{} <-- {}", msg.name, msg.method, msg.sender); + + /** + * ====================================================================== + */ + + client.send(CodecUtils.toJsonMsg(msg)); + } + } + + public Object publishCli(Message msg) { + if (msg.data == null || msg.data.length == 0) { + return null; + } + return msg.data[0]; + } + + /** + * DONT MODIFY NAME - JUST work on is Local - and InvokeOn should handle it + * + * if the incoming Message's remote Id is the (same as ours) OR (it can't be + * found it our route table) - peel it off and treat it as local. + * + * if we have an @{id/connection} but do not have the connection - we'll peel + * off the @{id/connection} and treat it as local if id is ours - peel it off + * ! + */ + @Override + public boolean isLocal(Message msg) { + + if (msg.getId() == null || getId().equals(msg.getId())) { + return true; + } + + return false; + } + + public Object localizeDefault(String key) { + key = key.toUpperCase(); + return defaultLocalization.get(key); + } + + static public void setAllLocales(String code) { + for (ServiceInterface si : getLocalServices().values()) { + si.setLocale(code); + } + } + + @Override + public String created(String name) { + return name; + } + + @Override + public String started(String name) { + // if this is to be used as a callback in Python + // users typically would want simple name ... not "fullname" + + return name; + } + + @Override + public String stopped(String name) { + return name; + } + + /** + * Wrapper for {@link ServiceData#getMetaData(String, String)} + * + * @param serviceName + * The name of the service + * @param serviceType + * The type of the service + * @return The metadata of the service. + */ + public static MetaData getMetaData(String serviceName, String serviceType) { + return ServiceData.getMetaData(serviceName, serviceType); + } + + /** + * Wrapper for {@link ServiceData#getMetaData(String)} + * + * @param serviceType + * The type of the service + * @return The metadata of the service. + */ + public static MetaData getMetaData(String serviceType) { + return ServiceData.getMetaData(serviceType); + } + + /** + * Whether the singleton has been created + * + * @return Whether the singleton exists + */ + public static boolean exists() { + return runtime != null; + } + + /** + * Attempt to get the most likely valid address priority would be a lan + * address - possibly the smallest class + * + * @return string address + * + */ + public String getAddress() { + List<String> addresses = getIpAddresses(); + if (addresses.size() > 0) { + + // class priority + for (String ip : addresses) { + if (ip.startsWith("192.168")) { + return ip; + } + } + + for (String ip : addresses) { + if (ip.startsWith("172.")) { + return ip; + } + } + + for (String ip : addresses) { + if (ip.startsWith("10.")) { + return ip; + } + } + + // give up - return first :P + return addresses.get(0); + } + return null; + } + + public List<Host> getHosts() { + List<String> ips = getIpAddresses(); + String selectedIp = (ips.size() == 1) ? ips.get(0) : null; + if (selectedIp == null) { + for (String ip : ips) { + if ((selectedIp != null) && (ip.startsWith(("192.")))) { + selectedIp = ip; + } else if (selectedIp == null) { + selectedIp = ip; + } + } + } + String subnet = selectedIp.substring(0, selectedIp.lastIndexOf(".")); + return getHosts(subnet); + } + + public List<Host> getHosts(String subnet) { + + if (hosts == null) { + hosts = new HashMap<String, Host>(); + File check = new File(FileIO.gluePaths(getDataDir(), "hosts.json")); + if (check.exists()) { + try { + Host[] hf = CodecUtils.fromJson(FileIO.toString(check), Host[].class); + for (Host h : hf) { + hosts.put(h.ip, h); + } + info("found %d saved hosts", hosts.size()); + } catch (Exception e) { + error("could not load %s - %s", check, e.getMessage()); + } + } + } + + int timeout = 1500; + try { + for (int i = 1; i < 255; i++) { + Thread pinger = new Thread(new Pinger(this, hosts, subnet + "." + i, timeout), "pinger-" + i); + pinger.start(); + } + } catch (Exception e) { + log.error("getHosts threw", e); + } + List<Host> h = new ArrayList<>(); + for (Host hst : hosts.values()) { + if (hst.lastActiveTs != null) { + h.add(hst); + } + } + return h; + } + + public Host publishFoundHost(Host host) { + log.info("found host {}", host); + return host; + } + + public Host publishFoundNewHost(Host host) { + log.info("found new host {}", host); + return host; + } + + public Host publishLostHost(Host host) { + log.info("lost host {}", host); + return host; + } + + public void saveHosts() throws IOException { + FileOutputStream fos = new FileOutputStream(FileIO.gluePaths(getDataDir(), "hosts.json")); + List<Host> h = new ArrayList<>(hosts.values()); + String json = CodecUtils.toPrettyJson(h); + fos.write(json.getBytes()); + fos.close(); + } + + /** + * start python interactively at the command line + */ + public void python() { + if (cli == null) { + startInteractiveMode(); + } + start("python", "Python"); + // since we've suscribed to pythons st + cli.relay("python", "exec", "publishStdOut"); + cli.relay("python", "exec", "publishStdError"); + Logging logging = LoggingFactory.getInstance(); + logging.removeAllAppenders(); + } + + /** + * Main entry point for the MyRobotLab Runtime Check CmdOptions for list of + * options -h help -v version -list jvm args -Dhttp.proxyHost=webproxy + * f-Dhttp.proxyPort=80 -Dhttps.proxyHost=webproxy -Dhttps.proxyPort=80 + * + * @param args + * cmd line args from agent spawn + * + */ + public static void main(String[] args) { + + try { + + // loading args + globalArgs = args; + new CommandLine(options).parseArgs(args); + log.info("in args {}", Launcher.toString(args)); + log.info("options {}", CodecUtils.toJson(options)); + log.info("\n" + Launcher.banner); + + // creating initial data/config directory + File cfgRoot = new File(ROOT_CONFIG_DIR); + cfgRoot.mkdirs(); + + // initialize logging + initLog(); + + // extract if necessary + FileIO.extractResources(); + + // help and exit + if (options.help) { + mainHelp(); + return; + } + + // start.yml file is required, if not pre-existing + // is created immediately. It contains static information + // which needs to be available before a Runtime is created + Runtime.startYml = ConfigUtils.loadStartYml(); + + // resolve configName before starting getting runtime configuration + Runtime.configName = (startYml.enable) ? startYml.config : "default"; + if (options.config != null) { + // cmd line options has the highest priority + Runtime.configName = options.config; + } + + // start.yml is processed, config name is set, runtime config + // is resolved, now we can start instance + Runtime.getInstance(); + + if (options.install != null) { + // resetting log level to info + // for an install otherwise ivy + // info will not be shown in the terminal + // during install of dependencies + // which makes users panic and hit ctrl+C + setLogLevel("info"); + + // we start the runtime so there is a status publisher which will + // display status updates from the repo install + log.info("requesting install"); + Repo repo = getInstance().getRepo(); + if (options.install.length == 0) { + repo.install(LIBRARIES, (String) null); + } else { + for (String service : options.install) { + repo.install(LIBRARIES, service); + } + } + shutdown(); + return; + } + + } catch (Exception e) { + log.error("runtime exception", e); + Runtime.mainHelp(); + shutdown(); + log.error("main threw", e); + } + } + + public static void initLog() { + if (options != null) { + LoggingFactory.init(options.logLevel); + } else { + LoggingFactory.init("info"); + } + } + + public void test() { + for (int statusCnt = 0; statusCnt < 500; statusCnt++) { + statusCnt++; + invoke("publishStatus", Status.info("this is status %d", statusCnt)); + } + } + + public Connection getConnectionFromId(String remoteId) { + for (Connection c : connections.values()) { + if (c.getId().equals(remoteId)) { + return c; + } + } + return null; + } + + /** + * A gateway is responsible for creating a key to associate a unique + * "Connection". This key should be retrievable, when a msg arrives at the + * service which needs to be sent remotely. This key is used to get the + * "Connection" to send the msg remotely + * + * @param string + * s + * @param uuid + * u + * + */ + public void addLocalGatewayKey(String string, String uuid) { + routeTable.addLocalGatewayKey(string, uuid); + } + + public boolean containsRoute(String remoteId) { + return routeTable.contains(remoteId); + } + + public String getConnectionUuidFromGatewayKey(String gatewayKey) { + return routeTable.getConnectionUuid(gatewayKey); + } + + /** + * This helper method will create, load then start a service + * + * @param name + * - name of instance + * @param type + * - type + * @return returns the service in the form of a ServiceInterface + */ + static public ServiceInterface loadAndStart(String name, String type) { + ServiceInterface s = null; + try { + s = create(name, type); + s.load(); + s.startService(); + } catch (Exception e) { + log.error("loadAndStart threw", e); + } + return s; + } + + /** + * DEFAULT IF NOTHING EXISTS DO NOT DEFAULT SOMETHING THAT'S ALREADY IN PLAN + * OVERRIDE WITH FILE + * + * Load a single service entry into the plan through yml or default. This + * method is responsible for resolving the Type and ServiceConfig for a single + * service. Since some service Types are composites and require Peers, it can + * potentially be recursive. The level of overrides are from highest priority + * to lowest : + * + * <pre> + * if a Plan definition of {name} exists, use it - "current" plan definition ! + * /data/config/{configName}/{service}.yml - user's yml override + * /resource/config/{configName}/{service}.yml - system yml default + * {ServiceConfig}.java - system java type default + * + * + * </pre> + * + * @param plan + * - plan to load + * @param name + * - name of service + * @param type + * - type of service + * @param start + * - weather to specify in RuntimeConfig.registry to "start" this + * service when createFromPlan is run + * @param level + * - level of the depth, services may load peers which in turn will + * load more, this is the depth of recursion + * @return + * @throws IOException + */ + public Plan loadService(Plan plan, String name, String type, boolean start, int level) throws IOException { + synchronized (processLock) { + + if (plan == null) { + log.error("plan required to load a system"); + return null; + } + + log.info("loading - {} {} {}", name, type, level); + // from recursive memory definition + ServiceConfig sc = plan.get(name); + + // HIGHEST PRIORITY - OVERRIDE WITH FILE + String configPath = runtime.getConfigPath(); + String configFile = configPath + fs + name + ".yml"; + + // PRIORITY #1 + // find if a current yml config file exists - highest priority + log.debug("priority #1 user's yml override {} ", configFile); + ServiceConfig fileSc = readServiceConfig(Runtime.getInstance().getConfigName(), name); + if (fileSc != null) { + // if definition exists in file form, it overrides current memory one + sc = fileSc; + } else if (sc != null) { + // if memory config is available but not file + // we save it + String yml = CodecUtils.toYaml(sc); + FileIO.toFile(configFile, yml); + } + + // special conflict case - type is specified, but its not the same as + // file version - in that case specified parameter type wins and + // overwrites + // config. User can force type by supplying one as a parameter, however, + // the + // recursive + // call other peer types will have name/file.yml definition precedence + if ((type != null && sc != null && !type.equals(sc.type) && level == 0) || (sc == null)) { + if (sc != null) { + warn("type %s overwriting type %s specified in %s.yml file", type, sc.type, name); + } + ServiceConfig.getDefault(plan, name, type); + sc = plan.get(name); + + // create new file if it didn't exist or overwrite it if new type is + // required + String yml = CodecUtils.toYaml(sc); + FileIO.toFile(configFile, yml); + } + + if (sc == null && type == null) { + log.error("no local config and unknown type"); + return plan; + } + + // finalize + if (sc != null) { + plan.put(name, sc); + // RECURSIVE load peers + Map<String, Peer> peers = sc.getPeers(); + for (String peerKey : peers.keySet()) { + Peer peer = peers.get(peerKey); + // recursive depth load - parent and child need to be started + runtime.loadService(plan, peer.name, peer.type, start && peer.autoStart, level + 1); + } + + // valid service config at this point - now determine if its supposed to + // start or not + // if its level 0 then it was requested by user or config - so it needs + // to + // start + // if its not level 0 then it was loaded because peers were defined and + // appropriate config loaded + // peer.autoStart should determine if the peer starts if not explicitly + // requested by the + // user or config + if (level == 0 || start) { + plan.addRegistry(name); + } + + } else { + log.info("could not load {} {} {}", name, type, level); + } + + return plan; + } + } + + /** + * read a service's configuration, in the context of current config set name + * or default + * + * @param name + * @return + */ + public ServiceConfig readServiceConfig(String name) { + return readServiceConfig(name, new StaticType<>() { + }); + } + + /** + * read a service's configuration, in the context of current config set name + * or default + * + * @param name + * @return + */ + public <C extends ServiceConfig> C readServiceConfig(String name, StaticType<C> configType) { + return readServiceConfig(null, name, configType); + } + + public ServiceConfig readServiceConfig(String configName, String name) { + return readServiceConfig(configName, name, new StaticType<>() { + }); + } + + /** + * + * @param configName + * - filename or dir of config set + * @param name + * - name of config file within that dir e.g. {name}.yml + * @return + */ + public <C extends ServiceConfig> C readServiceConfig(String configName, String name, StaticType<C> configType) { + // if config path set and yaml file exists - it takes precedence + + if (configName == null) { + configName = runtime.getConfigName(); + } + + if (configName == null) { + log.info("config name is null cannot load {} file system", name); + return null; + } + + String filename = ROOT_CONFIG_DIR + fs + configName + fs + name + ".yml"; + File check = new File(filename); + C sc = null; + if (check.exists()) { + try { + sc = CodecUtils.readServiceConfig(filename, configType); + } catch (ConstructorException e) { + error("config %s invalid %s %s. Please remove it from the file.", name, filename, e.getCause().getMessage()); + } catch (Exception e) { + error("config could not load %s file is invalid", filename); + } + } + return sc; + } + + public String publishConfigLoaded(String name) { + return name; + } + + @Override + public RuntimeConfig apply(RuntimeConfig config) { + super.apply(config); + + setLocale(config.locale); + + if (config.id == null) { + config.id = NameGenerator.getName(); + } + + if (config.logLevel != null) { + setLogLevel(config.logLevel); + } + + if (config.virtual != null) { + info("setting virtual to %b", config.virtual); + setAllVirtual(config.virtual); + } + + // APPLYING A RUNTIME CONFIG DOES NOT PROCESS THE REGISTRY + // USE startConfig(name) + + broadcastState(); + return config; + } + + /** + * release the current config + */ + static public void releaseConfig() { + String currentConfigPath = Runtime.getInstance().getConfigName(); + if (currentConfigPath != null) { + releaseConfigPath(currentConfigPath); + } + } + + /** + * wrapper + * + * @param configName + */ + static public void releaseConfig(String configName) { + setConfig(configName); + releaseConfigPath(Runtime.getInstance().getConfigName()); + } + + /** + * Release a configuration set - this depends on a runtime file - and it will + * release all the services defined in it, with the exception of the + * originally started services + * + * @param configPath + * config set to release + * + */ + static public void releaseConfigPath(String configPath) { + try { + String filename = ROOT_CONFIG_DIR + fs + Runtime.getInstance().getConfigName() + fs + "runtime.yml"; + String releaseData = FileIO.toString(new File(filename)); + RuntimeConfig config = CodecUtils.fromYaml(releaseData, RuntimeConfig.class); + List<String> registry = config.getRegistry(); + Collections.reverse(Arrays.asList(registry)); + + // get starting services if any entered on the command line + // -s log Log webgui WebGui ... etc - these will be protected + List<String> startingServices = new ArrayList<>(); + if (options.services.size() % 2 == 0) { + for (int i = 0; i < options.services.size(); i += 2) { + startingServices.add(options.services.get(i)); + } + } + + for (String name : registry) { + if (startingServices.contains(name)) { + continue; + } + release(name); + } + } catch (Exception e) { + Runtime.getInstance().error("could not release %s", configPath); + } + } + + public static String getConfigRoot() { + return ROOT_CONFIG_DIR; + } + + /** + * wrapper for saveConfigPath with default prefix path supplied + * + * @param configName + * @return + */ + static public boolean saveConfig(String configName) { + Runtime runtime = Runtime.getInstance(); + if (configName == null) { + runtime.error("saveConfig require a name cannot be null"); + return false; + } + boolean ret = runtime.saveService(configName, null, null); + runtime.broadcastState(); + return ret; + } + + /** + * + * Saves the current runtime, all services and all configuration for each + * service in the current "config path", if the config path does not exist + * will error + * + * @param configName + * - config set name if null defaults to default + * @param serviceName + * - service name if null defaults to saveAll + * @param filename + * - if not explicitly set - will be standard yml filename + * @return - true if all goes well + */ + public boolean saveService(String configName, String serviceName, String filename) { + try { + + if (configName == null) { + error("config name cannot be null"); + return false; + } + + setConfig(configName); + + String configPath = ROOT_CONFIG_DIR + fs + configName; + + // save running services + Set<String> servicesToSave = new HashSet<>(); + + // conditional boolean to flip and save a config name to start.yml ? + if (startYml.enable) { + startYml.config = configName; + FileIO.toFile("start.yml", CodecUtils.toYaml(startYml)); + } + + if (serviceName == null) { + // all services + servicesToSave = getLocalServices().keySet(); + } else { + // single service + servicesToSave.add(serviceName); + } + + for (String s : servicesToSave) { + ServiceInterface si = getService(s); + // TODO - switch to save "NON FILTERED" config !!!! + // get filtered clone of config for saving + ServiceConfig config = si.getFilteredConfig(); + String data = CodecUtils.toYaml(config); + String ymlFileName = configPath + fs + CodecUtils.getShortName(s) + ".yml"; + FileIO.toFile(ymlFileName, data.getBytes()); + info("saved %s", ymlFileName); + } + + invoke("publishConfigList"); + return true; + + } catch (Exception e) { + error(e); + } + return false; + } + + public String getConfigName() { + return configName; + } + + public boolean isProcessingConfig() { + return processingConfig; + } + + /** + * Sets the directory for the current config. This will be under configRoot + + * fs + configName. Static wrapper around setConfigName - so it can be used in + * the same way as all the other common static service methods + * + * @param name + * - config dir name under data/config/{config} + * @return config dir name + */ + public static String setConfig(String name) { + if (name == null) { + log.error("config cannot be null"); + if (runtime != null) { + runtime.error("config cannot be null"); + } + return null; + } + + if (name.contains(fs)) { + log.error("invalid character " + fs + " in configuration name"); + if (runtime != null) { + runtime.error("invalid character " + fs + " in configuration name"); + } + return name; + } + + configName = name.trim(); + + File configDir = new File(ROOT_CONFIG_DIR + fs + name); + if (!configDir.exists()) { + configDir.mkdirs(); + } + + if (runtime != null) { + runtime.invoke("publishConfigList"); + runtime.invoke("getConfigName"); + } + + return configName; + } + + public String deleteConfig(String configName) { + + File trashDir = new File(DATA_DIR + fs + "trash"); + if (!trashDir.exists()) { + trashDir.mkdirs(); + } + + File configDir = new File(ROOT_CONFIG_DIR + fs + configName); + // Create a new directory in the trash with a timestamp to avoid name + // conflicts + File trashTargetDir = new File(trashDir, configName + "_" + System.currentTimeMillis()); + try { + // Use Files.move to move the directory atomically + Files.move(configDir.toPath(), trashTargetDir.toPath(), StandardCopyOption.REPLACE_EXISTING); + log.info("Config moved to trash: " + trashTargetDir.getAbsolutePath()); + invoke("publishConfigList"); + } catch (IOException e) { + error("Failed to move config directory to trash: " + e.getMessage()); + return null; // Return null or throw a custom exception to indicate + // failure + } + + return configName; + } + + // FIXME - move this to service and add default (no servicename) method + // signature + @Deprecated /* + * I don't think this was a good solution - to handle interface + * lists in the js client - the js runtime should register for + * lifecycle events, the individiual services within that js + * runtime should only have local event handling to change attach + * lists + */ + public void registerForInterfaceChange(String requestor, Class<?> interestedInterface) { + registerForInterfaceChange(interestedInterface.getCanonicalName()); + } + + /** + * Builds the requestedAttachMatrix which is a mapping between new types and + * their requested interfaces - interfaces they are interested in. + * + * This data should be published whenever new "Type" definitions are found + * + * @param targetedInterface + * - interface this add new interface to requested interfaces - add + * current names of services which fulfill that interface "IS ASKING" + * + */ + public void registerForInterfaceChange(String targetedInterface) { + // boolean changed + Set<String> namesForRequestedInterface = interfaceToNames.get(targetedInterface); + if (namesForRequestedInterface == null) { + namesForRequestedInterface = new HashSet<>(); + interfaceToNames.put(targetedInterface, namesForRequestedInterface); + } + + // search through interfaceToType to find all types that implement this + // interface + + if (interfaceToType.containsKey(targetedInterface)) { + Set<String> types = interfaceToType.get(targetedInterface); + if (types != null) { + for (String type : types) { + Set<String> names = typeToNames.get(type); + namesForRequestedInterface.addAll(names); + } + } + } + invoke("publishInterfaceToNames"); + } + + /** + * Published whenever a new service type definition if found + * + * @return + */ + public Map<String, Set<String>> publishInterfaceTypeMatrix() { + return interfaceToType; + } + + public Map<String, Set<String>> publishInterfaceToNames() { + return interfaceToNames; + } + + static public Plan saveDefault(String className) { + try { + Runtime runtime = Runtime.getInstance(); + return runtime.saveDefault(className.toLowerCase(), className); + } catch (Exception e) { + log.error("saving default config failed", e); + } + return null; + } + + /** + * Helper method - returns if a service is started + * + * @param name + * - name of service + * @return - true if started + */ + static public boolean isStarted(String name) { + String fullname = null; + if (name == null) { + return false; + } + if (!name.contains("@")) { + fullname = name + "@" + Runtime.getInstance().getId(); + } else { + fullname = name; + } + if (registry.containsKey(fullname)) { + ServiceInterface si = registry.get(fullname); + return si.isRunning(); + } + + return false; + } + + /** + * Load all configuration files from a given directory. + * + * @param configPath + * The directory to load from + */ + public static void loadConfigPath(String configPath) { + + Runtime.setConfig(configPath); + Runtime runtime = Runtime.getInstance(); + + String configSetDir = runtime.getConfigName() + fs + runtime.getConfigName(); + File check = new File(configSetDir); + if (configPath == null || configPath.isEmpty() || !check.exists() || !check.isDirectory()) { + runtime.error("config set %s does not exist or is not a directory", check.getAbsolutePath()); + return; + } + + File[] configFiles = check.listFiles(); + runtime.info("%d config files found", configFiles.length); + for (File f : configFiles) { + if (!f.getName().toLowerCase().endsWith(".yml")) { + log.info("{} - none yml file found in config set", f.getAbsolutePath()); + } else { + runtime.loadFile(f.getAbsolutePath()); + } + } + } + + /** + * Load a service from a file + * + * @param path + * The full path of the file to load - this DOES NOT set the + * configPath + */ + public void loadFile(String path) { + try { + File f = new File(path); + if (!f.exists() || f.isDirectory()) { + error("loadFile cannot load %s - it does not exist", path); + return; + } + String name = f.getName().substring(0, f.getName().length() - 4); + ServiceConfig sc = CodecUtils.readServiceConfig(path); + loadService(new Plan("runtime"), name, sc.type, true, 0); + } catch (Exception e) { + error("loadFile requirese"); + } + } + + final public Plan getDefault(String name, String type) { + return ServiceConfig.getDefault(new Plan("runtime"), name, type); + } + + final public Plan saveDefault(String name, String type) { + return saveDefault(name, name, type, false); + } + + final public Plan saveDefault(String name, String type, boolean fullPlan) { + return saveDefault(name, name, type, fullPlan); + } + + final public Plan saveDefault(String configName, String name, String type, boolean fullPlan) { + + Plan plan = ServiceConfig.getDefault(new Plan(name), name, type); + String configPath = ROOT_CONFIG_DIR + fs + configName; + + if (!fullPlan) { + try { + String filename = configPath + fs + name + ".yml"; + ServiceConfig sc = plan.get(name); + String yaml = CodecUtils.toYaml(sc); + FileIO.toFile(filename, yaml); + info("saved %s", filename); + } catch (IOException e) { + error(e); + } + } else { + for (String service : plan.keySet()) { + try { + String filename = configPath + fs + service + ".yml"; + ServiceConfig sc = plan.get(service); + String yaml = CodecUtils.toYaml(sc); + FileIO.toFile(filename, yaml); + info("saved %s", filename); + } catch (IOException e) { + error(e); + } + } + } + return plan; + } + + public void savePlan(String name, String type) { + saveDefault(name, type, true); + } + + public void saveAllDefaults() { + saveAllDefaults(new File(getResourceDir()).getParent(), false); + } + + public void saveAllDefaults(String configPath, boolean fullPlan) { + List<MetaData> types = serviceData.getAvailableServiceTypes(); + for (MetaData meta : types) { + saveDefault(configPath + fs + meta.getSimpleName(), meta.getSimpleName().toLowerCase(), meta.getSimpleName(), fullPlan); + } + } + + /** + * Get current runtime's config path + * + * @return + */ + public String getConfigPath() { + return ROOT_CONFIG_DIR + fs + configName; + } + + /** + * Gets a {serviceName}.yml file config from configName directory + * + * @param configName + * @param serviceName + * @return ServiceConfig + */ + public ServiceConfig getConfig(String configName, String serviceName) { + return readServiceConfig(configName, serviceName); + } + + /** + * Get a {serviceName}.yml file in the current config directory + * + * @param serviceName + * @return + */ + public ServiceConfig getConfig(String serviceName) { + return readServiceConfig(serviceName); + } + + /** + * Save a config with a new Config + * + * @param name + * @param serviceConfig + * @throws IOException + */ + public static void saveConfig(String name, ServiceConfig serviceConfig) throws IOException { + String file = Runtime.ROOT_CONFIG_DIR + fs + runtime.getConfigName() + fs + name + ".yml"; + FileIO.toFile(file, CodecUtils.toYaml(serviceConfig)); + } + + /** + * get the service's peer config + * + * @param serviceName + * @param peerKey + * @return + */ + public ServiceConfig getPeerConfig(String serviceName, String peerKey) { + ServiceConfig sc = runtime.getConfig(serviceName); + if (sc == null) { + return null; + } + Peer peer = sc.getPeer(peerKey); + return runtime.getConfig(peer.name); + } + + /** + * Switches a service's .yml type definition while replacing the set of + * listeners to preserver subscriptions. Useful when switching services that + * support the same interface like SpeechSynthesis services etc. + * + * @param serviceName + * @param type + * @return + */ + public boolean changeType(String serviceName, String type) { + try { + ServiceConfig sc = getConfig(serviceName); + if (sc == null) { + error("could not find %s config", serviceName); + return false; + } + // get target + Plan targetPlan = getDefault(serviceName, type); + if (targetPlan == null || targetPlan.get(serviceName) == null) { + error("%s null", type); + return false; + } + ServiceConfig target = targetPlan.get(serviceName); + // replacing listeners + target.listeners = sc.listeners; + saveConfig(serviceName, target); + return true; + } catch (Exception e) { + error("could not save %s of type %s", serviceName, type); + return false; + } + } + + /** + * Get a peer's config + * + * @param sericeName + * @param peerKey + * @return + */ + public ServiceConfig getPeer(String sericeName, String peerKey) { + ServiceConfig sc = getConfig(sericeName); + if (sc == null) { + return null; + } + Peer peer = sc.getPeer(peerKey); + if (peer == null) { + return null; + } + return getConfig(peer.name); + } + + /** + * Removes a config set and all its files + * + * @param configName + * - name of config + */ + public static void removeConfig(String configName) { + try { + log.info("removing config"); + + File check = new File(ROOT_CONFIG_DIR + fs + configName); + + if (check.exists()) { + Path pathToBeDeleted = Paths.get(check.getAbsolutePath()); + Files.walk(pathToBeDeleted).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } + } catch (Exception e) { + log.error("removeConfig threw", e); + } + } } diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index bf611a1fd2..cebe3ea063 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -154,6 +154,8 @@ public class InMoov2Config extends ServiceConfig { public boolean virtual = false; + public boolean execScript = true; + public InMoov2Config() { } 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) -} + }, ]) From fef970145ea9f6894be66d8caa4e2088e21a898e Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Fri, 26 Apr 2024 12:20:58 -0700 Subject: [PATCH 118/131] quick-audio-player-fixes --- .../org/myrobotlab/audio/PlaylistPlayer.java | 5 +++++ .../WebGui/app/service/js/AudioFileGui.js | 14 ++----------- .../app/service/views/AudioFileGui.html | 20 +++++++++---------- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/myrobotlab/audio/PlaylistPlayer.java b/src/main/java/org/myrobotlab/audio/PlaylistPlayer.java index 46396bb50b..7ff3d128f0 100644 --- a/src/main/java/org/myrobotlab/audio/PlaylistPlayer.java +++ b/src/main/java/org/myrobotlab/audio/PlaylistPlayer.java @@ -4,10 +4,14 @@ import java.util.Collections; import java.util.List; +import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.AudioFile; +import org.slf4j.Logger; public class PlaylistPlayer implements Runnable { + static final Logger log = LoggerFactory.getLogger(PlaylistPlayer.class); + private transient AudioFile audioFile = null; private transient Thread player; private boolean shuffle; @@ -34,6 +38,7 @@ public void run() { audioFile.play(list.get(i), true, null, track); } if (!repeat) { + log.info("finished playing playlist"); done = true; } } diff --git a/src/main/resources/resource/WebGui/app/service/js/AudioFileGui.js b/src/main/resources/resource/WebGui/app/service/js/AudioFileGui.js index 2a34217609..2510678548 100644 --- a/src/main/resources/resource/WebGui/app/service/js/AudioFileGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/AudioFileGui.js @@ -8,19 +8,9 @@ angular.module('mrlapp.service.AudioFileGui', []).controller('AudioFileGuiCtrl', // playing paused stopped $scope.activity = null - // $scope.playFile = function() { - // msg.send('playFile', $scope.selectedFile) - // } - $scope.play = function() { - // if (blah){ - // $scope.selectedFile = selectedFiles[0] - // } else { - // $scope.selectedFile = selectedFiles[0] - // } let playFile = $scope.selectedFile msg.send('play', $scope.selectedFile) - } $scope.setSelectedFileFromTrack = function(selected) { @@ -29,7 +19,7 @@ angular.module('mrlapp.service.AudioFileGui', []).controller('AudioFileGuiCtrl', $scope.startPlaylist = function() { if ($scope.selectedPlaylist) { - msg.send('startPlaylist', $scope.selectedPlaylist[0]) + msg.send('startPlaylist', $scope.selectedPlaylist) } else { msg.send('startPlaylist') } @@ -37,7 +27,7 @@ angular.module('mrlapp.service.AudioFileGui', []).controller('AudioFileGuiCtrl', $scope.stopPlaylist = function() { if ($scope.selectedPlaylist) { - msg.send('stopPlaylist', $scope.selectedPlaylist[0]) + msg.send('stopPlaylist', $scope.selectedPlaylist) msg.send('stop') } else { msg.send('stopPlaylist') diff --git a/src/main/resources/resource/WebGui/app/service/views/AudioFileGui.html b/src/main/resources/resource/WebGui/app/service/views/AudioFileGui.html index ffd479fc41..77304e74f9 100644 --- a/src/main/resources/resource/WebGui/app/service/views/AudioFileGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/AudioFileGui.html @@ -23,10 +23,10 @@ <button class="btn btn-default" ng-click="msg.setMute(!service.mute);msg.broadcastState()"> <span class="glyphicon glyphicon-volume-off" title="mute track"/> </button> - <button class="btn btn-default" ng-click="msg.setVolume(service.volume - 0.01);msg.broadcastState()"> + <button class="btn btn-default" ng-click="msg.setVolume(service.config.volume - 0.01);msg.broadcastState()"> <span class="glyphicon glyphicon-volume-down" title="pump up the volume"/> </button> - <button class="btn btn-default" ng-click="msg.setVolume(service.volume + 0.01);msg.broadcastState()"> + <button class="btn btn-default" ng-click="msg.setVolume(service.config.volume + 0.01);msg.broadcastState()"> <span class="glyphicon glyphicon-volume-up" title="pump up the volume"/> </button> </td> @@ -47,7 +47,7 @@ </tr> <tr> <td>volume</td> - <td>{{service.volume.toFixed(2) * 100}}</td> + <td>{{service.config.volume.toFixed(2) * 100}}</td> </tr> <tr> <td>peak volume multiplier</td> @@ -69,7 +69,7 @@ <br/> <!--Selected playlist: {{service.currentPlaylist}}<br/>--> <table border="1" class="table table-hover table-condensed table-striped table-bordered"> - <!--tr ng-repeat="(name, fileset) in service.playlists"> + <!--tr ng-repeat="(name, fileset) in service.config.playlists"> <td>{{name}}{{fileset}}</td> </tr--> <tr> @@ -79,10 +79,10 @@ <button class="btn btn-default" ng-click="msg.addPlaylist(service.currentPlaylist, directoryName);msg.broadcastState()">Add playlist</button> <div class="form-group"> <label>select playlist:</label> - {{selectedPlaylist[0]}} + {{selectedPlaylist}} - <select multiple class="form-control vertical-menu" ng-change="setPlaylist(name)" ng-model="selectedPlaylist" id="service.playlists" title="select your playlist"> - <option ng-repeat="(name, fileset) in service.playlists" ng-value="{{name}}">{{name}}</option> + <select size="8" class="form-control vertical-menu" ng-change="msg.send('setPlaylist', name)" ng-model="selectedPlaylist" id="service.config.playlists" title="select your playlist"> + <option ng-repeat="(name, fileset) in service.config.playlists" ng-value="{{name}}">{{name}}</option> </select> </div> <button class="btn btn-default" ng-click="startPlaylist()"> @@ -91,10 +91,10 @@ <button class="btn btn-default" ng-click="stopPlaylist()"> <span class="glyphicon glyphicon-stop" title="stop play list"/> </button> - <div ng-show="service.playlists" class="form-group"> + <div ng-show="service.config.playlists" class="form-group"> <label>select file:</label> - <select multiple class="form-control vertical-menu" ng-model="selectedFiles" ng-change="setSelectedFileFromTrack(selectedFiles[0])" title="select your track"> - <option ng-repeat="(name, fileset) in service.playlists[selectedPlaylist]" ng-value="{{fileset}}">{{fileset}}</option> + <select size="8" class="form-control vertical-menu" ng-model="selectedFiles" ng-change="setSelectedFileFromTrack(selectedFiles)" title="select your track"> + <option ng-repeat="(name, fileset) in service.config.playlists[selectedPlaylist] track by $index" ng-value="fileset">{{fileset}}</option> </select> </div> </td> From 6ed9606e749256eb9f5361d930be91ab1aed10f6 Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Fri, 26 Apr 2024 15:53:59 -0700 Subject: [PATCH 119/131] full-feature-jukebox --- .../org/myrobotlab/audio/PlaylistPlayer.java | 18 +++++++++++-- .../org/myrobotlab/service/AudioFile.java | 4 +++ .../service/config/AudioFileConfig.java | 10 ++++++++ .../WebGui/app/service/js/AudioFileGui.js | 22 +++++++++++----- .../app/service/views/AudioFileGui.html | 25 ++++++++++++++++--- 5 files changed, 68 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/myrobotlab/audio/PlaylistPlayer.java b/src/main/java/org/myrobotlab/audio/PlaylistPlayer.java index 7ff3d128f0..8ee40c5583 100644 --- a/src/main/java/org/myrobotlab/audio/PlaylistPlayer.java +++ b/src/main/java/org/myrobotlab/audio/PlaylistPlayer.java @@ -25,8 +25,7 @@ public PlaylistPlayer(AudioFile audioFile) { } @Override - public void run() { - + public void run() { while (!done) { List<String> list = playlist; @@ -54,9 +53,24 @@ private List<String> shuffle(List<String> list) { public synchronized void stop() { done = true; + audioFile.stop(); + if (player != null) { + player.interrupt(); + } + } + + public synchronized void skip() { + if (player != null) { + audioFile.stop(); + } } + public synchronized void start(List<String> playlist, boolean shuffle, boolean repeat, String track) { + + audioFile.getConfig().repeat = repeat; + audioFile.getConfig().shuffle = shuffle; + if (player != null) { audioFile.warn("playlist player already playing a list - stop before starting a new playlist"); return; diff --git a/src/main/java/org/myrobotlab/service/AudioFile.java b/src/main/java/org/myrobotlab/service/AudioFile.java index c383cc586c..33cf606d22 100644 --- a/src/main/java/org/myrobotlab/service/AudioFile.java +++ b/src/main/java/org/myrobotlab/service/AudioFile.java @@ -167,6 +167,7 @@ public void stopService() { p.stopPlaying(); p.interrupt(); } + playlistPlayer.stop(); } public AudioData play(String filename) { @@ -531,6 +532,9 @@ public void stopPlaylist() { playlistPlayer.stop(); } + public void skip() { + playlistPlayer.skip(); + } public double publishPeak(double peak) { log.debug("publishPeak {}", peak); diff --git a/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java b/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java index d0ab902aee..6901626661 100644 --- a/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java +++ b/src/main/java/org/myrobotlab/service/config/AudioFileConfig.java @@ -12,6 +12,16 @@ public class AudioFileConfig extends ServiceConfig { public String currentPlaylist = "default"; + /** + * randomly shuffles a play list + */ + public boolean shuffle = false; + + /** + * repeats a playlist + */ + public boolean repeat = false; + @Deprecated /* temporal variable */ public String currentTrack = null; diff --git a/src/main/resources/resource/WebGui/app/service/js/AudioFileGui.js b/src/main/resources/resource/WebGui/app/service/js/AudioFileGui.js index 2510678548..989556d08e 100644 --- a/src/main/resources/resource/WebGui/app/service/js/AudioFileGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/AudioFileGui.js @@ -4,6 +4,7 @@ angular.module('mrlapp.service.AudioFileGui', []).controller('AudioFileGuiCtrl', var msg = this.msg $scope.peak = 0 $scope.peakMax = 0 + var firstUpdate = true // playing paused stopped $scope.activity = null @@ -19,26 +20,35 @@ angular.module('mrlapp.service.AudioFileGui', []).controller('AudioFileGuiCtrl', $scope.startPlaylist = function() { if ($scope.selectedPlaylist) { - msg.send('startPlaylist', $scope.selectedPlaylist) + msg.send('startPlaylist', $scope.selectedPlaylist, $scope.service.config.shuffle, $scope.service.config.repeat) } else { msg.send('startPlaylist') } + } + $scope.skip = function() { + msg.send('skip') } $scope.stopPlaylist = function() { - if ($scope.selectedPlaylist) { - msg.send('stopPlaylist', $scope.selectedPlaylist) - msg.send('stop') - } else { msg.send('stopPlaylist') msg.send('stop') - } + } + + $scope.setPlaylist = function(name){ + console.info('setPlaylist ' + name) + msg.send('setPlaylist', name) } // GOOD TEMPLATE TO FOLLOW this.updateState = function(service) { $scope.service = service $scope.service.loudness = 20 + + if (firstUpdate){ + $scope.selectedPlaylist = $scope.service.config.currentPlaylist + firstUpdate = false + } + if (!$scope.selectedFile) { if (service.lastPlayed) { diff --git a/src/main/resources/resource/WebGui/app/service/views/AudioFileGui.html b/src/main/resources/resource/WebGui/app/service/views/AudioFileGui.html index 77304e74f9..cc488c620b 100644 --- a/src/main/resources/resource/WebGui/app/service/views/AudioFileGui.html +++ b/src/main/resources/resource/WebGui/app/service/views/AudioFileGui.html @@ -1,3 +1,10 @@ + <style> + .active-toggle { + background-color: #286090; /* Blue color for active state */ + color: white; + } + </style> + <div class="col-md-6"> <table border="1" class="table table-hover table-condensed table-striped table-bordered"> <tr> @@ -81,15 +88,27 @@ <label>select playlist:</label> {{selectedPlaylist}} - <select size="8" class="form-control vertical-menu" ng-change="msg.send('setPlaylist', name)" ng-model="selectedPlaylist" id="service.config.playlists" title="select your playlist"> + <select size="8" class="form-control vertical-menu" ng-change="setPlaylist(selectedPlaylist)" ng-model="selectedPlaylist" id="service.config.playlists" title="select your playlist"> <option ng-repeat="(name, fileset) in service.config.playlists" ng-value="{{name}}">{{name}}</option> </select> </div> <button class="btn btn-default" ng-click="startPlaylist()"> - <span class="glyphicon glyphicon-play" title="play list"/> + <span class="glyphicon glyphicon-play" title="Start Play List"/> </button> <button class="btn btn-default" ng-click="stopPlaylist()"> - <span class="glyphicon glyphicon-stop" title="stop play list"/> + <span class="glyphicon glyphicon-stop" title="Stop Play List"/> + </button> + + <button class="btn btn-default" title="Skip" ng-click="skip()"> + <span class="glyphicon glyphicon-forward"></span> + </button> + <button class="btn btn-default" ng-click="service.config.shuffle = !service.config.shuffle" + ng-class="{'active-toggle': service.config.shuffle}"> + <span class="glyphicon glyphicon-random" title="Shuffle Playlist"></span> + </button> + <button class="btn btn-default" ng-click="service.config.repeat = !service.config.repeat" + ng-class="{'active-toggle': service.config.repeat}"> + <span class="glyphicon glyphicon-refresh" title="Repeat Playlist"></span> </button> <div ng-show="service.config.playlists" class="form-group"> <label>select file:</label> From 077f8b92837a1ceb15b3dfdda2f4351e9d243d37 Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Sat, 27 Apr 2024 07:48:01 -0700 Subject: [PATCH 120/131] default disable InMoov2.py --- TODO.md | 10 +++++++++ .../java/org/myrobotlab/service/InMoov2.java | 22 ++++++++++++++++++- .../service/config/InMoov2Config.java | 5 ++++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 93df2377f6..09f7492a96 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,16 @@ ## TODO +- 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 <star/> - can answer the question ... where are you ... ("you are just to the left of me") +- doesnt look like it defaults to Intro !?!? +- 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 + +## 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/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 269d9dc8db..130c24f2c4 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -1565,7 +1565,7 @@ public String publishFlash(String flashName) { * onHeartbeat at a regular interval */ public Heartbeat publishHeartbeat() { - log.info("publishHeartbeat"); + log.debug("publishHeartbeat"); heartbeatCount++; Heartbeat heartbeat = new Heartbeat(this); try { @@ -2295,4 +2295,24 @@ 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<String, Object> data = new HashMap<>(); + data.put("name", name); + data.put("confidence", confidence); + invoke("publishFoundPerson", data); + } + + + public Map<String, Object> publishFoundPerson(Map<String, Object> data) { + return data; + } + + } diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index cebe3ea063..98a593a2d3 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -154,7 +154,10 @@ public class InMoov2Config extends ServiceConfig { public boolean virtual = false; - public boolean execScript = true; + /** + * false for now to not interfere + */ + public boolean execScript = false; public InMoov2Config() { } From 605a163113553b0208ccd597b2a1f4171c33d81d Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Sat, 27 Apr 2024 08:14:52 -0700 Subject: [PATCH 121/131] formatting --- .../java/org/myrobotlab/service/InMoov2.java | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 130c24f2c4..7448db6d46 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -244,7 +244,7 @@ public void addTextListener(TextListener service) { public InMoov2Config apply(InMoov2Config c) { super.apply(c); try { - + if (c.locale != null) { setLocale(c.locale); } else { @@ -314,7 +314,7 @@ synchronized public void boot() { log.warn("will not boot again"); return; } - + if (bootCount == 0) { info("%s BOOTING ....", getName()); } @@ -326,28 +326,28 @@ synchronized public void boot() { 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"); + 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"); @@ -763,7 +763,7 @@ public void finishedGesture(String nameOfGesture) { * @param event */ public void fire(String event) { - FiniteStateMachine fsm =(FiniteStateMachine)getPeer("fsm"); + FiniteStateMachine fsm = (FiniteStateMachine) getPeer("fsm"); if (fsm != null) { fsm.fire(event); } else { @@ -861,9 +861,9 @@ public OpenCV getOpenCV() { } public String getPredicate(String key) { - ProgramAB chatBot = (ProgramAB)getPeer("chatBot"); + ProgramAB chatBot = (ProgramAB) getPeer("chatBot"); if (chatBot != null) { - return getPredicate(chatBot.getConfig().currentUserName, key); + return getPredicate(chatBot.getConfig().currentUserName, key); } else { log.info("chatBot not ready"); return null; @@ -871,10 +871,10 @@ public String getPredicate(String key) { } public String getPredicate(String user, String key) { - ProgramAB chatBot = (ProgramAB)getPeer("chatBot"); + ProgramAB chatBot = (ProgramAB) getPeer("chatBot"); if (chatBot != null) { - return chatBot.getPredicate(user, key); + return chatBot.getPredicate(user, key); } else { log.info("chatBot not ready"); return null; @@ -888,14 +888,14 @@ public String getPredicate(String user, String key) { * @return */ public Response getResponse(String text) { - ProgramAB chatBot = (ProgramAB)getPeer("chatBot"); + ProgramAB chatBot = (ProgramAB) getPeer("chatBot"); if (chatBot != null) { - Response response = chatBot.getResponse(text); - return response; + Response response = chatBot.getResponse(text); + return response; } else { log.info("chatBot not ready"); - return null; + return null; } } @@ -1074,7 +1074,7 @@ public boolean accept(File dir, String name) { if (files != null) { for (File file : files) { - + Python p = (Python) Runtime.getService("python"); if (p != null) { p.execFile(file.getAbsolutePath()); @@ -1442,7 +1442,10 @@ public void onText(String text) { } // TODO FIX/CHECK this, migrate from python land - @Deprecated /* these are fsm states and should be implemented in python callbacks */ + @Deprecated /* + * these are fsm states and should be implemented in python + * callbacks + */ public void powerDown() { rest(); @@ -1461,7 +1464,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 */ + @Deprecated /* + * these are fsm states and should be implemented in python + * callbacks + */ public void powerUp() { enable(); rest(); @@ -2025,7 +2031,7 @@ public boolean setPirPlaySounds(boolean b) { } public Object setPredicate(String key, Object data) { - ProgramAB chatBot = (ProgramAB)getPeer("chatBot"); + ProgramAB chatBot = (ProgramAB) getPeer("chatBot"); if (data == null) { chatBot.setPredicate(key, null); // "unknown" "null" other sillyness ? } else { @@ -2090,7 +2096,7 @@ public boolean setSpeechType(String speechType) { } public void setTopic(String topic) { - ProgramAB chatBot = (ProgramAB)getPeer("chatBot"); + ProgramAB chatBot = (ProgramAB) getPeer("chatBot"); if (chatBot != null) { chatBot.setTopic(topic); } else { @@ -2298,7 +2304,7 @@ public void waitTargetPos() { public void foundPerson(String name) { foundPerson(name, 1.0); } - + public void foundPerson(String name, Double confidence) { if (confidence == null) { confidence = 1.0; @@ -2308,11 +2314,9 @@ public void foundPerson(String name, Double confidence) { data.put("confidence", confidence); invoke("publishFoundPerson", data); } - - + public Map<String, Object> publishFoundPerson(Map<String, Object> data) { return data; } - - + } From 3c92a56a79bbbc353c1861ed5db24fcfe377bdb9 Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Sat, 27 Apr 2024 08:42:17 -0700 Subject: [PATCH 122/131] removed terminalmanager --- .../resources/resource/TerminalManager.png | Bin 1937 -> 0 bytes .../app/service/js/TerminalManagerGui.js | 72 ------------- .../app/service/views/TerminalManagerGui.html | 102 ------------------ 3 files changed, 174 deletions(-) delete mode 100644 src/main/resources/resource/TerminalManager.png delete mode 100644 src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js delete mode 100644 src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html diff --git a/src/main/resources/resource/TerminalManager.png b/src/main/resources/resource/TerminalManager.png deleted file mode 100644 index e212bb9b2d00a7208a1173f2514387b218bee6de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1937 zcmV;C2X6R@P)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf2PR2GK~!i%otQa` zRoxPXSGV(kqNs_nU~FjUKQI;yG*B=T%#B2IH8n61L{OXpQN%G2LopKs13^@rXZ!1C zU7vm5uHNU|&-+4k)v9?-d+$Co_wV1Io0*wuBIUWcxj)i>fNe~dsP=!0%&k0D52StO z*|TSJt5>g{T3F|llY`Wa;os)?Tg=?Od$;d^NfI@=vW3B<*lC!Q*PPSx>921;LS352 z=FOYivSrH_B(2CIV+E_d=9!ciXftjs9=v}2y8Za^qsue*?%kW4ot<s#)~##%_wR2{ zpFSNX(H-VFb`$<BvXL6Upt*TDTg9e5`P;W|+qG-g+Qy9=+pk~0{(FShuV3Hx?c3Kr zefrdvELpOkn8|9wX~4(E$Nbg({{6dik-o}u^3C7bcJ=Djwqe7DK7m;sFj`ROrAwD~ zVnXaf1-@A%3D8`Q;po&)YTg(rb@r)l=qep)mc}_}%a<>2Jf&8wSkaFbHWLVwPa<H` zV3G~W$yFVc{$Q3t1;$OAHnj^EF0}32xA*bNl`GrYwQJj|Rjc~s+$|^zOw*~iMvnCd z+N7$>+Oz!OEI~Q~R)5>JZSBmNGwuBO^KI9zT|JN;aS~o`V8SuV$$)9ij@dFk3H^eD z?aaXY_wU>8-MicE+qc`+ty|mCqet7VTesStJ$rid`J^HdFt!#1IhN87WFB>iF<svQ z4RDgU8*kpcX~&Ko>rHc<&>uW_u$?=1u6_CPWtafS9F?bBCzk}O-LV`{mtGk62>=ur zG67?VN9)U%FWb9!@47F?NS{i?Lm+dhPhU{S(-uWA#t+^nU_qggN$uUcw_Ut=u^l>e zsBPJ@rCqvosq47Z#yQc1MKk|2AN`Wlr;l(v>;SL;Ixgz`ORgUuF7)Zsr~5@de*Abp zS`^^@z+I5?osrEVTTrSE0%Ph#0lL&1H|zie9P`}g=o~q6r0v|fvz<MAwx4?@1Cb0L z-Vr=r*Q{C7$@c|z8?@PJ&{x>3{QLLsy&qt1@(!2SVC7e@UbP)NcC?!}Z+1P+fBg8d zbB-31z>gJY<$X6`V*=;}dW!;i!batQ8Z^xl@$1*GJ-0B&aD?ClyBh@4h%=}0Z{NQ4 zBqZdi^!V}P_VD4uHvdK29z1x^9zA;0o;-OnT=Hxtn?|2DfsVSLKY#8I44WSFB_FhC z2LKcJ`SWLQo~B7kB98(S<1$~re!X3}a;1MA@_}{v^5u5p#*H334Ox%o$jbt;Ff>p6 z*e8oH9-5hNV)z<8eE4vC@!~~qa1y+@1m_ElF_UD~&@*`&u~5RM@qv)lr*D<9vokCf zb%`6@fjUkQL}Mfa$PzXt=#FI${2}z7YjzhOZ3*>>+7sj9bJ|q^H6ivSnqcKvZML3m zhcnKV2j)Wm;lqdCJn?zSGq>a974#|%m)JttIKZAZ#t^|YBDo95=ETRd&E(7lQkIK4 zHeUsP{d3x8Kssj;;5dDZ7ud<gO7qy|IH9X~%ptG#G3W!g=V1rNFqRaK9<J;YgB|dg zP@u*;mZvUdVGW_|g5lpMP*<_S<XONm<oV_<*mxEOV>*DY628Fv0DXcw=obA;IR=Yo zazNvpZ_as7_Gc&b%Q!HJ1M?aK;OQe=Y;400=vWg78-iSME@s+Hj0}jc@>tG9+7mUF zT2Q~l7&O)b0+|b0pD2=JkogknBhqh@JPnI}E6Ii&dpUE(6VnPa*8hLSX|Fjz^@WBV z;BizRqnu}KJPNs-APdxH#F*ZK$`#c%Hc=?+(_5cF4W@BG(=^|m{^<0{MC!oUSY%Vx z58GJI>XUl<0(}A!;QNGg&atuaG2maDs$)<a8}ox~2xy06tI!wQf(g(x=R6zFVu?w~ zg>m}mU*l^2v?tFPpsshSu_jB@Bvr1BRT;{K(Jr@k#;F_YYskrVAjm~FB%hQhIY2hP z$`Hu>6_fPBUZG(JpxR&^>zBYs0@}?7DUZbh^b$}HQdebYmd7VE@?vCg1-48COe8Ws z^}u*g<3Pp&?CCF%-GxO@p_D<&fN>_B)Q~oH$<r^Pt@>1dn)lt+?*LC9cK}$p20E^K z;bcVmSDUCd<Q!)$_4W#1aESp<lM0R{);Gt>iPUGFvDh3>s>yd5yiWjP7f|RV#&i|L z*QOG+iD~(2o-toPd-@Ub600PrCUKm}st=gd*rYh14UR3x73%C4KI{O*cRs}D1aIUV zLn)7qCsF#5cgzM)TOF%&0uYm!Id$q(|6z+CW$)a%)1E(nzThX1{}*E`__wGz=#$U- z0|yQalbJbr@?`hoHP3IFa2(z0u|~83R>Dt&ziAp@>nIyPCa2DSVY~jsi4$|II1V%e z7Y2$PkJ&)#66p`ue-g&>*D(t>+yTxL%H+Vy0-+z19an#3DSdt7=`-eQ52;&h|JD8h X?w=+{ykB|700000NkvXXu0mjf3k<|N diff --git a/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js b/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js deleted file mode 100644 index 17a85e6e53..0000000000 --- a/src/main/resources/resource/WebGui/app/service/js/TerminalManagerGui.js +++ /dev/null @@ -1,72 +0,0 @@ -angular.module("mrlapp.service.TerminalManagerGui", []).controller("TerminalManagerGuiCtrl", ["$scope", "mrl", function($scope, mrl) { - console.info("TerminalManagerGuiCtrl") - var _self = this - var msg = this.msg - - $scope.processCommand = function(key, input) { - msg.send("processCommand", key, input) - $scope.service.inputValue = "" - } - - this.onMsg = function(inMsg) { - let data = inMsg.data[0] - switch (inMsg.method) { - case "onState": - $scope.service = data - $scope.$apply() - break - case "onLog": - $scope.service.terminals[data.terminal].output = $scope.service.terminals[data.terminal].output + data.msg - let length = $scope.service.terminals[data.terminal].output.length - if (length > 1024) { - let overLength = length - 1024; - $scope.service.terminals[data.terminal].output = $scope.service.terminals[data.terminal].output.substring(overLength); - } - // $scope.$apply() - $scope.$apply(function() { - // Scroll logic here - // Assuming you can uniquely identify the <pre> for this terminal - let terminalElement = document.querySelector('.terminal-wrapper[data-terminal-id="' + data.terminal + '"] .terminal2'); - if (terminalElement) { - terminalElement.scrollTop = terminalElement.scrollHeight; - } - }); - - break - case "onStdOut": - break - case "onCmd": - // FIXME - keep a list of commands ... can support history and maybe more importantly - // script generation to make automated packages - $scope.service.terminals[data.terminal].output = $scope.service.terminals[data.terminal].output + '# ' + data.cmd - $scope.$apply() - break - default: - console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) - break - } - } - - // Assuming `service` is your service managing terminals - $scope.startTerminal = function(key) { - msg.send("startTerminal", key) - } - - $scope.terminateTerminal = function(key) { - msg.send("terminateTerminal", key) - } - - $scope.saveTerminal = function(key) { - msg.send("saveTerminal", key) - } - - $scope.deleteTerminal = function(key) { - msg.send("deleteTerminal", key) - } - - msg.subscribe("publishLog") - // msg.subscribe("publishStdOut") - msg.subscribe("publishCmd") - msg.subscribe(this) -} -, ]) diff --git a/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html b/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html deleted file mode 100644 index fe42096ccb..0000000000 --- a/src/main/resources/resource/WebGui/app/service/views/TerminalManagerGui.html +++ /dev/null @@ -1,102 +0,0 @@ -<style> - .inline-buttons { - display: inline-block; - /* Or display: inline; */ - } - - .terminal2 { - background-color: black; - color: #33ff33; - font-family: 'Courier New', Courier, monospace; - font-size: 0.8em; - border-radius: 0; - margin: 0; - /* Removes default margin */ - padding: 10px; - /* Adjust based on your design needs */ - border: none; - /* Removes border */ - overflow: auto; - } - - .terminal-wrapper { - max-height: 800px; /* Adjust based on your needs */ - overflow-y: auto; - display: flex; - flex-direction: column; - align-items: stretch; - /* Ensures child elements fill the container */ - } - - .terminal-input { - background-color: black; - color: #33ff33; - border: none; - /* Removes border */ - outline: none; - /* Removes focus outline */ - font-family: 'Courier New', Courier, monospace; - font-size: 0.8em; - padding: 10px; - /* Should match the <pre> padding for alignment */ - width: 100%; - /* Ensures it takes up all available width */ - box-sizing: border-box; - /* Includes padding in the width calculation */ - margin: 0; - /* Removes default margin */ - } -</style> -<table class="table table-striped"> - <thead> - <tr> - <th></th> - <th></th> - <th></th> - <th>Name</th> - <th>PID</th> - <th>Shell</th> - <th>Command</th> - <th>Control</th> - </tr> - </thead> - <tr ng-repeat="(key, value) in service.terminals"> - <td> - <input type="radio" name="selectedTerminal" ng-model="ctrl.selectedTerminal" ng-value="terminal"/> - </td> - <td> - <img src="TerminalManager.png" width="16"/> - </td> - <td> - <img ng-src="{{value.isRunning ? 'connected.png' : 'disconnected.png'}}" alt="Connection Status" width="16"/> - </td> - <td> - <small>{{key}}</small> - </td> - <td> - <small>{{value.pid}}</small> - </td> - <td> - <small>{{value.shellCommand}}</small> - </td> - <td> - <small>{{value.lastInput}}</small> - </td> - <td> - <span class="inline-buttons"> - <button class="btn btn-sm" ng-click="startTerminal(key)">Start</button> - <button class="btn btn-sm" ng-click="terminateTerminal(key)">Terminate</button> - <button class="btn btn-sm" ng-click="saveTerminal(key)">Save</button> - <button class="btn btn-sm" ng-click="deleteTerminal(key)">Delete</button> - </span> - </td> - </tr> -</table> - -<div class="terminal-wrapper" ng-repeat="(key, value) in service.terminals" data-terminal-id="{{key}}"> - {{key}}<button class="btn btn-sm" ng-click="deleteTerminal(key)">Clear</button> - - <pre class="terminal2" id="terminal-{{key}}" >{{value.output}}</pre> - - <input class="terminal-input" type="text" class="form-control" ng-model="service.inputValue" ng-keyup="$event.keyCode == 13 ? processCommand(key, service.inputValue) : null" placeholder="type here..."> -</div> \ No newline at end of file From 3db187276c98e7a51faf95e584bfda423892ffdf Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Sat, 27 Apr 2024 08:47:45 -0700 Subject: [PATCH 123/131] fixed white space --- src/main/java/org/myrobotlab/service/Runtime.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 4b5f418a39..79439eea08 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -3687,10 +3687,10 @@ public static Double getBatteryLevel() { // 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")) { + 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); From 65e760ae305aa90c7f1dfd60b2b29739ad35cd24 Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Sat, 27 Apr 2024 08:50:31 -0700 Subject: [PATCH 124/131] reformat vertx --- src/main/java/org/myrobotlab/service/Vertx.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Vertx.java b/src/main/java/org/myrobotlab/service/Vertx.java index a47d75a954..c8e75a62ff 100644 --- a/src/main/java/org/myrobotlab/service/Vertx.java +++ b/src/main/java/org/myrobotlab/service/Vertx.java @@ -85,8 +85,7 @@ public void start() { if (config.autoStartBrowser) { log.info("auto starting default browser"); - String startUrl = (String.format((config.ssl) ? "https:" : "http:") - + String.format("//localhost:%d/index.html", config.port)); + String startUrl = (String.format((config.ssl) ? "https:" : "http:") + String.format("//localhost:%d/index.html", config.port)); BareBonesBrowserLaunch.openURL(startUrl); } listening = true; From a8abb8df9413b39f0aae72df0c372d5e04455303 Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Sat, 27 Apr 2024 08:52:20 -0700 Subject: [PATCH 125/131] reformat vertx --- src/main/java/org/myrobotlab/service/Vertx.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/Vertx.java b/src/main/java/org/myrobotlab/service/Vertx.java index c8e75a62ff..757f220ff1 100644 --- a/src/main/java/org/myrobotlab/service/Vertx.java +++ b/src/main/java/org/myrobotlab/service/Vertx.java @@ -78,9 +78,7 @@ public void start() { * </pre> */ - // vertx = io.vertx.core.Vertx.vertx(new - // VertxOptions().setWorkerPoolSize(125).setBlockedThreadCheckInterval(100000)); - vertx = io.vertx.core.Vertx.vertx(new VertxOptions().setBlockedThreadCheckInterval(100000)); + // vertx = io.vertx.core.Vertx.vertx(new VertxOptions().setWorkerPoolSize(125).setBlockedThreadCheckInterval(100000)); vertx = io.vertx.core.Vertx.vertx(new VertxOptions().setBlockedThreadCheckInterval(100000)); vertx.deployVerticle(new ApiVerticle(this)); if (config.autoStartBrowser) { From 58d0889aaa9c1b80a3c7d306b9364a90a99d679b Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Sat, 27 Apr 2024 09:00:31 -0700 Subject: [PATCH 126/131] vertx formatting --- src/main/java/org/myrobotlab/service/Vertx.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/myrobotlab/service/Vertx.java b/src/main/java/org/myrobotlab/service/Vertx.java index 757f220ff1..d51deeffb1 100644 --- a/src/main/java/org/myrobotlab/service/Vertx.java +++ b/src/main/java/org/myrobotlab/service/Vertx.java @@ -78,7 +78,8 @@ public void start() { * </pre> */ - // vertx = io.vertx.core.Vertx.vertx(new VertxOptions().setWorkerPoolSize(125).setBlockedThreadCheckInterval(100000)); vertx = io.vertx.core.Vertx.vertx(new VertxOptions().setBlockedThreadCheckInterval(100000)); + // vertx = io.vertx.core.Vertx.vertx(new VertxOptions().setWorkerPoolSize(125).setBlockedThreadCheckInterval(100000)); + vertx = io.vertx.core.Vertx.vertx(new VertxOptions().setBlockedThreadCheckInterval(100000)); vertx.deployVerticle(new ApiVerticle(this)); if (config.autoStartBrowser) { From 35704ac49d82ca87fb513846d72c9ad2f4061b23 Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Sat, 27 Apr 2024 09:01:58 -0700 Subject: [PATCH 127/131] finally formatting fixed --- .../java/org/myrobotlab/service/Runtime.java | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) 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"); From 868480a7b30e4090bd3c39bf21d950d275c63383 Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Sun, 28 Apr 2024 08:02:54 -0700 Subject: [PATCH 128/131] audioPlayer and locales figured out --- .../java/org/myrobotlab/service/InMoov2.java | 78 ++++++++++++++++++- .../service/config/InMoov2Config.java | 10 +-- .../WebGui/app/service/js/PollyGui.js | 4 + 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 7448db6d46..5ff30ed3c6 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -36,6 +36,7 @@ import org.myrobotlab.service.Log.LogEntry; import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; import org.myrobotlab.service.config.InMoov2Config; +import org.myrobotlab.service.data.AudioData; import org.myrobotlab.service.data.JoystickData; import org.myrobotlab.service.data.Locale; import org.myrobotlab.service.interfaces.IKJointAngleListener; @@ -229,6 +230,8 @@ public static void main(String[] args) { 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"); @@ -402,7 +405,7 @@ synchronized public void boot() { if (config.startupSound) { String startupsound = FileIO.gluePaths(getResourceDir(), "/system/sounds/startupsound.mp3"); - invoke("publishPlayAudioFile", startupsound); + playAudioFile(startupsound); } List<ServiceInterface> services = Runtime.getServices(); @@ -979,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 @@ -1189,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 @@ -1371,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; } @@ -1440,6 +1471,11 @@ 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 /* @@ -1484,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<String, List<String>> 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. @@ -2009,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); } } diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index 98a593a2d3..d0a022b139 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -154,9 +154,6 @@ public class InMoov2Config extends ServiceConfig { public boolean virtual = false; - /** - * false for now to not interfere - */ public boolean execScript = false; public InMoov2Config() { @@ -538,8 +535,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 +547,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)); + AudioFileConfig mouth_audioFile = (AudioFileConfig) plan.get(getPeerName("mouth.audioFile")); mouth_audioFile.listeners.add(new Listener("publishPeak", name)); 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 From 43f7677b09d7084f4f79849232b28738b7a20e09 Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Sun, 28 Apr 2024 08:03:13 -0700 Subject: [PATCH 129/131] list --- TODO.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 09f7492a96..912002262a 100644 --- a/TODO.md +++ b/TODO.md @@ -1,14 +1,18 @@ ## TODO +- 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 <star/> - can answer the question ... where are you ... ("you are just to the left of me") -- doesnt look like it defaults to Intro !?!? - 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 From 46a38304cb8be3bfaeaf0feca9567bb310a622b0 Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Tue, 30 Apr 2024 09:13:02 -0700 Subject: [PATCH 130/131] updated config --- TODO.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO.md b/TODO.md index 912002262a..ae9e523f54 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,7 @@ ## 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 From ef8793a8254c5e23bf21ffc16fe104b2eba7010b Mon Sep 17 00:00:00 2001 From: supertick <grog@myrobotlab.org> Date: Wed, 1 May 2024 06:37:03 -0700 Subject: [PATCH 131/131] updated with javadoc, timeout, and default --- src/main/java/org/myrobotlab/io/FileIO.java | 78 ++++++++++++++------- 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/myrobotlab/io/FileIO.java b/src/main/java/org/myrobotlab/io/FileIO.java index e027aeacd4..95c5134a3c 100644 --- a/src/main/java/org/myrobotlab/io/FileIO.java +++ b/src/main/java/org/myrobotlab/io/FileIO.java @@ -52,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; @@ -1427,34 +1428,61 @@ public static String normalize(String dirPath) { } } + /** + * 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) { - try { - // Attempt to execute the command - Process process = java.lang.Runtime.getRuntime().exec(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); - // Check the exit value of the process - // If the process has terminated correctly, the command is available - if (process.waitFor() == 0) { - return true; - } + // Wait for the process to complete with a timeout + if (process.waitFor(timeoutSeconds, TimeUnit.SECONDS) && process.exitValue() == 0) { + return true; + } - // Read any errors from the attempted command - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { - String line; - while ((line = reader.readLine()) != null) { - System.out.println(line); - } - } + // 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; + } + } - return false; - } catch (IOException e) { - log.info("IOException: " + e.getMessage()); - return false; - } catch (InterruptedException e) { - log.info("InterruptedException: " + e.getMessage()); - return false; - } -} - }