Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
# Conflicts:
#	README.md
  • Loading branch information
dave committed Nov 9, 2024
2 parents 038c705 + 7f0102c commit 699bd95
Show file tree
Hide file tree
Showing 12 changed files with 640 additions and 3 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Build

on:
push:
pull_request:
branches: [ "main" ]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '23'
distribution: 'liberica'
cache: maven

- name: Build EmbedControl
run: mvn -B install -Dgpg.skip=true --file java/embedControlJavaFx/pom.xml

- name: Build EmbeddedUi
run: mvn -B install -Dgpg.skip=true --file java/embeddedJavaDeviceUI/pom.xml

- name: Build Examples
run: mvn -B install -Dgpg.skip=true --file java/javaApiExamples/pom.xml

- name: Build Websocket Example
run: mvn -B install -Dgpg.skip=true --file java/javaApiWebsocketServer/pom.xml
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# TcMenu examples and starter projects

[![Java Build](https://github.com/TcMenu/tcmenu-examples-starters/actions/workflows/build.yaml/badge.svg)](https://github.com/TcMenu/tcmenu-examples-starters/actions/workflows/build.yaml)
[![davetcc](https://img.shields.io/badge/davetcc-dev-blue.svg)](https://github.com/davetcc)
[![JSC TechMinds](https://img.shields.io/badge/JSC-TechMinds-green.svg)](https://www.jsctm.cz)

## License

* All examples are Apache licensed.
Expand Down
2 changes: 1 addition & 1 deletion java/embedControlJavaFx/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<jfx.version>23.0.1</jfx.version>
<jmetro.version>11.6.15</jmetro.version>
<springframework.version>6.0.11</springframework.version>
<java.api.version>4.4.0-SNAPSHOT</java.api.version>
<java.api.version>4.4.0</java.api.version>
</properties>

<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion java/embeddedJavaDeviceUI/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<properties>
<jfx.version>23.0.1</jfx.version>
<jserialcomm.version>2.11.0</jserialcomm.version>
<tcmenu.api.version>4.4.0-SNAPSHOT</tcmenu.api.version>
<tcmenu.api.version>4.4.0</tcmenu.api.version>
<timestamp>${maven.build.timestamp}</timestamp>
</properties>

Expand Down
17 changes: 17 additions & 0 deletions java/javaApiExamples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Various demonstrations of how to use the Java API in your applications. Kept as simple as possible so that you can quickly apply these in your solution.

## 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.

* 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`

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

These examples cover the API running remotely and connecting to a device.

* `ClientThatAcceptsForRemoteExample.java` an API "client" that accepts connections from a device, where the device provides the bootstrap and you can control the remote application.
* `ConnectToRemoteDeviceServerExample.java` connect to a remote device on a particular socket configuration.
* `StandaloneRs232Test.java` same as above but an RS232 example.

52 changes: 52 additions & 0 deletions java/javaApiExamples/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- it is safe to edit this file, it will not be replaced by TcMenu designer unless you delete it --><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.thecoderscorner.menuexample</groupId>
<artifactId>javaApiExamples</artifactId>
<name>javaApiExamples</name>
<description>Example usage of TcMenu/EmbedControl APIs</description>
<version>0.0.1-SNAPSHOT</version>

<properties>
<jserialcomm.version>2.11.0</jserialcomm.version>
<tcmenu.api.version>4.4.0</tcmenu.api.version>
<timestamp>${maven.build.timestamp}</timestamp>
</properties>

<dependencies>
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>${jserialcomm.version}</version>
</dependency>
<dependency>
<groupId>com.thecoderscorner.tcmenu</groupId>
<artifactId>tcMenuJavaAPI</artifactId>
<version>${tcmenu.api.version}</version>
</dependency>
<dependency>
<groupId>com.thecoderscorner.tcmenu</groupId>
<artifactId>embedCONTROLCore</artifactId>
<version>${tcmenu.api.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>

<build>
<finalName>javaApiExamples</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>22</source>
<target>22</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2016-2019 https://www.thecoderscorner.com (Dave Cherry).
* This product is licensed under an Apache license, see the LICENSE file in the top-level directory.
*
*/

package com.thecoderscorner.menu.examples;

import com.thecoderscorner.menu.domain.MenuItem;
import com.thecoderscorner.menu.domain.state.MenuTree;
import com.thecoderscorner.menu.remote.AuthStatus;
import com.thecoderscorner.menu.remote.RemoteControllerListener;
import com.thecoderscorner.menu.remote.RemoteInformation;
import com.thecoderscorner.menu.remote.RemoteMenuController;
import com.thecoderscorner.menu.remote.commands.AckStatus;
import com.thecoderscorner.menu.remote.commands.MenuDialogCommand;
import com.thecoderscorner.menu.remote.protocol.CorrelationId;
import com.thecoderscorner.menu.remote.socket.SocketControllerBuilder;

import java.util.Optional;
import java.util.UUID;

import static java.lang.System.Logger.Level.INFO;

/**
* An example showing the use of an RS232 based connection between a device and the API acting as a client. In this
* case the device acts as a server and the API acts as a client receiving the menu tree from the device. You can
* monitor and control the menu on the device over the serial connection. The protocol is quite lightweight and works
* from about 9600 baud upward depending on number of updates per second.
*/
public class StandaloneRs232Test {

private final static System.Logger logger = System.getLogger("StandaloneRs232Test");

// Before use change the UUID shown below. From jshell run UUID.randomUUID() to get a new one
private final UUID uuid = UUID.fromString("575d327e-fe76-4e68-b0b8-45eea154a126");

/**
* A MenuTree object represents a hierarchy of MenuItem's in a tree like format.
* We pass an empty one of these to the controller, and it populates them. Note that
* each controller manages exactly one MenuTree.
*/
private final MenuTree menuTree = new MenuTree();
private RemoteMenuController controller;

/**
* Just create an instance of the class to proceed.
*/
public static void main(String[] args) {
new StandaloneRs232Test().start();
}

/**
* Here we create the connection and register our listener.
*/
public void start() {

// This is the name that will appear on the Arduino side for this connection
String myName = "OfficeMac";

// Change this to the name of your serial port
String portName = "/dev/cu.usbmodemFD131";

// Change this to set the baud rate
int baud = 9600;

logger.log(INFO, "Creating an rs232 connection to {0} at {1} baud", portName, baud);

// Now we use the rs232 builder to make a suitably configured instance of a
// controller that can talk over serial and work with our menuTree.
controller = new SocketControllerBuilder()
.withAddress("192.168.0.22")
.withPort(3333)
.withMenuTree(menuTree)
.withLocalName(myName)
.withUUID(uuid)
//.withHeartbeatFrequency(10000000) // uncomment when debugging to prevent timeouts.
.build();

// now we simply add our remote listener (class definition below) and start up the comms.
controller.addListener(new MyRemoteListener());
controller.start();

// here you could do whatever tasks you'd normally perform..
while(!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

private class MyRemoteListener implements RemoteControllerListener {
@Override
public void menuItemChanged(MenuItem item, boolean valueOnly) {
logger.log(INFO, "Menu Item has changed: " + item);
}

@Override
public void treeFullyPopulated() {
logger.log(INFO, "Tree is fully populated");

// here we first traverse through all the submenus (even ROOT is a submenu)!
menuTree.getAllSubMenus().forEach(subMenu -> {
logger.log(INFO, "SubMenu {0} has the following child elements", subMenu);
// and then we go through all the items within that submenu.
menuTree.getMenuItems(subMenu).forEach(item -> logger.log(INFO, "----->>> " + item));
});

// how to get an item by its id, we look for ID 1 in the root menu, if it's there we log it and send
// a delta change command.
Optional<MenuItem> maybeItem = menuTree.getMenuById(1);
maybeItem.ifPresent( item -> {
logger.log(INFO, "Retrieved {0} by its ID {1}, change by 5", item.getName(), item.getId());
CorrelationId id = controller.sendDeltaUpdate(item, +5);
logger.log(INFO, "Correlation id was " + id);
});
}

@Override
public void connectionState(RemoteInformation remoteInformation, AuthStatus connected) {
logger.log(INFO, "Connection information: " + remoteInformation + ". Connected: " + connected);
}

@Override
public void ackReceived(CorrelationId key, MenuItem item, AckStatus st) {
logger.log(INFO, "Ack -" + key + " item " + item + " status " + st);
}

@Override
public void dialogUpdate(MenuDialogCommand cmd) {
// not interested in dialog updates.
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.thecoderscorner.menu.examples.client;

import com.thecoderscorner.menu.domain.state.MenuTree;
import com.thecoderscorner.menu.remote.RemoteMenuController;
import com.thecoderscorner.menu.remote.socket.SocketClientRemoteConnector;
import com.thecoderscorner.menu.remote.socket.SocketClientServerListener;
import com.thecoderscorner.menu.remote.socket.SocketControllerBuilder;

import java.io.IOException;
import java.lang.System.Logger.Level;
import java.util.Arrays;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.LogManager;

import static com.thecoderscorner.menu.remote.socket.SocketClientRemoteServer.UuidAndSerial;
import static java.util.logging.Level.FINEST;

/**
* This example is a client in that it receives menu state and can control menu applications running on a device, but
* however it is the accepting side of the connection. You can control the number of connections that you're prepared
* to accept, maybe in some cases like now to 1, but by default the acceptor will allow up to 99999 connections.
*
* See DeviceWithClientConnectionExample for the other side of this example
*/
public class ClientThatAcceptsForRemoteExample {
private final static System.Logger logger = System.getLogger("ExampleClient");

// the port on which we will accept.
public static final int MY_PORT = 3333;
private static final String MY_LOCAL_NAME = "Test Client";
private static final UUID MY_LOCAL_UUID = UUID.fromString("8A19E904-B007-498A-9BCB-5F0C0A7B9D71");
public static final String ENCRYPTED_AES_KEY = "A8UvLzdTzUCYeqir6DODRquIbch04kN1EuyocNqoJI4=";
public static final String ENCRYPTED_AES_IV = "PouIJPG+eN5WtbbGESuPeg==";

public static void main(String[] args) throws IOException {
// enable all logging including debug.
LogManager.getLogManager().getLogger("").setLevel(FINEST);
Arrays.stream(LogManager.getLogManager().getLogger("").getHandlers()).forEach(h -> h.setLevel(FINEST));

// here we minimally configure a socket accept client, that in this case will only accept one connection at once
// but you control that using withMaximumInstances.
var builder = new SocketControllerBuilder()
.withMaximumInstances(1)
.withPort(MY_PORT)
.withLocalName(MY_LOCAL_NAME)
.withUUID(MY_LOCAL_UUID)
.withAESEncryption(ENCRYPTED_AES_KEY, ENCRYPTED_AES_IV);
var deviceConnections = builder.buildClient();

// Now we add a listener that gets notified every time a connection is created or closed.
deviceConnections.addConnectionListener(new MyConnectionListener());

// start up the server, at this point it starts accepting connections.
deviceConnections.start();

//
// if you wanted to access device connections yourself, without using the below listener support
//

//var allConnections = deviceConnections.getConnections();

var maybeUuid = deviceConnections.getFirstConnectionWithUUID(UUID.randomUUID());
maybeUuid.ifPresent(connector -> logger.log(Level.INFO, "Connector for my UUID is " + connector));

var maybeUuidSerial = deviceConnections.getConnection(UUID.randomUUID(), 1234);
maybeUuidSerial.ifPresent(connector -> logger.log(Level.INFO, "Connector is " + connector));
}

/**
* This is an example of how you'd process connections as they come in, you get notified when there's a new connection
* and when one is closed. In most cases you'd want a RemoteMenuController, unless you wanted custom control over the
* protocol yourself. You could create your own object here that managed the connection using the methods available
* on either the connector or the controller.
*/
private static class MyConnectionListener implements SocketClientServerListener {
private final Map<UuidAndSerial, RemoteMenuController> remoteMenuControllers = new ConcurrentHashMap<>();
@Override
public void onConnectionCreated(SocketClientRemoteConnector connector) {
logger.log(Level.INFO, "Connection has been received from " + connector.getRemoteParty());
// here you could create an object that represented your side of the connection etc.
// in this case I just create a remote controller that handles the bootstrapping and update logic
// Creating and starting a remote controller also starts the underlying connection.
var controller = new RemoteMenuController(connector, new MenuTree());
controller.start();
remoteMenuControllers.put(UuidAndSerial.fromRemote(connector.getRemoteParty()), controller);
}

@Override
public void onConnectionClosed(SocketClientRemoteConnector connector) {
logger.log(Level.INFO, "Connection closed from " + connector.getRemoteParty());
// here you would do any cleaning up needed when a connection closed.
var controller = remoteMenuControllers.get(UuidAndSerial.fromRemote(connector.getRemoteParty()));
if(controller != null) {
controller.stop();
}
}
}
}
Loading

0 comments on commit 699bd95

Please sign in to comment.