Skip to content

Commit

Permalink
interim commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dave committed Nov 17, 2024
1 parent 8df4b56 commit 8432588
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 74 deletions.
22 changes: 13 additions & 9 deletions java/embeddedJavaDeviceUI/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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.
18 changes: 13 additions & 5 deletions java/embeddedJavaDeviceUI/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
<!--
!!!!!!!JavaFX - Special notes!!!!!!
If you are using BellSoft Liberica Full JDK leave the below scopes set to "test"
If you are using another JDK without JavaFX, comment out the scope for each org.openjfx component.
If you are using another JDK without JavaFX, then simply add <scope>test</scope> to each
java fx dependency.
-->

<dependency>
Expand All @@ -57,14 +58,14 @@
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>10.0.23</version>
<version>12.0.15</version>
</dependency>

<!-- To run javax.websocket in embedded server -->
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-javax-server</artifactId>
<version>10.0.23</version>
<groupId>org.eclipse.jetty.ee10.websocket</groupId>
<artifactId>jetty-ee10-websocket-jetty-server</artifactId>
<version>12.0.15</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
Expand Down Expand Up @@ -189,4 +190,11 @@
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>maven_central</id>
<name>Maven Central</name>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
</repositories>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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();
}
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, TcJettyWebServer.TcJettyWebSocketConnection> 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<ServerConnection> getAllConnections() {
return List.copyOf(connectionsBySession.values());
}
Expand Down
8 changes: 2 additions & 6 deletions java/embeddedJavaDeviceUI/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 8432588

Please sign in to comment.