diff --git a/src/main/java/org/myrobotlab/codec/ForeignProcessUtils.java b/src/main/java/org/myrobotlab/codec/ForeignProcessUtils.java index 1c008f3ea2..635d926f48 100644 --- a/src/main/java/org/myrobotlab/codec/ForeignProcessUtils.java +++ b/src/main/java/org/myrobotlab/codec/ForeignProcessUtils.java @@ -83,6 +83,11 @@ public class ForeignProcessUtils { * @return Whether name is a valid FQCN */ public static boolean isValidJavaClassName(String name) { + // TODO: this is temporary, until proxy java classes + // are proxied in the same way as other services + if (name.equals("Unknown")) { + return false; + } return JAVA_FQCN_PATTERN.matcher(name).matches(); } diff --git a/src/main/java/org/myrobotlab/framework/ProxyFactory.java b/src/main/java/org/myrobotlab/framework/ProxyFactory.java index c8782a20e1..965092f2a1 100644 --- a/src/main/java/org/myrobotlab/framework/ProxyFactory.java +++ b/src/main/java/org/myrobotlab/framework/ProxyFactory.java @@ -1,77 +1,87 @@ package org.myrobotlab.framework; -import net.bytebuddy.ByteBuddy; -import net.bytebuddy.dynamic.DynamicType; -import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; -import net.bytebuddy.implementation.MethodDelegation; -import org.myrobotlab.framework.interfaces.ServiceInterface; +import static net.bytebuddy.matcher.ElementMatchers.any; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.stream.Collectors; -import static net.bytebuddy.matcher.ElementMatchers.any; +import org.myrobotlab.framework.interfaces.ServiceInterface; +import org.myrobotlab.logging.LoggerFactory; +import org.slf4j.Logger; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.implementation.MethodDelegation; /** - * ProxyFactory takes a service description via {@link Registration} - * and uses ByteBuddy to generate - * a new class and instance for it, delegating all method calls to a new instance - * of {@link ProxyInterceptor}. The registration must contain at least the - * fully-qualified name of {@link ServiceInterface} in its {@link Registration#interfaces} - * list. If this name is not present, an {@link IllegalArgumentException} is thrown. + * ProxyFactory takes a service description via {@link Registration} and uses + * ByteBuddy to generate a new class and + * instance for it, delegating all method calls to a new instance of + * {@link ProxyInterceptor}. The registration must contain at least the + * fully-qualified name of {@link ServiceInterface} in its + * {@link Registration#interfaces} list. If this name is not present, an + * {@link IllegalArgumentException} is thrown. * * @author AutonomicPerfectionist */ public class ProxyFactory { - /** - * Creates a proxy class and instantiates it using the given registration. - * If the registration's {@link Registration#interfaces} list does not contain - * the fully-qualified name of {@link ServiceInterface}, an {@link IllegalArgumentException} - * is thrown. The generated proxy uses the name, id, and interfaces present in - * the registration to create the new service. The proxy - * delegates to a new instance of {@link ProxyInterceptor}. - * - * @param registration The information required to generate a new proxy - * @return A newly-instantiated proxy - * @throws IllegalArgumentException if registration's interfaces list - * does not contain ServiceInterface - */ - public static ServiceInterface createProxyService(Registration registration) { - // TODO add caching support - if (!registration.interfaces.contains(ServiceInterface.class.getName())) { - throw new IllegalArgumentException( - "Registration must list at least ServiceInterface in the interfaces list." - ); - } - ByteBuddy buddy = new ByteBuddy(); - DynamicType.Builder builder = buddy.subclass(Object.class); - List> interfaceClasses = registration.interfaces.stream().map(i -> { - try { - return Class.forName(i); - } catch (ClassNotFoundException e) { - throw new RuntimeException("Unable to load interface " + i + " for registration " + registration, e); - } - }).collect(Collectors.toList()); + transient public final static Logger log = LoggerFactory.getLogger(ProxyFactory.class); + + /** + * Creates a proxy class and instantiates it using the given registration. If + * the registration's {@link Registration#interfaces} list does not contain + * the fully-qualified name of {@link ServiceInterface}, an + * {@link IllegalArgumentException} is thrown. The generated proxy uses the + * name, id, and interfaces present in the registration to create the new + * service. The proxy delegates to a new instance of {@link ProxyInterceptor}. + * + * @param registration + * The information required to generate a new proxy + * @return A newly-instantiated proxy + * @throws IllegalArgumentException + * if registration's interfaces list does not + * contain + * ServiceInterface + */ + public static ServiceInterface createProxyService(Registration registration) { - builder = builder.implement(interfaceClasses) - .method(any()) - .intercept(MethodDelegation - .withDefaultConfiguration() - .to(new ProxyInterceptor(registration.name, registration.id))); + if (registration.interfaces == null) { + log.info("remote did not provide any interfaces, creating minimal getId and getName from registration data"); + } else { + // TODO add caching support + if (!registration.interfaces.contains(ServiceInterface.class.getName())) { + throw new IllegalArgumentException("Registration must list at least ServiceInterface in the interfaces list."); + } + } + ByteBuddy buddy = new ByteBuddy(); + DynamicType.Builder builder = buddy.subclass(Object.class); + List> interfaceClasses = registration.interfaces.stream().map(i -> { + try { + return Class.forName(i); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to load interface " + i + " for registration " + registration, e); + } + }).collect(Collectors.toList()); + builder = builder.implement(interfaceClasses).method(any()).intercept(MethodDelegation.withDefaultConfiguration() + .to(new ProxyInterceptor(registration.name, registration.id, registration.typeKey))); - Class proxyClass = builder.make() - .load(ProxyFactory.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) - .getLoaded(); - try { - // We never defined any constructors so the default no-args is available - return (ServiceInterface) proxyClass.getConstructors()[0].newInstance(); + Class proxyClass = builder.make().load(ProxyFactory.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) + .getLoaded(); + try { + // We never defined any constructors so the default no-args is available + ServiceInterface si = (ServiceInterface) proxyClass.getConstructors()[0].newInstance(); + MethodCache cache = MethodCache.getInstance(); + cache.cacheMethodEntries(si.getClass()); + return si; - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - // Really shouldn't happen since we have full control over the - // newly-generated class - throw new RuntimeException(e); - } + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + // Really shouldn't happen since we have full control over the + // newly-generated class + throw new RuntimeException(e); } + } } diff --git a/src/main/java/org/myrobotlab/framework/ProxyInterceptor.java b/src/main/java/org/myrobotlab/framework/ProxyInterceptor.java index f56167d7a8..205f04e2d5 100644 --- a/src/main/java/org/myrobotlab/framework/ProxyInterceptor.java +++ b/src/main/java/org/myrobotlab/framework/ProxyInterceptor.java @@ -1,14 +1,16 @@ package org.myrobotlab.framework; -import net.bytebuddy.implementation.bind.annotation.AllArguments; -import net.bytebuddy.implementation.bind.annotation.Origin; -import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import java.lang.reflect.Method; +import java.util.Arrays; + +import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.Runtime; import org.slf4j.Logger; -import java.lang.reflect.Method; -import java.util.Arrays; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; /** * This class is used internally to intercept @@ -24,30 +26,82 @@ */ public class ProxyInterceptor { - protected static Logger log = LoggerFactory.getLogger(ProxyInterceptor.class); + protected static Logger log = LoggerFactory.getLogger(ProxyInterceptor.class); + + public static volatile int timeout = 3000; + + /** + * name of remote service + */ + private final String name; + + /** + * id of remote instance + */ + private final String id; + + private final String typeKey; + + public ProxyInterceptor(String name, String id, String typeKey) { + this.name = name; + this.id = id; + this.typeKey = typeKey; + // this.state ? + } + + /** + * Name, Id, FullName, TypeKey and list of interfaces + * are all available during registration, these methods + * should not later go out to the client to resolve. + * @return + */ + public String getId() { + return id; + } - public static volatile int timeout = 3000; - private final String name; + /** + * Name is availble at registration, don't need to ask the + * remote service again + * @return + */ + public String getName() { + return name; + } - private final String id; + /** + * Convenience method + * @return + */ + public String getFullName() { + return String.format("%s@%s", name, id); + } - public ProxyInterceptor(String name, String id) { - this.name = name; - this.id = id; - } + /** + * Given on registration, don't need client to be queried for it + * @return + */ + public String getTypeKey() { + return typeKey; + } + /** + * A guess at what might be best + */ + public String toString() { + return CodecUtils.toJson(this); + } - @RuntimeType - public Object intercept(@Origin Method method, @AllArguments Object... args) throws InterruptedException, TimeoutException { - log.debug( - "Executing proxy method {}@{}.{}({})", - name, - id, - method, - ((args == null) ? "" : Arrays.toString(args)) - ); - // Timeout should be more sophisticated for long blocking methods - return Runtime.getInstance().sendBlocking(name + "@" + id, timeout, method.getName(), - (args != null) ? args : new Object[0]); - } + @RuntimeType + public Object intercept(@Origin Method method, @AllArguments Object... args) + throws InterruptedException, TimeoutException { + log.debug( + "Executing proxy method {}@{}.{}({})", + name, + id, + method, + ((args == null) ? "" : Arrays.toString(args))); + // Timeout should be more sophisticated for long blocking methods + return Runtime.getInstance().sendBlocking(name + "@" + id, timeout, method.getName(), + (args != null) ? args : new Object[0]); + } } diff --git a/src/main/java/org/myrobotlab/framework/Registration.java b/src/main/java/org/myrobotlab/framework/Registration.java index 4f0b3070dc..a8321bdef1 100644 --- a/src/main/java/org/myrobotlab/framework/Registration.java +++ b/src/main/java/org/myrobotlab/framework/Registration.java @@ -6,6 +6,7 @@ import java.util.Objects; import org.myrobotlab.codec.CodecUtils; +import org.myrobotlab.codec.ForeignProcessUtils; import org.myrobotlab.framework.interfaces.ServiceInterface; import org.myrobotlab.logging.LoggerFactory; import org.slf4j.Logger; @@ -70,9 +71,17 @@ public Registration(ServiceInterface service) { this.id = service.getId(); this.name = service.getName(); this.typeKey = service.getTypeKey(); - // when this registration is re-broadcasted to remotes it will use this + // When this registration is re-broadcasted to remotes it will use this // serialization to init state - this.state = CodecUtils.toJson(service); + + // FIXME: This switch would not be necessary + // if Java remote services were handled the same way, would not need to do this + if (ForeignProcessUtils.isValidJavaClassName(service.getTypeKey())) { + this.state = CodecUtils.toJson(service); + } else { + this.state = service.toString(); + } + // if this is a local registration - need reference to service this.service = service; } diff --git a/src/main/java/org/myrobotlab/generics/SlidingWindowList.java b/src/main/java/org/myrobotlab/generics/SlidingWindowList.java new file mode 100644 index 0000000000..2c048a532f --- /dev/null +++ b/src/main/java/org/myrobotlab/generics/SlidingWindowList.java @@ -0,0 +1,22 @@ +package org.myrobotlab.generics; + +import java.util.ArrayList; + +public class SlidingWindowList extends ArrayList { + private static final long serialVersionUID = 1L; + private final int maxSize; + + public SlidingWindowList(int maxSize) { + this.maxSize = maxSize; + } + + @Override + public boolean add(E element) { + boolean added = super.add(element); + if (size() > maxSize) { + removeRange(0, size() - maxSize); // Remove oldest elements if size exceeds maxSize + } + return added; + } + +} diff --git a/src/main/java/org/myrobotlab/service/MockGateway.java b/src/main/java/org/myrobotlab/service/MockGateway.java new file mode 100644 index 0000000000..f03e7cc2f7 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/MockGateway.java @@ -0,0 +1,311 @@ +package org.myrobotlab.service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.myrobotlab.codec.CodecUtils; +import org.myrobotlab.framework.Message; +import org.myrobotlab.framework.MethodEntry; +import org.myrobotlab.framework.Service; +import org.myrobotlab.framework.TimeoutException; +import org.myrobotlab.framework.interfaces.ServiceInterface; +import org.myrobotlab.generics.SlidingWindowList; +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.logging.LoggingFactory; +import org.myrobotlab.net.Connection; +import org.myrobotlab.service.config.MockGatewayConfig; +import org.myrobotlab.service.interfaces.Gateway; +import org.slf4j.Logger; + +public class MockGateway extends Service implements Gateway { + + transient public final static Logger log = LoggerFactory.getLogger(MockGateway.class); + + private static final long serialVersionUID = 1L; + + protected final transient BlockingQueue sendQueuex = new LinkedBlockingQueue<>(); + + protected SlidingWindowList msgs = new SlidingWindowList<>(100); + + transient protected Map> sendQueues = new HashMap<>(); + + /** + * Id of a fake remote instance + */ + protected String remoteId = "mockId"; + + /** + * list of remote services that have registered + */ + protected Map remoteServices = new TreeMap<>(); + + public class RemoteService { + protected transient MockGateway gateway; + protected String name; + + public RemoteService(MockGateway gateway, String fullname) { + this.name = fullname; + this.gateway = gateway; + } + + public void handle(Message msg) { + if (msg.method.equals("getMethodMap")) { + Map emptyMap = new HashMap<>(); + // emptyMap = Runtime.getMethodMap("clock"); + Message returnMsg = Message.createMessage(name, msg.sender, "onMethodMap", emptyMap); + gateway.send(returnMsg); + } + + } + } + + public MockGateway(String reservedKey, String inId) { + super(reservedKey, inId); + } + + @Override + public void connect(String uri) throws Exception { + log.info("connecting {}", uri); + } + + @Override + public List getClientIds() { + return Runtime.getInstance().getConnectionUuids(getName()); + } + + @Override + public Map getClients() { + return Runtime.getInstance().getConnections(getName()); + } + + /** + * Connect a remote instance identified by remote id + * + * @param id + */ + public void addConnection(String id) { + String uuid = UUID.randomUUID().toString(); + Connection connection = new Connection(uuid, id, getName()); + Runtime.getInstance().addConnection(uuid, id, connection); + } + + /** + * Messages are sent to remote services if they are published and routed + */ + @Override + public void sendRemote(Message msg) throws Exception { + log.info("mock gateway got a sendRemote {}", msg); + + String key = String.format("%s.%s", msg.name, msg.method); + + BlockingQueue q = null; + if (!sendQueues.containsKey(key)) { + q = new LinkedBlockingQueue<>(); + sendQueues.put(key, q); + } else { + q = sendQueues.get(key); + } + + q.add(msg); + invoke("publishSendMessage", msg); + msgs.add(msg); + + if (!remoteServices.containsKey(msg.name)) { + error("got remote message from %s - and do not have its client !!!", msg.name); + return; + } + remoteServices.get(msg.name).handle(msg); + } + + public void clear() { + sendQueues.clear(); + } + + public int size() { + return msgs.size(); + } + + @Override + public boolean isLocal(Message msg) { + return Runtime.getInstance().isLocal(msg); + } + + + public void sendWithDelay(String name, String method, Object... data) { + Message msg = Message.createMessage(method, name, method, data); + addTask(UUID.randomUUID().toString(), true, 0, 0, "send", new Object[] { msg }); + } + + + /** + * Send an asynchronous message so waiting for a callback can be done easily + * with inline code e.g. + * + *
+   * mock.sendWithDelay(10, "mouth", "speakBlocking");
+   * mock.waitForMsg(100, "mocker", "publishSpeaking");
+   * 
+   * 
+ * + * @param wait + * @param name + * @param method + */ + public void sendWithDelay(long wait, String name, String method) { + sendWithDelay(wait, name, method, (Object[]) null); + } + + public void sendWithDelay(long wait, String name, String method, Object... data) { + Message msg = Message.createMessage(method, name, method, data); + addTask(UUID.randomUUID().toString(), true, wait, wait, "send", new Object[] { msg }); + } + + // FIXME - must have a radix of names and block on specific publishing methods + public Message waitForMsg(String name, String callback, long maxTimeWaitMs) throws TimeoutException { + try { + String fullName = getFullRemoteName(name); + + String key = String.format("%s.%s", fullName, callback); + if (!sendQueues.containsKey(key)) { + return null; + } + + Message msg = sendQueues.get(key).poll(maxTimeWaitMs, TimeUnit.MILLISECONDS); + if (msg == null) { + String timeout = String.format("waited %dms for %s.%s", maxTimeWaitMs, name, callback); + throw new TimeoutException(timeout); + } else { + return msg; + } + } catch (InterruptedException e) { + log.info("releasing polling thread {}", Thread.currentThread().getId()); + } + return null; + } + + /** + * set the current remote id + * + * @param id + */ + public void setRemoteId(String id) { + remoteId = id; + } + + /** + * get the current remote id + * + * @return + */ + public String getRemoteId() { + return remoteId; + } + + @Override + public void startService() { + super.startService(); + // add the remote instance over a connection + addConnection(remoteId); + // add a default remote service + ArrayList interfaces = new ArrayList<>(); + interfaces.add(ServiceInterface.class.getName()); + registerRemoteService("mocker", interfaces); + + } + + /** + * Registers a non Java service with mrl runtime, so it can be added to + * listeners and verified in testing + * + * @param remoteServiceName + */ + public void registerRemoteService(String remoteServiceName) { + registerRemoteService(remoteServiceName, null); + } + + public String getFullRemoteName(String name) { + if (!name.contains("@")) { + return String.format("%s@%s", name, remoteId); + } else { + return name; + } + } + + /** + * Registers a non Java service with mrl runtime, so it can be added to + * listeners and verified in testing + * + * @param remoteServiceName + * @param interfaces + */ + public void registerRemoteService(String remoteServiceName, ArrayList interfaces) { + String fullName = getFullRemoteName(remoteServiceName); + remoteServices.put(fullName, new RemoteService(this, fullName)); + + // Runtime.register(remoteId, remoteServiceName, "mock:mock", interfaces); + Runtime.register(remoteId, remoteServiceName, "Unknown", interfaces); + + + } + + /** + * Sends an asynchronous message with a slight delay so that testing for a + * callback publish can be done inline + * + * @param name + * @param method + */ + public void sendWithDelay(String name, String method) { + sendWithDelay(0, name, method); + } + + // + public String onToString() { + return toString(); + } + + public Message publishSendMessage(Message msg) { + return msg; + } + + public Message publishReceiveMessage(Message msg) { + return msg; + } + + public static void main(String[] args) { + try { + + LoggingFactory.setLevel("WARN"); + + WebGui webgui = (WebGui) Runtime.create("webgui", "WebGui"); + webgui.autoStartBrowser(false); + webgui.startService(); + + // starts a mocking gateway with default id instance + MockGateway gateway = (MockGateway) Runtime.start("gateway", "MockGateway"); + + Clock clock = (Clock) Runtime.start("clock", "Clock"); + + clock.addListener("publishTime", "mocker"); + + // mocker.send("clock", "startClock"); + // first click is after 1 second + + // mocker.send("clock", "StartClock"); + // gateway.sendWithDelay("clock", "startClock"); + // gateway.sendWithDelay(0, "clock", "startClock"); + Message msg = gateway.waitForMsg("mocker@mockid", "onTime", 1100); + log.info("message {}", msg); + + } catch (Exception e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index e17cd987b9..7cf25153ef 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -1740,10 +1740,10 @@ public static synchronized Registration register(Registration registration) { 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(), Platform.getLocalInstance().getId(), registration.getTypeKey()); diff --git a/src/main/java/org/myrobotlab/service/config/MockGatewayConfig.java b/src/main/java/org/myrobotlab/service/config/MockGatewayConfig.java new file mode 100644 index 0000000000..481abecdac --- /dev/null +++ b/src/main/java/org/myrobotlab/service/config/MockGatewayConfig.java @@ -0,0 +1,6 @@ +package org.myrobotlab.service.config; + + +public class MockGatewayConfig extends ServiceConfig { + +} diff --git a/src/main/java/org/myrobotlab/service/meta/MockGatewayMeta.java b/src/main/java/org/myrobotlab/service/meta/MockGatewayMeta.java new file mode 100644 index 0000000000..e3bb9b1306 --- /dev/null +++ b/src/main/java/org/myrobotlab/service/meta/MockGatewayMeta.java @@ -0,0 +1,16 @@ +package org.myrobotlab.service.meta; + +import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.service.meta.abstracts.MetaData; +import org.slf4j.Logger; + +public class MockGatewayMeta extends MetaData { + private static final long serialVersionUID = 1L; + public final static Logger log = LoggerFactory.getLogger(MockGatewayMeta.class); + + public MockGatewayMeta() { + addDescription("Service for testing."); + addCategory("testing"); + } + +} diff --git a/src/main/resources/resource/MockGateway.png b/src/main/resources/resource/MockGateway.png new file mode 100644 index 0000000000..fb74a154a4 Binary files /dev/null and b/src/main/resources/resource/MockGateway.png differ diff --git a/src/main/resources/resource/Unknown.png b/src/main/resources/resource/Unknown.png new file mode 100644 index 0000000000..2a65350ca7 Binary files /dev/null and b/src/main/resources/resource/Unknown.png differ diff --git a/src/main/resources/resource/WebGui/app/service/js/MockGatewayGui.js b/src/main/resources/resource/WebGui/app/service/js/MockGatewayGui.js new file mode 100644 index 0000000000..33bada7cf1 --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/js/MockGatewayGui.js @@ -0,0 +1,36 @@ +angular.module("mrlapp.service.MockGatewayGui", []).controller("MockGatewayGuiCtrl", [ + "$scope", + "mrl", + function ($scope, mrl) { + console.info("MockGatewayGuiCtrl") + var _self = this + var msg = this.msg + + // GOOD TEMPLATE TO FOLLOW + this.updateState = function (service) { + $scope.service = service + } + + + this.onMsg = function (inMsg) { + let data = inMsg.data[0] + switch (inMsg.method) { + case "onState": + _self.updateState(data) + $scope.$apply() + break + case "onSendMessage": + const date = new Date(data) + $scope.onTime = date.toLocaleString() + $scope.$apply() + break + default: + console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) + break + } + } + + // msg.subscribe("publishSendMessage") + msg.subscribe(this) + }, +]) diff --git a/src/main/resources/resource/WebGui/app/service/js/UnknownGui.js b/src/main/resources/resource/WebGui/app/service/js/UnknownGui.js new file mode 100644 index 0000000000..410edd27d9 --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/js/UnknownGui.js @@ -0,0 +1,33 @@ +angular.module("mrlapp.service.UnknownGui", []).controller("UnknownGuiCtrl", [ + "$scope", + "mrl", + function ($scope, mrl) { + console.info("UnknownGuiCtrl") + 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 + default: + console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method) + break + } + } + + msg.subscribe(this) + }, +]) diff --git a/src/main/resources/resource/WebGui/app/service/views/MockGatewayGui.html b/src/main/resources/resource/WebGui/app/service/views/MockGatewayGui.html new file mode 100644 index 0000000000..f4f7f52fd7 --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/views/MockGatewayGui.html @@ -0,0 +1,24 @@ +
+ + + + + + + + + + + + + + + + + + + + + +
tssendernamemethoddata
{{msg.msgId}}{{msg.sender}} {{msg.name}}[{{msg.method}}]{{msg.data}}
+
diff --git a/src/main/resources/resource/WebGui/app/service/views/UnknownGui.html b/src/main/resources/resource/WebGui/app/service/views/UnknownGui.html new file mode 100644 index 0000000000..c026231c57 --- /dev/null +++ b/src/main/resources/resource/WebGui/app/service/views/UnknownGui.html @@ -0,0 +1,3 @@ +
+

unknown type

+