From 84325886aed13863dc35cff9038e632ace5a55e3 Mon Sep 17 00:00:00 2001 From: dave Date: Sun, 17 Nov 2024 09:03:18 +0000 Subject: [PATCH] interim commit --- java/embeddedJavaDeviceUI/README.md | 22 ++++--- java/embeddedJavaDeviceUI/pom.xml | 18 ++++-- .../devicedemo/optional/TcJettyWebServer.java | 59 ++++++++----------- .../optional/TcJettyWebSocketEndpoint.java | 40 +++++++------ .../src/main/java/module-info.java | 8 +-- .../target/classes/application.properties | 2 +- 6 files changed, 75 insertions(+), 74 deletions(-) diff --git a/java/embeddedJavaDeviceUI/README.md b/java/embeddedJavaDeviceUI/README.md index 717a2a4..b7fa668 100644 --- a/java/embeddedJavaDeviceUI/README.md +++ b/java/embeddedJavaDeviceUI/README.md @@ -1,14 +1,25 @@ # EmbeddedJavaDemo menu application -This application is an example of how to use tcMenu's EmbedControl to produce a basic user interface that would run on an embedded device such as a raspberry PI. The application is composed of objects that are wired together in the main class. We also include a really cut down dependency framework that is fully JPMS compliant and very small. Should you wish to, you can use our very light weight database objects that are also used within tcMenu itself. +This application is the starting point for building an embedded Java TcMenu application. It is based upon tcMenu's EmbedControl libraries that provide the user interface based on JavaFX. This can run on most Raspberry PI devices and other embedded Linux flavours too. The application as provided has considerable functionality, but you can choose yourself what to keep and what to remove. Embed Control core also includes a really cut down dependency framework that is fully JPMS compliant and very small. Should you wish to, you can use our very light weight database objects that are also used within tcMenu itself. We'll go through each of the components below. + +This app framework is somewhat opinionated, but if it doesn't match with what you need, you can always look at the Java API examples. ## How the app is organised. The application is split up into several files: +### Components required for operation + * `EmbeddedJavaDemoApp` - this is the class that starts up your application and initialises any plugins you selected. You should not touch this class as it is overwritten every time around. * `EmbeddedJavaDemoMenu` - this is the class that holds all the menu definitions and the menu tree. It is available in the spring context, and you can inject it into any of your components that need it. It also has short-cut methods to get every menu item. * `EmbeddedJavaDemoController` - this is where any callbacks that you register go, at the moment we support only one controller, in future we may provide support for more than one. Each function callback that you declare in TcMenu Designer will turn into a method in here. This also allows you to listen for any menu item, and for start and stop events. Further, you can change the controller's constructor to include other components if needed. +* `MenuConfig` - this is generally used to wire together all of your components. You can create additional components in this class by creating a function annotated with `@TcComponent`, any parameters to the function will be auto-wired using components already available in the context. + +### Components that are optional + +* `JfxLocalAutoUI` and `LocalTreeComponentManager` provide the local UI, it produces a panel that fills the entire window and represents the menu structure. +* `StatusPanelDrawable` demonstrates how to override drawing with a custom arranged grid for the status submenu. It demonstrates custom drawing too. +* `TcJettyWebServer` and `TcJettyWebSocketEndpoint` both configure and serve up a website that contains EmbedControlJS, which can produce a lightweight remote control application in the browser. ## Building the app @@ -23,12 +34,5 @@ By default, the app uses maven to build, you'll need a couple of things installe If you use the standard maven setup, after running the above build steps, you should see the following directory has been created: `target/jfx/` containing an `app` directory and a `deps` directory. We recommend running the application from the `target/jfx/app` directory. -If you used a modular build (IE you have a `module-info.java` file in the `src/main/java` directory) then to run the application ensure that the right version of Java using `java -version` is on your path and then the run command should be `java --module-path ../deps "-Dprism.lcdtext=false" --add-modules com.thecoderscorner.menuexample.embeddedjavademo com.thecoderscorner.menu.devicedemo.EmbeddedJavaDemoApp` - -## JavaFx local UI - -In the optional folder, there's a couple of classes that will generate a JavaFX UI automatically for your UI. These are documented within the folder. - -## Web server and TagVal server +If you used a modular build (IE you have a `module-info.java` file in the `src/main/java` directory) then to run the application ensure that the right version of Java using `java -version` is on your path and then the run command should be `java --module-path ../deps "-Dprism.lcdtext=false" --add-modules com.thecoderscorner.menuexample.embeddedjavademo com.thecoderscorner.menu.devicedemo.EmbeddedJavaDemoApp`. Given TcMenu is JPMS compliant, it can be packaged using `jpackage`. -There's both a webserver that serves up [EmbedControlJS](https://github.com/TcMenu/embedcontrolJS) to a browser, and also TagVal support too, so you connect to it from the API. diff --git a/java/embeddedJavaDeviceUI/pom.xml b/java/embeddedJavaDeviceUI/pom.xml index faa96b0..3f26866 100644 --- a/java/embeddedJavaDeviceUI/pom.xml +++ b/java/embeddedJavaDeviceUI/pom.xml @@ -38,7 +38,8 @@ @@ -57,14 +58,14 @@ org.eclipse.jetty jetty-server - 10.0.23 + 12.0.15 - org.eclipse.jetty.websocket - websocket-javax-server - 10.0.23 + org.eclipse.jetty.ee10.websocket + jetty-ee10-websocket-jetty-server + 12.0.15 org.slf4j @@ -189,4 +190,11 @@ + + + maven_central + Maven Central + https://repo.maven.apache.org/maven2/ + + \ No newline at end of file diff --git a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebServer.java b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebServer.java index 459d535..8dae028 100644 --- a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebServer.java +++ b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebServer.java @@ -8,18 +8,17 @@ import com.thecoderscorner.menu.remote.commands.MenuCommand; import com.thecoderscorner.menu.remote.protocol.CommandProtocol; import com.thecoderscorner.menu.remote.protocol.ProtocolHelper; -import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.HandlerList; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.ResourceHandler; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; +import org.eclipse.jetty.server.handler.gzip.GzipHandler; +import org.eclipse.jetty.util.resource.ResourceFactory; +import org.eclipse.jetty.websocket.api.Callback; +import org.eclipse.jetty.websocket.api.Session; -import javax.websocket.Session; -import java.io.IOException; import java.nio.ByteBuffer; import java.time.Clock; import java.util.List; @@ -61,32 +60,22 @@ public void start(NewServerConnectionListener listener) { server.addConnector(serverConnector); var staticHandler = new ResourceHandler(); - staticHandler.setDirectoriesListed(listDirectories); - staticHandler.setWelcomeFiles(new String[]{"index.html"}); - staticHandler.setBaseResource(Resource.newResource(resourceDirectory)); + staticHandler.setDirAllowed(listDirectories); + staticHandler.setWelcomeFiles("index.html"); + staticHandler.setBaseResource(ResourceFactory.of(serverConnector).newResource(resourceDirectory)); var contextHandler = new ContextHandler(); contextHandler.setHandler(staticHandler); - webSockEndpoint = new TcJettyWebSocketEndpoint(); - - // Create a ServletContextHandler with the given context path. - var serverHandler = new ServletContextHandler(server, "/"); - // Initialize javax.websocket layer - JavaxWebSocketServletContainerInitializer.configure(serverHandler, (servletContext, wsContainer) -> - { - // This lambda will be called at the appropriate place in the - // ServletContext initialization phase where you can initialize - // and configure your websocket container. + ServletContextHandler static_handler = new ServletContextHandler(); + static_handler.insertHandler(new GzipHandler()); + static_handler.setContextPath("/"); + static_handler.setBaseResourceAsString(resource_base); - // Configure defaults for container - wsContainer.setDefaultMaxTextMessageBufferSize(65535); - // Add WebSocket endpoint to javax.websocket layer - wsContainer.addEndpoint(TcJettyWebSocketEndpoint.class); - }); + webSockEndpoint = new TcJettyWebSocketEndpoint(); - var handlers = new HandlerList(); - handlers.setHandlers(new Handler[]{contextHandler, serverHandler}); + var handlers = new ContextHandlerCollection(); + handlers.addHandler(contextHandler); server.setHandler(handlers); server.start(); @@ -151,8 +140,8 @@ public void closeConnection() { try { session.close(); if (connectionListener.get() != null) connectionListener.get().accept(this, false); - } catch (IOException e) { - logger.log(System.Logger.Level.ERROR, "Close on session failed ", session.getId()); + } catch (Exception e) { + logger.log(System.Logger.Level.ERROR, "Close on session failed ", session); } } @@ -169,20 +158,20 @@ public long lastTransmittedHeartbeat() { @Override public void sendCommand(MenuCommand command) { lastMsgOut.set(clock.millis()); - logger.log(System.Logger.Level.DEBUG, session.getId() + " - " + command); + logger.log(System.Logger.Level.DEBUG, session + " - " + command); try { synchronized (socketLock) { if(protocol.getProtocolForCmd(command) == CommandProtocol.TAG_VAL_PROTOCOL) { String text = protocolHelper.protoBufferToText(command); - session.getBasicRemote().sendText(text); + session.sendText(text, Callback.NOOP); } else { protocol.toChannel(outputBuffer, command); outputBuffer.flip(); - session.getBasicRemote().sendBinary(outputBuffer); + session.sendBinary(outputBuffer, Callback.NOOP); } } } catch (Exception e) { - logger.log(System.Logger.Level.ERROR, "Socket failed to write - " + session.getId(), e); + logger.log(System.Logger.Level.ERROR, "Socket failed to write - " + session, e); closeConnection(); } } @@ -214,7 +203,7 @@ public String getUserName() { @Override public String getConnectionName() { - return String.format("JettyWS %s as %s", session.getId(), getUserName()); + return String.format("JettyWS %s as %s", session, getUserName()); } public void stringDataRx(String data) { @@ -223,7 +212,7 @@ public void stringDataRx(String data) { protocolHelper.dataReceived(this, data); } catch (Exception e) { closeConnection(); - logger.log(System.Logger.Level.ERROR, "Problem while reading data from " + session.getId(), e); + logger.log(System.Logger.Level.ERROR, "Problem while reading data from " + session, e); } } diff --git a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebSocketEndpoint.java b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebSocketEndpoint.java index 23b99df..eba858d 100644 --- a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebSocketEndpoint.java +++ b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebSocketEndpoint.java @@ -2,60 +2,64 @@ import com.thecoderscorner.menu.mgr.MenuManagerServer; import com.thecoderscorner.menu.mgr.ServerConnection; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.*; -import javax.websocket.*; -import javax.websocket.server.ServerEndpoint; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static java.lang.System.Logger.Level.*; -@ServerEndpoint(value = "/ws") +@WebSocket public class TcJettyWebSocketEndpoint { private static final Map connectionsBySession = new ConcurrentHashMap<>(); private static final System.Logger logger = System.getLogger(MenuManagerServer.class.getSimpleName()); - @OnOpen + @OnWebSocketOpen public void onOpen(Session session) { TcJettyWebServer server = TcJettyWebServer.getInstance(); var newSession = new TcJettyWebServer.TcJettyWebSocketConnection(session, server.getClock(), server.getProtocol()); - connectionsBySession.put(session.getId(), newSession); - logger.log(INFO, "Creating new session for ID " + session.getId()); + connectionsBySession.put(sessionId(session), newSession); + logger.log(INFO, "Creating new session for ID " + session); server.getListener().connectionCreated(newSession); } - @OnMessage + @OnWebSocketMessage public void onMessage(Session session, String message) { - var connection = connectionsBySession.get(session.getId()); - logger.log(DEBUG, "Message Received on " + session.getId() + " message = " + message); + var connection = connectionsBySession.get(sessionId(session)); + logger.log(DEBUG, "Message Received on " + session + " message = " + message); if (connection != null) { connection.stringDataRx(message); } } - @OnClose + @OnWebSocketClose public void onClose(Session session) { - var con = connectionsBySession.get(session.getId()); + var con = connectionsBySession.get(sessionId(session)); if (con != null) { - logger.log(INFO, "Close of session " + session.getId()); + logger.log(INFO, "Close of session " + sessionId(session)); con.socketDidClose(); - connectionsBySession.remove(session.getId()); + connectionsBySession.remove(sessionId(session)); } else { - logger.log(INFO, "Close of session with no reference " + session.getId()); + logger.log(INFO, "Close of session with no reference " + sessionId(session)); } } - @OnError + @OnWebSocketError public void onError(Session session, Throwable throwable) { - logger.log(ERROR, "Error on session " + session.getId(), throwable); - var con = connectionsBySession.get(session.getId()); + logger.log(ERROR, "Error on session " + sessionId(session), throwable); + var con = connectionsBySession.get(sessionId(session)); if (con != null) { con.closeConnection(); - connectionsBySession.remove(session.getId()); + connectionsBySession.remove(sessionId(session)); } } + String sessionId(Session session) { + return session.getRemoteSocketAddress().toString(); + } + public List getAllConnections() { return List.copyOf(connectionsBySession.values()); } diff --git a/java/embeddedJavaDeviceUI/src/main/java/module-info.java b/java/embeddedJavaDeviceUI/src/main/java/module-info.java index bee6187..f857796 100644 --- a/java/embeddedJavaDeviceUI/src/main/java/module-info.java +++ b/java/embeddedJavaDeviceUI/src/main/java/module-info.java @@ -1,7 +1,5 @@ module com.thecoderscorner.menuexample.embeddedjavademo { - requires java.logging; requires java.prefs; - requires java.desktop; requires com.google.gson; requires com.fazecast.jSerialComm; requires com.thecoderscorner.tcmenu.javaapi; @@ -13,8 +11,6 @@ exports com.thecoderscorner.menu.devicedemo.optional; opens com.thecoderscorner.menu.devicedemo; - requires org.eclipse.jetty.server; - requires org.eclipse.jetty.servlet; - requires jetty.websocket.api; - requires org.eclipse.jetty.websocket.javax.server; + requires org.eclipse.jetty.websocket.api; + requires org.eclipse.jetty.ee10.servlet; } diff --git a/java/embeddedJavaDeviceUI/target/classes/application.properties b/java/embeddedJavaDeviceUI/target/classes/application.properties index 2439e69..022bb6c 100644 --- a/java/embeddedJavaDeviceUI/target/classes/application.properties +++ b/java/embeddedJavaDeviceUI/target/classes/application.properties @@ -2,7 +2,7 @@ build.version=0.0.1-SNAPSHOT build.groupId=com.thecoderscorner.menuexample build.artifactId=embeddedJavaDeviceUI -build.timestamp=2024-11-09T17:04:31Z +build.timestamp=2024-11-16T08:51:43Z # server name properties server.name=Embedded Java Demo