diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b8c8e5b4a..93d82942aa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,11 +1,6 @@ name: Java CI -#on: [push] - -on: - push: - branches: - - develop +on: [push] jobs: build: @@ -20,37 +15,7 @@ jobs: with: java-version: '11' distribution: 'adopt' - - name: Dependency Test run: mvn test -Dtest=org.myrobotlab.framework.DependencyTest -q - - - name: Build and Test with Maven + - name: Build with Maven run: mvn --batch-mode -Dtest=!**/OpenCV* test -q - - - name: Get next version - uses: reecetech/version-increment@2023.9.3 - id: version - with: - scheme: semver - increment: patch - - - name: Package with Maven - run: "mvn package -DskipTests -Dversion=${{ steps.version.outputs.version }} -q" - - # - name: Fake Build - # run: | - # mkdir -p target - # echo ${{ github.sha }} > ./target/myrobotlab.zip - - - name: Release - id: release - uses: softprops/action-gh-release@v1 - with: - token: ${{ secrets.ACCESS_TOKEN }} - files: | - ./target/myrobotlab.zip - ./target/myrobotlab.jar - name: "${{ steps.version.outputs.version }} Nixie" - tag_name: ${{ steps.version.outputs.version }} - generate_release_notes: true - body_path: ./release-template.md diff --git a/.gitignore b/.gitignore index fb07e725f0..4fa1d43508 100644 --- a/.gitignore +++ b/.gitignore @@ -116,4 +116,7 @@ build/ /lastRestart.py /.factorypath start.yml +config +src/main/resources/resource/InMoov2 +src/main/resources/resource/ProgramAB *.iml diff --git a/pom.xml b/pom.xml index a00bd0db4f..6e8fdf4829 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,9 @@ # fast build mvn -DskipTests package -o + # execute + mvn exec:java -Dexec.mainClass=org.myrobotlab.service.Runtime -Dexec.args="-s webgui WebGui intro Intro python Python" + # specific test mvn test -Dtest="org.myrobotlab.service.WebGuiTest#postTest" diff --git a/src/main/java/org/myrobotlab/codec/CodecUtils.java b/src/main/java/org/myrobotlab/codec/CodecUtils.java index cb7698d26a..bd512189b8 100644 --- a/src/main/java/org/myrobotlab/codec/CodecUtils.java +++ b/src/main/java/org/myrobotlab/codec/CodecUtils.java @@ -5,6 +5,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InvalidObjectException; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; @@ -1481,6 +1482,10 @@ public static boolean isLocal(String name, String id) { return name.substring(name.indexOf("@") + 1).equals(id); } + public static ServiceConfig readServiceConfig(String filename) throws IOException { + return readServiceConfig(filename, new StaticType<>() {}); + } + /** * Read a YAML file given by the filename and convert it into a ServiceConfig * object by deserialization. @@ -1491,10 +1496,15 @@ public static boolean isLocal(String name, String id) { * @throws IOException * if reading the file fails */ - public static ServiceConfig readServiceConfig(String filename) throws IOException { + public static C readServiceConfig(String filename, StaticType type) throws IOException { String data = Files.readString(Paths.get(filename)); Yaml yaml = new Yaml(); - return yaml.load(data); + C parsed = yaml.load(data); + if (type.asClass().isAssignableFrom(parsed.getClass())) { + return parsed; + } else { + throw new InvalidObjectException("Deserialized type was " + parsed.getClass() + ", expected " + type + ". Deserialized object: " + parsed); + } } /** diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java index 08534f2081..a84d9b8f2a 100644 --- a/src/main/java/org/myrobotlab/framework/Service.java +++ b/src/main/java/org/myrobotlab/framework/Service.java @@ -101,11 +101,11 @@ public abstract class Service implements Runnable, Seri protected MetaData serviceType; /** - * Config member - configuration of type {ServiceType}Config - * Runtime applys either the default config or a saved config during service creation + * Config member - configuration of type {ServiceType}Config Runtime applys + * either the default config or a saved config during service creation */ protected T config; - + private static final long serialVersionUID = 1L; transient public final static Logger log = LoggerFactory.getLogger(Service.class); @@ -190,8 +190,8 @@ public abstract class Service implements Runnable, Seri /** * This is the map of interfaces - its really "static" information, since its - * a definition. However, since serialization will not process statics - we are making - * it a member variable + * a definition. However, since serialization will not process statics - we + * are making it a member variable */ // FIXME - this should be a map protected Map interfaceSet; @@ -279,11 +279,7 @@ public static Object copyShallowFrom(Object target, Object source) { * ){ log.info("here"); } */ - if (Modifier.isPrivate(modifiers) - || fname.equals("log") - || Modifier.isTransient(modifiers) - || Modifier.isStatic(modifiers) - || Modifier.isFinal(modifiers)) { + if (Modifier.isPrivate(modifiers) || fname.equals("log") || Modifier.isTransient(modifiers) || Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) { log.debug("skipping {}", field.getName()); continue; } else { @@ -472,18 +468,17 @@ static public String getResourceDir(Class clazz, String additionalPath) { * to glue together * @return the full resolved path * - * FIXME - DO NOT USE STATIC !!!! - * all instances of services should be able to get the resource directory - * If its static and "configurable" then it needs an instance of Runtime - * which is not available. + * FIXME - DO NOT USE STATIC !!!! all instances of services should be + * able to get the resource directory If its static and "configurable" + * 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 + // setting resource directory String resourceDir = null; - + // stupid solution to get past static problem if (!"Runtime".equals(serviceType)) { resourceDir = Runtime.getInstance().getConfig().resource + fs + serviceType; @@ -495,6 +490,7 @@ static public String getResourceDir(String serviceType, String additionalPath) { } return resourceDir; } + /** * non static get resource path return the path to a resource - since the root * can change depending if in debug or runtime - it gets the appropriate root @@ -1015,47 +1011,49 @@ public String[] getMethodNames() { public Method[] getMethods() { return this.getClass().getMethods(); } - + /** - * Returns a map containing all interface names from the class hierarchy and the interface hierarchy of the - * current class. + * Returns a map containing all interface names from the class hierarchy and + * the interface hierarchy of the current class. * * @return A map containing all interface names. */ public Map getInterfaceSet() { - Map ret = new TreeMap<>(); - Set> visitedClasses = new HashSet<>(); - getAllInterfacesHelper(getClass(), ret, visitedClasses); - return ret; + Map ret = new TreeMap<>(); + Set> visitedClasses = new HashSet<>(); + getAllInterfacesHelper(getClass(), ret, visitedClasses); + return ret; } /** - * Recursively traverses the class hierarchy and the interface hierarchy to add all interface names to the - * specified map. + * Recursively traverses the class hierarchy and the interface hierarchy to + * add all interface names to the specified map. * - * @param c The class to start the traversal from. - * @param ret The map to store the interface names. - * @param visitedClasses A set to keep track of visited classes to avoid infinite loops. + * @param c + * The class to start the traversal from. + * @param ret + * The map to store the interface names. + * @param visitedClasses + * A set to keep track of visited classes to avoid infinite loops. */ private void getAllInterfacesHelper(Class c, Map ret, Set> visitedClasses) { - if (c != null && !visitedClasses.contains(c)) { - // Add interfaces from the current class - Class[] interfaces = c.getInterfaces(); - for (Class interfaze : interfaces) { - ret.put(interfaze.getName(), interfaze.getName()); - } - - // Add interfaces from interfaces implemented by the current class - for (Class interfaze : interfaces) { - getAllInterfacesHelper(interfaze, ret, visitedClasses); - } + if (c != null && !visitedClasses.contains(c)) { + // Add interfaces from the current class + Class[] interfaces = c.getInterfaces(); + for (Class interfaze : interfaces) { + ret.put(interfaze.getName(), interfaze.getName()); + } - // Recursively traverse the superclass hierarchy - visitedClasses.add(c); - getAllInterfacesHelper(c.getSuperclass(), ret, visitedClasses); + // Add interfaces from interfaces implemented by the current class + for (Class interfaze : interfaces) { + getAllInterfacesHelper(interfaze, ret, visitedClasses); } + + // Recursively traverse the superclass hierarchy + visitedClasses.add(c); + getAllInterfacesHelper(c.getSuperclass(), ret, visitedClasses); + } } - public Message getMsg() throws InterruptedException { return inbox.getMsg(); @@ -1314,12 +1312,11 @@ final public Object invokeOn(boolean blockLocally, Object obj, String methodName if (blockLocally) { Outbox outbox = null; if (obj instanceof ServiceInterface) { - outbox = ((ServiceInterface)obj).getOutbox(); + outbox = ((ServiceInterface) obj).getOutbox(); } else { return retobj; } - - + List subList = outbox.notifyList.get(methodName); // correct? get local (default?) gateway Runtime runtime = Runtime.getInstance(); @@ -1399,27 +1396,93 @@ public boolean isRunning() { /** * getConfig returns current config of the service. This default super method - * will also filter webgui subscriptions out, in addition for any local subscriptions it - * will remove the instance "id" from any service. The reason it removes the webgui - * subscriptions is to avoid overwelming the user when modifying config. UI subscriptions - * tend to be very numerous and not very useful to the user. The reason it removes the - * instance id from local subscriptions is to allow the config to be used with any instance. - * Unless the user is controlling instance id, its random every restart. + * will also filter webgui subscriptions out, in addition for any local + * subscriptions it will remove the instance "id" from any service. The reason + * it removes the webgui subscriptions is to avoid overwelming the user when + * modifying config. UI subscriptions tend to be very numerous and not very + * useful to the user. The reason it removes the instance id from local + * subscriptions is to allow the config to be used with any instance. Unless + * the user is controlling instance id, its random every restart. */ public T getConfig() { return config; } /** - * Super class apply using template type. The default assigns config of the templated type, and also - * add listeners from subscriptions found on the base class ServiceConfig.listeners + * Get a service's peer's configuration. This method is used to get the + * configuration of a peer service regarless if it is currently running or + * not. If the peer is running the configuration is pulled from the active + * peer service, if it is not currently running the configuration is read from + * the current config set's service configuration file, if that does not exist + * the default configuration for this peer is used. + * + * @param peerKey + * - key of the peer service. e.g. "opencv" in the case of + * i01."opencv" + * @return */ - public T apply(T c) { - config = c; - addConfigListeners(c); - return config; + public

