Skip to content

Commit

Permalink
Initial public version
Browse files Browse the repository at this point in the history
  • Loading branch information
davetcc committed Nov 9, 2024
1 parent 699bd95 commit d87f1ed
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 47 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,18 @@

## Java examples

All the examples are set up to work with the latest version of Java `OpenJDK` and `maven` build tool. You can use more or less any OpenJDK distribution, but we tend to use Adoptium at the moment.
### Setting up a Java build

For development we recommend IntelliJ, either edition of it should be fine to get started with. To build on the command line, ensure that you have the most recent OpenJDK and a recent version of maven on your system, without these it will not be possible to build.

* All OpenJDK's that we've tested work for this, we've tried: Liberica, Adoptium, Amazon Corretto and Microsoft JDK.
* For Apache maven we recommend using [https://maven.apache.org/]

Using git or zip download, get the contents of the tcMenu repository locally, for example:

git clone https://github.com/TcMenu/tcmenu-examples-starters.git

Once you've set up the above you can start with any of the following projects:

* embedControlJavaFX: A starting point for deploying a desktop control and monitoring UI app that works on many platforms.
* embeddedJavaDeviceUI: A starting point embedded control application for embedded boards that can run JavaFX.
Expand Down
22 changes: 20 additions & 2 deletions java/embedControlJavaFx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,28 @@

Contained in this package is the source code for the complete user interface that connects to a remote device running tcMenu, as long as the board is using one of the supported Serial or Network based plugins.

Before releasing a package based on this we recommend that you change the icons and naming.
Before releasing a package based on this we recommend that you change the icons and naming, especially if releasing publicly.

## Building a packaged version

This is a Java application, and builds using the maven build system.
The below will produce you a package that can be used on most platforms supported by Java. It will use the [Java SDK jpackage tool](https://docs.oracle.com/en/java/javase/21/docs/specs/man/jpackage.html) to build a native image that can be installed onto a user system.

### Building the embedCONTROLFx desktop UI for Windows

Ensure you are in the embedCONTROLFx/target directory. This will produce an executable that can be run directly on Windows, depending on the options you chose, it will either generate an MSI installer, self installing EXE, or just an executable.

cp classes/fximg/embedCONTROL.ico .

jpackage --type app-image -n embedCONTROL -p jfx/deps --input jfx/app --resource-dir .\classes\fximg\ --icon embedCONTROL.ico --app-version 4.4.0 --verbose --java-options "-Dprism.lcdtext=false" --add-modules "jdk.crypto.cryptoki" -m com.thecoderscorner.tcmenu.embedcontrolfx/com.thecoderscorner.embedcontrol.jfxapp.EmbedControlApp

### Building the embedCONTROLFx desktop for macOS

Ensure you are in the embedCONTROLFx/target directory. This will produce a disk image in the regular macOS install format. You can also choose to create an install package or just an executable.

jpackage -n embedCONTROL -p jfx/deps --input jfx/app --icon ./classes/fximg/MyIcon.icns --vendor TheCodersCorner --app-version 4.4.0 --verbose --license-file ../../LICENSE --java-options "-Dprism.lcdtext=false" --add-modules "jdk.crypto.cryptoki" -m com.thecoderscorner.tcmenu.embedcontrolfx/com.thecoderscorner.embedcontrol.jfxapp.EmbedControlApp

### Building the embedCONTROLFx desktop for Linux

Ensure you are in the embedCONTROLFx/target directory. This will create a native package depending on which Linux you are using. It has been tested on Ubuntu and also ArmArch64 NOMone.

jpackage -n embedCONTROL -p jfx/deps --input jfx/app --icon ./classes/fximg/large_icon.png --verbose --license-file ../../LICENSE --linux-app-category Utility --linux-menu-group "Utility;" --java-options "-Dprism.lcdtext=false" --app-version 4.4.0 --add-modules "jdk.crypto.cryptoki" -m com.thecoderscorner.tcmenu.embedcontrolfx/com.thecoderscorner.embedcontrol.jfxapp.EmbedControlApp
6 changes: 3 additions & 3 deletions java/javaApiExamples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ Various demonstrations of how to use the Java API in your applications. Kept as

## Examples for when the API is acting as a device

These examples cover the cases where the API is acting as a device, for example an embedded Raspberry PI.
These examples cover the cases where the API is acting as a `device`, for example a Raspberry PI or other Linux system embedded into a device.

* An example showing a device that tries to connect to client connections - `DeviceWithClientConnectionExample.java`
* This code allows you to make AES encryption keys `MakeEncryptionKeyExample.java`
* An example showing a `device` that tries to connect to the `API` which is accepting. As a `device` we would be responsible for bootstrapping and hosting the menu items - `DeviceWithClientConnectionExample.java`
* This code allows you to make AES encryption keys that work with the other examples or elsewhere `MakeEncryptionKeyExample.java`

## Examples where the API is running remotely connecting to a device

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*
* See DeviceWithClientConnectionExample for the other side of this example
*/
public class ClientThatAcceptsForRemoteExample {
public class APIClientThatAcceptsForRemoteDevice {
private final static System.Logger logger = System.getLogger("ExampleClient");

// the port on which we will accept.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
* as an example of where to start with the Java API. This example creates a connection to a socket server, this is the
* default case.
*/
public class ConnectToRemoteDeviceServerExample implements RemoteControllerListener {
public class APIConnectSocketToRemoteDevice implements RemoteControllerListener {
// where we want to connect, host and port
private static final String MY_HOST = "192.168.0.96";
private static final int MY_PORT = 3333;
Expand All @@ -44,11 +44,11 @@ public class ConnectToRemoteDeviceServerExample implements RemoteControllerListe
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(4);

public static void main(String[] args) {
var server = new ConnectToRemoteDeviceServerExample();
var server = new APIConnectSocketToRemoteDevice();
server.start();
}

public ConnectToRemoteDeviceServerExample() {
public APIConnectSocketToRemoteDevice() {
// here we create the connection and controller using the builder.
var builder = new SocketControllerBuilder()
.withAddress(MY_HOST).withPort(MY_PORT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*
*/

package com.thecoderscorner.menu.examples;
package com.thecoderscorner.menu.examples.client;

import com.thecoderscorner.menu.domain.MenuItem;
import com.thecoderscorner.menu.domain.state.MenuTree;
Expand Down Expand Up @@ -91,6 +91,11 @@ public void start() {
}
}

/**
* This class implements the `RemoteControllerListener` interface and as such is made aware of events from the
* connector layer, and also told when we've managed to bootstrap successfully or when items change. The controller
* takes care of the menu tree, which it builds during the device bootstrap.
*/
private class MyRemoteListener implements RemoteControllerListener {
@Override
public void menuItemChanged(MenuItem item, boolean valueOnly) {
Expand Down Expand Up @@ -125,6 +130,9 @@ public void connectionState(RemoteInformation remoteInformation, AuthStatus conn

@Override
public void ackReceived(CorrelationId key, MenuItem item, AckStatus st) {
// This is called when an update or other event is acknowledged. It provides back the same correlation ID
// for acknowledgement that the API generated during send. Whenever you call send on the controller, you're
// provided by return with a correlation.
logger.log(INFO, "Ack -" + key + " item " + item + " status " + st);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.thecoderscorner.menu.examples.server;

import com.thecoderscorner.menu.auth.PreDefinedAuthenticator;
import com.thecoderscorner.menu.domain.util.DomainFixtures;
import com.thecoderscorner.menu.mgr.MenuManagerServer;
import com.thecoderscorner.menu.remote.mgrclient.ClientBasedConnectionManager;
import com.thecoderscorner.menu.remote.mgrclient.SocketServerConnectionManager;
import com.thecoderscorner.menu.remote.protocol.ConfigurableProtocolConverter;

import java.time.Clock;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

/**
* This example shows how to create a Java device server, IE where this code would reside as an application on an
* embedded device such as a Raspberry PI. This example will accept connections in TagVal format from API clients.
* It takes a port to accept on, and will hold connections open until connection failure.
*/
public class DeviceSocketServerAcceptingConnections {
// the port on which we'll listen for connections.
public final static int MY_PORT = 3333;
// the name of this device, sent during join
public static final String MY_SERVER_NAME = "ClientDevExample";
// the UUID of this device, sent during join
public static final UUID MY_SERVER_UUID = UUID.fromString("A2BA2013-E17A-4551-8462-A0F6D15968AC");

public static void main(String[] args) {
// set up logging
ConsoleHandler handler = new ConsoleHandler();
handler.setFormatter(new SimpleFormatter());
handler.setLevel(Level.FINEST);
Logger.getLogger("").addHandler(handler);
Logger.getLogger("").setLevel(Level.FINEST);

// this protocol handler is capable of handling all regular TagVal messages, and you can even add your own extras
var protocol = new ConfigurableProtocolConverter(true);
// the clock in system UTC time, you can use any implementation.
var clock = Clock.systemUTC();
// the thread pool that we'll use to handle connections.
var executor = Executors.newScheduledThreadPool(4);

// create the connection logic, in this case we are a device that connects to an "accepting" client.
var deviceRemote = new SocketServerConnectionManager(protocol, executor, MY_PORT, clock, 15000);

// create a basic tree that we'll send remotely during bootstrap.
var tree = DomainFixtures.fullEspAmplifierTestTree();

// now we create the menu manager object, it is responsible for handling remote connections and bootstrapping
// clients that connect.
var menuManager = new MenuManagerServer(executor, tree, MY_SERVER_NAME, MY_SERVER_UUID, new PreDefinedAuthenticator(true), clock);
menuManager.addConnectionManager(deviceRemote);
// you can provide an implementation that returns the serial number of the board
menuManager.setBoardSerialProvider(() -> 1234);

menuManager.start();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,10 @@

import com.thecoderscorner.menu.auth.PreDefinedAuthenticator;
import com.thecoderscorner.menu.domain.util.DomainFixtures;
import com.thecoderscorner.menu.domain.state.MenuTree;
import com.thecoderscorner.menu.mgr.MenuInMenu;
import com.thecoderscorner.menu.mgr.MenuManagerServer;
import com.thecoderscorner.menu.remote.ConnectMode;
import com.thecoderscorner.menu.remote.LocalIdentifier;
import com.thecoderscorner.menu.remote.RemoteConnector;
import com.thecoderscorner.menu.remote.encryption.AESEncryptionHandlerFactory;
import com.thecoderscorner.menu.remote.mgrclient.ClientBasedConnectionManager;
import com.thecoderscorner.menu.remote.protocol.ConfigurableProtocolConverter;
import com.thecoderscorner.menu.remote.socket.SocketBasedConnector;

import java.time.Clock;
import java.util.UUID;
Expand All @@ -26,59 +20,39 @@
* See ClientThatAcceptsForRemoteExample for the other side of this example.
*/
public class DeviceWithClientConnectionExample {
// where we want to connect to both host and port
// where we want to connect to (both host and port)
public static final String MY_HOST = "localhost";
public static final int MY_PORT = 3333;
// the name that we'll provide when we join
public static final String MY_SERVER_NAME = "ClientDevExample";
// the UUID of this device
public static final UUID MY_SERVER_UUID = UUID.fromString("A2BA2013-E17A-4551-8462-A0F6D15968AC");
// the encryption key the API will use
public static final String ENCRYPTED_AES_KEY = "A8UvLzdTzUCYeqir6DODRquIbch04kN1EuyocNqoJI4=";
private static final String MENU_IN_MENU_REMOTE = "192.168.0.222";
private static final int MENU_IN_MENU_PORT = 3333;

public static void main(String[] args) {
// First we create a few objects that are needed by both the remote connection logic and the menu manager.
// this protocol handler is capable of handling all regular TagVal messages, and you can even add your own extras
var protocol = new ConfigurableProtocolConverter(true);
// the clock in system UTC time, you can use any implementation.
var clock = Clock.systemUTC();
// the thread pool that we'll use to handle connections.
var executor = Executors.newScheduledThreadPool(4);
// an encryption factory, you can use `NoEncryptionFactory` too if not needed.
var aesEncryptionFactory = new AESEncryptionHandlerFactory(ENCRYPTED_AES_KEY);

// create the connection logic, in this case we are a device that connects to an "accepting" client.
var deviceRemote = new ClientBasedConnectionManager(MY_HOST, MY_PORT, protocol, clock, executor, aesEncryptionFactory);

// create a basic menu manager, in any menu app we'd normally have one of these.
// create a basic tree that we'll send remotely during bootstrap.
var tree = DomainFixtures.fullEspAmplifierTestTree();

// now we create the menu manager object, it is responsible for handling remote connections and bootstrapping
// clients that connect.
var menuManager = new MenuManagerServer(executor, tree, MY_SERVER_NAME, MY_SERVER_UUID, new PreDefinedAuthenticator(true), clock);
menuManager.addConnectionManager(deviceRemote);
// you can provide an implementation that returns the serial number of the board
menuManager.setBoardSerialProvider(() -> 1234);

// start optional menu in menu set up.

// optionally we can now add one or more menu-in-menu structures. See here for more information:
// https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/tcmenu-iot/java-menu-in-menu/

// first we create a remote connector that will read the source menu and receive updates as it changes.
RemoteConnector connector = new SocketBasedConnector(
new LocalIdentifier(MY_SERVER_UUID, MY_SERVER_NAME),
executor, clock, protocol, MENU_IN_MENU_REMOTE, MENU_IN_MENU_PORT,
ConnectMode.FULLY_AUTHENTICATED, null
);

// now we create a menu in menu item that puts all the items in the "source" menu into our menu at an offset.
var menuInMenu = new MenuInMenu(connector, menuManager, MenuTree.ROOT,
MenuInMenu.ReplicationMode.REPLICATE_SILENTLY,
50100, 55000);

// now we start the menu in menu configuration, it puts the "source" menu structure into our tree and keeps it
// in sync in both directions.
menuInMenu.start();

// end optional menu in menu setup.

// this finally spins up the example
menuManager.start();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

/**
* This example shows how to make an encryption key pair, use them, serialise them, and create a tcMenuApi protocol
* encryption handler from it too.
* encryption handler from it too. The encryption keys are 256 bits in length using AES format.
*/
public class MakeEncryptionKeyExample {
public static void main(String[] args) throws Exception {
Expand Down
8 changes: 7 additions & 1 deletion java/javaApiWebsocketServer/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
WebSocket Demo
## Very basic Java Websocket example

This example shows how to use [Java-WebSocket](https://github.com/TooTallNate/Java-WebSocket) with TcMenu API to provide the "device" side of a websocket connection. Websocket connections are normally used with [EmbedControlJS](https://github.com/TcMenu/embedcontrolJS) to provide the API connection in a browser environment.

This is for the case where the websocket is separate to the hosting, IE you for example are using Apache or other webserver to host the static application files, and want to handle the websocket connection separately.

This is often very useful during development of the JavaScript application, as it allows you to run a simple "device" on your machine that the application can connect to.
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,44 @@
public class SimpleWebSocketExample {

public static void main(String[] args) {
// enable logging onto the console.
ConsoleHandler handler = new ConsoleHandler();
handler.setFormatter(new SimpleFormatter());
handler.setLevel(Level.FINEST);
Logger.getLogger("").addHandler(handler);
Logger.getLogger("").setLevel(Level.FINEST);

// now build a tree that the menu manager will manage.
var tree = DomainFixtures.fullEspAmplifierTestTree();
// and an executor, simple single thread one is enough for this.
var executor = Executors.newSingleThreadScheduledExecutor();
// and a clock, we choose default timezone.
var clock = Clock.systemDefaultZone();
// this protocol handler can deal with all TagVal messages, you can even add your own custom ones too.
MenuCommandProtocol tagValProtocol = new ConfigurableProtocolConverter(true);
// now create the menu manager, it manages all remotes, bootstrapping API connections etc.
var menuManager = new MenuManagerServer(executor, tree,
"WS Test", UUID.randomUUID(),
new PropertiesAuthenticator("./auth.properties", new NoDialogFacilities()),
clock);
// then we add the websocket and regular endpoints to it, it can handle both regular socket connections and
// also websocket connections.
menuManager.addConnectionManager(new WebSocketServerConnectionManager(tagValProtocol, 3333, clock));
menuManager.addConnectionManager(new SocketServerConnectionManager(tagValProtocol, executor, 3334, clock, 15000));
// fire it up so we can connect
menuManager.start();

// an example of updating a menu item, in this case a list.
var menuList = menuManager.getManagedMenu().getMenuById(21).orElseThrow();
menuManager.updateMenuItem(menuManager, menuList, List.of("salad", "pasta", "pizza"));

// if you want to send simulated updates when running set VM option -DsendSimulatedUpdates=true
if(Boolean.getBoolean("sendSimulatedUpdates")) {

// update the list item randomly every 5 seconds.
executor.scheduleAtFixedRate(() -> menuManager.updateMenuItem(menuManager, menuList, randomListData()), 5000, 5000, TimeUnit.MILLISECONDS);

// now update a few analog items randomly.
executor.scheduleAtFixedRate(() -> {
if (menuManager.isAnyRemoteConnection()) return;
var menuVolume = (AnalogMenuItem) tree.getMenuById(1).orElseThrow();
Expand Down

0 comments on commit d87f1ed

Please sign in to comment.