diff --git a/src/main/java/org/myrobotlab/service/ProgramAB.java b/src/main/java/org/myrobotlab/service/ProgramAB.java
index ff123320fc..6812d8d096 100644
--- a/src/main/java/org/myrobotlab/service/ProgramAB.java
+++ b/src/main/java/org/myrobotlab/service/ProgramAB.java
@@ -794,7 +794,7 @@ public String addBotPath(String path) {
broadcastState();
} else {
- error("invalid bot path - a bot must be a directory with a subdirectory named \"aiml\"");
+ error("invalid bot path %s - a bot must be a directory with a subdirectory named \"aiml\"", path);
return null;
}
return path;
diff --git a/src/main/java/org/myrobotlab/service/Vertx.java b/src/main/java/org/myrobotlab/service/Vertx.java
new file mode 100644
index 0000000000..f87e9b9e4c
--- /dev/null
+++ b/src/main/java/org/myrobotlab/service/Vertx.java
@@ -0,0 +1,135 @@
+package org.myrobotlab.service;
+
+import java.util.HashMap;
+import java.util.Set;
+
+import org.myrobotlab.framework.Service;
+import org.myrobotlab.logging.Level;
+import org.myrobotlab.logging.LoggerFactory;
+import org.myrobotlab.logging.LoggingFactory;
+import org.myrobotlab.vertx.ApiVerticle;
+import org.slf4j.Logger;
+
+import io.vertx.core.VertxOptions;
+
+/**
+ * Vertx gateway - used to support a http and websocket gateway for myrobotlab.
+ * Write business logic in Verticles. Also, try not to write any logic besides initialization inside start() method.
+ *
+ * It currently does not utilize the Vertx event bus - which is pretty much the most important part of Vertx.
+ * TODO: take advantage of publishing on the event bus
+ *
+ * @see https://medium.com/@pvub/https-medium-com-pvub-vert-x-workers-6a8df9b2b9ee
+ *
+ * @author greg
+ *
+ */
+public class Vertx extends Service {
+
+ private static final long serialVersionUID = 1L;
+
+ private transient io.vertx.core.Vertx vertx = null;
+
+ public final static Logger log = LoggerFactory.getLogger(Vertx.class);
+
+ public Vertx(String n, String id) {
+ super(n, id);
+ }
+
+ /**
+ * deploys a http and websocket verticle on a secure TLS channel with self signed certificate
+ */
+ public void start() {
+ log.info("starting driver");
+
+ /**
+ * FIXME - might have to revisit this This is a block comment, but takes
+ * advantage of javadoc pre non-formatting in ide to preserve the code
+ * formatting
+ *
+ *
+ *
+ * final Vertx that = this;
+ *
+ * java.lang.Runtime.getRuntime().addShutdownHook(new Thread() {
+ * public void run() {
+ * System.out.println("Running Shutdown Hook");
+ * that.stop();
+ * }
+ * });
+ *
+ *
+ */
+
+ vertx = io.vertx.core.Vertx.vertx(new VertxOptions().setBlockedThreadCheckInterval(100000));
+ vertx.deployVerticle(new ApiVerticle(this));
+
+ }
+
+ @Override
+ public void startService() {
+ super.startService();
+ start();
+ }
+
+ @Override
+ public void stopService() {
+ super.stopService();
+ stop();
+ }
+
+
+ /**
+ *
+ */
+ public void stop() {
+ log.info("stopping driver");
+ Set ids = vertx.deploymentIDs();
+ for (String id : ids) {
+ vertx.undeploy(id, (result) -> {
+ if (result.succeeded()) {
+ log.info("succeeded");
+ } else {
+ log.error("failed");
+ }
+ });
+ }
+ }
+
+ public static class Matrix {
+ public String name;
+ public HashMap matrix;
+
+ public Matrix() {
+ };
+ }
+
+ public Matrix publishMatrix(Matrix data) {
+ // log.info("publishMatrix {}", data.name);
+ return data;
+ }
+
+ public static void main(String[] args) {
+ try {
+
+ LoggingFactory.init(Level.INFO);
+
+ Vertx vertx = (Vertx) Runtime.start("vertx", "Vertx");
+ vertx.start();
+
+ InMoov2 i01 = (InMoov2)Runtime.start("i01", "InMoov2");
+ // i01.startSimulator();
+ JMonkeyEngine jme = (JMonkeyEngine)i01.startPeer("simulator");
+// Runtime.start("python", "Python");
+//
+ WebGui webgui = (WebGui) Runtime.create("webgui", "WebGui");
+ // webgui.setSsl(true);
+ webgui.autoStartBrowser(false);
+ webgui.setPort(8888);
+ webgui.startService();
+
+ } catch (Exception e) {
+ log.error("main threw", e);
+ }
+ }
+}
diff --git a/src/main/java/org/myrobotlab/service/WebXR.java b/src/main/java/org/myrobotlab/service/WebXR.java
new file mode 100644
index 0000000000..d194df1e4e
--- /dev/null
+++ b/src/main/java/org/myrobotlab/service/WebXR.java
@@ -0,0 +1,89 @@
+package org.myrobotlab.service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.myrobotlab.framework.Service;
+import org.myrobotlab.logging.Level;
+import org.myrobotlab.logging.LoggerFactory;
+import org.myrobotlab.logging.LoggingFactory;
+import org.myrobotlab.math.MapperSimple;
+import org.myrobotlab.service.config.WebXRConfig;
+import org.myrobotlab.service.data.Pose;
+import org.slf4j.Logger;
+
+public class WebXR extends Service {
+
+ private static final long serialVersionUID = 1L;
+
+ public final static Logger log = LoggerFactory.getLogger(WebXR.class);
+
+ public WebXR(String n, String id) {
+ super(n, id);
+ }
+
+ public Pose publishPose(Pose pose) {
+ log.warn("publishPose {}", pose);
+ System.out.println(pose.toString());
+
+ // process mappings config into joint angles
+ Map map = new HashMap<>();
+
+ WebXRConfig c = (WebXRConfig)config;
+ String path = String.format("%s.orientation.roll", pose.name);
+ if (c.mappings.containsKey(path)) {
+ Map mapper = c.mappings.get(path);
+ for (String name: mapper.keySet()) {
+ map.put(name, mapper.get(name).calcOutput(pose.orientation.roll));
+ }
+ }
+
+ path = String.format("%s.orientation.pitch", pose.name);
+ if (c.mappings.containsKey(path)) {
+ Map mapper = c.mappings.get(path);
+ for (String name: mapper.keySet()) {
+ map.put(name, mapper.get(name).calcOutput(pose.orientation.pitch));
+ }
+ }
+
+ path = String.format("%s.orientation.yaw", pose.name);
+ if (c.mappings.containsKey(path)) {
+ Map mapper = c.mappings.get(path);
+ for (String name: mapper.keySet()) {
+ map.put(name, mapper.get(name).calcOutput(pose.orientation.yaw));
+ }
+ }
+
+ invoke("publishJointAngles", map);
+
+ // TODO - publishQuaternion
+ // invoke("publishQuaternion", map);
+
+ return pose;
+ }
+
+
+ public Map publishJointAngles(Map map){
+ return map;
+ }
+
+ public static void main(String[] args) {
+ try {
+
+ LoggingFactory.init(Level.INFO);
+
+ Runtime.start("webxr", "WebXr");
+ WebGui webgui = (WebGui) Runtime.create("webgui", "WebGui");
+ // webgui.setSsl(true);
+ webgui.autoStartBrowser(false);
+ webgui.startService();
+ Runtime.start("vertx", "Vertx");
+ InMoov2 i01 = (InMoov2)Runtime.start("i01", "InMoov2");
+ i01.startPeer("simulator");
+
+
+ } catch (Exception e) {
+ log.error("main threw", e);
+ }
+ }
+}
diff --git a/src/main/java/org/myrobotlab/service/config/VertxConfig.java b/src/main/java/org/myrobotlab/service/config/VertxConfig.java
new file mode 100644
index 0000000000..f2119d8ddd
--- /dev/null
+++ b/src/main/java/org/myrobotlab/service/config/VertxConfig.java
@@ -0,0 +1,9 @@
+package org.myrobotlab.service.config;
+
+public class VertxConfig extends ServiceConfig {
+
+ public Integer port = 8443;
+ public Integer workerCount = 1;
+ public boolean ssl = true;
+
+}
diff --git a/src/main/java/org/myrobotlab/service/data/Orientation.java b/src/main/java/org/myrobotlab/service/data/Orientation.java
index b2d5d5658e..b7df4102dc 100644
--- a/src/main/java/org/myrobotlab/service/data/Orientation.java
+++ b/src/main/java/org/myrobotlab/service/data/Orientation.java
@@ -11,6 +11,7 @@ public class Orientation {
public Double roll = null;
public Double pitch = null;
public Double yaw = null;
+ public String src = null;
// default constructor (values will be null until set)
public Orientation() {
diff --git a/src/main/java/org/myrobotlab/service/data/Pose.java b/src/main/java/org/myrobotlab/service/data/Pose.java
new file mode 100644
index 0000000000..767d9be81d
--- /dev/null
+++ b/src/main/java/org/myrobotlab/service/data/Pose.java
@@ -0,0 +1,22 @@
+package org.myrobotlab.service.data;
+
+public class Pose {
+ public String name = null;
+ public Long ts = null;
+ public Position position = null;
+ public Orientation orientation = null;
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format("name:%s", name));
+ if (position != null) {
+ sb.append(String.format(" x:%.2f y:%.2f z:%.2f", position.x, position.y, position.z));
+ }
+ if (orientation != null) {
+ sb.append(String.format(" roll:%.2f pitch:%.2f yaw:%.2f", orientation.roll, orientation.pitch, orientation.yaw));
+ }
+ return sb.toString();
+ }
+
+
+}
diff --git a/src/main/java/org/myrobotlab/service/data/Position.java b/src/main/java/org/myrobotlab/service/data/Position.java
new file mode 100644
index 0000000000..83fe574a44
--- /dev/null
+++ b/src/main/java/org/myrobotlab/service/data/Position.java
@@ -0,0 +1,43 @@
+package org.myrobotlab.service.data;
+
+public class Position {
+
+ public Double x;
+ public Double y;
+ public Double z;
+ public String src;
+
+ public Position(double x, double y, double z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public Position(double x, double y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public Position(int x, int y, int z) {
+ this.x = (double) x;
+ this.y = (double) y;
+ this.z = (double) z;
+ }
+
+ public Position(int x, int y) {
+ this.x = (double) x;
+ this.y = (double) y;
+ }
+
+ public Position(float x, float y, float z) {
+ this.x = (double) x;
+ this.y = (double) y;
+ this.z = (double) z;
+ }
+
+ public Position(float x, float y) {
+ this.x = (double) x;
+ this.y = (double) y;
+ }
+
+}
diff --git a/src/main/java/org/myrobotlab/service/meta/WebXRMeta.java b/src/main/java/org/myrobotlab/service/meta/WebXRMeta.java
new file mode 100644
index 0000000000..171959fbf5
--- /dev/null
+++ b/src/main/java/org/myrobotlab/service/meta/WebXRMeta.java
@@ -0,0 +1,33 @@
+package org.myrobotlab.service.meta;
+
+import org.myrobotlab.logging.LoggerFactory;
+import org.myrobotlab.service.meta.abstracts.MetaData;
+import org.slf4j.Logger;
+
+public class WebXRMeta extends MetaData {
+ private static final long serialVersionUID = 1L;
+ public final static Logger log = LoggerFactory.getLogger(WebXRMeta.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 WebXRMeta() {
+
+ // add a cool description
+ addDescription("WebXr allows hmi devices to add input and get data back from mrl");
+
+ // false will prevent it being seen in the ui
+ setAvailable(true);
+
+ // add it to one or many categories
+ addCategory("remote","control");
+
+ // add a sponsor to this service
+ // the person who will do maintenance
+ // setSponsor("GroG");
+
+ }
+
+}
diff --git a/src/main/java/org/myrobotlab/vertx/ApiVerticle.java b/src/main/java/org/myrobotlab/vertx/ApiVerticle.java
new file mode 100644
index 0000000000..6b3ca595c7
--- /dev/null
+++ b/src/main/java/org/myrobotlab/vertx/ApiVerticle.java
@@ -0,0 +1,105 @@
+package org.myrobotlab.vertx;
+
+import java.lang.reflect.Method;
+
+import org.myrobotlab.codec.CodecUtils;
+import org.myrobotlab.framework.MethodCache;
+import org.myrobotlab.framework.interfaces.ServiceInterface;
+import org.myrobotlab.service.Runtime;
+import org.myrobotlab.service.config.VertxConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.vertx.core.AbstractVerticle;
+import io.vertx.core.Handler;
+import io.vertx.core.http.HttpMethod;
+import io.vertx.core.http.HttpServer;
+import io.vertx.core.http.HttpServerOptions;
+import io.vertx.core.http.ServerWebSocket;
+import io.vertx.core.net.SelfSignedCertificate;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.handler.CorsHandler;
+import io.vertx.ext.web.handler.StaticHandler;
+
+/**
+ * verticle to handle api requests
+ *
+ * @author GroG
+ */
+public class ApiVerticle extends AbstractVerticle {
+
+ public final static Logger log = LoggerFactory.getLogger(ApiVerticle.class);
+
+ private Router router;
+
+ transient private org.myrobotlab.service.Vertx service;
+
+ public ApiVerticle(org.myrobotlab.service.Vertx service) {
+ super();
+ this.service = service;
+ }
+
+ @Override
+ public void start() throws Exception {
+ // process configuration and create handlers
+ log.info("starting api verticle");
+ VertxConfig config = (VertxConfig) service.getConfig();
+
+ // create a router
+ router = Router.router(vertx);
+
+ // handle cors requests
+ router.route().handler(CorsHandler.create("*").allowedMethod(HttpMethod.GET).allowedMethod(HttpMethod.OPTIONS).allowedHeader("Accept").allowedHeader("Authorization")
+ .allowedHeader("Content-Type"));
+
+ // static file routing
+
+ //StaticHandler root = StaticHandler.create("src/main/resources/resource/Vertx/app");
+ // StaticHandler root = StaticHandler.create("src/main/resources/resource/Vertx/app");
+ StaticHandler root = StaticHandler.create("../robotlab-x-app/build/");
+ root.setCachingEnabled(false);
+ root.setDirectoryListing(true);
+ root.setIndexPage("index.html");
+ // root.setAllowRootFileSystemAccess(true);
+ // root.setWebRoot(null);
+ router.route("/*").handler(root);
+
+
+ // router.get("/health").handler(this::generateHealth);
+ // router.get("/api/transaction/:customer/:tid").handler(this::handleTransaction);
+
+ // create the HTTP server and pass the
+ // "accept" method to the request handler
+ HttpServerOptions httpOptions = new HttpServerOptions();
+
+ if (config.ssl) {
+ SelfSignedCertificate certificate = SelfSignedCertificate.create();
+ httpOptions.setSsl(true);
+ httpOptions.setKeyCertOptions(certificate.keyCertOptions());
+ httpOptions.setTrustOptions(certificate.trustOptions());
+ }
+ httpOptions.setPort(config.port);
+
+
+ HttpServer server = vertx.createHttpServer(httpOptions);
+ // TODO - this is where multiple workers would be defined
+ // .createHttpServer()
+
+ // WebSocketHandler webSocketHandler = new WebSocketHandler(service);
+ // server.webSocketHandler(webSocketHandler);
+
+ // FIXME - don't do "long" or "common" processing in the start()
+ // FIXME - how to do this -> server.webSocketHandler(this::handleWebSocket);
+ server.webSocketHandler(new WebSocketHandler(service));
+ server.requestHandler(router);
+ // start servers
+ server.listen();
+ }
+
+
+ @Override
+ public void stop() throws Exception {
+ log.info("stopping api verticle");
+ }
+
+}
diff --git a/src/main/java/org/myrobotlab/vertx/WebSocketHandler.java b/src/main/java/org/myrobotlab/vertx/WebSocketHandler.java
new file mode 100644
index 0000000000..76d0fb8c1f
--- /dev/null
+++ b/src/main/java/org/myrobotlab/vertx/WebSocketHandler.java
@@ -0,0 +1,93 @@
+package org.myrobotlab.vertx;
+
+import java.lang.reflect.Method;
+
+import org.myrobotlab.codec.CodecUtils;
+import org.myrobotlab.framework.MethodCache;
+import org.myrobotlab.framework.interfaces.ServiceInterface;
+import org.myrobotlab.logging.LoggerFactory;
+import org.myrobotlab.service.Runtime;
+import org.slf4j.Logger;
+
+import io.vertx.core.Handler;
+import io.vertx.core.http.ServerWebSocket;
+
+/**
+ *
+ * TODO - what else besides text messages - websocket binary streams ??? text stream ?
+ *
+ * @author GroG
+ *
+ */
+public class WebSocketHandler implements Handler {
+
+ public final static Logger log = LoggerFactory.getLogger(WebSocketHandler.class);
+
+ transient private org.myrobotlab.service.Vertx service = null;
+ TextMessageHandler textMessageHandler = null;
+
+ public static class TextMessageHandler implements Handler {
+
+ org.myrobotlab.service.Vertx service = null;
+
+ public TextMessageHandler(org.myrobotlab.service.Vertx service) {
+ this.service = service;
+ }
+
+ @Override
+ public void handle(String json) {
+ log.info("handling {}", json);
+
+ Method method;
+ try {
+
+ org.myrobotlab.framework.Message msg = CodecUtils.fromJson(json, org.myrobotlab.framework.Message.class);
+
+ Class> clazz = Runtime.getClass(msg.name);
+ if (clazz == null) {
+ log.error("cannot derive local type from service {}", msg.name);
+ return;
+ }
+
+ MethodCache cache = MethodCache.getInstance();
+ Object[] params = cache.getDecodedJsonParameters(clazz, msg.method, msg.data);
+
+ method = cache.getMethod(clazz, msg.method, params);
+ if (method == null) {
+ service.error("method cache could not find %s.%s(%s)", clazz.getSimpleName(), msg.method, msg.data);
+ return;
+ }
+
+ ServiceInterface si = Runtime.getService(msg.name);
+ Object ret = method.invoke(si, params);
+
+ // put msg on mrl msg bus :)
+ // service.in(msg); <- NOT DECODE PARAMS !!
+
+ // if ((new Random()).nextInt(100) == 0) {
+ // ctx.close(); - will close the websocket !!!
+ // } else {
+ // ctx.writeTextMessage("ping"); Useful is writing back
+ // }
+
+ } catch (Exception e) {
+ service.error(e);
+ }
+ }
+ }
+
+ public WebSocketHandler(org.myrobotlab.service.Vertx service) {
+ this.service = service;
+ this.textMessageHandler = new TextMessageHandler(service);
+ }
+
+ @Override
+ public void handle(ServerWebSocket event) {
+
+ // ctx.writeTextMessage("ping"); FIXME - query ?
+ // FIXME - thread-safe ? how many connections mapped to objects ?
+ event.textMessageHandler(new TextMessageHandler(service));
+
+ }
+
+}