P getPeerConfig(String peerKey, StaticType

type) { + String peerName = getPeerName(peerKey); + if (peerName == null) { + error("peer name not found for peer key %s", peerKey); + return null; + } + + // Java generics don't let us create a new StaticType using + // P here because the type variable is erased, so we have to cast anyway for now + ConfigurableService

si = (ConfigurableService

) Runtime.getService(peerName); + if (si != null) { + // peer is currently running - get its config + P c = si.getConfig(); + if (type.asClass().isAssignableFrom(c.getClass())) { + return c; + } + } + + // peer is not currently running attempt to read from config + Runtime runtime = Runtime.getInstance(); + // read current service config for this peer service + P sc = runtime.readServiceConfig(peerName, type); + if (sc == null) { + error("peer service %s is defined, but %s.yml not available on filesystem", peerKey, peerName); + return null; + } + return sc; } + public void setPeerConfigValue(String peerKey, String fieldname, Object value) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + ServiceConfig sc = getPeerConfig(peerKey, new StaticType<>() {}); + if (sc == null) { + error("invalid config for peer key %s field name %s", peerKey, fieldname); + return; + } + Field field = sc.getClass().getDeclaredField(fieldname); + field.set(sc, value); + savePeerConfig(peerKey, sc); + String peerName = getPeerName(peerKey); + var cs = Runtime.getConfigurableService(peerName, new StaticType>() {}); + if (cs != null) { + cs.apply(sc); // TODO - look for applies if its read from the file system + // it needs to update Runtime.plan + } + + // broadcast change + invoke("getPeerConfig", peerKey); + Runtime.getPlan().put(peerName, sc); + Runtime runtime = Runtime.getInstance(); + runtime.broadcastState(); + } + + /** + * Super class apply using template type. The default assigns config of the + * templated type, and also add listeners from subscriptions found on the base + * class ServiceConfig.listeners + */ + public T apply(T c) { + config = c; + addConfigListeners(c); + return config; + } /** * The basic ServiceConfig has a list of listeners. These are definitions of @@ -1436,7 +1499,8 @@ public ServiceConfig addConfigListeners(ServiceConfig config) { } /** - * Default filtered config, used when saving, can be overriden by concrete class + * Default filtered config, used when saving, can be overriden by concrete + * class */ @Override public ServiceConfig getFilteredConfig() { @@ -1467,7 +1531,6 @@ public ServiceConfig getFilteredConfig() { return sc; } - @Override public void setConfigValue(String fieldname, Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { log.info("setting field name fieldname {} to {}", fieldname, value); @@ -1475,6 +1538,7 @@ public void setConfigValue(String fieldname, Object value) throws IllegalArgumen Field field = getConfig().getClass().getDeclaredField(fieldname); // field.setAccessible(true); should not need this - it "should" be public field.set(getConfig(), value); + save(); } @Override @@ -1579,11 +1643,12 @@ public void removeListener(String outMethod, String serviceName, String inMethod return true; } - // Previously we were not checking inMethod, which meant if a service had multiple - // subscriptions to the same topic (one to many mapping), the first in the list would be removed + // Previously we were not checking inMethod, which meant if a service + // had multiple + // subscriptions to the same topic (one to many mapping), the first in + // the list would be removed // instead of the requested one. - if (listener.callbackMethod.equals(inMethod) - && CodecUtils.checkServiceNameEquality(listener.callbackName, fullName)) { + if (listener.callbackMethod.equals(inMethod) && CodecUtils.checkServiceNameEquality(listener.callbackName, fullName)) { log.info("removeListener requested {}.{} to be removed", fullName, outMethod); return true; } @@ -1659,6 +1724,24 @@ public boolean save() { return runtime.saveService(runtime.getConfigName(), getName(), null); } + /** + * Save a service's peer's config to current config set + * + * @param peerKey + */ + public void savePeerConfig(String peerKey, ServiceConfig config) { + try { + Runtime runtime = Runtime.getInstance(); + String peerName = getPeerName(peerKey); + String data = CodecUtils.toYaml(config); + String ymlFileName = runtime.getConfigPath() + fs + CodecUtils.getShortName(peerName) + ".yml"; + FileIO.toFile(ymlFileName, data.getBytes()); + info("saved %s", ymlFileName); + } catch (Exception e) { + error(e); + } + } + public ServiceInterface getPeer(String peerKey) { String actualName = getPeerName(peerKey); return Runtime.getService(actualName); @@ -2009,7 +2092,7 @@ public void subscribe(String topicName, String topicMethod) { String callbackMethod = CodecUtils.getCallbackTopicName(topicMethod); subscribe(topicName, topicMethod, getFullName(), callbackMethod); } - + @Override public void subscribe(String service, String method, String callback) { subscribe(service, method, getFullName(), callback); @@ -2068,7 +2151,7 @@ public void unsubscribe(String topicName, String topicMethod) { String callbackMethod = CodecUtils.getCallbackTopicName(topicMethod); unsubscribe(topicName, topicMethod, getFullName(), callbackMethod); } - + @Override public void unsubscribe(String topicName, String topicMethod, String callback) { unsubscribe(topicName, topicMethod, getFullName(), callback); @@ -2097,12 +2180,7 @@ public Status error(Exception e) { @Override public Status error(String format, Object... args) { Status ret; - ret = Status.error( - String.format( - Objects.requireNonNullElse(format, ""), - args - ) - ); + ret = Status.error(String.format(Objects.requireNonNullElse(format, ""), args)); ret.name = getName(); log.error(ret.toString()); lastError = ret; @@ -2584,7 +2662,10 @@ public String localize(String key, Object... args) { } @Override - @Deprecated /* this system should be removed in favor of a ProgramAB instance with ability to translate */ + @Deprecated /* + * this system should be removed in favor of a ProgramAB instance + * with ability to translate + */ public void loadLocalizations() { if (defaultLocalization == null) { @@ -2756,16 +2837,40 @@ public void apply() { Runtime runtime = Runtime.getInstance(); String configName = runtime.getConfigName(); ServiceConfig sc = runtime.readServiceConfig(configName, name); - + if (sc == null) { error("config file %s not found", Runtime.getConfigRoot() + fs + configName + fs + name + ".yml"); return; } - - // updating plan + + // updating plan - FIXME remove plan Runtime.getPlan().put(getName(), sc); + // applying config to self - apply((T)sc); + apply((T) sc); + } + + public void applyPeerConfig(String peerKey, ServiceConfig config) { + applyPeerConfig(peerKey, config, new StaticType<>() {}); + } + + /** + * Apply the config to a peer, regardless if the peer is currently running or + * not + * + * @param peerKey + * @param config + */ + public

void applyPeerConfig(String peerKey, P config, StaticType> configServiceType) { + String peerName = getPeerName(peerKey); + + Runtime.getPlan().put(peerName, config); + + // meh - templating is not very helpful here + ConfigurableService

si = Runtime.getService(peerName, configServiceType); + if (si != null) { + si.apply(config); + } } /** @@ -2786,11 +2891,11 @@ public void setPeerName(String key, String fullName) { // should we also make or update a config file - if the config path is set? info("updated %s name to %s", oldName, peer.name); } - + /** * get all the subscriptions to this service */ - public Map> getNotifyList(){ + public Map> getNotifyList() { return getOutbox().getNotifyList(); } diff --git a/src/main/java/org/myrobotlab/framework/StaticType.java b/src/main/java/org/myrobotlab/framework/StaticType.java index fddc2912f9..6de5b48504 100644 --- a/src/main/java/org/myrobotlab/framework/StaticType.java +++ b/src/main/java/org/myrobotlab/framework/StaticType.java @@ -81,6 +81,27 @@ public Type getType() { return storedType; } + /** + * Get the stored type as a Class object + * that can be used for checking cast + * compatibility. Note that the resulting + * Class object will not check for generic + * compatibility. If the stored type + * is not a Class type, then this method + * throws {@link IllegalStateException}. + * + * @return The internal stored type cast to a Class object + * @throws IllegalStateException if the stored type is not a Class. + */ + @SuppressWarnings("unchecked") + public Class asClass() { + if (storedType instanceof Class) { + return (Class) storedType; + } else { + throw new IllegalStateException("Stored type " + storedType + " is not a Class."); + } + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -96,6 +117,11 @@ public int hashCode() { return storedType != null ? storedType.hashCode() : 0; } + + @Override + public String toString() { + return storedType.toString(); + } /** * Function to recursively validate type parameters * to ensure they are all concrete so no type variables sneak in. diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index 03035b6d2f..e430ac75dd 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -16,6 +16,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; @@ -28,6 +29,7 @@ import org.myrobotlab.service.abstracts.AbstractSpeechRecognizer; import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; import org.myrobotlab.service.config.InMoov2Config; +import org.myrobotlab.service.config.OpenCVConfig; import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.JoystickData; import org.myrobotlab.service.data.LedDisplayData; @@ -140,11 +142,11 @@ public static boolean loadFile(String file) { protected String voiceSelected; - public InMoov2(String n, String id) { super(n, id); } + // should be removed in favor of general listeners public void addTextListener(TextListener service) { // CORRECT WAY ! - no direct reference - just use the name in a subscription addListener("publishText", service.getName()); @@ -407,7 +409,7 @@ public String execGesture(String gesture) { subscribe("python", "publishStatus", this.getName(), "onGestureStatus"); startedGesture(gesture); lastGestureExecuted = gesture; - Python python = (Python)Runtime.getService("python"); + Python python = (Python) Runtime.getService("python"); if (python == null) { error("python service not started"); return null; @@ -773,19 +775,19 @@ public void moveHead(Integer neck, Integer rothead, Integer rollNeck) { moveHead((double) neck, (double) rothead, null, null, null, (double) rollNeck); } - public void moveHeadBlocking(Double neck, Double rothead) { + public void moveHeadBlocking(Double neck, Double rothead) { moveHeadBlocking(neck, rothead, null); } - public void moveHeadBlocking(Double neck, Double rothead, Double rollNeck) { + public void moveHeadBlocking(Double neck, Double rothead, Double rollNeck) { moveHeadBlocking(neck, rothead, null, null, null, rollNeck); } - public void moveHeadBlocking(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw) { + public void moveHeadBlocking(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw) { moveHeadBlocking(neck, rothead, eyeX, eyeY, jaw, null); } - public void moveHeadBlocking(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, Double rollNeck) { + public void moveHeadBlocking(Double neck, Double rothead, Double eyeX, Double eyeY, Double jaw, Double rollNeck) { try { sendBlocking(getPeerName("head"), "moveToBlocking", neck, rothead, eyeX, eyeY, jaw, rollNeck); } catch (Exception e) { @@ -866,7 +868,7 @@ public void onGestureStatus(Status status) { error("I cannot execute %s, please check logs", lastGestureExecuted); } finishedGesture(lastGestureExecuted); - + unsubscribe("python", "publishStatus", this.getName(), "onGestureStatus"); } @@ -994,10 +996,10 @@ public void onStarted(String name) { 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"); -// } + // 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) { @@ -1229,8 +1231,8 @@ public String publishHeartbeat() { } /** - * A more extensible interface point than publishEvent - * FIXME - create interface for this + * A more extensible interface point than publishEvent FIXME - create + * interface for this * * @param msg * @return @@ -1561,7 +1563,7 @@ public void setTorsoSpeed(Integer topStom, Integer midStom, Integer lowStom) { setTorsoSpeed((double) topStom, (double) midStom, (double) lowStom); } - @Deprecated + @Deprecated /* use setTorsoSpeed */ public void setTorsoVelocity(Double topStom, Double midStom, Double lowStom) { setTorsoSpeed(topStom, midStom, lowStom); } @@ -1814,8 +1816,7 @@ public void startService() { subscribe("runtime", "shutdown"); // power up loopback subscription addListener(getName(), "powerUp"); - - + subscribe("runtime", "publishConfigList"); if (runtime.isProcessingConfig()) { invoke("publishEvent", "configStarted"); @@ -1923,7 +1924,6 @@ public void waitTargetPos() { sendToPeer("torso", "waitTargetPos"); } - public static void main(String[] args) { try { @@ -1934,20 +1934,28 @@ public static void main(String[] args) { // Runtime.startConfig("pr-1213-1"); - Runtime.main(new String[] {"--log-level", "info", "-s", "webgui", "WebGui", "intro", "Intro", "python", "Python"}); + 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"); + 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" }); + boolean done = true; if (done) { return; } - WebGui webgui = (WebGui) Runtime.create("webgui", "WebGui"); - // webgui.setSsl(true); - webgui.autoStartBrowser(false); - // webgui.setPort(8888); - webgui.startService(); - Runtime.start("python", "Python"); // Runtime.start("ros", "Ros"); Runtime.start("intro", "Intro"); @@ -1959,7 +1967,6 @@ public static void main(String[] args) { // 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"); @@ -1999,9 +2006,9 @@ public static void main(String[] args) { random.save(); -// i01.startChatBot(); -// -// i01.startAll("COM3", "COM4"); + // i01.startChatBot(); + // + // i01.startAll("COM3", "COM4"); Runtime.start("python", "Python"); } catch (Exception e) { @@ -2009,4 +2016,5 @@ public static void main(String[] args) { } } + } diff --git a/src/main/java/org/myrobotlab/service/OpenCV.java b/src/main/java/org/myrobotlab/service/OpenCV.java index 7abc45de8b..ff093721bd 100644 --- a/src/main/java/org/myrobotlab/service/OpenCV.java +++ b/src/main/java/org/myrobotlab/service/OpenCV.java @@ -1967,6 +1967,19 @@ public void enableFilter(String name) { } } + /** + * flip the video display vertically + * @param toFlip + */ + public void flip(boolean toFlip) { + config.flip = toFlip; + if (config.flip) { + addFilter("Flip"); + } else { + removeFilter("Flip"); + } + } + @Override public void disableFilter(String name) { OpenCVFilter f = filters.get(name); @@ -2062,6 +2075,8 @@ public OpenCVConfig apply(OpenCVConfig c) { // TODO: better configuration of the filter when it's added. } } + + flip(c.flip); if (c.capturing) { capture(); diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 3d8a92f2cc..9a385bcd7d 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -61,6 +61,7 @@ import org.myrobotlab.framework.Registration; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.ServiceReservation; +import org.myrobotlab.framework.StaticType; import org.myrobotlab.framework.Status; import org.myrobotlab.framework.interfaces.ConfigurableService; import org.myrobotlab.framework.interfaces.MessageListener; @@ -141,7 +142,7 @@ public class Runtime extends Service implements MessageListener, * all these requests. */ @Deprecated /* use the filesystem only no memory plan */ - transient final Plan masterPlan = new Plan("runtime"); + protected final Plan masterPlan = new Plan("runtime"); /** * thread for non-blocking install of services @@ -1152,6 +1153,14 @@ public static Map getRegistry() { return registry;// FIXME should return copy } + public static ServiceInterface getService(String inName) { + return getService(inName, new StaticType<>() {}); + } + + public static > S getConfigurableService(String inName, StaticType 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. @@ -1160,7 +1169,8 @@ public static Map getRegistry() { * The name of the service * @return The service if it exists, or null */ - public static ServiceInterface getService(String inName) { + @SuppressWarnings("unchecked") + public static S getService(String inName, StaticType serviceType) { if (inName == null) { return null; } @@ -1170,7 +1180,7 @@ public static ServiceInterface getService(String inName) { if (!registry.containsKey(name)) { return null; } else { - return registry.get(name); + return (S) registry.get(name); } } @@ -2572,7 +2582,7 @@ static public void startConfig(String configName) { Runtime runtime = Runtime.getInstance(); runtime.processingConfig = true; // multiple inbox threads not available runtime.invoke("publishStartConfig", configName); - RuntimeConfig rtConfig = (RuntimeConfig) runtime.readServiceConfig(runtime.getConfigName(), "runtime"); + 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; @@ -2661,6 +2671,7 @@ synchronized static public ServiceInterface start(String name, String type) { } Runtime.load(name, type); + Runtime.savePlan(null); // FIXME - does some order need to be maintained Map services = createServicesFromPlan(Runtime.getPlan(), null, name); @@ -4806,6 +4817,24 @@ synchronized private Plan loadService(Plan plan, String name, String type, boole return plan; } + 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 readServiceConfig(String name, StaticType configType) { + return readServiceConfig(null, name, configType); + } + + public ServiceConfig readServiceConfig(String configName, String name) { + return readServiceConfig(configName, name, new StaticType<>() {}); + } + /** * * @param configName @@ -4814,7 +4843,7 @@ synchronized private Plan loadService(Plan plan, String name, String type, boole * - name of config file within that dir e.g. {name}.yml * @return */ - public ServiceConfig readServiceConfig(String configName, String name) { + public C readServiceConfig(String configName, String name, StaticType configType) { // if config path set and yaml file exists - it takes precedence if (configName == null) { @@ -4828,10 +4857,10 @@ public ServiceConfig readServiceConfig(String configName, String name) { String filename = CONFIG_ROOT + fs + configName + fs + name + ".yml"; File check = new File(filename); - ServiceConfig sc = null; + C sc = null; if (check.exists()) { try { - sc = CodecUtils.readServiceConfig(filename); + sc = CodecUtils.readServiceConfig(filename, configType); } catch (ConstructorException e) { error("%s invalid %s %s. Please remove it from the file.", name, filename, e.getCause().getMessage()); } catch (IOException e) { @@ -4845,19 +4874,6 @@ public String publishConfigLoaded(String name) { return name; } - // @Override - // public ServiceConfig getConfig() { - // RuntimeConfig config = (RuntimeConfig)super.getConfig(); - // List listeners = new - // ArrayList - // for (org.myrobotlab.service.config.ServiceConfig.Listener listener: - // config.listeners) { - // if (listener.equals("stopped") || listener.equals("created")|| - // listener.equals("registered")|| listener.equals("released")) { - // - // } - // } - // } public String setAllIds(String id) { Platform.getLocalInstance().setId(id); @@ -5154,6 +5170,10 @@ private void savePlanInternal(String configName) { for (String s : masterPlan.keySet()) { String filename = CONFIG_ROOT + fs + configName + fs + s + ".yml"; + File check = new File(filename); + if (check.exists()) { + continue; + } String data = CodecUtils.toYaml(masterPlan.get(s)); try { FileIO.toFile(filename, data.getBytes()); diff --git a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java index af16e23a04..9ebecf8262 100644 --- a/src/main/java/org/myrobotlab/service/config/InMoov2Config.java +++ b/src/main/java/org/myrobotlab/service/config/InMoov2Config.java @@ -52,9 +52,7 @@ public class InMoov2Config extends ServiceConfig { public boolean neoPixelFlashWhenSpeaking = true; public boolean openCVFaceRecognizerActivated=true; - - public boolean openCVFlipPicture=false; - + public boolean pirEnableTracking = false; /** diff --git a/src/main/java/org/myrobotlab/service/config/OpenCVConfig.java b/src/main/java/org/myrobotlab/service/config/OpenCVConfig.java index 2c786aa648..2087539836 100644 --- a/src/main/java/org/myrobotlab/service/config/OpenCVConfig.java +++ b/src/main/java/org/myrobotlab/service/config/OpenCVConfig.java @@ -15,5 +15,9 @@ public class OpenCVConfig extends ServiceConfig { public boolean webViewer = false; public boolean capturing = false; public Map filters = new LinkedHashMap<>(); + /** + * flip the video vertically + */ + public boolean flip = false; } diff --git a/src/main/resources/resource/WebGui/app/peer.js b/src/main/resources/resource/WebGui/app/peer.js index 78c15f5da3..e033693b52 100644 --- a/src/main/resources/resource/WebGui/app/peer.js +++ b/src/main/resources/resource/WebGui/app/peer.js @@ -4,7 +4,7 @@ console.info('peer') -angular.module('peer', []).service('peer', function( mrl /*$rootScope, $log*/ +angular.module('peer', []).service('peer', function( mrl ) { service = {}; @@ -40,6 +40,20 @@ angular.module('peer', []).service('peer', function( mrl /*$rootScope, $log*/ return null } + service.getPeerConfig = function(service, key) { + try { + if (service && service.config && service.config.peers && service.config.peers[key]){ + return mrl.getService('runtime').masterPlan['config'][service.config.peers[key].name] + // return mrl.getService(service.config.peers[key].name).config + } else { + // if peer is not started - return the filesystem config + } + } catch (error) { + console.error(error); + } + return null + } + service.changePeerTab = function(service, key) { try { 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 09eab9b6f6..4f452e9834 100644 --- a/src/main/resources/resource/WebGui/app/service/js/RuntimeGui.js +++ b/src/main/resources/resource/WebGui/app/service/js/RuntimeGui.js @@ -132,6 +132,10 @@ angular.module('mrlapp.service.RuntimeGui', []).controller('RuntimeGuiCtrl', ['$ _self.updateState(data) $scope.$apply() break + case 'onPlan': + $scope.plan = data + $scope.$apply() + break case 'onLocalServices': $scope.registry = data // $scope.$apply() @@ -408,6 +412,7 @@ angular.module('mrlapp.service.RuntimeGui', []).controller('RuntimeGuiCtrl', ['$ msg.subscribe("publishStatus") msg.subscribe('publishConfigList') msg.subscribe('publishInterfaceToNames') + // msg.subscribe("getPlan") //msg.send("getLocalServices") msg.send("getStartYml") @@ -417,6 +422,7 @@ angular.module('mrlapp.service.RuntimeGui', []).controller('RuntimeGuiCtrl', ['$ msg.send("getLocales") msg.send("publishInterfaceToNames") msg.send("getConfigName") + // msg.send("getPlan") // msg.send("getHosts") msg.subscribe(this) diff --git a/src/main/resources/resource/framework/pom.xml.template b/src/main/resources/resource/framework/pom.xml.template index ce45f6285a..f0ae2b4183 100644 --- a/src/main/resources/resource/framework/pom.xml.template +++ b/src/main/resources/resource/framework/pom.xml.template @@ -13,6 +13,9 @@ # fast build mvn -DskipTests package -o + # execute + mvn exec:java -Dexec.mainClass=org.myrobotlab.service.Runtime -Dexec.args="-s webgui WebGui intro Intro python Python" + # specific test mvn test -Dtest="org.myrobotlab.service.WebGuiTest#postTest" diff --git a/src/test/java/org/myrobotlab/service/InMoov2Test.java b/src/test/java/org/myrobotlab/service/InMoov2Test.java new file mode 100644 index 0000000000..c9ae337b69 --- /dev/null +++ b/src/test/java/org/myrobotlab/service/InMoov2Test.java @@ -0,0 +1,29 @@ +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; + +public class InMoov2Test { + + @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); + + } + + +} +