diff --git a/JCS_SETUP_NL.md b/JCS_SETUP_NL.md
index 0e343ae5..2e7291ff 100644
--- a/JCS_SETUP_NL.md
+++ b/JCS_SETUP_NL.md
@@ -18,16 +18,16 @@ In JCS wordt een blok altijd gemarkeerd door twee sensoren.
## Het tekenen van de baan
Wanneer JCS is gestart, selecteer je de Bewerken-knop om de bewerkingsmodus te activeren.
-
+ Via toesenbord door Ctrl + E.
JCS toont het scherm voor het bewerken van de baan.

De werkbalk bevat de meest voorkomende elementen om een baan te tekenen. Een plattegrond van een baan bestaat uit tegels.
Een tegel stelt een component, zoals een recht stuk, sensor, blok, enz. voor.
-Gebruik de __+__ knop om een tegel op het canvas toe te voegen. De prullenbakknop verwijdert een tegel.
-Klik met de rechtermuisknop op een tegel om eigenschappen te bekijken of de tegel te draaien of om te keren indien van toepassing.
-Wanneer een tegel is geselecteerd, kan deze naar de juiste positie worden gesleept.
+Gebruik de __+__ knop, of via toetsenbord Alt + A om een tegel op het canvas toe te voegen. De prullenbakknop (Alt +D) verwijdert een tegel.
+Klik met de rechtermuisknop op een tegel om eigenschappen te bekijken of de tegel te draaien (Alt + R) of om te keren (Alt + H of Alt + V),
+indien van toepassing. Wanneer een tegel is geselecteerd, kan deze naar de juiste positie worden gesleept.
Tegels worden automatisch opgeslagen.
Het voorbeeld ziet er als volgt uit wanneer alle tegels op het canvas zijn geplaatst.
diff --git a/README.md b/README.md
index 4ff0d4cb..7d8018ff 100644
--- a/README.md
+++ b/README.md
@@ -15,14 +15,16 @@
JCS is a hobby project of me where I try to automate my Model Railway. Over the past years I have worked on and off on several aspects and modules of the software which are needed to drive automatically.
A short summary of the topics which are needed and used to be able to drive trains automatically:
-- Connectivity to the Command Station hardware. (DCC-EX,HSI-S88, Marklin CS2/3)
+- Connectivity to the Command Station hardware. (DCC-EX,HSI-S88, Marklin CS2/3 ESU Ecos)
- Edit and display graphically a layout
- With the layout be able to route all the possible drive ways
- Show the routes and driveways in the layout screen
- Graphically feedback events on track to the layout screen
+- Automatically run locomotives on the layout
- Input dialogs to setup Accessories, Locomotives, Command stations, etc
- Locomotive Drive Cap so tha you can manually run you locomotive
- Virtual Command Station, to ease testing and simulate automatic driving
+- Build in VNC viewer
- Monitor Sensor events
I created a [short video](https://youtu.be/xP6eUdScMY0) demonstrating automatic running of locomotives. Also a [video of pysical locomotives running on the Test Layout](https://www.youtube.com/watch?v=CyLmGk6gfHA)
@@ -78,7 +80,7 @@ To debug or easly setup your feedback sensors
## Releases
-- [First Release V 0.0.1](https://github.com/fransjacobs/model-railway/releases/tag/V0.0.1)
+- [Latest Release V 0.0.2](https://github.com/fransjacobs/model-railway/releases/tag/V0.0.2)
## Supported Hardware
@@ -94,10 +96,9 @@ To debug or easly setup your feedback sensors
Currently the following feature are in development:
- Documentation
-- Internationalization enable multiple languages
-- Add support for ESU ECOS
-- Show Signal aspects in automatic driving
- Enhance GUI
+- Show Signal aspects in automatic driving
+- Internationalization enable multiple languages
- Add more Unit tests
- ...
@@ -125,7 +126,7 @@ Currently the following feature are in development:
-## Copyright 2019 - 2024 Frans Jacobs
+## Copyright 2019 - 2025 Frans Jacobs
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
diff --git a/nb-configuration.xml b/nb-configuration.xml
index e5ed60ac..1b67107a 100644
--- a/nb-configuration.xml
+++ b/nb-configuration.xml
@@ -35,5 +35,9 @@ Any value defined here will override the pom.xml file value but is only applicab
true
project
all
+ true
+ true
+ LF
+ false
diff --git a/nbactions.xml b/nbactions.xml
index c2d9de65..b341619c 100644
--- a/nbactions.xml
+++ b/nbactions.xml
@@ -81,4 +81,18 @@
test
-
\ No newline at end of file
+
+ CUSTOM-checkstyle:check
+ checkstyle:check
+
+ checkstyle:check
+
+
+
+ CUSTOM-test
+ test
+
+ test
+
+
+
diff --git a/nbproject/private/profiler/settings.xml b/nbproject/private/profiler/settings.xml
new file mode 100644
index 00000000..31c9a1f4
--- /dev/null
+++ b/nbproject/private/profiler/settings.xml
@@ -0,0 +1,9 @@
+
+
+
+10
+#org.netbeans.modules.profiler.v2.features.LocksFeature@#org.netbeans.modules.profiler.v2.features.MethodsFeature@#org.netbeans.modules.profiler.v2.features.MonitorFeature@
+true
+ProjectClassesMode
+false
+
diff --git a/nbproject/private/profiler/snapshot-1736349327025.nps b/nbproject/private/profiler/snapshot-1736349327025.nps
new file mode 100644
index 00000000..194a49f3
Binary files /dev/null and b/nbproject/private/profiler/snapshot-1736349327025.nps differ
diff --git a/pom.xml b/pom.xml
index 7222ff10..ec56d1c3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -172,6 +172,11 @@
logback-classic
1.2.13
+
org.netbeans.external
AbsoluteLayout
@@ -204,9 +209,10 @@
maven-compiler-plugin
3.13.0
- --enable-preview
+
+ -verbose
21
- false
+ true
@@ -233,6 +239,7 @@
3.3.1
UTF-8
+ ${project.build.sourceEncoding}
db:encryptable
db
@@ -289,7 +296,7 @@
maven-surefire-plugin
3.5.2
- false
+
-Dfile.encoding=UTF-8
junit.jupiter.execution.parallel.enabled=false
@@ -347,6 +354,27 @@
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.6.0
+
+
+ com.puppycrawl.tools
+ checkstyle
+ 10.23.0
+
+
+
+
+ checkstyle
+ checkstyle
+
+ checkstyle
+
+
+
+
io.github.fvarrui
javapackager
diff --git a/src/main/java/jcs/JCS.java b/src/main/java/jcs/JCS.java
index 93ef061a..a24b9757 100755
--- a/src/main/java/jcs/JCS.java
+++ b/src/main/java/jcs/JCS.java
@@ -15,27 +15,38 @@
*/
package jcs;
+import java.awt.Desktop;
import java.awt.GraphicsEnvironment;
+import java.awt.Taskbar;
+import java.awt.desktop.AboutEvent;
+import java.awt.desktop.AboutHandler;
+import java.awt.desktop.OpenFilesEvent;
+import java.awt.desktop.OpenFilesHandler;
+import java.awt.desktop.PreferencesEvent;
+import java.awt.desktop.PreferencesHandler;
+import java.awt.desktop.QuitEvent;
+import java.awt.desktop.QuitHandler;
+import java.awt.desktop.QuitResponse;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
+import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
+import javax.swing.JOptionPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import jcs.commandStation.JCSCommandStation;
-import jcs.commandStation.JCSCommandStationImpl;
import jcs.commandStation.events.PowerEvent;
import jcs.commandStation.events.PowerEventListener;
-import jcs.commandStation.events.RefreshEvent;
-import jcs.commandStation.events.RefreshEventListener;
import jcs.persistence.PersistenceFactory;
import jcs.persistence.PersistenceService;
import jcs.persistence.util.H2DatabaseUtil;
import jcs.ui.JCSFrame;
import jcs.ui.splash.JCSSplash;
import jcs.ui.util.FrameMonitor;
-import jcs.ui.util.MacOsAdapter;
import jcs.ui.util.ProcessFactory;
+import jcs.ui.util.UICallback;
import jcs.util.RunUtil;
import jcs.util.VersionInfo;
import org.tinylog.Logger;
@@ -46,22 +57,23 @@
*
*/
public class JCS extends Thread {
-
+
private static JCS instance = null;
private static JCSSplash splashScreen;
private static PersistenceService persistentStore;
private static JCSCommandStation jcsCommandStation;
-
- private static MacOsAdapter osAdapter;
+
private static JCSFrame jcsFrame;
private static String version;
-
- private final List refreshEventListeners;
-
+
+ private static UICallback uiCallback;
+
+ //private final List refreshEventListeners;
+
private JCS() {
- refreshEventListeners = new ArrayList<>();
+ //refreshEventListeners = new ArrayList<>();
}
-
+
public static void logProgress(String message) {
if (splashScreen != null) {
splashScreen.logProgress(message);
@@ -69,80 +81,28 @@ public static void logProgress(String message) {
Logger.info(message);
}
}
-
+
public static JCSFrame getParentFrame() {
return jcsFrame;
}
-
+
public static PersistenceService getPersistenceService() {
if (persistentStore == null) {
persistentStore = PersistenceFactory.getService();
}
return persistentStore;
}
-
+
public static JCSCommandStation getJcsCommandStation() {
if (jcsCommandStation == null) {
if (getPersistenceService() != null) {
- jcsCommandStation = new JCSCommandStationImpl();
+ jcsCommandStation = new JCSCommandStation();
} else {
Logger.error("Can't obtain the persistent store!");
}
}
return jcsCommandStation;
}
-
- private void startGui() {
- JCS.logProgress("Check OS...");
-
- if (RunUtil.isMacOSX()) {
- MacOsAdapter.setMacOsProperties();
- osAdapter = new MacOsAdapter();
- }
-
- java.awt.EventQueue.invokeLater(() -> {
- jcsFrame = new JCSFrame();
-
- if (RunUtil.isMacOSX()) {
- osAdapter.setUiCallback(jcsFrame);
- }
-
- //URL iconUrl = JCS.class.getResource("/media/jcs-train-64.png");
- URL iconUrl = JCS.class.getResource("/media/jcs-train-2-512.png");
- if (iconUrl != null) {
- jcsFrame.setIconImage(new ImageIcon(iconUrl).getImage());
- }
-
- FrameMonitor.registerFrame(jcsFrame, JCS.class.getName());
-
- jcsFrame.setVisible(true);
- jcsFrame.toFront();
- jcsFrame.showOverviewPanel();
- if ("true".equalsIgnoreCase(System.getProperty("controller.autoconnect", "true"))) {
- jcsFrame.connect(true);
- }
- });
-
- JCS.logProgress("JCS started...");
-
- int mb = 1024 * 1024;
- Runtime runtime = Runtime.getRuntime();
-
- StringBuilder sb = new StringBuilder();
- sb.append("Used Memory: ");
- sb.append((runtime.totalMemory() - runtime.freeMemory()) / mb);
- sb.append(" [MB]. Free Memory: ");
- sb.append(runtime.freeMemory() / mb);
- sb.append(" [MB]. Available Memory: ");
- sb.append(runtime.totalMemory() / mb);
- sb.append(" [MB]. Max Memory: ");
- sb.append(runtime.maxMemory() / mb);
- sb.append(" [MB].");
-
- Logger.info(sb);
- splashScreen.hideSplash(200);
- splashScreen.close();
- }
/**
* Executed at shutdown in response to a Ctrl-C etc.
@@ -155,21 +115,7 @@ public void run() {
ProcessFactory.getInstance().shutdown();
Logger.info("JCS " + VersionInfo.getVersion() + " session finished");
}
-
- public static void addRefreshListener(RefreshEventListener refreshListener) {
- instance.refreshEventListeners.add(refreshListener);
- }
-
- public static void removeRefreshListener(RefreshEventListener refreshListener) {
- instance.refreshEventListeners.remove(refreshListener);
- }
-
- public static void settingsChanged(RefreshEvent refreshEvent) {
- for (RefreshEventListener rel : instance.refreshEventListeners) {
- rel.onChange(refreshEvent);
- }
- }
-
+
public static JCS getInstance() {
if (instance == null) {
instance = new JCS();
@@ -179,21 +125,37 @@ public static JCS getInstance() {
}
return instance;
}
-
+
+ private static boolean lockAppInstance() {
+ try {
+ String lockFilePath = System.getProperty("user.home") + File.separator + "jcs" + File.separator + "jcs.lock";
+
+ final File file = new File(lockFilePath);
+ if (file.createNewFile()) {
+ file.deleteOnExit();
+ return true;
+ }
+ return false;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
public static void main(String[] args) {
System.setProperty("fazecast.jSerialComm.appid", "JCS");
version = VersionInfo.getVersion();
Logger.info("Starting JCS Version " + version + "...");
-
+
if (GraphicsEnvironment.isHeadless()) {
Logger.error("This JDK environment is headless, can't start a GUI!");
//Quit....
System.exit(1);
}
+
//Load properties
RunUtil.loadProperties();
RunUtil.loadExternalProperties();
-
+
try {
String plaf = System.getProperty("jcs.plaf", "com.formdev.flatlaf.FlatLightLaf");
if (plaf != null) {
@@ -204,11 +166,29 @@ public static void main(String[] args) {
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
Logger.error(ex);
}
-
+
+ if (!lockAppInstance()) {
+ Logger.warn("Can not obtain a lock. Check if an other instance of JCS is running");
+ JOptionPane.showMessageDialog(null, "There is another instance of JCS running.", "JCS allready running", JOptionPane.INFORMATION_MESSAGE, null);
+ System.exit(0);
+ }
+
+ if (RunUtil.isMacOSX()) {
+ System.setProperty("apple.awt.application.name", "JCS");
+ System.setProperty("apple.laf.useScreenMenuBar", "true");
+ System.setProperty("apple.awt.application.appearance", "system");
+ }
+
splashScreen = new JCSSplash();
- splashScreen.showSplash();
+
+ if ("true".equalsIgnoreCase(System.getProperty("disable.splash", "false"))) {
+ Logger.info("Splasscreen is disabled");
+ } else {
+ splashScreen.showSplash();
+ }
+
splashScreen.setProgressMax(25);
-
+
logProgress("JCS is Starting...");
//Check the persistent properties, prepare environment
@@ -221,24 +201,26 @@ public static void main(String[] args) {
//Database file exist check whether an update is needed
String dbVersion = H2DatabaseUtil.getDataBaseVersion();
+
if (!H2DatabaseUtil.DB_VERSION.equals(dbVersion)) {
Logger.trace("Current DB Version " + dbVersion + " need to be updated to: " + H2DatabaseUtil.DB_VERSION + "...");
logProgress("Updating JCS Database to version " + H2DatabaseUtil.DB_VERSION + "...");
dbVersion = H2DatabaseUtil.updateDatabase();
}
+
logProgress("Connecting to existing Database version " + dbVersion + "...");
-
+
logProgress("Starting JCS Command Station...");
persistentStore = getPersistenceService();
jcsCommandStation = getJcsCommandStation();
-
+
if (persistentStore != null) {
if ("true".equalsIgnoreCase(System.getProperty("commandStation.autoconnect", "true"))) {
if (jcsCommandStation != null) {
boolean connected = jcsCommandStation.connect();
if (connected) {
logProgress("Connected with Command Station...");
-
+
boolean power = jcsCommandStation.isPowerOn();
logProgress("Track Power is " + (power ? "on" : "off"));
Logger.info("Track Power is " + (power ? "on" : "off"));
@@ -250,10 +232,11 @@ public static void main(String[] args) {
logProgress("NO Default Command Station found...");
}
}
-
+
logProgress("Starting UI...");
-
+
JCS jcs = JCS.getInstance();
+
jcs.startGui();
} else {
Logger.error("Could not obtain a Persistent store. Quitting....");
@@ -263,20 +246,119 @@ public static void main(String[] args) {
System.exit(0);
}
}
-
+
+ private void startGui() {
+ JCS.logProgress("Starting UI...");
+
+ if (RunUtil.isMacOSX()) {
+ try {
+ Desktop desktop = Desktop.getDesktop();
+ desktop.setAboutHandler(new JCSAboutHandler());
+ desktop.setQuitHandler(new JCSQuitHandler());
+ desktop.setPreferencesHandler(new JCSPreferencesHandler());
+
+ Taskbar taskbar = Taskbar.getTaskbar();
+ try {
+ //BufferedImage img = ImageIO.read(JCS.class.getResource("/media/jcs-train-64.png"));
+ BufferedImage img = ImageIO.read(JCS.class.getResource("/media/jcs-train-2-512.png"));
+ taskbar.setIconImage(img);
+ } catch (final UnsupportedOperationException e) {
+ Logger.warn("The os does not support: 'taskbar.setIconImage'");
+ } catch (final SecurityException e) {
+ Logger.warn("There was a security exception for: 'taskbar.setIconImage'");
+ }
+ } catch (SecurityException | IllegalArgumentException | IOException ex) {
+ Logger.warn("Failed to register with MacOS: " + ex);
+ }
+ }
+
+ java.awt.EventQueue.invokeLater(() -> {
+ jcsFrame = new JCSFrame();
+ JCS.uiCallback = jcsFrame;
+
+ //URL iconUrl = JCS.class.getResource("/media/jcs-train-64.png");
+ URL iconUrl = JCS.class.getResource("/media/jcs-train-2-512.png");
+ if (iconUrl != null) {
+ jcsFrame.setIconImage(new ImageIcon(iconUrl).getImage());
+ }
+
+ FrameMonitor.registerFrame(jcsFrame, JCS.class.getName());
+
+ jcsFrame.setVisible(true);
+ jcsFrame.toFront();
+ jcsFrame.showOverviewPanel();
+ if ("true".equalsIgnoreCase(System.getProperty("controller.autoconnect", "true"))) {
+ jcsFrame.connect(true);
+ }
+ });
+
+ JCS.logProgress("JCS started...");
+
+ int mb = 1024 * 1024;
+ Runtime runtime = Runtime.getRuntime();
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("Used Memory: ");
+ sb.append((runtime.totalMemory() - runtime.freeMemory()) / mb);
+ sb.append(" [MB]. Free Memory: ");
+ sb.append(runtime.freeMemory() / mb);
+ sb.append(" [MB]. Available Memory: ");
+ sb.append(runtime.totalMemory() / mb);
+ sb.append(" [MB]. Max Memory: ");
+ sb.append(runtime.maxMemory() / mb);
+ sb.append(" [MB].");
+
+ Logger.info(sb);
+ splashScreen.hideSplash(200);
+ splashScreen.close();
+ }
+
private static class Powerlistener implements PowerEventListener {
-
+
Powerlistener() {
}
-
+
@Override
public void onPowerChange(PowerEvent event) {
Logger.info("Track Power is " + (event.isPower() ? "on" : "off"));
-
+
if (JCS.jcsFrame != null) {
JCS.jcsFrame.powerChanged(event);
}
}
}
-
+
+ private class JCSQuitHandler implements QuitHandler {
+
+ @Override
+ public void handleQuitRequestWith(QuitEvent e, QuitResponse response) {
+ uiCallback.handleQuitRequest();
+ }
+ }
+
+ private class JCSAboutHandler implements AboutHandler {
+
+ @Override
+ public void handleAbout(AboutEvent e) {
+ uiCallback.handleAbout();
+ }
+ }
+
+ private class JCSPreferencesHandler implements PreferencesHandler {
+
+ @Override
+ public void handlePreferences(PreferencesEvent e) {
+ uiCallback.handlePreferences();
+ }
+ }
+
+ private class JCSOpenFilesHandler implements OpenFilesHandler {
+
+ @Override
+ public void openFiles(OpenFilesEvent e) {
+ //STUB
+ uiCallback.openFiles(null);
+ }
+ }
+
}
diff --git a/src/main/java/jcs/commandStation/AbstractController.java b/src/main/java/jcs/commandStation/AbstractController.java
index 03ed9600..07df81f5 100644
--- a/src/main/java/jcs/commandStation/AbstractController.java
+++ b/src/main/java/jcs/commandStation/AbstractController.java
@@ -20,7 +20,6 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import jcs.commandStation.events.AccessoryEventListener;
-import jcs.commandStation.events.DisconnectionEventListener;
import jcs.commandStation.events.LocomotiveDirectionEventListener;
import jcs.commandStation.events.LocomotiveFunctionEventListener;
import jcs.commandStation.events.LocomotiveSpeedEventListener;
@@ -28,6 +27,8 @@
import jcs.commandStation.events.SensorEventListener;
import jcs.entities.CommandStationBean;
import org.tinylog.Logger;
+import jcs.commandStation.events.ConnectionEventListener;
+import jcs.commandStation.events.MeasurementEventListener;
public abstract class AbstractController implements GenericController {
@@ -42,7 +43,9 @@ public abstract class AbstractController implements GenericController {
protected final List locomotiveDirectionEventListeners;
protected final List locomotiveSpeedEventListeners;
- protected final List disconnectionEventListeners;
+ protected final List connectionEventListeners;
+
+ protected final List measurementEventListeners;
protected ExecutorService executor;
@@ -70,7 +73,9 @@ public AbstractController(boolean autoConnect, CommandStationBean commandStation
locomotiveDirectionEventListeners = new LinkedList<>();
locomotiveSpeedEventListeners = new LinkedList<>();
- disconnectionEventListeners = new LinkedList<>();
+ connectionEventListeners = new LinkedList<>();
+
+ measurementEventListeners = new LinkedList<>();
if (this.commandStationBean != null) {
this.virtual = commandStationBean.isVirtual();
@@ -101,13 +106,13 @@ public boolean isVirtual() {
}
@Override
- public void addDisconnectionEventListener(DisconnectionEventListener listener) {
- this.disconnectionEventListeners.add(listener);
+ public void addConnectionEventListener(ConnectionEventListener listener) {
+ this.connectionEventListeners.add(listener);
}
@Override
- public void removeDisconnectionEventListener(DisconnectionEventListener listener) {
- this.disconnectionEventListeners.remove(listener);
+ public void removeConnectionEventListener(ConnectionEventListener listener) {
+ this.connectionEventListeners.remove(listener);
}
public boolean isPower() {
@@ -162,6 +167,14 @@ public void removeLocomotiveSpeedEventListener(LocomotiveSpeedEventListener list
this.locomotiveSpeedEventListeners.remove(listener);
}
+ public void addMeasurementEventListener(MeasurementEventListener listener) {
+ this.measurementEventListeners.add(listener);
+ }
+
+ public void removeMeasurementEventListener(MeasurementEventListener listener) {
+ this.measurementEventListeners.remove(listener);
+ }
+
protected void pause(long millis) {
try {
Thread.sleep(millis);
diff --git a/src/main/java/jcs/commandStation/AccessoryController.java b/src/main/java/jcs/commandStation/AccessoryController.java
index d943c3ff..2e387d6b 100644
--- a/src/main/java/jcs/commandStation/AccessoryController.java
+++ b/src/main/java/jcs/commandStation/AccessoryController.java
@@ -22,11 +22,13 @@
public interface AccessoryController extends GenericController {
- void switchAccessory(Integer address, AccessoryValue value);
+ //void switchAccessory(Integer address, AccessoryValue value);
- void switchAccessory(Integer address, AccessoryValue value, Integer switchTime);
+ //void switchAccessory(Integer address, AccessoryValue value, Integer switchTime);
- void switchAccessory(String id, AccessoryBean.AccessoryValue value);
+ //void switchAccessory(String id, AccessoryBean.AccessoryValue value);
+
+ void switchAccessory(Integer address, String protocol, AccessoryValue value, Integer switchTime);
void addAccessoryEventListener(AccessoryEventListener listener);
diff --git a/src/main/java/jcs/commandStation/DecoderController.java b/src/main/java/jcs/commandStation/DecoderController.java
index 9b914e49..2cf3e5b7 100644
--- a/src/main/java/jcs/commandStation/DecoderController.java
+++ b/src/main/java/jcs/commandStation/DecoderController.java
@@ -17,12 +17,11 @@
import java.awt.Image;
import java.util.List;
-import java.util.Map;
import jcs.commandStation.events.LocomotiveDirectionEventListener;
import jcs.commandStation.events.LocomotiveFunctionEventListener;
import jcs.commandStation.events.LocomotiveSpeedEventListener;
+import jcs.commandStation.events.MeasurementEventListener;
import jcs.commandStation.events.PowerEventListener;
-import jcs.entities.ChannelBean;
import jcs.entities.LocomotiveBean;
import jcs.entities.LocomotiveBean.Direction;
@@ -54,8 +53,6 @@ public interface DecoderController extends GenericController {
void removeLocomotiveSpeedEventListener(LocomotiveSpeedEventListener listener);
- //List getLocomotiveSpeedEventListeners();
-
List getLocomotives();
Image getLocomotiveImage(String icon);
@@ -64,5 +61,8 @@ public interface DecoderController extends GenericController {
boolean isSupportTrackMeasurements();
- Map getTrackMeasurements();
+ void addMeasurementEventListener(MeasurementEventListener listener);
+
+ void removeMeasurementEventListener(MeasurementEventListener listener);
+
}
diff --git a/src/main/java/jcs/commandStation/FeedbackController.java b/src/main/java/jcs/commandStation/FeedbackController.java
index 79e27ef1..ea15cffd 100644
--- a/src/main/java/jcs/commandStation/FeedbackController.java
+++ b/src/main/java/jcs/commandStation/FeedbackController.java
@@ -18,8 +18,7 @@
import java.util.List;
import jcs.commandStation.events.SensorEvent;
import jcs.commandStation.events.SensorEventListener;
-import jcs.commandStation.entities.DeviceBean;
-import jcs.entities.FeedbackModuleBean;
+import jcs.commandStation.entities.FeedbackModule;
public interface FeedbackController extends GenericController {
@@ -27,9 +26,9 @@ public interface FeedbackController extends GenericController {
void removeSensorEventListener(SensorEventListener listener);
- DeviceBean getFeedbackDevice();
+ //DeviceBean getFeedbackDevice();
- List getFeedbackModules();
+ List getFeedbackModules();
void fireSensorEventListeners(SensorEvent sensorEvent);
diff --git a/src/main/java/jcs/commandStation/GenericController.java b/src/main/java/jcs/commandStation/GenericController.java
index 3920e50e..374510ba 100755
--- a/src/main/java/jcs/commandStation/GenericController.java
+++ b/src/main/java/jcs/commandStation/GenericController.java
@@ -16,10 +16,10 @@
package jcs.commandStation;
import java.util.List;
-import jcs.commandStation.events.DisconnectionEventListener;
+import jcs.commandStation.entities.Device;
import jcs.entities.CommandStationBean;
-import jcs.commandStation.entities.DeviceBean;
import jcs.commandStation.entities.InfoBean;
+import jcs.commandStation.events.ConnectionEventListener;
public interface GenericController {
@@ -35,15 +35,13 @@ public interface GenericController {
boolean isVirtual();
- void addDisconnectionEventListener(DisconnectionEventListener listener);
+ void addConnectionEventListener(ConnectionEventListener listener);
- void removeDisconnectionEventListener(DisconnectionEventListener listener);
+ void removeConnectionEventListener(ConnectionEventListener listener);
InfoBean getCommandStationInfo();
- DeviceBean getDevice();
-
- List getDevices();
+ List getDevices();
String getIp();
diff --git a/src/main/java/jcs/commandStation/JCSCommandStation.java b/src/main/java/jcs/commandStation/JCSCommandStation.java
index 75babf65..977c55a6 100755
--- a/src/main/java/jcs/commandStation/JCSCommandStation.java
+++ b/src/main/java/jcs/commandStation/JCSCommandStation.java
@@ -16,93 +16,882 @@
package jcs.commandStation;
import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+import javax.imageio.ImageIO;
+import jcs.commandStation.events.AccessoryEvent;
import jcs.commandStation.events.AccessoryEventListener;
-import jcs.commandStation.events.DisconnectionEventListener;
+import jcs.commandStation.events.ConnectionEvent;
+import jcs.commandStation.events.LocomotiveDirectionEvent;
import jcs.commandStation.events.LocomotiveDirectionEventListener;
+import jcs.commandStation.events.LocomotiveFunctionEvent;
import jcs.commandStation.events.LocomotiveFunctionEventListener;
+import jcs.commandStation.events.LocomotiveSpeedEvent;
import jcs.commandStation.events.LocomotiveSpeedEventListener;
import jcs.commandStation.events.MeasurementEventListener;
import jcs.commandStation.events.PowerEventListener;
+import jcs.commandStation.events.SensorEvent;
import jcs.commandStation.events.SensorEventListener;
import jcs.entities.AccessoryBean;
import jcs.entities.AccessoryBean.AccessoryValue;
import jcs.entities.CommandStationBean;
+import jcs.entities.CommandStationBean.Protocol;
+import jcs.entities.FunctionBean;
import jcs.commandStation.entities.InfoBean;
import jcs.entities.LocomotiveBean;
+import jcs.entities.LocomotiveBean.DecoderType;
+import jcs.entities.LocomotiveBean.Direction;
+import jcs.entities.SensorBean;
+import jcs.persistence.PersistenceFactory;
+import org.tinylog.Logger;
+import jcs.commandStation.events.ConnectionEventListener;
/**
- * The Track repository contain all track item which are used on the Track This can be Locomotives, Turnouts, Signals, etc There For future use the implementation of the Repository could be changed to
- * an other storage provider
- *
- * @author frans
+ * The JCSCommandStation is the layer between the UI, engines and Command stations
*/
-public interface JCSCommandStation {
-
- void switchPower(boolean on);
-
- boolean isPowerOn();
-
- boolean connect();
-
- boolean isConnected();
-
- void disconnect();
-
- void setVirtual(boolean flag);
-
- boolean isVirtual();
-
- void addDisconnectionEventListener(DisconnectionEventListener listener);
-
- void addPowerEventListener(PowerEventListener listener);
-
- void removePowerEventListener(PowerEventListener listener);
-
- void changeLocomotiveDirection(LocomotiveBean.Direction direction, LocomotiveBean locomotive);
-
- void changeLocomotiveSpeed(Integer speed, LocomotiveBean locomotive);
-
- void changeLocomotiveFunction(Boolean value, Integer functionNumber, LocomotiveBean locomotive);
-
- void switchAccessory(AccessoryBean accessory, AccessoryValue value);
-
- void addAccessoryEventListener(AccessoryEventListener listener);
-
- void removeAccessoryEventListener(AccessoryEventListener listener);
-
- void addSensorEventListener(SensorEventListener listener);
-
- void removeSensorEventListener(SensorEventListener listener);
-
- void addLocomotiveFunctionEventListener(LocomotiveFunctionEventListener listener);
-
- void removeLocomotiveFunctionEventListener(LocomotiveFunctionEventListener listener);
-
- void addLocomotiveDirectionEventListener(LocomotiveDirectionEventListener listener);
-
- void removeLocomotiveDirectionEventListener(LocomotiveDirectionEventListener listener);
-
- void addLocomotiveSpeedEventListener(LocomotiveSpeedEventListener listener);
-
- void removeLocomotiveSpeedEventListener(LocomotiveSpeedEventListener listener);
-
- void addMeasurementEventListener(MeasurementEventListener listener);
-
- void removeMeasurementListener(MeasurementEventListener listener);
-
- CommandStationBean getCommandStationBean();
-
- InfoBean getCommandStationInfo();
-
- Image getLocomotiveImage(String imageName);
-
- Image getLocomotiveFunctionImage(String imageName);
-
- DecoderController getDecoderController();
-
- List getAccessoryControllers();
-
- List getFeedbackControllers();
+public class JCSCommandStation {
+
+ private DecoderController decoderController;
+ private Map accessoryControllers;
+ private Map feedbackControllers;
+
+ private final List sensorListeners;
+
+ private final List accessoryEventListeners;
+ private final List locomotiveFunctionEventListeners;
+
+ private final List locomotiveDirectionEventListeners;
+ private final List locomotiveSpeedEventListeners;
+
+ private final Set supportedProtocols;
+ private CommandStationBean commandStation;
+
+ private final ExecutorService executor;
+
+ private static final String AWT_THREAD = "AWT-EventQueue-0";
+
+ /**
+ * Wrapper around the "real" CommandStation implementation.
+ * Operations to commandStations should not be performed in the EventDispatch thread.
+ * Operations to commandStations are performed in a worker thread to avoid blocking the EventDispatch thread.
+ */
+ public JCSCommandStation() {
+ this("true".equalsIgnoreCase(System.getProperty("skip.controller.autoconnect", "true")));
+ }
+
+ private JCSCommandStation(boolean autoConnectController) {
+ executor = Executors.newCachedThreadPool();
+ accessoryControllers = new HashMap<>();
+ feedbackControllers = new HashMap<>();
+
+ sensorListeners = new LinkedList<>();
+ accessoryEventListeners = new LinkedList<>();
+ locomotiveFunctionEventListeners = new LinkedList<>();
+ locomotiveDirectionEventListeners = new LinkedList<>();
+ locomotiveSpeedEventListeners = new LinkedList<>();
+ supportedProtocols = new HashSet<>();
+
+ try {
+ if (autoConnectController && decoderController != null && decoderController.getCommandStationBean() != null || accessoryControllers.isEmpty() || feedbackControllers.isEmpty()) {
+ connect();
+ Logger.trace(decoderController != null ? "Aquired " + decoderController.getClass().getSimpleName() : "Could not aquire a Command Station! " + (decoderController.isConnected() ? "Connected" : "NOT Connected"));
+ } else {
+ Logger.trace("Auto Connect disabled");
+ }
+ } catch (Exception e) {
+ Logger.warn("Can't connect with default Command Station!");
+ }
+ }
+
+ public final boolean connect() {
+ boolean decoderControllerConnected = false;
+ boolean allreadyConnected = false;
+
+ //Check if already connected to avoid duplication....
+ if (commandStation != null && decoderController != null) {
+ decoderControllerConnected = decoderController.isConnected();
+ allreadyConnected = true;
+ Logger.trace(decoderController.getClass().getName() + " allready connected...");
+ } else {
+ commandStation = PersistenceFactory.getService().getDefaultCommandStation();
+ }
+
+ int accessoryCntrConnected = 0;
+ int feedbackCntrConnected = 0;
+
+ if (commandStation == null) {
+ Logger.error("No Default Command Station found!");
+ return false;
+ }
+
+ if (decoderController == null && commandStation != null) {
+ decoderController = ControllerFactory.getDecoderController(commandStation, false);
+ }
+
+ if (decoderController == null) {
+ Logger.error("No DecoderController configured!");
+ return false;
+ }
+
+ if (accessoryControllers.isEmpty()) {
+ List acl = ControllerFactory.getAccessoryControllers();
+ for (AccessoryController ac : acl) {
+ accessoryControllers.put(ac.getCommandStationBean().getId(), ac);
+ }
+ }
+
+ //TODO: a warning log in the main screen
+ if (accessoryControllers.isEmpty()) {
+ Logger.warn("No Accessory Controllers configured!");
+ }
+
+ if (feedbackControllers.isEmpty()) {
+ List fcl = ControllerFactory.getFeedbackControllers();
+ for (FeedbackController fc : fcl) {
+ feedbackControllers.put(fc.getCommandStationBean().getId(), fc);
+ }
+ }
+
+ //TODO: a warning log in the main screen
+ if (feedbackControllers.isEmpty()) {
+ Logger.warn("No Feedback Controllers configured!");
+ }
+
+ if (decoderController != null && !decoderControllerConnected) {
+ decoderControllerConnected = decoderController.isConnected();
+ if (!decoderControllerConnected) {
+ decoderControllerConnected = decoderController.connect();
+ }
+ }
+
+ //Connect the Accessories controllers if needed
+ if (!accessoryControllers.isEmpty() && !allreadyConnected) {
+ for (AccessoryController ac : accessoryControllers.values()) {
+ if (ac.isConnected()) {
+ accessoryCntrConnected++;
+ } else {
+ try {
+ if (ac.connect()) {
+ accessoryCntrConnected++;
+ }
+ } catch (Exception e) {
+ Logger.warn(" Can't connected to " + ac.getCommandStationBean().getDescription());
+ }
+ }
+ }
+ }
+
+ //Connect the Feedback Controllers controllers if needed
+ if (!feedbackControllers.isEmpty() && !allreadyConnected) {
+ for (FeedbackController fc : feedbackControllers.values()) {
+ if (fc.isConnected()) {
+ feedbackCntrConnected++;
+ } else {
+ try {
+ if (fc.connect()) {
+ feedbackCntrConnected++;
+ }
+ } catch (Exception e) {
+ Logger.warn(" Can't connected to " + fc.getCommandStationBean().getDescription());
+ }
+ }
+ }
+ }
+
+ Logger.trace("Connected Controllers: Decoder: " + (decoderControllerConnected ? "Yes" : "No") + " Accessory: " + accessoryCntrConnected + " Feedback: " + feedbackCntrConnected);
+
+ if (decoderControllerConnected && !allreadyConnected && decoderController != null) {
+ decoderController.addConnectionEventListener(new ConnectionListener(this));
+
+ decoderController.addLocomotiveFunctionEventListener(new LocomotiveFunctionChangeEventListener(this));
+ decoderController.addLocomotiveDirectionEventListener(new LocomotiveDirectionChangeEventListener(this));
+ decoderController.addLocomotiveSpeedEventListener(new LocomotiveSpeedChangeEventListener(this));
+
+ supportedProtocols.addAll(decoderController.getCommandStationBean().getSupportedProtocols());
+ }
+
+ if (accessoryCntrConnected > 0 && !allreadyConnected) {
+ for (AccessoryController ac : accessoryControllers.values()) {
+ if (ac.isConnected()) {
+ ac.addAccessoryEventListener(new AccessoryChangeEventListener(this));
+ ac.addConnectionEventListener(new ConnectionListener(this));
+ }
+ }
+ }
+
+ if (feedbackCntrConnected > 0 && !allreadyConnected) {
+ for (FeedbackController fc : feedbackControllers.values()) {
+ if (fc.isConnected()) {
+ fc.addSensorEventListener(new SensorChangeEventListener(this));
+ fc.addConnectionEventListener(new ConnectionListener(this));
+ }
+ }
+ }
+
+ //TODO implement get the day end i.e. the current state of all Objects on track
+ return decoderControllerConnected;
+ }
+
+ public CommandStationBean getCommandStationBean() {
+ if (decoderController != null) {
+ return decoderController.getCommandStationBean();
+ } else {
+ return null;
+ }
+ }
+
+ public boolean isConnected() {
+ if (decoderController != null) {
+ return decoderController.isConnected();
+ } else {
+ return false;
+ }
+ }
+
+ public void disconnect() {
+ for (FeedbackController fc : feedbackControllers.values()) {
+ if (fc != decoderController) {
+ fc.disconnect();
+ }
+ }
+ for (AccessoryController ac : accessoryControllers.values()) {
+ if (ac != decoderController) {
+ ac.disconnect();
+ }
+ }
+
+ if (decoderController != null) {
+ decoderController.disconnect();
+ }
+
+ //Enable command station switching so
+ decoderController = null;
+ accessoryControllers.clear();
+ feedbackControllers.clear();
+ commandStation = null;
+ ControllerFactory.reset();
+ }
+
+ public void setVirtual(boolean flag) {
+ Logger.info("Switch Virtual Mode " + (flag ? "On" : "Off"));
+ commandStation.setVirtual(flag);
+ PersistenceFactory.getService().persist(commandStation);
+
+ decoderController.setVirtual(flag);
+ }
+
+ public boolean isVirtual() {
+ if (decoderController != null) {
+ return decoderController.isVirtual();
+ } else {
+ return false;
+ }
+ }
+
+ public Image getLocomotiveImage(String imageName) {
+ Image image = null;
+
+ if (decoderController != null) {
+ image = decoderController.getLocomotiveImage(imageName);
+ if (image != null) {
+ storeImage(image, imageName, true);
+ }
+ }
+ return image;
+ }
+
+ public Image getLocomotiveFunctionImage(String imageName) {
+ Image image = null;
+ if (decoderController != null) {
+ image = decoderController.getLocomotiveFunctionImage(imageName);
+ if (image != null) {
+ storeImage(image, imageName, false);
+ }
+ }
+ return image;
+ }
+
+ private void storeImage(Image image, String imageName, boolean locomotive) {
+ Path path;
+ String csp = null;
+ if (decoderController != null) {
+ csp = this.decoderController.getCommandStationBean().getLastUsedSerial();
+ if (csp == null) {
+ csp = this.decoderController.getCommandStationBean().getId();
+ }
+ }
+
+ String basePath = System.getProperty("user.home") + File.separator + "jcs" + File.separator + "cache" + File.separator + csp;
+
+ if (locomotive) {
+ path = Paths.get(basePath);
+ } else {
+ path = Paths.get(basePath + File.separator + "functions");
+ }
+
+ File imageFile = new File(path + File.separator + imageName.toLowerCase() + ".png");
+
+ try {
+ if (!Files.exists(path)) {
+ Files.createDirectories(path);
+ Logger.trace("Created new directory " + path);
+ }
+ ImageIO.write((BufferedImage) image, "png", imageFile);
+ } catch (IOException ex) {
+ Logger.error("Can't store image " + imageFile + "! ", ex.getMessage());
+ }
+ Logger.trace("Stored image " + imageName + ".png in the cache");
+ }
+
+ public InfoBean getCommandStationInfo() {
+ if (decoderController != null) {
+ return decoderController.getCommandStationInfo();
+ } else {
+ return null;
+ }
+ }
+
+ public String getCommandStationName() {
+ if (decoderController != null && decoderController.getCommandStationInfo() != null) {
+ return decoderController.getCommandStationInfo().getProductName();
+ } else if (decoderController != null && decoderController.getCommandStationBean() != null) {
+ return decoderController.getCommandStationBean().getDescription();
+ } else {
+ return null;
+ }
+ }
+
+ public String getCommandStationSerialNumber() {
+ if (decoderController != null && decoderController.getCommandStationInfo() != null) {
+ return decoderController.getCommandStationInfo().getSerialNumber();
+ } else {
+ return null;
+ }
+ }
+
+ public String getCommandStationArticleNumber() {
+ if (decoderController != null && decoderController.getCommandStationInfo() != null) {
+ return decoderController.getCommandStationInfo().getArticleNumber();
+ } else {
+ return null;
+ }
+ }
+
+ public void switchPower(boolean on) {
+ //Logger.trace("Switch Power " + (on ? "On" : "Off"));
+ if (decoderController != null && !AWT_THREAD.equals(Thread.currentThread().getName())) {
+ decoderController.power(on);
+ } else {
+ executor.execute(() -> {
+ if (decoderController != null) {
+ decoderController.power(on);
+ }
+ });
+ }
+ }
+
+ public boolean isPowerOn() {
+ boolean power = false;
+ if (decoderController != null) {
+ power = decoderController.isPower();
+ }
+ return power;
+ }
+
+ public void changeLocomotiveDirection(Direction newDirection, LocomotiveBean locomotive) {
+ Logger.debug("Changing direction to " + newDirection + " for: " + locomotive.getName() + " id: " + locomotive.getId());
+
+ int address;
+ if ("marklin.cs".equals(locomotive.getCommandStationId()) || "esu-ecos".equals(locomotive.getCommandStationId())) {
+ address = locomotive.getId().intValue();
+ } else {
+ //TODO: check this probably not needed anymore
+ if (supportedProtocols.size() == 1) {
+ address = locomotive.getAddress();
+ } else {
+ if (locomotive.getUid() != null) {
+ address = locomotive.getUid().intValue();
+ } else {
+ address = locomotive.getId().intValue();
+ }
+ }
+ }
+
+ if (decoderController != null && !AWT_THREAD.equals(Thread.currentThread().getName())) {
+ decoderController.changeVelocity(address, 0, locomotive.getDirection());
+ decoderController.changeDirection(address, newDirection);
+ } else {
+ executor.execute(() -> {
+ decoderController.changeVelocity(address, 0, locomotive.getDirection());
+ decoderController.changeDirection(address, newDirection);
+ });
+ }
+ }
+
+ public void changeLocomotiveSpeed(Integer newVelocity, LocomotiveBean locomotive) {
+ Logger.trace("Changing velocity to " + newVelocity + " for " + locomotive.getName());
+
+ int address;
+ if ("marklin.cs".equals(locomotive.getCommandStationId()) || "esu-ecos".equals(locomotive.getCommandStationId())) {
+ address = locomotive.getId().intValue();
+ } else {
+ //TODO: check this probably not needed anymore
+ if (supportedProtocols.size() == 1) {
+ address = locomotive.getAddress();
+ } else {
+ if (locomotive.getUid() != null) {
+ address = locomotive.getUid().intValue();
+ } else {
+ address = locomotive.getId().intValue();
+ }
+ }
+ }
+
+ if (decoderController != null && !AWT_THREAD.equals(Thread.currentThread().getName())) {
+ decoderController.changeVelocity(address, newVelocity, locomotive.getDirection());
+ } else {
+ executor.execute(() -> decoderController.changeVelocity(address, newVelocity, locomotive.getDirection()));
+ }
+ }
+
+ public void changeLocomotiveFunction(Boolean newValue, Integer functionNumber, LocomotiveBean locomotive) {
+ Logger.trace("Changing Function " + functionNumber + " to " + (newValue ? "on" : "off") + " on " + locomotive.getName());
+ int address;
+ if ("marklin.cs".equals(locomotive.getCommandStationId()) || "esu-ecos".equals(locomotive.getCommandStationId())) {
+ address = locomotive.getId().intValue();
+ } else {
+ //TODO: check this probably not needed anymore
+ if (supportedProtocols.size() == 1) {
+ address = locomotive.getAddress();
+ } else {
+ if (locomotive.getUid() != null) {
+ address = locomotive.getUid().intValue();
+ } else {
+ address = locomotive.getId().intValue();
+ }
+ }
+ }
+ if (decoderController != null && !AWT_THREAD.equals(Thread.currentThread().getName())) {
+ decoderController.changeFunctionValue(address, functionNumber, newValue);
+ } else {
+ executor.execute(() -> decoderController.changeFunctionValue(address, functionNumber, newValue));
+ }
+ }
+
+ public void switchAccessory(AccessoryBean accessory, AccessoryValue value) {
+ String id = accessory.getId();
+ Integer address = accessory.getAddress();
+ Integer switchTime = accessory.getSwitchTime();
+ AccessoryBean.Protocol protocol = accessory.getProtocol();
+ if (protocol == null) {
+ protocol = AccessoryBean.Protocol.DCC;
+ }
+ AccessoryValue val = value;
+ Integer states = accessory.getStates();
+ Integer state = accessory.getState();
+
+ if (states == null) {
+ states = 2;
+ }
+ if (state == null) {
+ state = AccessoryValue.RED == val ? 0 : 1;
+ }
+
+ if (states > 2) {
+ if (accessory.getState() > 1) {
+ address = address + 1;
+ val = AccessoryValue.get(state - 2);
+ }
+ }
+
+ Logger.trace("Changing accessory with address: " + address + ", " + accessory.getName() + " to " + val.getValue());
+ changeAccessory(address, protocol.getValue(), val, switchTime);
+ }
+
+ private void changeAccessory(final Integer address, final String protocol, final AccessoryValue value, final Integer switchTime) {
+ if (!AWT_THREAD.equals(Thread.currentThread().getName())) {
+ for (AccessoryController ac : accessoryControllers.values()) {
+ ac.switchAccessory(address, protocol, value, switchTime);
+ }
+ } else {
+ executor.execute(() -> {
+ for (AccessoryController ac : accessoryControllers.values()) {
+ ac.switchAccessory(address, protocol, value, switchTime);
+ }
+ });
+ }
+ }
+
+ public void addSensorEventListener(SensorEventListener listener) {
+ sensorListeners.add(listener);
+ }
+
+ public void removeSensorEventListener(SensorEventListener listener) {
+ sensorListeners.remove(listener);
+ }
+
+ public void addAccessoryEventListener(AccessoryEventListener listener) {
+ accessoryEventListeners.add(listener);
+ }
+
+ public void removeAccessoryEventListener(AccessoryEventListener listener) {
+ accessoryEventListeners.remove(listener);
+ }
+
+ public void addLocomotiveFunctionEventListener(LocomotiveFunctionEventListener listener) {
+ locomotiveFunctionEventListeners.add(listener);
+ }
+
+ public void removeLocomotiveFunctionEventListener(LocomotiveFunctionEventListener listener) {
+ locomotiveFunctionEventListeners.remove(listener);
+ }
+
+ public void addLocomotiveDirectionEventListener(LocomotiveDirectionEventListener listener) {
+ locomotiveDirectionEventListeners.add(listener);
+ }
+
+ public void removeLocomotiveDirectionEventListener(LocomotiveDirectionEventListener listener) {
+ this.locomotiveDirectionEventListeners.remove(listener);
+ }
+
+ public void addLocomotiveSpeedEventListener(LocomotiveSpeedEventListener listener) {
+ locomotiveSpeedEventListeners.add(listener);
+ }
+
+ public void removeLocomotiveSpeedEventListener(LocomotiveSpeedEventListener listener) {
+ locomotiveSpeedEventListeners.remove(listener);
+ }
+
+ public void addDisconnectionEventListener(ConnectionEventListener listener) {
+ if (decoderController != null) {
+ decoderController.addConnectionEventListener(listener);
+ }
+ for (AccessoryController ac : accessoryControllers.values()) {
+ if (ac != decoderController) {
+ ac.addConnectionEventListener(listener);
+ }
+ }
+
+ for (FeedbackController fc : feedbackControllers.values()) {
+ if (fc != decoderController) {
+ fc.addConnectionEventListener(listener);
+ }
+ }
+ }
+
+ public void addPowerEventListener(PowerEventListener listener) {
+ if (decoderController != null) {
+ decoderController.addPowerEventListener(listener);
+ }
+ }
+
+ public void removePowerEventListener(PowerEventListener listener) {
+ if (decoderController != null) {
+ decoderController.removePowerEventListener(listener);
+ }
+ }
+
+ public void addMeasurementEventListener(MeasurementEventListener listener) {
+ if (decoderController != null && decoderController.isSupportTrackMeasurements()) {
+ decoderController.addMeasurementEventListener(listener);
+ }
+ }
+
+ public void removeMeasurementListener(MeasurementEventListener listener) {
+ if (decoderController != null && decoderController.isSupportTrackMeasurements()) {
+ decoderController.removeMeasurementEventListener(listener);
+ }
+ }
+
+ public DecoderController getDecoderController() {
+ return decoderController;
+ }
+
+ public List getAccessoryControllers() {
+ return accessoryControllers.values().stream().collect(Collectors.toList());
+ }
+
+ public List getFeedbackControllers() {
+ return feedbackControllers.values().stream().collect(Collectors.toList());
+ }
+
+ private class SensorChangeEventListener implements SensorEventListener {
+
+ private final JCSCommandStation commandStation;
+
+ SensorChangeEventListener(JCSCommandStation commandStation) {
+ this.commandStation = commandStation;
+ }
+
+ @Override
+ public void onSensorChange(SensorEvent event) {
+ SensorBean sb = event.getSensorBean();
+ boolean newValue = event.isActive();
+ //SensorBean dbsb = PersistenceFactory.getService().getSensor(sb.getDeviceId(), sb.getContactId());
+ SensorBean dbsb = PersistenceFactory.getService().getSensor(event.getSensorId());
+
+ if (dbsb == null) {
+ //Try using the deviceId and contactId
+ dbsb = PersistenceFactory.getService().getSensor(sb.getDeviceId(), sb.getContactId());
+ }
+
+ if (dbsb != null) {
+ if (sb.getId() == null) {
+ sb.setId(dbsb.getId());
+ }
+ sb.setName(dbsb.getName());
+ sb.setActive(newValue);
+ PersistenceFactory.getService().persist(sb);
+ }
+
+ //Avoid concurrent modification exceptions
+ List snapshot = new ArrayList<>(commandStation.sensorListeners);
+
+ for (SensorEventListener sl : snapshot) {
+ if (sl != null) {
+ sl.onSensorChange(event);
+ }
+ }
+ }
+ }
+
+ private class AccessoryChangeEventListener implements AccessoryEventListener {
+
+ private final JCSCommandStation trackService;
+
+ AccessoryChangeEventListener(JCSCommandStation trackService) {
+ this.trackService = trackService;
+ }
+
+ @Override
+ public void onAccessoryChange(AccessoryEvent event) {
+ AccessoryBean ab = event.getAccessoryBean();
+
+ int address = ab.getAddress();
+ String commandStationId = ab.getCommandStationId();
+
+ AccessoryBean dbab = PersistenceFactory.getService().getAccessoryByAddressAndCommandStationId(address, commandStationId);
+ if (dbab == null) {
+ //check if address is even, might be the second address of a signal
+ if (address % 2 == 0) {
+ address = address - 1;
+ dbab = PersistenceFactory.getService().getAccessoryByAddressAndCommandStationId(address, commandStationId);
+ if (dbab != null && dbab.isSignal() && dbab.getStates() > 2) {
+ ab.setAddress(address);
+ int p = ab.getState() + 2;
+ ab.setState(p);
+ } else {
+ dbab = null;
+ }
+ }
+ }
+
+ if (dbab != null) {
+ //set all properties
+ ab.setId(dbab.getId());
+ ab.setDecoder(dbab.getDecoder());
+ ab.setDecType(dbab.getDecType());
+ ab.setName(dbab.getName());
+ ab.setType(dbab.getType());
+ ab.setGroup(dbab.getGroup());
+ ab.setIcon(dbab.getIcon());
+ ab.setIconFile(dbab.getIconFile());
+ ab.setStates(dbab.getStates());
+ ab.setCommandStationId(dbab.getCommandStationId());
+ //might be set by the event
+ if (ab.getSwitchTime() == null) {
+ ab.setSwitchTime(dbab.getSwitchTime());
+ }
+
+ PersistenceFactory.getService().persist(ab);
+
+ for (AccessoryEventListener al : this.trackService.accessoryEventListeners) {
+ al.onAccessoryChange(event);
+ }
+ }
+ }
+ }
+
+ private class LocomotiveFunctionChangeEventListener implements LocomotiveFunctionEventListener {
+
+ private final JCSCommandStation trackService;
+
+ LocomotiveFunctionChangeEventListener(JCSCommandStation trackService) {
+ this.trackService = trackService;
+ }
+
+ @Override
+ public void onFunctionChange(LocomotiveFunctionEvent functionEvent) {
+ FunctionBean fb = functionEvent.getFunctionBean();
+
+ FunctionBean dbfb = null;
+ String commandStationId = trackService.getDecoderController().getCommandStationBean().getId();
+
+ if ("marklin.cs".equals(commandStationId) || "esu-ecos".equals(commandStationId)) {
+ dbfb = PersistenceFactory.getService().getLocomotiveFunction(fb.getLocomotiveId(), fb.getNumber());
+ } else {
+ Integer address = fb.getLocomotiveId().intValue();
+
+ LocomotiveBean dblb = PersistenceFactory.getService().getLocomotive(address, DecoderType.get(fb.getDecoderTypeString()), fb.getCommandStationId());
+ if (dblb != null) {
+ dbfb = PersistenceFactory.getService().getLocomotiveFunction(dblb.getId(), fb.getNumber());
+ }
+ }
+
+ if (dbfb != null) {
+ if (!Objects.equals(dbfb.getValue(), fb.getValue())) {
+ dbfb.setValue(fb.getValue());
+ if (!dbfb.isMomentary()) {
+ PersistenceFactory.getService().persist(dbfb);
+ functionEvent.setFunctionBean(dbfb);
+ }
+ for (LocomotiveFunctionEventListener fl : trackService.locomotiveFunctionEventListeners) {
+ fl.onFunctionChange(functionEvent);
+ }
+ }
+ }
+ }
+ }
+
+ private class LocomotiveDirectionChangeEventListener implements LocomotiveDirectionEventListener {
+
+ private final JCSCommandStation trackService;
+
+ LocomotiveDirectionChangeEventListener(JCSCommandStation trackService) {
+ this.trackService = trackService;
+ }
+
+ @Override
+ public void onDirectionChange(LocomotiveDirectionEvent directionEvent) {
+ LocomotiveBean lb = directionEvent.getLocomotiveBean();
+ if (lb != null) {
+ LocomotiveBean dblb = null;
+ //For marklin and Ecos use the ID
+ if ("marklin.cs".equals(lb.getCommandStationId()) || "esu-ecos".equals(lb.getCommandStationId())) {
+ dblb = PersistenceFactory.getService().getLocomotive(lb.getId());
+ } else {
+ Integer address;
+ if (lb.getAddress() != null) {
+ address = lb.getAddress();
+ } else {
+ address = lb.getId().intValue();
+ }
+ if (lb.getDecoderType() != null) {
+ dblb = PersistenceFactory.getService().getLocomotive(address, lb.getDecoderType(), lb.getCommandStationId());
+ } else {
+ //Try to match one...
+ Set protocols = PersistenceFactory.getService().getDefaultCommandStation().getSupportedProtocols();
+ for (Protocol protocol : protocols) {
+ DecoderType decoder = DecoderType.get(protocol.getProtocol());
+ dblb = PersistenceFactory.getService().getLocomotive(address, decoder, lb.getCommandStationId());
+ if (dblb != null) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (dblb != null) {
+ if (!Objects.equals(dblb.getRichtung(), lb.getRichtung())) {
+ Integer richtung = lb.getRichtung();
+ dblb.setRichtung(richtung);
+ PersistenceFactory.getService().persist(dblb);
+
+ Logger.trace(dblb.getId() + ", " + dblb.getName() + ": " + dblb.getDirection().getDirection());
+
+ directionEvent.setLocomotiveBean(dblb);
+
+ for (LocomotiveDirectionEventListener dl : this.trackService.locomotiveDirectionEventListeners) {
+ dl.onDirectionChange(directionEvent);
+ }
+ }
+ } else {
+ Logger.trace("No loc found for " + lb.toLogString());
+ }
+ }
+ }
+ }
+
+ private class LocomotiveSpeedChangeEventListener implements LocomotiveSpeedEventListener {
+
+ private final JCSCommandStation trackService;
+
+ LocomotiveSpeedChangeEventListener(JCSCommandStation trackService) {
+ this.trackService = trackService;
+ }
+
+ @Override
+ public void onSpeedChange(LocomotiveSpeedEvent speedEvent) {
+ LocomotiveBean lb = speedEvent.getLocomotiveBean();
+ if (lb != null) {
+ LocomotiveBean dblb;
+ //For marklin and Ecos use the ID
+ if ("marklin.cs".equals(lb.getCommandStationId()) || "esu-ecos".equals(lb.getCommandStationId())) {
+ dblb = PersistenceFactory.getService().getLocomotive(lb.getId());
+ } else {
+ Integer address;
+ if (lb.getAddress() != null) {
+ address = lb.getAddress();
+ } else {
+ address = lb.getId().intValue();
+ }
+ dblb = PersistenceFactory.getService().getLocomotive(address, lb.getDecoderType(), lb.getCommandStationId());
+ }
+
+ if (dblb != null) {
+ Integer velocity = lb.getVelocity();
+ dblb.setVelocity(velocity);
+ PersistenceFactory.getService().persist(dblb);
+
+ speedEvent.setLocomotiveBean(dblb);
+ for (LocomotiveSpeedEventListener dl : trackService.locomotiveSpeedEventListeners) {
+ if (dl != null) {
+ dl.onSpeedChange(speedEvent);
+ }
+ }
+ } else {
+ if ("marklin.cs".equals(lb.getCommandStationId()) || "esu-ecos".equals(lb.getCommandStationId())) {
+ Logger.trace("No loc with id " + lb.getId() + ", " + lb.getCommandStationId());
+ } else {
+ Logger.trace("No loc found for " + lb.toLogString());
+ }
+ }
+ }
+ }
+ }
+
+ private class ConnectionListener implements ConnectionEventListener {
+
+ private final JCSCommandStation jcsCommandStation;
+
+ ConnectionListener(JCSCommandStation jcsCommandStation) {
+ this.jcsCommandStation = jcsCommandStation;
+ }
+
+ @Override
+ public void onConnectionChange(ConnectionEvent event) {
+ if (event.isConnected()) {
+ Logger.trace(event.getSource() + " has re-connected!");
+ } else {
+ Logger.trace(event.getSource() + " is Disconnected!");
+ //jcsCommandStationImpl.disconnect();
+ }
+ }
+ }
}
diff --git a/src/main/java/jcs/commandStation/JCSCommandStationImpl.java b/src/main/java/jcs/commandStation/JCSCommandStationImpl.java
deleted file mode 100755
index d0b0aa2e..00000000
--- a/src/main/java/jcs/commandStation/JCSCommandStationImpl.java
+++ /dev/null
@@ -1,909 +0,0 @@
-/*
- * Copyright 2023 Frans Jacobs.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jcs.commandStation;
-
-import java.awt.Image;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.stream.Collectors;
-import javax.imageio.ImageIO;
-import jcs.commandStation.events.AccessoryEvent;
-import jcs.commandStation.events.AccessoryEventListener;
-import jcs.commandStation.events.DisconnectionEvent;
-import jcs.commandStation.events.DisconnectionEventListener;
-import jcs.commandStation.events.LocomotiveDirectionEvent;
-import jcs.commandStation.events.LocomotiveDirectionEventListener;
-import jcs.commandStation.events.LocomotiveFunctionEvent;
-import jcs.commandStation.events.LocomotiveFunctionEventListener;
-import jcs.commandStation.events.LocomotiveSpeedEvent;
-import jcs.commandStation.events.LocomotiveSpeedEventListener;
-import jcs.commandStation.events.MeasurementEvent;
-import jcs.commandStation.events.MeasurementEventListener;
-import jcs.commandStation.events.PowerEventListener;
-import jcs.commandStation.events.SensorEvent;
-import jcs.commandStation.events.SensorEventListener;
-import jcs.entities.AccessoryBean;
-import jcs.entities.AccessoryBean.AccessoryValue;
-import jcs.entities.ChannelBean;
-import jcs.entities.CommandStationBean;
-import jcs.entities.CommandStationBean.Protocol;
-import jcs.entities.FunctionBean;
-import jcs.commandStation.entities.InfoBean;
-import jcs.entities.LocomotiveBean;
-import jcs.entities.LocomotiveBean.DecoderType;
-import jcs.entities.LocomotiveBean.Direction;
-import jcs.entities.SensorBean;
-import jcs.persistence.PersistenceFactory;
-import org.tinylog.Logger;
-
-/**
- * The JCSCommandStation is the layer between the UI, engines and Command stations
- */
-public class JCSCommandStationImpl implements JCSCommandStation {
-
- private DecoderController decoderController;
- private Map accessoryControllers;
- private Map feedbackControllers;
-
- private final List anonymousSensorListeners;
- //private final Map sensorEventListeners;
-
- private final List accessoryEventListeners;
- private final List LocomotiveFunctionEventListeners;
-
- private final List locomotiveDirectionEventListeners;
- private final List locomotiveSpeedEventListeners;
-
- private final List measurementEventListeners;
-
- private final Set supportedProtocols;
- private CommandStationBean commandStation;
-
- public JCSCommandStationImpl() {
- this("true".equalsIgnoreCase(System.getProperty("skip.controller.autoconnect", "true")));
- }
-
- private JCSCommandStationImpl(boolean autoConnectController) {
- accessoryControllers = new HashMap<>();
- feedbackControllers = new HashMap<>();
-
- anonymousSensorListeners = new LinkedList<>();
- accessoryEventListeners = new LinkedList<>();
- LocomotiveFunctionEventListeners = new LinkedList<>();
- locomotiveDirectionEventListeners = new LinkedList<>();
- locomotiveSpeedEventListeners = new LinkedList<>();
- measurementEventListeners = new LinkedList<>();
- supportedProtocols = new HashSet<>();
-
- try {
- if (autoConnectController && decoderController != null && decoderController.getCommandStationBean() != null || accessoryControllers.isEmpty() || feedbackControllers.isEmpty()) {
- connect();
- Logger.trace(decoderController != null ? "Aquired " + decoderController.getClass().getSimpleName() : "Could not aquire a Command Station! " + (decoderController.isConnected() ? "Connected" : "NOT Connected"));
- } else {
- Logger.trace("Auto Connect disabled");
- }
- } catch (Exception e) {
- Logger.warn("Can't connect with default Command Station!");
- }
- }
-
- @Override
- public final boolean connect() {
- //TODO revice the connect, to nices code and preventing duplicat instantiations...
- boolean decoderControllerConnected = false;
- boolean allreadyConnected = false;
-
- //Check if already connected to avoid duplication....
- if (commandStation != null && decoderController != null) {
- decoderControllerConnected = decoderController.isConnected();
- allreadyConnected = true;
- Logger.trace(decoderController.getClass().getName() + " allready connected...");
- } else {
- commandStation = PersistenceFactory.getService().getDefaultCommandStation();
- }
-
- int accessoryCntrConnected = 0;
- int feedbackCntrConnected = 0;
-
- if (commandStation == null) {
- Logger.error("No Default Command Station found!");
- return false;
- }
-
- if (decoderController == null && commandStation != null) {
- decoderController = ControllerFactory.getDecoderController(commandStation, false);
- }
-
- if (decoderController == null) {
- Logger.error("No DecoderController configured!");
- return false;
- }
-
- if (accessoryControllers.isEmpty()) {
- List acl = ControllerFactory.getAccessoryControllers();
- for (AccessoryController ac : acl) {
- accessoryControllers.put(ac.getCommandStationBean().getId(), ac);
- }
- }
-
- //TODO: a warning log in the main screen
- if (accessoryControllers.isEmpty()) {
- Logger.warn("No Accessory Controllers configured!");
- }
-
- if (feedbackControllers.isEmpty()) {
- List fcl = ControllerFactory.getFeedbackControllers();
- for (FeedbackController fc : fcl) {
- feedbackControllers.put(fc.getCommandStationBean().getId(), fc);
- }
- }
-
- //TODO: a warning log in the main screen
- if (feedbackControllers.isEmpty()) {
- Logger.warn("No Feedback Controllers configured!");
- }
-
- if (decoderController != null && !decoderControllerConnected) {
- decoderControllerConnected = decoderController.isConnected();
- if (!decoderControllerConnected) {
- decoderControllerConnected = decoderController.connect();
- }
- }
-
- //Connect the Accessories controllers if needed
- if (!accessoryControllers.isEmpty() && !allreadyConnected) {
- for (AccessoryController ac : accessoryControllers.values()) {
- if (ac.isConnected()) {
- accessoryCntrConnected++;
- } else {
- try {
- if (ac.connect()) {
- accessoryCntrConnected++;
- }
- } catch (Exception e) {
- Logger.warn(" Can't connected to " + ac.getCommandStationBean().getDescription());
- }
- }
- }
- }
-
- //Connect the Feedback Controllers controllers if needed
- if (!feedbackControllers.isEmpty() && !allreadyConnected) {
- for (FeedbackController fc : feedbackControllers.values()) {
- if (fc.isConnected()) {
- feedbackCntrConnected++;
- } else {
- try {
- if (fc.connect()) {
- feedbackCntrConnected++;
- }
- } catch (Exception e) {
- Logger.warn(" Can't connected to " + fc.getCommandStationBean().getDescription());
- }
- }
- }
- }
-
- Logger.trace("Connected Controllers: Decoder: " + (decoderControllerConnected ? "Yes" : "No") + " Accessory: " + accessoryCntrConnected + " Feedback: " + feedbackCntrConnected);
-
- if (decoderControllerConnected && !allreadyConnected) {
- decoderController.addDisconnectionEventListener(new DisconnectionListener(this));
-
- decoderController.addLocomotiveFunctionEventListener(new LocomotiveFunctionChangeEventListener(this));
- decoderController.addLocomotiveDirectionEventListener(new LocomotiveDirectionChangeEventListener(this));
- decoderController.addLocomotiveSpeedEventListener(new LocomotiveSpeedChangeEventListener(this));
-
- supportedProtocols.addAll(decoderController.getCommandStationBean().getSupportedProtocols());
-
- if (this.decoderController.isSupportTrackMeasurements()) {
- //Start the measurements background task
- long measureInterval = Long.parseLong(System.getProperty("track.measurements.interval", "5"));
- measureInterval = measureInterval * 1000;
-
- if (measureInterval > 0) {
- TrackMeasurementTask measurementTask = new TrackMeasurementTask(this);
- Timer timer = new Timer("Timer");
- timer.schedule(measurementTask, 0, measureInterval);
- Logger.debug("Started Track measurements with an interval of " + measureInterval + "s");
- } else {
- Logger.debug("Skipping Track measurements");
- }
- } else {
- Logger.debug("Track measurements are not supported");
- }
- }
-
- if (accessoryCntrConnected > 0 && !allreadyConnected) {
- for (AccessoryController ac : accessoryControllers.values()) {
- if (ac.isConnected()) {
- ac.addAccessoryEventListener(new AccessoryChangeEventListener(this));
- ac.addDisconnectionEventListener(new DisconnectionListener(this));
- }
- }
- }
-
- if (feedbackCntrConnected > 0 && !allreadyConnected) {
- for (FeedbackController fc : feedbackControllers.values()) {
- if (fc.isConnected()) {
- fc.addSensorEventListener(new SensorChangeEventListener(this));
- fc.addDisconnectionEventListener(new DisconnectionListener(this));
- }
- }
- }
-
- //TODO implement get the day end i.e. the current state of all Objects on track
- return decoderControllerConnected;
- }
-
- @Override
- public CommandStationBean getCommandStationBean() {
- if (this.decoderController != null) {
- return this.decoderController.getCommandStationBean();
- } else {
- return null;
- }
- }
-
- @Override
- public boolean isConnected() {
- if (decoderController != null) {
- return decoderController.isConnected();
- } else {
- return false;
- }
- }
-
- @Override
- public void disconnect() {
- for (FeedbackController fc : feedbackControllers.values()) {
- if (fc != decoderController) {
- fc.disconnect();
- }
- }
- for (AccessoryController ac : accessoryControllers.values()) {
- if (ac != decoderController) {
- ac.disconnect();
- }
- }
-
- if (decoderController != null) {
- decoderController.disconnect();
- }
-
- //Enable command station switching so
- this.decoderController = null;
- this.accessoryControllers.clear();
- this.feedbackControllers.clear();
-
- this.commandStation = null;
- ControllerFactory.reset();
- }
-
- @Override
- public void setVirtual(boolean flag) {
- Logger.info("Switch Virtual Mode " + (flag ? "On" : "Off"));
- this.decoderController.setVirtual(flag);
- }
-
- @Override
- public boolean isVirtual() {
- if (this.decoderController != null) {
- return this.decoderController.isVirtual();
- } else {
- return false;
- }
- }
-
- @Override
- public Image getLocomotiveImage(String imageName) {
- Image image = null;
-
- if (decoderController != null) {
- image = decoderController.getLocomotiveImage(imageName);
- if (image != null) {
- storeImage(image, imageName, true);
- }
- }
- return image;
- }
-
- @Override
- public Image getLocomotiveFunctionImage(String imageName) {
- Image image = null;
- if (decoderController != null) {
- image = decoderController.getLocomotiveFunctionImage(imageName);
- if (image != null) {
- storeImage(image, imageName, false);
- }
- }
- return image;
- }
-
- private void storeImage(Image image, String imageName, boolean locomotive) {
- Path path;
- String csp = null;
- if (decoderController != null) {
- csp = this.decoderController.getCommandStationBean().getLastUsedSerial();
- if (csp == null) {
- csp = this.decoderController.getCommandStationBean().getId();
- }
- }
-
- String basePath = System.getProperty("user.home") + File.separator + "jcs" + File.separator + "cache" + File.separator + csp;
-
- if (locomotive) {
- path = Paths.get(basePath);
- } else {
- path = Paths.get(basePath + File.separator + "functions");
- }
-
- File imageFile = new File(path + File.separator + imageName.toLowerCase() + ".png");
-
- try {
- if (!Files.exists(path)) {
- Files.createDirectories(path);
- Logger.trace("Created new directory " + path);
- }
- ImageIO.write((BufferedImage) image, "png", imageFile);
- } catch (IOException ex) {
- Logger.error("Can't store image " + imageFile + "! ", ex.getMessage());
- }
- Logger.trace("Stored image " + imageName + ".png in the cache");
- }
-
- @Override
- public InfoBean getCommandStationInfo() {
- if (this.decoderController != null && this.decoderController.getDevice() != null) {
- return this.decoderController.getCommandStationInfo();
- } else {
- return null;
- }
- }
-
- //@Override
- public String getCommandStationName() {
- if (decoderController != null) {
- if (decoderController.getDevice() != null) {
- return decoderController.getDevice().getName();
- } else {
- return decoderController.getCommandStationBean().getDescription();
- }
- } else {
- return null;
- }
- }
-
- //@Override
- public String getCommandStationSerialNumber() {
- if (decoderController != null && decoderController.getDevice() != null) {
- return decoderController.getDevice().getSerial();
- } else {
- return null;
- }
- }
-
- //@Override
- public String getCommandStationArticleNumber() {
- if (decoderController != null && decoderController.getDevice() != null) {
- return decoderController.getDevice().getArticleNumber();
- } else {
- return null;
- }
- }
-
- @Override
- public void switchPower(boolean on) {
- //Logger.trace("Switch Power " + (on ? "On" : "Off"));
- if (decoderController != null) {
- decoderController.power(on);
- }
- }
-
- @Override
- public boolean isPowerOn() {
- boolean power = false;
- if (decoderController != null) {
- power = decoderController.isPower();
- }
- return power;
- }
-
- @Override
- public void changeLocomotiveDirection(Direction newDirection, LocomotiveBean locomotive) {
- Logger.debug("Changing direction to " + newDirection + " for: " + locomotive.getName() + " id: " + locomotive.getId());
-
- int address;
- if (supportedProtocols.size() == 1) {
- address = locomotive.getAddress();
- } else {
- if (locomotive.getUid() != null) {
- address = locomotive.getUid().intValue();
- } else {
- address = locomotive.getId().intValue();
- }
- }
- if (decoderController != null) {
- //Set the velocity to zero before changing the direction
- //Run this in a worker thread...
-
- decoderController.changeVelocity(address, 0, locomotive.getDirection());
- decoderController.changeDirection(address, newDirection);
- }
- }
-
- @Override
- public void changeLocomotiveSpeed(Integer newVelocity, LocomotiveBean locomotive) {
- Logger.trace("Changing velocity to " + newVelocity + " for " + locomotive.getName());
- int address;
- if (supportedProtocols.size() == 1) {
- address = locomotive.getAddress();
- } else {
- if (locomotive.getUid() != null) {
- address = locomotive.getUid().intValue();
- } else {
- address = locomotive.getId().intValue();
- }
- }
- if (decoderController != null) {
- decoderController.changeVelocity(address, newVelocity, locomotive.getDirection());
- }
- }
-
- @Override
- public void changeLocomotiveFunction(Boolean newValue, Integer functionNumber, LocomotiveBean locomotive) {
- Logger.trace("Changing Function " + functionNumber + " to " + (newValue ? "on" : "off") + " on " + locomotive.getName());
- int address;
- if (this.supportedProtocols.size() == 1) {
- address = locomotive.getAddress();
- } else {
- if (locomotive.getUid() != null) {
- address = locomotive.getUid().intValue();
- } else {
- address = locomotive.getId().intValue();
- }
- }
- if (decoderController != null) {
- decoderController.changeFunctionValue(address, functionNumber, newValue);
- }
- }
-
- @Override
- public void switchAccessory(AccessoryBean accessory, AccessoryValue value) {
- Integer address = accessory.getAddress();
- Integer switchTime = accessory.getSwitchTime();
- AccessoryValue val = value;
- Integer states = accessory.getStates();
- Integer state = accessory.getState();
-
- if (states == null) {
- states = 2;
- }
- if (state == null) {
- state = AccessoryValue.RED == val ? 0 : 1;
- }
-
- if (states > 2) {
- if (accessory.getState() > 1) {
- address = address + 1;
- //val = AccessoryValue.cs3Get(state - 2);
- val = AccessoryValue.get(state - 2);
- }
- }
-
- Logger.trace("Change accessory with address: " + address + ", " + accessory.getName() + " to " + val.getValue());
-
- for (AccessoryController ac : accessoryControllers.values()) {
- ac.switchAccessory(address, val, switchTime);
- }
- }
-
- @Override
- public void addSensorEventListener(SensorEventListener listener) {
- this.anonymousSensorListeners.add(listener);
- }
-
- @Override
- public void removeSensorEventListener(SensorEventListener listener) {
- this.anonymousSensorListeners.remove(listener);
- }
-
- @Override
- public void addAccessoryEventListener(AccessoryEventListener listener) {
- this.accessoryEventListeners.add(listener);
- }
-
- @Override
- public void removeAccessoryEventListener(AccessoryEventListener listener) {
- this.accessoryEventListeners.remove(listener);
- }
-
- @Override
- public void addLocomotiveFunctionEventListener(LocomotiveFunctionEventListener listener) {
- this.LocomotiveFunctionEventListeners.add(listener);
- }
-
- @Override
- public void removeLocomotiveFunctionEventListener(LocomotiveFunctionEventListener listener) {
- this.LocomotiveFunctionEventListeners.remove(listener);
- }
-
- @Override
- public void addLocomotiveDirectionEventListener(LocomotiveDirectionEventListener listener) {
- this.locomotiveDirectionEventListeners.add(listener);
- }
-
- @Override
- public void removeLocomotiveDirectionEventListener(LocomotiveDirectionEventListener listener) {
- this.locomotiveDirectionEventListeners.remove(listener);
- }
-
- @Override
- public void addLocomotiveSpeedEventListener(LocomotiveSpeedEventListener listener) {
- this.locomotiveSpeedEventListeners.add(listener);
- }
-
- @Override
- public void removeLocomotiveSpeedEventListener(LocomotiveSpeedEventListener listener) {
- this.locomotiveSpeedEventListeners.remove(listener);
- }
-
- @Override
- public void addDisconnectionEventListener(DisconnectionEventListener listener) {
- if (this.decoderController != null) {
- this.decoderController.addDisconnectionEventListener(listener);
- }
- for (AccessoryController ac : this.accessoryControllers.values()) {
- if (ac != this.decoderController) {
- ac.addDisconnectionEventListener(listener);
- }
- }
-
- for (FeedbackController fc : this.feedbackControllers.values()) {
- if (fc != this.decoderController) {
- fc.addDisconnectionEventListener(listener);
- }
- }
- }
-
- @Override
- public void addPowerEventListener(PowerEventListener listener) {
- if (this.decoderController != null) {
- this.decoderController.addPowerEventListener(listener);
- }
- }
-
- @Override
- public void removePowerEventListener(PowerEventListener listener) {
- if (this.decoderController != null) {
- this.decoderController.removePowerEventListener(listener);
- }
- }
-
- @Override
- public void addMeasurementEventListener(MeasurementEventListener listener) {
- this.measurementEventListeners.add(listener);
- }
-
- @Override
- public void removeMeasurementListener(MeasurementEventListener listener) {
- this.measurementEventListeners.remove(listener);
- }
-
- @Override
- public DecoderController getDecoderController() {
- return decoderController;
- }
-
- @Override
- public List getAccessoryControllers() {
- return accessoryControllers.values().stream().collect(Collectors.toList());
- }
-
- @Override
- public List getFeedbackControllers() {
- return feedbackControllers.values().stream().collect(Collectors.toList());
- }
-
- private class TrackMeasurementTask extends TimerTask {
-
- private final JCSCommandStationImpl Controller;
- private boolean debuglog = false;
-
- TrackMeasurementTask(JCSCommandStationImpl Controller) {
- this.Controller = Controller;
- this.debuglog = System.getProperty("debug.measurements", "false").equalsIgnoreCase("true");
- }
-
- @Override
- public void run() {
- if (this.Controller != null && this.Controller.decoderController != null) {
- Map measurements = this.Controller.decoderController.getTrackMeasurements();
- for (ChannelBean ch : measurements.values()) {
- if (ch.isChanged()) {
- MeasurementEvent me = new MeasurementEvent(ch);
- if (debuglog) {
- Logger.trace("Changed Channel " + ch.getNumber() + ", " + ch.getName() + ": " + ch.getHumanValue() + " " + ch.getUnit());
- }
- for (MeasurementEventListener mel : this.Controller.measurementEventListeners) {
- mel.onMeasurement(me);
- }
- }
- }
- }
- }
- }
-
- private class SensorChangeEventListener implements SensorEventListener {
-
- private final JCSCommandStationImpl commandStation;
-
- SensorChangeEventListener(JCSCommandStationImpl commandStation) {
- this.commandStation = commandStation;
- }
-
- @Override
- public void onSensorChange(SensorEvent event) {
- SensorBean sb = event.getSensorBean();
- SensorBean dbsb = PersistenceFactory.getService().getSensor(sb.getDeviceId(), sb.getContactId());
-
- if (dbsb != null) {
- sb.setId(dbsb.getId());
- sb.setName(dbsb.getName());
- PersistenceFactory.getService().persist(sb);
- }
-
- //Avoid concurrent modification exceptions
- List snapshot = new ArrayList<>(commandStation.anonymousSensorListeners);
-
- for (SensorEventListener sl : snapshot) {
- if (sl != null) {
- sl.onSensorChange(event);
- }
- }
- }
- }
-
- private class AccessoryChangeEventListener implements AccessoryEventListener {
-
- private final JCSCommandStationImpl trackService;
-
- AccessoryChangeEventListener(JCSCommandStationImpl trackService) {
- this.trackService = trackService;
- }
-
- @Override
- public void onAccessoryChange(AccessoryEvent event) {
- AccessoryBean ab = event.getAccessoryBean();
-
- int address = ab.getAddress();
- String commandStationId = ab.getCommandStationId();
-
- AccessoryBean dbab = PersistenceFactory.getService().getAccessoryByAddressAndCommandStationId(address, commandStationId);
- if (dbab == null) {
- //check if address is even, might be the second address of a signal
- if (address % 2 == 0) {
- address = address - 1;
- dbab = PersistenceFactory.getService().getAccessoryByAddressAndCommandStationId(address, commandStationId);
- if (dbab != null && dbab.isSignal() && dbab.getStates() > 2) {
- ab.setAddress(address);
- int p = ab.getState() + 2;
- ab.setState(p);
- } else {
- dbab = null;
- }
- }
- }
-
- if (dbab != null) {
- //set all properties
- ab.setId(dbab.getId());
- ab.setDecoder(dbab.getDecoder());
- ab.setDecType(dbab.getDecType());
- ab.setName(dbab.getName());
- ab.setType(dbab.getType());
- ab.setGroup(dbab.getGroup());
- ab.setIcon(dbab.getIcon());
- ab.setIconFile(dbab.getIconFile());
- ab.setStates(dbab.getStates());
- ab.setCommandStationId(dbab.getCommandStationId());
- //might be set by the event
- if (ab.getSwitchTime() == null) {
- ab.setSwitchTime(dbab.getSwitchTime());
- }
-
- PersistenceFactory.getService().persist(ab);
-
- for (AccessoryEventListener al : this.trackService.accessoryEventListeners) {
- al.onAccessoryChange(event);
- }
- }
- }
- }
-
- private class LocomotiveFunctionChangeEventListener implements LocomotiveFunctionEventListener {
-
- private final JCSCommandStationImpl trackService;
-
- LocomotiveFunctionChangeEventListener(JCSCommandStationImpl trackService) {
- this.trackService = trackService;
- }
-
- @Override
- public void onFunctionChange(LocomotiveFunctionEvent functionEvent) {
- FunctionBean fb = functionEvent.getFunctionBean();
-
- FunctionBean dbfb = null;
- String commandStationId = trackService.getDecoderController().getCommandStationBean().getId();
-
- if ("marklin.cs".equals(commandStationId) || "esu-ecos".equals(commandStationId)) {
- dbfb = PersistenceFactory.getService().getLocomotiveFunction(fb.getLocomotiveId(), fb.getNumber());
- } else {
- Integer address = fb.getLocomotiveId().intValue();
-
- LocomotiveBean dblb = PersistenceFactory.getService().getLocomotive(address, DecoderType.get(fb.getDecoderTypeString()), fb.getCommandStationId());
- if (dblb != null) {
- dbfb = PersistenceFactory.getService().getLocomotiveFunction(dblb.getId(), fb.getNumber());
- }
- }
-
- if (dbfb != null) {
- if (!Objects.equals(dbfb.getValue(), fb.getValue())) {
- dbfb.setValue(fb.getValue());
- if (!dbfb.isMomentary()) {
- PersistenceFactory.getService().persist(dbfb);
- functionEvent.setFunctionBean(dbfb);
- }
- for (LocomotiveFunctionEventListener fl : trackService.LocomotiveFunctionEventListeners) {
- fl.onFunctionChange(functionEvent);
- }
- }
- }
- }
- }
-
- private class LocomotiveDirectionChangeEventListener implements LocomotiveDirectionEventListener {
-
- private final JCSCommandStationImpl trackService;
-
- LocomotiveDirectionChangeEventListener(JCSCommandStationImpl trackService) {
- this.trackService = trackService;
- }
-
- @Override
- public void onDirectionChange(LocomotiveDirectionEvent directionEvent) {
- LocomotiveBean lb = directionEvent.getLocomotiveBean();
- if (lb != null) {
- LocomotiveBean dblb = null;
- //For marklin and Ecos use the ID
- if ("marklin.cs".equals(lb.getCommandStationId()) || "esu-ecos".equals(lb.getCommandStationId())) {
- dblb = PersistenceFactory.getService().getLocomotive(lb.getId());
- } else {
- Integer address;
- if (lb.getAddress() != null) {
- address = lb.getAddress();
- } else {
- address = lb.getId().intValue();
- }
- if (lb.getDecoderType() != null) {
- dblb = PersistenceFactory.getService().getLocomotive(address, lb.getDecoderType(), lb.getCommandStationId());
- } else {
- //Try to match one...
- Set protocols = PersistenceFactory.getService().getDefaultCommandStation().getSupportedProtocols();
- for (Protocol protocol : protocols) {
- DecoderType decoder = DecoderType.get(protocol.getProtocol());
- dblb = PersistenceFactory.getService().getLocomotive(address, decoder, lb.getCommandStationId());
- if (dblb != null) {
- break;
- }
- }
- }
- }
-
- if (dblb != null) {
- if (!Objects.equals(dblb.getRichtung(), lb.getRichtung())) {
- Integer richtung = lb.getRichtung();
- dblb.setRichtung(richtung);
- PersistenceFactory.getService().persist(dblb);
-
- Logger.trace(dblb.getId() + ", " + dblb.getName() + ": " + dblb.getDirection().getDirection());
-
- directionEvent.setLocomotiveBean(dblb);
-
- for (LocomotiveDirectionEventListener dl : this.trackService.locomotiveDirectionEventListeners) {
- dl.onDirectionChange(directionEvent);
- }
- }
- } else {
- Logger.trace("No loc found for " + lb.toLogString());
- }
- }
- }
- }
-
- private class LocomotiveSpeedChangeEventListener implements LocomotiveSpeedEventListener {
-
- private final JCSCommandStationImpl trackService;
-
- LocomotiveSpeedChangeEventListener(JCSCommandStationImpl trackService) {
- this.trackService = trackService;
- }
-
- @Override
- public void onSpeedChange(LocomotiveSpeedEvent speedEvent) {
- LocomotiveBean lb = speedEvent.getLocomotiveBean();
- if (lb != null) {
- LocomotiveBean dblb;
- //For marklin and Ecos use the ID
- if ("marklin.cs".equals(lb.getCommandStationId()) || "esu-ecos".equals(lb.getCommandStationId())) {
- dblb = PersistenceFactory.getService().getLocomotive(lb.getId());
- } else {
- Integer address;
- if (lb.getAddress() != null) {
- address = lb.getAddress();
- } else {
- address = lb.getId().intValue();
- }
- dblb = PersistenceFactory.getService().getLocomotive(address, lb.getDecoderType(), lb.getCommandStationId());
- }
-
- if (dblb != null) {
- Integer velocity = lb.getVelocity();
- dblb.setVelocity(velocity);
- PersistenceFactory.getService().persist(dblb);
-
- speedEvent.setLocomotiveBean(dblb);
- for (LocomotiveSpeedEventListener dl : trackService.locomotiveSpeedEventListeners) {
- if (dl != null) {
- dl.onSpeedChange(speedEvent);
- }
- }
- } else {
- Logger.trace("No loc found for " + lb.toLogString());
- }
- }
- }
- }
-
- private class DisconnectionListener implements DisconnectionEventListener {
-
- private final JCSCommandStationImpl jcsCommandStationImpl;
-
- DisconnectionListener(JCSCommandStationImpl jcsCommandStationImpl) {
- this.jcsCommandStationImpl = jcsCommandStationImpl;
- }
-
- @Override
- public void onDisconnect(DisconnectionEvent event) {
- Logger.trace(event.getSource() + " is Disconnected!");
- jcsCommandStationImpl.disconnect();
- }
- }
-
-}
diff --git a/src/main/java/jcs/commandStation/autopilot/ActionCommandHandler.java b/src/main/java/jcs/commandStation/autopilot/ActionCommandHandler.java
new file mode 100644
index 00000000..62195505
--- /dev/null
+++ b/src/main/java/jcs/commandStation/autopilot/ActionCommandHandler.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2025 fransjacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.autopilot;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import org.tinylog.Logger;
+
+/**
+ *
+ * A Handler to Monitor the AutoPilot engine.
+ *
+ */
+public class ActionCommandHandler extends Thread {
+
+ private boolean stop = false;
+ private boolean quit = true;
+
+ private final ConcurrentLinkedQueue eventQueue;
+
+ ActionCommandHandler(ConcurrentLinkedQueue eventQueue) {
+ this.eventQueue = eventQueue;
+ }
+
+ void quit() {
+ this.quit = true;
+ }
+
+ boolean isRunning() {
+ return !this.quit;
+ }
+
+ boolean isFinished() {
+ return this.stop;
+ }
+
+ @Override
+ public void run() {
+ quit = false;
+ setName("AUTOPILOT-COMMAND-HANDLER");
+
+ Logger.trace("AutoPilot ActionCommandHandler Started...");
+
+ while (isRunning()) {
+ try {
+ AutoPilotActionEvent event = eventQueue.poll();
+ if (event != null) {
+ switch (event.getActionCommand()) {
+ case "start" -> {
+ AutoPilot.startAutoMode();
+ }
+ case "stop" -> {
+ AutoPilot.stopAutoMode();
+ }
+ case "startLocomotive" -> {
+ AutoPilot.startDispatcher(event.getLocomotiveBean());
+ }
+ case "stopLocomotive" -> {
+ AutoPilot.stopDispatcher(event.getLocomotiveBean());
+ }
+ case "startAllLocomotives" -> {
+ AutoPilot.startLocomotives();
+ }
+ case "removeLocomotive" -> {
+ AutoPilot.removeDispatcher(event.getLocomotiveBean());
+ }
+ case "addLocomotive" -> {
+ AutoPilot.addDispatcher(event.getLocomotiveBean());
+ }
+ case "reset" -> {
+ AutoPilot.resetStates();
+ }
+ }
+ } else {
+ //lets sleep for a while
+ synchronized (this) {
+ wait(10000);
+ }
+ }
+
+ } catch (InterruptedException ex) {
+ Logger.error(ex);
+ }
+ }
+
+ stop = true;
+ Logger.trace("Tile ActionEventHandler Stopped...");
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/autopilot/AutoPilot.java b/src/main/java/jcs/commandStation/autopilot/AutoPilot.java
index 32b22ac2..ec5a5670 100644
--- a/src/main/java/jcs/commandStation/autopilot/AutoPilot.java
+++ b/src/main/java/jcs/commandStation/autopilot/AutoPilot.java
@@ -23,8 +23,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;
import java.util.stream.Collectors;
import jcs.JCS;
@@ -37,67 +36,107 @@
import jcs.entities.RouteBean;
import jcs.entities.SensorBean;
import jcs.persistence.PersistenceFactory;
-import jcs.ui.layout.events.TileEvent;
-import jcs.ui.layout.tiles.TileFactory;
+import jcs.ui.layout.tiles.Tile;
+import jcs.ui.layout.tiles.TileCache;
import org.tinylog.Logger;
/**
- *
- * @author frans
+ * The AutoPilot is the "automatic driving engine".
+ * Every Locomotive on the track will start it own Thread.
+ * The Dispatcher is run in this Thread.
+ * The AutoPilot has it own Monitor Thread: AutoPilotMonitorThread.
*
*/
public final class AutoPilot {
- private static AutoPilotThread autoPilotThread = null;
+ private static AutoPilotMonitorThread autoPilotThread = null;
private static CommandStationBean commandStationBean;
- //private final Map sensorHandlers = Collections.synchronizedMap(new HashMap<>());
- private static final Map sensorHandlers = new HashMap<>();
- //private final Map dispatchers = Collections.synchronizedMap(new HashMap<>());
+ private static final Map sensorHandlers = new HashMap<>();
private static final Map dispatchers = new HashMap<>();
//Need a list to be able to unregister
private static final List autoPilotStatusListeners = Collections.synchronizedList(new ArrayList<>());
private static final Semaphore semaphore = new Semaphore(1);
- private static final ExecutorService executor = Executors.newCachedThreadPool();
private static final ThreadGroup autoPilotRunners = new ThreadGroup("AUTOPILOT");
+ private static final ConcurrentLinkedQueue actionCommandQueue = new ConcurrentLinkedQueue();
+
+ private static final ActionCommandHandler actionCommandHandler = new ActionCommandHandler(actionCommandQueue);
+
+ static {
+ actionCommandHandler.start();
+ }
+
private AutoPilot() {
}
public static void runAutoPilot(boolean flag) {
if (flag) {
- executor.execute(() -> startAutoMode());
+ enqueCommand(new AutoPilotActionEvent("start"));
} else {
- executor.execute(() -> stopAutoMode());
+ enqueCommand(new AutoPilotActionEvent("stop"));
+ }
+ }
+
+ public static void startLocomotive(LocomotiveBean locomotiveBean) {
+ enqueCommand(new AutoPilotActionEvent("startLocomotive", locomotiveBean));
+ }
+
+ public static void stopLocomotive(LocomotiveBean locomotiveBean) {
+ enqueCommand(new AutoPilotActionEvent("stopLocomotive", locomotiveBean));
+ }
+
+ public static void removeLocomotive(LocomotiveBean locomotiveBean) {
+ enqueCommand(new AutoPilotActionEvent("removeLocomotive", locomotiveBean));
+ }
+
+ public static void addLocomotive(LocomotiveBean locomotiveBean) {
+ enqueCommand(new AutoPilotActionEvent("addLocomotive", locomotiveBean));
+ }
+
+ public static void startAllLocomotives() {
+ enqueCommand(new AutoPilotActionEvent("startAllLocomotives"));
+ }
+
+ public static void reset() {
+ enqueCommand(new AutoPilotActionEvent("reset"));
+ }
+
+ private static void enqueCommand(AutoPilotActionEvent command) {
+ actionCommandQueue.offer(command);
+ synchronized (AutoPilot.actionCommandHandler) {
+ actionCommandHandler.notifyAll();
}
}
- public synchronized static void startAutoMode() {
+ static boolean startAutoMode() {
if (JCS.getJcsCommandStation().isPowerOn()) {
if (autoPilotThread != null && autoPilotThread.isRunning()) {
Logger.trace("Allready running");
+ return true;
} else {
commandStationBean = JCS.getJcsCommandStation().getCommandStationBean();
dispatchers.clear();
sensorHandlers.clear();
- autoPilotThread = new AutoPilotThread(autoPilotRunners);
+ autoPilotThread = new AutoPilotMonitorThread(autoPilotRunners);
autoPilotThread.start();
+ Logger.debug("AutoMode Started");
+ return true;
}
} else {
- Logger.warn("Can't start Automode is Power is Off!");
+ Logger.warn("Can't start Automode, Command Station Power is Off!");
+ return false;
}
}
- public static void stopAutoMode() {
+ static void stopAutoMode() {
if (autoPilotThread != null) {
- autoPilotThread.stopAutoMode();
-
- //Notify all dispachers so thath the ones which waiting and Idle will stop
+ //Notify all dispachers so that the ones which waiting and Idle will stop
Set snapshot = new HashSet<>(dispatchers.values());
for (Dispatcher d : snapshot) {
d.stopLocomotiveAutomode();
@@ -106,12 +145,16 @@ public static void stopAutoMode() {
}
}
+ autoPilotThread.stopAutoMode();
+
try {
autoPilotThread.join();
} catch (InterruptedException ex) {
Logger.error("Interruppted during join " + ex);
}
}
+ Logger.debug("AutoMode Stopped");
+
}
public static boolean isAutoModeActive() {
@@ -139,7 +182,7 @@ public static boolean isRunning(LocomotiveBean locomotive) {
}
}
- public static boolean areDispatchersRunning() {
+ public static boolean isADispatcherRunning() {
boolean isRunning = false;
Set snapshot = new HashSet<>(dispatchers.values());
for (Dispatcher ld : snapshot) {
@@ -162,7 +205,7 @@ public static int getRunningDispatcherCount() {
return runningDispatchers;
}
- public static synchronized Dispatcher createDispatcher(LocomotiveBean locomotiveBean) {
+ static Dispatcher createDispatcher(LocomotiveBean locomotiveBean) {
Dispatcher dispatcher = null;
//check if the locomotive is on track
if (isOnTrack(locomotiveBean)) {
@@ -180,6 +223,32 @@ public static synchronized Dispatcher createDispatcher(LocomotiveBean locomotive
return dispatcher;
}
+ static void removeDispatcher(LocomotiveBean locomotiveBean) {
+ if (dispatchers.containsKey(locomotiveBean.getName())) {
+ Dispatcher dispatcher = dispatchers.remove(locomotiveBean.getName());
+ Logger.trace("removing Dispatcher for locomotive " + locomotiveBean.getName());
+ if (dispatcher.isRunning()) {
+ Logger.trace("Stopping Automode for " + locomotiveBean.getName() + "...");
+ dispatcher.stopLocomotiveAutomode();
+ dispatcher.stopRunning();
+ }
+
+ dispatcher.removeAllStateEventListeners();
+
+ for (AutoPilotStatusListener asl : autoPilotStatusListeners) {
+ asl.statusChanged(autoPilotThread.running);
+ }
+ }
+ }
+
+ static void addDispatcher(LocomotiveBean locomotiveBean) {
+ createDispatcher(locomotiveBean);
+
+ for (AutoPilotStatusListener asl : autoPilotStatusListeners) {
+ asl.statusChanged(autoPilotThread.running);
+ }
+ }
+
public static void prepareAllDispatchers() {
Logger.trace("Preparing Dispatchers for all on track locomotives...");
List locs = getOnTrackLocomotives();
@@ -192,7 +261,7 @@ public static void prepareAllDispatchers() {
if (snapshot.containsKey(loc.getName())) {
dispatcher = snapshot.get(loc.getName());
dispatchers.put(loc.getName(), dispatcher);
- Logger.trace("Reused dispatcher for " + loc.getName() + "...");
+ Logger.trace("Re use dispatcher " + loc.getName() + "...");
} else {
createDispatcher(loc);
}
@@ -200,7 +269,7 @@ public static void prepareAllDispatchers() {
}
public static synchronized void clearDispatchers() {
- Logger.trace("Remove all Dispatchers...");
+ Logger.trace("Removing all Dispatchers...");
for (Dispatcher dispatcher : dispatchers.values()) {
dispatcher.stopLocomotiveAutomode();
@@ -209,32 +278,42 @@ public static synchronized void clearDispatchers() {
dispatchers.clear();
}
- public static void startStopLocomotive(LocomotiveBean locomotiveBean, boolean start) {
- Logger.trace((start ? "Starting" : "Stopping") + " auto drive for " + locomotiveBean.getName());
+ static void startDispatcher(LocomotiveBean locomotiveBean) {
+ Logger.trace("Starting locomotive for " + locomotiveBean.getName());
String key = locomotiveBean.getName();
- if (start) {
- Dispatcher dispatcher;
- if (dispatchers.containsKey(key)) {
- dispatcher = dispatchers.get(key);
- Logger.trace("Dispatcher " + key + " exists");
- } else {
- dispatcher = createDispatcher(locomotiveBean);
- Logger.trace("Dispatcher " + key + " created");
- }
+ Dispatcher dispatcher;
+ if (dispatchers.containsKey(key)) {
+ dispatcher = dispatchers.get(key);
+ //Logger.trace("Dispatcher " + key + " exists");
+ } else {
+ dispatcher = createDispatcher(locomotiveBean);
+ Logger.trace("Dispatcher " + key + " created");
+ }
- if (!dispatcher.isRunning()) {
- Logger.trace("Starting dispatcher thread" + key);
- dispatcher.startLocomotiveAutomode();
- }
+ if (!dispatcher.isRunning()) {
+ Logger.trace("Starting dispatcher thread " + key);
+ dispatcher.startLocomotiveAutomode();
+ }
- Logger.trace("Started dispatcher" + key + " automode...");
- } else {
- Dispatcher dispatcher = dispatchers.get(key);
- if (dispatcher != null && dispatcher.isRunning()) {
- dispatcher.stopLocomotiveAutomode();
- Logger.trace("Stopped dispatcher" + key + " automode...");
- }
+ Logger.trace("Started locomotive " + key + "...");
+ }
+
+ static void stopDispatcher(LocomotiveBean locomotiveBean) {
+ Logger.trace("Stopping locomotive " + locomotiveBean.getName());
+ String key = locomotiveBean.getName();
+
+ Dispatcher dispatcher = dispatchers.get(key);
+ if (dispatcher != null && dispatcher.isRunning()) {
+ dispatcher.stopLocomotiveAutomode();
+ Logger.trace("Stopped locomotive " + key + "...");
+ }
+ }
+
+ static void startLocomotives() {
+ List locos = getOnTrackLocomotives();
+ for (LocomotiveBean loc : locos) {
+ startDispatcher(loc);
}
}
@@ -254,24 +333,11 @@ public static synchronized void resetDispatcher(LocomotiveBean locomotiveBean) {
dispatcher.reset();
}
- private static void startStopAllLocomotivesInBackground(boolean start) {
- List onTrackLocos = getOnTrackLocomotives();
- Logger.trace((start ? "Starting" : "Stopping") + " automode for " + onTrackLocos.size() + " ontrack locomotives...");
-
- for (LocomotiveBean locomotiveBean : onTrackLocos) {
- startStopLocomotive(locomotiveBean, start);
- }
- }
-
- public static void startAllLocomotives() {
- executor.execute(() -> startStopAllLocomotivesInBackground(true));
- }
+ static void resetStates() {
+ Logger.trace("Resetting AutoPilot...");
- public static void stopAllLocomotives() {
- executor.execute(() -> startStopAllLocomotivesInBackground(false));
- }
+ stopAutoMode();
- public static void resetStates() {
List routes = PersistenceFactory.getService().getRoutes();
int lockedCounter = 0;
for (RouteBean route : routes) {
@@ -289,6 +355,7 @@ public static void resetStates() {
int freeBlockCounter = 0;
List blocks = PersistenceFactory.getService().getBlocks();
for (BlockBean block : blocks) {
+ Tile tile = TileCache.findTile(block.getTileId());
if (block.getLocomotiveId() != null) {
if (null == block.getBlockState()) {
if (BlockBean.BlockState.OCCUPIED == block.getBlockState()) {
@@ -298,35 +365,32 @@ public static void resetStates() {
switch (block.getBlockState()) {
case LOCKED, INBOUND -> {
//destinations block, reset!
- block.setLocomotive(null);
- block.setBlockState(BlockBean.BlockState.FREE);
- block.setArrivalSuffix(null);
+ tile.setLocomotive(null);
+ tile.setBlockState(BlockBean.BlockState.FREE);
+ tile.setArrivalSuffix(null);
freeBlockCounter++;
}
case OUTBOUND -> {
- block.setBlockState(BlockBean.BlockState.OCCUPIED);
- block.setArrivalSuffix(null);
+ tile.setBlockState(BlockBean.BlockState.OCCUPIED);
+ tile.setArrivalSuffix(null);
occupiedBlockCounter++;
}
default -> {
if (BlockBean.BlockState.OCCUPIED == block.getBlockState()) {
- block.setArrivalSuffix(null);
+ tile.setArrivalSuffix(null);
occupiedBlockCounter++;
}
}
}
}
} else {
- block.setBlockState(BlockBean.BlockState.FREE);
+ tile.setBlockState(BlockBean.BlockState.FREE);
freeBlockCounter++;
}
- PersistenceFactory.getService().persist(block);
- TileEvent tileEvent = new TileEvent(block);
- TileFactory.fireTileEventListener(tileEvent);
+ PersistenceFactory.getService().persist(tile.getBlockBean());
}
JCS.getJcsCommandStation().switchPower(true);
-
Logger.debug("Occupied blocks: " + occupiedBlockCounter + " Free blocks " + freeBlockCounter + " of total " + blocks.size() + " blocks");
}
@@ -360,21 +424,26 @@ public static List getOnTrackLocomotives() {
List occupiedBlocks = blocks.stream().filter(t -> t.getLocomotive() != null && t.getLocomotive().getId() != null).collect(Collectors.toList());
//Logger.trace("There " + (occupiedBlocks.size() == 1 ? "is" : "are") + " " + occupiedBlocks.size() + " occupied block(s)");
- Set activeLocomotives = new HashSet<>();
+ //Set activeLocomotives = new HashSet<>();
+ ArrayList activeLocomotives = new ArrayList<>();
for (BlockBean occupiedBlock : occupiedBlocks) {
LocomotiveBean dbl = PersistenceFactory.getService().getLocomotive(occupiedBlock.getLocomotiveId());
if (dbl != null) {
- activeLocomotives.add(dbl);
+ if (activeLocomotives.contains(dbl)) {
+ Logger.warn("Loc " + dbl.getName() + " Is allready in the list! ");
+ } else {
+ activeLocomotives.add(dbl);
+ }
}
}
- //if (Logger.isDebugEnabled()) {
- // Logger.trace("There are " + activeLocomotives.size() + " Locomotives on the track: ");
- for (LocomotiveBean loc : activeLocomotives) {
- Logger.trace(loc);
- }
+ //if (Logger.isTraceEnabled()) {
+ //Logger.trace("There are " + activeLocomotives.size() + " Locomotives on the track: ");
+ //for (LocomotiveBean loc : activeLocomotives) {
+ // Logger.trace(loc);
+ //}
//}
- return new ArrayList<>(activeLocomotives);
+ return activeLocomotives; //new ArrayList<>(activeLocomotives);
}
public static boolean isGostDetected() {
@@ -388,44 +457,43 @@ public static boolean isGostDetected() {
}
private static void handleGhost(SensorEvent event) {
- Logger.trace("Check for possible Ghost! @ Sensor " + event.getId());
+ Logger.trace("Check for possible Ghost! @ Sensor " + event.getSensorId());
List blocks = PersistenceFactory.getService().getBlocks();
- String sensorId = event.getId();
+ Integer sensorId = event.getSensorId();
for (BlockBean block : blocks) {
+ Tile tile = TileCache.findTile(block.getTileId());
+
if ((block.getMinSensorId().equals(sensorId) || block.getPlusSensorId().equals(sensorId)) && block.getLocomotiveId() == null) {
if (event.getSensorBean().isActive()) {
block.setBlockState(BlockBean.BlockState.GHOST);
+ tile.setBlockState(BlockBean.BlockState.GHOST);
//Also persist
PersistenceFactory.getService().persist(block);
-
Logger.warn("Ghost Detected! @ Sensor " + sensorId + " in block " + block.getId());
//Switch power OFF!
JCS.getJcsCommandStation().switchPower(false);
-
- TileEvent tileEvent = new TileEvent(block);
- TileFactory.fireTileEventListener(tileEvent);
} else {
if (block.getLocomotiveId() != null) {
//keep state as is
} else {
block.setBlockState(BlockBean.BlockState.FREE);
+ tile.setBlockState(BlockBean.BlockState.FREE);
}
}
break;
}
}
-
}
static void handleSensorEvent(SensorEvent event) {
if (event.isChanged()) {
- SensorEventHandler sh = sensorHandlers.get(event.getId());
- Boolean registered = sh != null; //sensorHandlers.containsKey(event.getId());
- Logger.trace((registered ? "Registered " : "") + event.getId() + " has changed " + event.isChanged());
+ SensorEventHandler sh = sensorHandlers.get(event.getSensorId());
+ Boolean registered = sh != null;
+
+ Logger.trace((registered ? "Registered " : "") + event.getSensorId() + " has changed " + event.isChanged());
if (sh != null) {
//there is a handler registered for this id, pass the event through
- //SensorEventHandler sh = sensorHandlers.get(event.getId());
sh.handleEvent(event);
} else {
//sensor is not registered and thus not expected!
@@ -440,11 +508,11 @@ public static synchronized void addSensorEventHandler(SensorEventHandler handler
sensorHandlers.put(handler.getSensorId(), handler);
}
- public static boolean isSensorHandlerRegistered(String sensorId) {
+ public static boolean isSensorHandlerRegistered(Integer sensorId) {
return sensorHandlers.containsKey(sensorId);
}
- public static synchronized void removeHandler(String sensorId) {
+ public static synchronized void removeHandler(Integer sensorId) {
sensorHandlers.remove(sensorId);
}
@@ -470,15 +538,15 @@ public static int avialablePermits() {
return semaphore.availablePermits();
}
- private static class AutoPilotThread extends Thread {
+ private static class AutoPilotMonitorThread extends Thread {
private final List sensorListeners = new ArrayList<>();
private boolean running = false;
private boolean stopped = false;
- AutoPilotThread(ThreadGroup parent) {
- super(parent, "AUTO_MAIN");
+ AutoPilotMonitorThread(ThreadGroup parent) {
+ super(parent, "AUTOPILOT-MONITOR");
}
void stopAutoMode() {
@@ -499,7 +567,7 @@ private void registerAllSensors() {
List sensors = PersistenceFactory.getService().getAssignedSensors();
int cnt = 0;
for (SensorBean sb : sensors) {
- String key = sb.getId();
+ Integer key = sb.getId();
if (!sensorHandlers.containsKey(key)) {
SensorListener seh = new SensorListener(key);
sensorListeners.add(seh);
@@ -527,7 +595,7 @@ public void run() {
registerAllSensors();
prepareAllDispatchers();
- Logger.trace("Autopilot Started. Notify " + autoPilotStatusListeners.size() + " Listeners...");
+ Logger.trace("Autopilot Started. There are " + dispatchers.size() + " Dispatchers created...");
for (AutoPilotStatusListener asl : autoPilotStatusListeners) {
asl.statusChanged(running);
@@ -549,7 +617,7 @@ public void run() {
long start = now;
long timeout = now + 30000;
//Check if all dispachers are stopped
- boolean dispatchersRunning = areDispatchersRunning();
+ boolean dispatchersRunning = isADispatcherRunning();
Logger.trace("Try to finish all dispatchers. There are " + getRunningDispatcherCount() + " Dispatchers running...");
@@ -564,7 +632,7 @@ public void run() {
}
while (dispatchersRunning && now < timeout) {
- dispatchersRunning = areDispatchersRunning();
+ dispatchersRunning = isADispatcherRunning();
try {
synchronized (this) {
wait(10);
@@ -604,15 +672,15 @@ boolean isStopped() {
private static class SensorListener implements SensorEventListener {
- private final String sensorId;
+ private final Integer sensorId;
- SensorListener(String sensorId) {
+ SensorListener(Integer sensorId) {
this.sensorId = sensorId;
}
@Override
public void onSensorChange(SensorEvent event) {
- if (sensorId.equals(event.getId())) {
+ if (sensorId.equals(event.getSensorId())) {
AutoPilot.handleSensorEvent(event);
}
}
diff --git a/src/main/java/jcs/commandStation/autopilot/AutoPilotActionEvent.java b/src/main/java/jcs/commandStation/autopilot/AutoPilotActionEvent.java
new file mode 100644
index 00000000..9360dd09
--- /dev/null
+++ b/src/main/java/jcs/commandStation/autopilot/AutoPilotActionEvent.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2025 fransjacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.autopilot;
+
+import jcs.entities.LocomotiveBean;
+
+/**
+ *
+ * @author fransjacobs
+ */
+public class AutoPilotActionEvent {
+
+ private final String actionCommand;
+ private final LocomotiveBean locomotiveBean;
+
+ AutoPilotActionEvent(String actionCommand) {
+ this(actionCommand, null);
+ }
+
+ AutoPilotActionEvent(String actionCommand, LocomotiveBean locomotiveBean) {
+ this.actionCommand = actionCommand;
+ this.locomotiveBean = locomotiveBean;
+ }
+
+ String getActionCommand() {
+ return actionCommand;
+ }
+
+ LocomotiveBean getLocomotiveBean() {
+ return locomotiveBean;
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/autopilot/DriveSimulator.java b/src/main/java/jcs/commandStation/autopilot/DriveSimulator.java
index c9e2a371..eb32c7c7 100644
--- a/src/main/java/jcs/commandStation/autopilot/DriveSimulator.java
+++ b/src/main/java/jcs/commandStation/autopilot/DriveSimulator.java
@@ -16,14 +16,11 @@
package jcs.commandStation.autopilot;
import java.util.List;
-import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import jcs.JCS;
-import jcs.commandStation.AccessoryController;
import jcs.commandStation.FeedbackController;
-import jcs.commandStation.GenericController;
import jcs.commandStation.autopilot.state.Dispatcher;
import jcs.commandStation.events.SensorEvent;
import jcs.entities.LocomotiveBean;
@@ -54,22 +51,22 @@ public void simulateDriving(int locUid, int speed, LocomotiveBean.Direction dire
if (dispatcher.isLocomotiveAutomodeOn()) {
Logger.trace("Try to simulate the next sensor of " + dispatcher.getName());
- String occupationSensorId = dispatcher.getOccupationSensorId();
+ Integer occupationSensorId = dispatcher.getOccupationSensorId();
if (occupationSensorId != null) {
//Start a timer which execute a worker thread which fires the sensor
scheduledExecutor.schedule(() -> setSensorValue(occupationSensorId, false), 500, TimeUnit.MILLISECONDS);
}
- String exitSensorId = dispatcher.getExitSensorId();
+ Integer exitSensorId = dispatcher.getExitSensorId();
if (exitSensorId != null) {
//Start a time which execute a worker thread which fires the sensor
scheduledExecutor.schedule(() -> setSensorValue(exitSensorId, false), 1500, TimeUnit.MILLISECONDS);
}
- String enterSensorId = dispatcher.getEnterSensorId();
- String inSensorId = dispatcher.getInSensorId();
+ Integer enterSensorId = dispatcher.getEnterSensorId();
+ Integer inSensorId = dispatcher.getInSensorId();
- String sensorId = dispatcher.getWaitingForSensorId();
+ Integer sensorId = dispatcher.getWaitingForSensorId();
int time = 5000;
if (sensorId != null && sensorId.equals(enterSensorId)) {
@@ -89,7 +86,7 @@ public void simulateDriving(int locUid, int speed, LocomotiveBean.Direction dire
}
}
- private void setSensorValue(String sensorId, boolean active) {
+ private void setSensorValue(Integer sensorId, boolean active) {
SensorBean sensor = PersistenceFactory.getService().getSensor(sensorId);
sensor.setActive(active);
SensorEvent sensorEvent = new SensorEvent(sensor);
diff --git a/src/main/java/jcs/commandStation/autopilot/ExpectedSensorEventHandler.java b/src/main/java/jcs/commandStation/autopilot/ExpectedSensorEventHandler.java
index 4befc51e..d71e182c 100644
--- a/src/main/java/jcs/commandStation/autopilot/ExpectedSensorEventHandler.java
+++ b/src/main/java/jcs/commandStation/autopilot/ExpectedSensorEventHandler.java
@@ -23,23 +23,23 @@
*/
public class ExpectedSensorEventHandler implements SensorEventHandler {
- private final String sensorId;
+ private final Integer sensorId;
private final Dispatcher dispatcher;
- public ExpectedSensorEventHandler(String sensorId, Dispatcher dispatcher) {
+ public ExpectedSensorEventHandler(Integer sensorId, Dispatcher dispatcher) {
this.sensorId = sensorId;
this.dispatcher = dispatcher;
}
@Override
public void handleEvent(SensorEvent event) {
- if (this.sensorId.equals(event.getId())) {
+ if (this.sensorId.equals(event.getSensorId())) {
this.dispatcher.onIgnoreEvent(event);
}
}
@Override
- public String getSensorId() {
+ public Integer getSensorId() {
return this.sensorId;
}
diff --git a/src/main/java/jcs/commandStation/autopilot/SensorEventHandler.java b/src/main/java/jcs/commandStation/autopilot/SensorEventHandler.java
index fb711173..362a8ed8 100644
--- a/src/main/java/jcs/commandStation/autopilot/SensorEventHandler.java
+++ b/src/main/java/jcs/commandStation/autopilot/SensorEventHandler.java
@@ -25,5 +25,5 @@ public interface SensorEventHandler {
void handleEvent(SensorEvent event);
- String getSensorId();
+ Integer getSensorId();
}
diff --git a/src/main/java/jcs/commandStation/autopilot/state/Dispatcher.java b/src/main/java/jcs/commandStation/autopilot/state/Dispatcher.java
index 8c982a21..019ea6b2 100644
--- a/src/main/java/jcs/commandStation/autopilot/state/Dispatcher.java
+++ b/src/main/java/jcs/commandStation/autopilot/state/Dispatcher.java
@@ -32,13 +32,13 @@
import jcs.entities.RouteElementBean;
import jcs.entities.TileBean;
import jcs.persistence.PersistenceFactory;
-import jcs.ui.layout.events.TileEvent;
-import jcs.ui.layout.tiles.TileFactory;
+import jcs.ui.layout.tiles.TileCache;
+import jcs.ui.layout.tiles.Tile;
import org.tinylog.Logger;
/**
* The Dispatcher is the controlling class during auto mode of one Locomotive
- * When a Locomotive runs a separate stateMachineThread is started which handles all the states.
+ * When a Locomotive runs a separate stateMachine is started which handles all the states.
*
*/
public class Dispatcher {
@@ -50,22 +50,22 @@ public class Dispatcher {
private String departureBlockId;
private String destinationBlockId;
- private String waitingForSensorId;
+ private Integer waitingForSensorId;
//Enter Sensor of the destination
- private String enterSensorId;
+ private Integer enterSensorId;
//In Sensor of the destination
- private String inSensorId;
+ private Integer inSensorId;
//The Occupation sensor of the departure
- private String occupationSensorId;
+ private Integer occupationSensorId;
//The exit of the departure
- private String exitSensorId;
+ private Integer exitSensorId;
private final List stateEventListeners;
private final ThreadGroup parent;
- private StateMachineThread stateMachineThread;
+ private StateMachine stateMachine;
public Dispatcher(ThreadGroup parent, LocomotiveBean locomotiveBean) {
this.parent = parent;
@@ -73,11 +73,11 @@ public Dispatcher(ThreadGroup parent, LocomotiveBean locomotiveBean) {
//Prefill with the current locomotive direction
this.locomotiveBean.setDispatcherDirection(locomotiveBean.getDirection());
this.stateEventListeners = new LinkedList<>();
- this.stateMachineThread = new StateMachineThread(parent, this);
+ this.stateMachine = new StateMachine(parent, this);
}
- StateMachineThread getStateMachineThread() {
- return stateMachineThread;
+ StateMachine getStateMachine() {
+ return stateMachine;
}
public Long getId() {
@@ -108,45 +108,45 @@ void setRouteBean(RouteBean routeBean) {
}
public boolean isLocomotiveAutomodeOn() {
- return this.stateMachineThread.isEnableAutomode();
+ return stateMachine.isAutomodeEnabled();
}
public boolean startLocomotiveAutomode() {
//Only when the Autopilot is ON!
if (AutoPilot.isAutoModeActive()) {
- stateMachineThread.setEnableAutomode(true);
+ stateMachine.setEnableAutomode(true);
//is the thread running?
startRunning();
}
- return this.stateMachineThread.isEnableAutomode();
+ return this.stateMachine.isAutomodeEnabled();
}
public void stopLocomotiveAutomode() {
- stateMachineThread.setEnableAutomode(false);
+ stateMachine.setEnableAutomode(false);
}
void startRunning() {
- if (this.stateMachineThread != null && this.stateMachineThread.isThreadRunning()) {
+ if (this.stateMachine != null && this.stateMachine.isThreadRunning()) {
return;
}
- if (this.stateMachineThread == null || !this.stateMachineThread.isAlive()) {
- stateMachineThread = new StateMachineThread(this.parent, this);
+ if (this.stateMachine == null || !this.stateMachine.isAlive()) {
+ stateMachine = new StateMachine(this.parent, this);
}
- this.stateMachineThread.setEnableAutomode(true);
- if (!this.stateMachineThread.isThreadRunning()) {
- this.stateMachineThread.start();
+ this.stateMachine.setEnableAutomode(true);
+ if (!this.stateMachine.isThreadRunning()) {
+ this.stateMachine.start();
}
}
public void stopRunning() {
- if (stateMachineThread != null && stateMachineThread.isThreadRunning()) {
- stateMachineThread.stopRunningThread();
+ if (stateMachine != null && stateMachine.isThreadRunning()) {
+ stateMachine.stopRunningThread();
try {
Logger.trace(this.getName() + " Thread Joining...");
- stateMachineThread.join();
+ stateMachine.join();
} catch (InterruptedException ex) {
Logger.trace("Join error " + ex);
}
@@ -168,13 +168,13 @@ void resetDispatcher() {
public void reset() {
Logger.trace("Resetting dispatcher " + getName() + " StateMachine...");
- this.stateMachineThread.reset();
+ this.stateMachine.reset();
resetDispatcher();
}
public boolean isRunning() {
- if (stateMachineThread != null) {
- return stateMachineThread.isThreadRunning();
+ if (stateMachine != null) {
+ return stateMachine.isThreadRunning();
} else {
return false;
}
@@ -189,7 +189,7 @@ BlockBean getDepartureBlock() {
return PersistenceFactory.getService().getBlockByTileId(departureBlockId);
} else {
BlockBean departureBlock = PersistenceFactory.getService().getBlockByLocomotiveId(locomotiveBean.getId());
- this.departureBlockId = departureBlock.getTileId();
+ departureBlockId = departureBlock.getTileId();
return departureBlock;
}
}
@@ -206,59 +206,59 @@ BlockBean getDestinationBlock() {
}
public String getStateName() {
- if (stateMachineThread != null) {
- return stateMachineThread.getDispatcherStateName();
+ if (stateMachine != null) {
+ return stateMachine.getDispatcherStateName();
} else {
return "#Idle";
}
}
- void setWaitForSensorid(String sensorId) {
+ void setWaitForSensorid(Integer sensorId) {
this.waitingForSensorId = sensorId;
}
- public String getWaitingForSensorId() {
+ public Integer getWaitingForSensorId() {
return waitingForSensorId;
}
- public String getEnterSensorId() {
+ public Integer getEnterSensorId() {
return enterSensorId;
}
- void setEnterSensorId(String enterSensorId) {
+ void setEnterSensorId(Integer enterSensorId) {
this.enterSensorId = enterSensorId;
}
- public String getInSensorId() {
+ public Integer getInSensorId() {
return inSensorId;
}
- void setInSensorId(String inSensorId) {
+ void setInSensorId(Integer inSensorId) {
this.inSensorId = inSensorId;
}
- public String getOccupationSensorId() {
+ public Integer getOccupationSensorId() {
return occupationSensorId;
}
- void setOccupationSensorId(String occupationSensorId) {
+ void setOccupationSensorId(Integer occupationSensorId) {
this.occupationSensorId = occupationSensorId;
}
- public String getExitSensorId() {
+ public Integer getExitSensorId() {
return exitSensorId;
}
- void setExitSensorId(String exitSensorId) {
+ void setExitSensorId(Integer exitSensorId) {
this.exitSensorId = exitSensorId;
}
void clearDepartureIgnoreEventHandlers() {
if (departureBlockId != null) {
BlockBean departureBlock = getDepartureBlock();
- String minSensorId = departureBlock.getMinSensorId();
+ Integer minSensorId = departureBlock.getMinSensorId();
AutoPilot.removeHandler(minSensorId);
- String plusSensorId = departureBlock.getPlusSensorId();
+ Integer plusSensorId = departureBlock.getPlusSensorId();
AutoPilot.removeHandler(plusSensorId);
}
}
@@ -266,32 +266,31 @@ void clearDepartureIgnoreEventHandlers() {
public void onIgnoreEvent(SensorEvent event) {
//Only in Simulator mode
if (JCS.getJcsCommandStation().getCommandStationBean().isVirtual()) {
- if (this.waitingForSensorId != null && this.waitingForSensorId.equals(event.getId())) {
+ if (this.waitingForSensorId != null && this.waitingForSensorId.equals(event.getSensorId())) {
if (event.isActive()) {
this.waitingForSensorId = null;
}
}
- if (this.enterSensorId != null && this.enterSensorId.equals(event.getId())) {
+ if (this.enterSensorId != null && this.enterSensorId.equals(event.getSensorId())) {
if (!event.isActive()) {
this.enterSensorId = null;
}
}
- if (this.inSensorId != null && this.inSensorId.equals(event.getId())) {
+ if (this.inSensorId != null && this.inSensorId.equals(event.getSensorId())) {
if (!event.isActive()) {
this.inSensorId = null;
}
}
- if (this.exitSensorId != null && this.exitSensorId.equals(event.getId())) {
+ if (this.exitSensorId != null && this.exitSensorId.equals(event.getSensorId())) {
if (!event.isActive()) {
this.exitSensorId = null;
}
}
-
}
- //Logger.trace("Event for a ignored listener: " + event.getId() + " Changed: " + event.isChanged() + ", active: " + event.getSensorBean().isActive());
+ //Logger.trace("Event for a ignored listener: " + event.getIdString() + " Changed: " + event.isChanged() + ", active: " + event.getSensorBean().isActive());
}
synchronized void switchAccessory(AccessoryBean accessory, AccessoryValue value) {
@@ -308,7 +307,7 @@ synchronized void changeLocomotiveDirection(LocomotiveBean locomotive, Direction
locomotiveBean.setDirection(newDirection);
}
- void fireStateListeners(String s) {
+ public void fireStateListeners(String s) {
for (StateEventListener sel : stateEventListeners) {
sel.onStateChange(this);
}
@@ -322,36 +321,68 @@ public void removeStateEventListener(StateEventListener listener) {
stateEventListeners.remove(listener);
}
+ public void removeAllStateEventListeners() {
+ stateEventListeners.clear();
+ }
+
public static void resetRoute(RouteBean route) {
List routeElements = route.getRouteElements();
for (RouteElementBean re : routeElements) {
String tileId = re.getTileId();
- TileEvent tileEvent = new TileEvent(tileId, false);
- TileFactory.fireTileEventListener(tileEvent);
+ Tile tile = TileCache.findTile(tileId);
+ if (tile != null) {
+ if (tile.isBlock()) {
+ if (tile.getLocomotive() != null) {
+ tile.setBlockState(BlockBean.BlockState.OCCUPIED);
+ } else {
+ tile.setBlockState(BlockBean.BlockState.FREE);
+ }
+ }
+ if (tile.isJunction()) {
+ tile.setRouteValue(AccessoryBean.AccessoryValue.OFF);
+ }
+ tile.setShowRoute(false);
+ } else {
+ Logger.warn(("Tile with id " + tileId + " NOT in TileCache!"));
+ }
}
}
void showBlockState(BlockBean blockBean) {
- Logger.trace("Show block " + blockBean);
- TileEvent tileEvent = new TileEvent(blockBean);
- TileFactory.fireTileEventListener(tileEvent);
+ Tile tile = TileCache.findTile(blockBean.getTileId());
+ if (tile != null) {
+ tile.setBlockBean(blockBean);
+ }
}
void showRoute(RouteBean routeBean, Color routeColor) {
Logger.trace("Show route " + routeBean.toLogString());
List routeElements = routeBean.getRouteElements();
+
for (RouteElementBean re : routeElements) {
String tileId = re.getTileId();
- TileBean.Orientation incomingSide = re.getIncomingOrientation();
-
- TileEvent tileEvent;
- if (re.isTurnout()) {
- AccessoryBean.AccessoryValue routeState = re.getAccessoryValue();
- tileEvent = new TileEvent(tileId, true, incomingSide, routeState, routeColor);
+ Tile tile = TileCache.findTile(tileId);
+ if (tile != null) {
+ TileBean.Orientation incomingSide = re.getIncomingOrientation();
+
+ tile.setIncomingSide(incomingSide);
+ tile.setTrackRouteColor(Tile.DEFAULT_ROUTE_TRACK_COLOR);
+
+ if (re.isTurnout()) {
+ AccessoryBean.AccessoryValue routeState = re.getAccessoryValue();
+ tile.setRouteValue(routeState);
+ } else if (re.isBlock()) {
+ if (re.getTileId().equals(routeBean.getFromTileId())) {
+ //departure block
+ tile.setBlockState(BlockBean.BlockState.OUTBOUND);
+ } else {
+ tile.setBlockState(BlockBean.BlockState.INBOUND);
+ }
+ }
+ tile.setShowRoute(true);
} else {
- tileEvent = new TileEvent(tileId, true, incomingSide, routeColor);
+ Logger.warn("Tile with id " + tileId + " NOT in TileCache!");
}
- TileFactory.fireTileEventListener(tileEvent);
}
}
diff --git a/src/main/java/jcs/commandStation/autopilot/state/EnterBlockState.java b/src/main/java/jcs/commandStation/autopilot/state/EnterBlockState.java
index 1d21e6aa..bf72abfd 100644
--- a/src/main/java/jcs/commandStation/autopilot/state/EnterBlockState.java
+++ b/src/main/java/jcs/commandStation/autopilot/state/EnterBlockState.java
@@ -31,8 +31,7 @@ class EnterBlockState extends DispatcherState implements SensorEventListener {
private boolean locomotiveBraking = false;
private boolean canAdvanceToNextState = false;
- private String inSensorId;
- //private Dispatcher dispatcher;
+ private Integer inSensorId;
@Override
DispatcherState execute(Dispatcher dispatcher) {
@@ -53,7 +52,6 @@ DispatcherState execute(Dispatcher dispatcher) {
dispatcher.setWaitForSensorid(inSensorId);
//Register this state as a SensorEventListener
- //this.dispatcher = dispatcher;
JCS.getJcsCommandStation().addSensorEventListener(this);
Logger.trace("Destination block " + destinationBlock.getId() + " In SensorId: " + inSensorId);
@@ -67,19 +65,16 @@ DispatcherState execute(Dispatcher dispatcher) {
destinationBlock.setBlockState(BlockBean.BlockState.INBOUND);
PersistenceFactory.getService().persist(departureBlock);
- dispatcher.showBlockState(departureBlock);
+ PersistenceFactory.getService().persist(destinationBlock);
+ dispatcher.showBlockState(departureBlock);
dispatcher.showRoute(route, Color.magenta);
-
- PersistenceFactory.getService().persist(destinationBlock);
dispatcher.showBlockState(destinationBlock);
//Switch the departure block sensors on again
- //dispatcher.clearDepartureIgnoreEventHandlers();
-
- //dispatcher.setOccupationSensorId(null);
+ //dispatcher.clearDepartureIgnoreEventHandlers();
+ //dispatcher.setOccupationSensorId(null);
//dispatcher.setExitSensorId(null);
-
Logger.trace("Now Waiting for the IN event from SensorId: " + this.inSensorId + " Running loco: " + locomotive.getName() + " [" + locomotive.getDecoderType().getDecoderType() + " (" + locomotive.getAddress() + ")] Direction: " + locomotive.getDirection().getDirection() + " current velocity: " + locomotive.getVelocity());
}
@@ -107,15 +102,14 @@ DispatcherState execute(Dispatcher dispatcher) {
@Override
public void onSensorChange(SensorEvent sensorEvent) {
- if (this.inSensorId.equals(sensorEvent.getId())) {
+ if (this.inSensorId.equals(sensorEvent.getSensorId())) {
if (sensorEvent.isActive()) {
this.canAdvanceToNextState = true;
- Logger.trace("In Event from Sensor " + sensorEvent.getId());
+ Logger.trace("In Event from Sensor " + sensorEvent.getSensorId());
synchronized (this) {
this.notifyAll();
}
}
}
}
-
}
diff --git a/src/main/java/jcs/commandStation/autopilot/state/IdleState.java b/src/main/java/jcs/commandStation/autopilot/state/IdleState.java
index df5b5dbb..4ec46479 100644
--- a/src/main/java/jcs/commandStation/autopilot/state/IdleState.java
+++ b/src/main/java/jcs/commandStation/autopilot/state/IdleState.java
@@ -59,6 +59,19 @@ DispatcherState execute(Dispatcher dispatcher) {
} catch (InterruptedException ex) {
Logger.trace("Interrupted: " + ex.getMessage());
}
+ } else {
+ //Locomotive is stopped...
+ Logger.trace(dispatcher.getName() + " Automode: " + AutoPilot.isAutoModeActive() + " Dispacher " + dispatcher.isLocomotiveAutomodeOn());
+
+ //stop this thread...
+ try {
+ synchronized (this) {
+ wait(10000);
+ }
+ } catch (InterruptedException ex) {
+ Logger.trace("Interrupted: " + ex.getMessage());
+ }
+
}
}
return this;
diff --git a/src/main/java/jcs/commandStation/autopilot/state/PrepareRouteState.java b/src/main/java/jcs/commandStation/autopilot/state/PrepareRouteState.java
index c23b62e2..b129986a 100644
--- a/src/main/java/jcs/commandStation/autopilot/state/PrepareRouteState.java
+++ b/src/main/java/jcs/commandStation/autopilot/state/PrepareRouteState.java
@@ -76,7 +76,7 @@ boolean searchRoute(Dispatcher dispatcher) {
Logger.trace("Search a free route for " + locomotive.getName() + "...");
BlockBean departureBlock = dispatcher.getDepartureBlock();
- if(departureBlock.getLogicalDirection() == null) {
+ if (departureBlock.getLogicalDirection() == null) {
departureBlock.setLogicalDirection(locomotive.getDirection().getDirection());
}
Direction logicalDirection = Direction.get(departureBlock.getLogicalDirection());
@@ -103,7 +103,7 @@ boolean searchRoute(Dispatcher dispatcher) {
Direction newDirection = LocomotiveBean.toggle(oldDirection);
Logger.trace("Reversing Locomotive, from " + oldDirection + " to " + newDirection + "...");
- this.swapLocomotiveDirection = true;
+ swapLocomotiveDirection = true;
//Do NOT persist the direction yet, just test....
//locomotive.setDispatcherDirection(newDirection);
departureBlock.setLogicalDirection(newDirection.getDirection());
@@ -153,7 +153,7 @@ boolean searchRoute(Dispatcher dispatcher) {
route = checkedRoutes.get(rIdx);
Logger.trace("Choosen route " + route.toLogString());
//persist the departure block
- PersistenceFactory.getService().persist(departureBlock);
+ PersistenceFactory.getService().persist(departureBlock);
} else {
Logger.debug("No route available for " + locomotive.getName() + " ...");
if (swapLocomotiveDirection) {
@@ -223,7 +223,7 @@ boolean reserveRoute(Dispatcher dispatcher) {
//Are playing a role.
//On the departure side we have the OccupiedSensor, ie the IN sensor when arriving.
//The exit sensor i.e the last sensor to leave the departure block.
- String occupancySensorId, exitSensorId;
+ Integer occupancySensorId, exitSensorId;
if ("+".equals(departureSuffix)) {
occupancySensorId = departureBlock.getMinSensorId();
exitSensorId = departureBlock.getPlusSensorId();
@@ -236,7 +236,7 @@ boolean reserveRoute(Dispatcher dispatcher) {
//On the destination side we have the enterSensor end the IN sensor.
//From which side on the block is the train expected to arrive?
- String enterSensorId, inSensorId;
+ Integer enterSensorId, inSensorId;
if ("+".equals(arrivalSuffix)) {
enterSensorId = destinationBlock.getPlusSensorId();
inSensorId = destinationBlock.getMinSensorId();
@@ -256,7 +256,7 @@ boolean reserveRoute(Dispatcher dispatcher) {
if (swapLocomotiveDirection) {
//Direction newDir = locomotive.getDispatcherDirection();
- Direction newDir = Direction.get(departureBlock.getLogicalDirection());
+ Direction newDir = Direction.get(departureBlock.getLogicalDirection());
Logger.trace("Changing Direction to " + newDir);
dispatcher.changeLocomotiveDirection(locomotive, newDir);
}
diff --git a/src/main/java/jcs/commandStation/autopilot/state/StartState.java b/src/main/java/jcs/commandStation/autopilot/state/StartState.java
index e39a7a3b..7e9ad095 100644
--- a/src/main/java/jcs/commandStation/autopilot/state/StartState.java
+++ b/src/main/java/jcs/commandStation/autopilot/state/StartState.java
@@ -32,7 +32,7 @@ class StartState extends DispatcherState implements SensorEventListener {
private boolean locomotiveStarted = false;
private boolean canAdvanceToNextState = false;
- private String enterSensorId;
+ private Integer enterSensorId;
private Dispatcher dispatcher;
@Override
@@ -42,8 +42,8 @@ DispatcherState execute(Dispatcher dispatcher) {
BlockBean departureBlock = dispatcher.getDepartureBlock();
BlockBean destinationBlock = dispatcher.getDestinationBlock();
- String occupancySensorId = dispatcher.getOccupationSensorId();
- String exitSensorId = dispatcher.getExitSensorId();
+ Integer occupancySensorId = dispatcher.getOccupationSensorId();
+ Integer exitSensorId = dispatcher.getExitSensorId();
//Register them both to ignore event form these sensors.
ExpectedSensorEventHandler osh = new ExpectedSensorEventHandler(occupancySensorId, dispatcher);
@@ -112,10 +112,10 @@ DispatcherState execute(Dispatcher dispatcher) {
@Override
public void onSensorChange(SensorEvent sensorEvent) {
- if (enterSensorId.equals(sensorEvent.getId())) {
+ if (enterSensorId.equals(sensorEvent.getSensorId())) {
if (sensorEvent.isActive()) {
canAdvanceToNextState = true;
- Logger.trace("Enter Event from Sensor " + sensorEvent.getId());
+ Logger.trace("Enter Event from Sensor " + sensorEvent.getSensorId());
synchronized (this) {
this.notifyAll();
}
diff --git a/src/main/java/jcs/commandStation/autopilot/state/StateMachineThread.java b/src/main/java/jcs/commandStation/autopilot/state/StateMachine.java
similarity index 92%
rename from src/main/java/jcs/commandStation/autopilot/state/StateMachineThread.java
rename to src/main/java/jcs/commandStation/autopilot/state/StateMachine.java
index a15defc5..2c3aeb39 100644
--- a/src/main/java/jcs/commandStation/autopilot/state/StateMachineThread.java
+++ b/src/main/java/jcs/commandStation/autopilot/state/StateMachine.java
@@ -41,33 +41,33 @@
*
* @author frans
*/
-class StateMachineThread extends Thread {
-
+class StateMachine extends Thread {
+
private final Dispatcher dispatcher;
private DispatcherState dispatcherState;
-
+
private boolean running = false;
-
+
private boolean enableAutomode = false;
private boolean resetRequested = false;
-
- StateMachineThread(ThreadGroup parent, Dispatcher dispatcher) {
- super(parent, "STM->" + dispatcher.getLocomotiveBean().getName());
+
+ StateMachine(ThreadGroup parent, Dispatcher dispatcher) {
+ super(parent, "STM->" + dispatcher.getLocomotiveBean().getName().toUpperCase());
this.dispatcher = dispatcher;
this.dispatcherState = new IdleState();
}
-
+
boolean isThreadRunning() {
return this.running;
}
-
+
synchronized void stopRunningThread() {
this.running = false;
this.enableAutomode = false;
notifyAll();
}
-
- boolean isEnableAutomode() {
+
+ boolean isAutomodeEnabled() {
return this.enableAutomode;
}
@@ -82,46 +82,56 @@ synchronized void reset() {
//Switch of running
this.enableAutomode = false;
this.resetRequested = true;
-
+
Logger.trace("Reset requested...");
if (running) {
notifyAll();
}
}
-
+
DispatcherState getDispatcherState() {
return dispatcherState;
}
-
+
String getDispatcherStateName() {
return dispatcherState.getName();
}
-
+
void handleState() {
//Obtain current dispatcherState
DispatcherState previousState = dispatcherState;
//Execute the action for the current dispatcherState
dispatcherState = dispatcherState.execute(dispatcher);
-
+
if ("true".equals(System.getProperty("state.machine.stepTest", "false"))) {
Logger.debug("Current State: " + dispatcherState.getName() + " Previous State: " + previousState.getName());
}
-
+
if (!AutoPilot.isAutoModeActive()) {
//Automode has stopped, let the Thread finish when WaitState is reached or is Idle
if (dispatcherState instanceof IdleState || dispatcherState instanceof WaitState) {
Logger.trace(getName() + " Stopping thread as Autopilot automode is stopped");
this.running = false;
//Just stop
- synchronized(this) {
+ synchronized (this) {
+ this.notifyAll();
+ }
+ }
+ }
+
+ if (!dispatcher.isLocomotiveAutomodeOn()) {
+ if (dispatcherState instanceof IdleState) {
+ Logger.trace(getName() + " Stopping thread as Locomotive " + dispatcher.getName() + " has stopped...");
+ this.running = false;
+ synchronized (this) {
this.notifyAll();
}
}
}
-
+
if (previousState != dispatcherState || dispatcherState instanceof WaitState) {
//handle the dispatcherState changes
- Logger.trace("Fire for "+dispatcherState.getName());
+ Logger.trace("Fire for " + dispatcherState.getName());
dispatcher.fireStateListeners(dispatcherState.getName());
}
@@ -145,7 +155,7 @@ void resetStateMachine() {
Logger.trace("Removing " + sensorEventListener.toString() + " as Sensor Listener...");
JCS.getJcsCommandStation().removeSensorEventListener(sensorEventListener);
}
-
+
dispatcher.clearDepartureIgnoreEventHandlers();
//Restore the Departure block state
@@ -158,7 +168,7 @@ void resetStateMachine() {
destinationBlock.setLocomotive(null);
destinationBlock.setArrivalSuffix(null);
destinationBlock.setReverseArrival(false);
-
+
PersistenceFactory.getService().persist(departureBlock);
PersistenceFactory.getService().persist(destinationBlock);
@@ -166,15 +176,15 @@ void resetStateMachine() {
RouteBean route = dispatcher.getRouteBean();
route.setLocked(false);
Dispatcher.resetRoute(route);
-
+
PersistenceFactory.getService().persist(route);
dispatcher.setRouteBean(null);
-
+
dispatcher.showBlockState(departureBlock);
dispatcher.showBlockState(destinationBlock);
-
+
dispatcherState = new IdleState();
-
+
this.resetRequested = false;
}
@@ -200,7 +210,7 @@ public void run() {
this.running = true;
Logger.trace(getName() + " Started. State " + dispatcherState.getName() + "...");
}
-
+
while (running) {
if (resetRequested) {
resetStateMachine();
@@ -208,7 +218,7 @@ public void run() {
handleState();
}
}
-
+
Logger.trace(getName() + " in state " + dispatcherState.getClass().getSimpleName() + " is ending...");
//Make sure that also the linked locomotive is stopped
@@ -223,19 +233,14 @@ public void run() {
} else {
Logger.trace("No sensor listeners are registered...");
}
-
+
dispatcher.clearDepartureIgnoreEventHandlers();
Logger.trace("Ignore Handlers removed...");
-
+
dispatcher.fireStateListeners(getName() + " Finished");
Logger.trace(getName() + " State listeners fired...");
-
+
Logger.trace(getName() + " last state " + dispatcherState.getClass().getSimpleName() + " is Finished");
}
-
-// synchronized void sensorUpdated(final SensorEvent sensorEvent) {
-// Logger.trace("Sensor " + sensorEvent.getId() + " Value " + (sensorEvent.isActive() ? "On" : "Off"));
-// this.notifyAll();
-// }
-
+
}
diff --git a/src/main/java/jcs/commandStation/dccex/DccExCommandStationImpl.java b/src/main/java/jcs/commandStation/dccex/DccExCommandStationImpl.java
index acc797ca..7f292924 100644
--- a/src/main/java/jcs/commandStation/dccex/DccExCommandStationImpl.java
+++ b/src/main/java/jcs/commandStation/dccex/DccExCommandStationImpl.java
@@ -17,9 +17,7 @@
import java.awt.Image;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.Executors;
import jcs.JCS;
import jcs.commandStation.AbstractController;
@@ -29,10 +27,10 @@
import jcs.commandStation.dccex.connection.DccExMessageListener;
import jcs.commandStation.dccex.events.CabEvent;
import jcs.commandStation.dccex.events.DccExMeasurementEvent;
+import jcs.commandStation.entities.Device;
import jcs.commandStation.events.AccessoryEvent;
import jcs.commandStation.events.AccessoryEventListener;
-import jcs.commandStation.events.DisconnectionEvent;
-import jcs.commandStation.events.DisconnectionEventListener;
+import jcs.commandStation.events.ConnectionEvent;
import jcs.commandStation.events.LocomotiveDirectionEvent;
import jcs.commandStation.events.LocomotiveDirectionEventListener;
import jcs.commandStation.events.LocomotiveFunctionEvent;
@@ -43,34 +41,31 @@
import jcs.commandStation.events.PowerEventListener;
import jcs.entities.AccessoryBean;
import jcs.entities.AccessoryBean.AccessoryValue;
-import jcs.entities.ChannelBean;
import jcs.entities.CommandStationBean;
import jcs.entities.CommandStationBean.ConnectionType;
-import jcs.commandStation.entities.DeviceBean;
import jcs.entities.FunctionBean;
import jcs.commandStation.entities.InfoBean;
import jcs.entities.LocomotiveBean;
import jcs.entities.LocomotiveBean.Direction;
-import jcs.util.KeyValuePair;
import jcs.util.SerialPortUtil;
import org.tinylog.Logger;
+import jcs.commandStation.events.ConnectionEventListener;
public class DccExCommandStationImpl extends AbstractController implements DecoderController, AccessoryController {
private DccExConnection connection;
private InfoBean infoBean;
- private DeviceBean mainDevice;
+ //private DeviceBean mainDevice;
private boolean powerStatusSet = false;
- Map measurementChannels;
-
+ //Map measurementChannels;
public DccExCommandStationImpl(CommandStationBean commandStationBean) {
this(commandStationBean, false);
}
public DccExCommandStationImpl(CommandStationBean commandStationBean, boolean autoConnect) {
super(autoConnect, commandStationBean);
- measurementChannels = new HashMap<>();
+ //measurementChannels = new HashMap<>();
this.executor = Executors.newCachedThreadPool();
if (commandStationBean != null) {
@@ -83,26 +78,25 @@ public DccExCommandStationImpl(CommandStationBean commandStationBean, boolean au
}
}
- private void initMeasurements() {
- //Prepare the measurements as far as I know DCC-EX has only track current for both PROG and MAIN
- String response = connection.sendMessage(DccExMessageFactory.trackManagerConfigRequest());
- if (response != null) {
- DccExMeasurementEvent crq = new DccExMeasurementEvent(response);
- handleMeasurement(crq);
- }
- response = connection.sendMessage(DccExMessageFactory.maxCurrentRequest());
- if (response != null) {
- DccExMeasurementEvent mcrq = new DccExMeasurementEvent(response);
- handleMeasurement(mcrq);
- }
-
- response = this.connection.sendMessage(DccExMessageFactory.currentStatusRequest());
- if (response != null) {
- DccExMeasurementEvent csrq = new DccExMeasurementEvent(response);
- handleMeasurement(csrq);
- }
- }
-
+// private void initMeasurements() {
+// //Prepare the measurements as far as I know DCC-EX has only track current for both PROG and MAIN
+// String response = connection.sendMessage(DccExMessageFactory.trackManagerConfigRequest());
+// if (response != null) {
+// DccExMeasurementEvent crq = new DccExMeasurementEvent(response);
+// handleMeasurement(crq);
+// }
+// response = connection.sendMessage(DccExMessageFactory.maxCurrentRequest());
+// if (response != null) {
+// DccExMeasurementEvent mcrq = new DccExMeasurementEvent(response);
+// handleMeasurement(mcrq);
+// }
+//
+// response = this.connection.sendMessage(DccExMessageFactory.currentStatusRequest());
+// if (response != null) {
+// DccExMeasurementEvent csrq = new DccExMeasurementEvent(response);
+// handleMeasurement(csrq);
+// }
+// }
@Override
public final boolean connect() {
if (!connected) {
@@ -165,51 +159,49 @@ public final boolean connect() {
long start = now;
timeout = now + (conType == ConnectionType.NETWORK ? 200L : 15000L);
- while (this.mainDevice == null && now < timeout) {
- pause(100);
- now = System.currentTimeMillis();
- }
-
- if (mainDevice != null) {
- if (debug) {
- Logger.trace("Main Device found in " + (now - start) + " ms");
- }
- } else {
- //TODO: Fix this!
- if (conType == ConnectionType.NETWORK) {
- //When using the net work the DCC-EX does not braodcast all kind of setting so ask them
- JCS.logProgress("Obtaining Device information...");
- String response = connection.sendMessage(DccExMessageFactory.versionHarwareInfoRequest());
- Logger.trace(response);
-
- DccExMessage rsp = new DccExMessage(response);
-
- if ("i".equals(rsp.getOpcode())) {
- String content = rsp.getFilteredContent();
- DeviceBean d = new DeviceBean();
- String[] dccexdev = content.split(" ");
- d.setName(content);
- for (int i = 0; i < dccexdev.length; i++) {
- switch (i) {
- case 0 ->
- d.setName(dccexdev[i]);
- case 1 ->
- d.setVersion(dccexdev[i]);
- case 5 ->
- d.setTypeName(dccexdev[i]);
- case 6 ->
- d.setSerial(dccexdev[i]);
- }
- }
- this.mainDevice = d;
- Logger.trace("Main Device set to: " + d);
- }
- }
- if (debug && mainDevice == null) {
- Logger.trace("No Main Device found in " + (now - start) + " ms");
- }
- }
-
+// while (this.mainDevice == null && now < timeout) {
+// pause(100);
+// now = System.currentTimeMillis();
+// }
+// if (mainDevice != null) {
+// if (debug) {
+// Logger.trace("Main Device found in " + (now - start) + " ms");
+// }
+// } else {
+// //TODO: Fix this!
+// if (conType == ConnectionType.NETWORK) {
+// //When using the net work the DCC-EX does not braodcast all kind of setting so ask them
+// JCS.logProgress("Obtaining Device information...");
+// String response = connection.sendMessage(DccExMessageFactory.versionHarwareInfoRequest());
+// Logger.trace(response);
+//
+// DccExMessage rsp = new DccExMessage(response);
+//
+// if ("i".equals(rsp.getOpcode())) {
+// String content = rsp.getFilteredContent();
+// DeviceBean d = new DeviceBean();
+// String[] dccexdev = content.split(" ");
+// d.setName(content);
+// for (int i = 0; i < dccexdev.length; i++) {
+// switch (i) {
+// case 0 ->
+// d.setName(dccexdev[i]);
+// case 1 ->
+// d.setVersion(dccexdev[i]);
+// case 5 ->
+// d.setTypeName(dccexdev[i]);
+// case 6 ->
+// d.setSerial(dccexdev[i]);
+// }
+// }
+// this.mainDevice = d;
+// Logger.trace("Main Device set to: " + d);
+// }
+// }
+// if (debug && mainDevice == null) {
+// Logger.trace("No Main Device found in " + (now - start) + " ms");
+// }
+// }
//Create Info
this.infoBean = new InfoBean();
this.infoBean.setProductName(commandStationBean.getDescription());
@@ -246,8 +238,8 @@ public final boolean connect() {
Logger.trace(response);
}
- initMeasurements();
- Logger.trace("Connected with: " + (this.mainDevice != null ? this.mainDevice.getName() : "Unknown"));
+ //initMeasurements();
+ //Logger.trace("Connected with: " + (this.mainDevice != null ? this.mainDevice.getName() : "Unknown"));
JCS.logProgress("Power is " + (this.power ? "On" : "Off"));
} else {
Logger.warn("Can't connect with a DCC-EX Command Station!");
@@ -334,15 +326,8 @@ public void changeFunctionValue(int address, int functionNumber, boolean flag) {
}
}
- //Direct approach no feed back...
- //maybe register the accessories and then via id?
- @Override
- public void switchAccessory(Integer address, AccessoryValue value) {
- switchAccessory(address, value, null);
- }
-
@Override
- public void switchAccessory(Integer address, AccessoryValue value, Integer switchTime) {
+ public void switchAccessory(Integer address, String protocol, AccessoryValue value, Integer switchTime) {
if (this.power && this.connected) {
String activate = AccessoryValue.GREEN == value ? "0" : "1";
String message = DccExMessageFactory.activateAccessory(address, activate);
@@ -365,11 +350,10 @@ public void switchAccessory(Integer address, AccessoryValue value, Integer switc
}
}
- @Override
- public void switchAccessory(String id, AccessoryValue value) {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
+// @Override
+// public void switchAccessory(String id, AccessoryValue value) {
+// throw new UnsupportedOperationException("Not supported yet.");
+// }
@Override
public List getLocomotives() {
throw new UnsupportedOperationException("Not supported yet.");
@@ -390,42 +374,34 @@ public List getAccessories() {
throw new UnsupportedOperationException("Not supported yet.");
}
- @Override
- public DeviceBean getDevice() {
- return this.mainDevice;
- }
-
- @Override
- public List getDevices() {
- List devices = new ArrayList<>();
- devices.add(this.mainDevice);
- return devices;
- }
-
@Override
public InfoBean getCommandStationInfo() {
return this.infoBean;
}
@Override
- public boolean isSupportTrackMeasurements() {
- return true;
+ public List getDevices() {
+ List devices = new ArrayList<>();
+ return devices;
}
@Override
- public Map getTrackMeasurements() {
- //Measure the currents
- if (connected) {
- this.connection.sendMessage(DccExMessageFactory.currentStatusRequest());
- //TODO depends a bit on the connection SERIAL or NETWORK
- this.pause(50);
- }
- return this.measurementChannels;
- }
-
- private void fireAllDisconnectionEventListeners(final DisconnectionEvent disconnectionEvent) {
- for (DisconnectionEventListener listener : this.disconnectionEventListeners) {
- listener.onDisconnect(disconnectionEvent);
+ public boolean isSupportTrackMeasurements() {
+ return false; // true;
+ }
+
+// public Map getTrackMeasurements() {
+// //Measure the currents
+// if (connected) {
+// this.connection.sendMessage(DccExMessageFactory.currentStatusRequest());
+// //TODO depends a bit on the connection SERIAL or NETWORK
+// this.pause(50);
+// }
+// return this.measurementChannels;
+// }
+ private void fireAllDisconnectionEventListeners(final ConnectionEvent disconnectionEvent) {
+ for (ConnectionEventListener listener : this.connectionEventListeners) {
+ listener.onConnectionChange(disconnectionEvent);
}
disconnect();
}
@@ -494,76 +470,75 @@ private void fireAllAccessoryEventListeners(final AccessoryEvent accessoryEvent)
}
}
- private void handleMeasurement(DccExMeasurementEvent measurementEvent) {
- if ("=".equals(measurementEvent.getOpcode())) {
- // config
- KeyValuePair track = measurementEvent.getTrack();
- if (track != null && "A".equals(track.getKey())) {
- //Main, or channel 1
- ChannelBean main = this.measurementChannels.get(1);
- if (main == null) {
- main = new ChannelBean();
- measurementChannels.put(1, main);
- }
- main.setName(track.getValue());
- main.setNumber(1);
- main.setUnit("mA");
- main.setStartValue(0.0);
- main.setScale(1000);
- main.setHumanValue(0.0);
- main.setValue(0);
- } else if (track != null && "B".equals(track.getKey())) {
- //Prog, or channel 0
- ChannelBean prog = this.measurementChannels.get(0);
- if (prog == null) {
- prog = new ChannelBean();
- measurementChannels.put(0, prog);
- }
- prog.setName(track.getValue());
- prog.setNumber(0);
- prog.setUnit("mA");
- prog.setStartValue(0.0);
- prog.setScale(1000);
- prog.setHumanValue(0.0);
- prog.setValue(0);
- }
- } else if ("j".equals(measurementEvent.getOpcode())) {
- if (measurementEvent.isMeasurement()) {
- ChannelBean main = this.measurementChannels.get(1);
- if (main == null) {
- main = new ChannelBean();
- measurementChannels.put(1, main);
- }
- main.setValue(measurementEvent.getCurrentMain());
- main.setHumanValue((double) measurementEvent.getCurrentMain());
-
- ChannelBean prog = this.measurementChannels.get(0);
- if (prog == null) {
- prog = new ChannelBean();
- measurementChannels.put(0, prog);
- }
- prog.setValue(measurementEvent.getCurrentProg());
- prog.setHumanValue((double) measurementEvent.getCurrentProg());
- } else {
- ChannelBean main = this.measurementChannels.get(1);
- if (main == null) {
- main = new ChannelBean();
- measurementChannels.put(1, main);
- }
- main.setRangeMax(measurementEvent.getCurrentMainMax());
- main.setEndValue((double) measurementEvent.getCurrentMainMax());
-
- ChannelBean prog = this.measurementChannels.get(0);
- if (prog == null) {
- prog = new ChannelBean();
- measurementChannels.put(0, prog);
- }
- prog.setRangeMax(measurementEvent.getCurrentProgMax());
- prog.setEndValue((double) measurementEvent.getCurrentProgMax());
- }
- }
- }
-
+// private void handleMeasurement(DccExMeasurementEvent measurementEvent) {
+// if ("=".equals(measurementEvent.getOpcode())) {
+// // config
+// KeyValuePair track = measurementEvent.getTrack();
+// if (track != null && "A".equals(track.getKey())) {
+// //Main, or channel 1
+// ChannelBean main = this.measurementChannels.get(1);
+// if (main == null) {
+// main = new ChannelBean();
+// measurementChannels.put(1, main);
+// }
+// main.setName(track.getValue());
+// main.setNumber(1);
+// main.setUnit("mA");
+// main.setStartValue(0.0);
+// main.setScale(1000);
+// main.setHumanValue(0.0);
+// main.setValue(0);
+// } else if (track != null && "B".equals(track.getKey())) {
+// //Prog, or channel 0
+// ChannelBean prog = this.measurementChannels.get(0);
+// if (prog == null) {
+// prog = new ChannelBean();
+// measurementChannels.put(0, prog);
+// }
+// prog.setName(track.getValue());
+// prog.setNumber(0);
+// prog.setUnit("mA");
+// prog.setStartValue(0.0);
+// prog.setScale(1000);
+// prog.setHumanValue(0.0);
+// prog.setValue(0);
+// }
+// } else if ("j".equals(measurementEvent.getOpcode())) {
+// if (measurementEvent.isMeasurement()) {
+// ChannelBean main = this.measurementChannels.get(1);
+// if (main == null) {
+// main = new ChannelBean();
+// measurementChannels.put(1, main);
+// }
+// main.setValue(measurementEvent.getCurrentMain());
+// main.setHumanValue((double) measurementEvent.getCurrentMain());
+//
+// ChannelBean prog = this.measurementChannels.get(0);
+// if (prog == null) {
+// prog = new ChannelBean();
+// measurementChannels.put(0, prog);
+// }
+// prog.setValue(measurementEvent.getCurrentProg());
+// prog.setHumanValue((double) measurementEvent.getCurrentProg());
+// } else {
+// ChannelBean main = this.measurementChannels.get(1);
+// if (main == null) {
+// main = new ChannelBean();
+// measurementChannels.put(1, main);
+// }
+// main.setRangeMax(measurementEvent.getCurrentMainMax());
+// main.setEndValue((double) measurementEvent.getCurrentMainMax());
+//
+// ChannelBean prog = this.measurementChannels.get(0);
+// if (prog == null) {
+// prog = new ChannelBean();
+// measurementChannels.put(0, prog);
+// }
+// prog.setRangeMax(measurementEvent.getCurrentProgMax());
+// prog.setEndValue((double) measurementEvent.getCurrentProgMax());
+// }
+// }
+// }
private void handleInfoMessage(String message) {
//executor.execute(() -> fireAllPowerEventListeners(powerEvent));
Logger.trace("Info: " + message);
@@ -621,22 +596,22 @@ public void onMessage(DccExMessage message) {
}
case "i" -> {
// System information
- DeviceBean d = new DeviceBean();
- String[] dccexdev = content.split(" ");
- d.setName(content);
- for (int i = 0; i < dccexdev.length; i++) {
- switch (i) {
- case 0 ->
- d.setName(dccexdev[i]);
- case 1 ->
- d.setVersion(dccexdev[i]);
- case 5 ->
- d.setTypeName(dccexdev[i]);
- case 6 ->
- d.setSerial(dccexdev[i]);
- }
- }
- commandStation.mainDevice = d;
+// DeviceBean d = new DeviceBean();
+// String[] dccexdev = content.split(" ");
+// d.setName(content);
+// for (int i = 0; i < dccexdev.length; i++) {
+// switch (i) {
+// case 0 ->
+// d.setName(dccexdev[i]);
+// case 1 ->
+// d.setVersion(dccexdev[i]);
+// case 5 ->
+// d.setTypeName(dccexdev[i]);
+// case 6 ->
+// d.setSerial(dccexdev[i]);
+// }
+// }
+// commandStation.mainDevice = d;
//Logger.trace("Main Device set to: " + d);
}
case "c" -> {
@@ -653,7 +628,7 @@ public void onMessage(DccExMessage message) {
}
case "j" -> {
DccExMeasurementEvent me2 = new DccExMeasurementEvent(opcode, content);
- commandStation.handleMeasurement(me2);
+ //commandStation.handleMeasurement(me2);
}
case "H" -> {
}
@@ -677,14 +652,15 @@ public void onMessage(DccExMessage message) {
}
@Override
- public void onDisconnect(DisconnectionEvent event) {
+ public void onDisconnect(ConnectionEvent event) {
this.commandStation.fireAllDisconnectionEventListeners(event);
}
}
-//////////////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////////////
// For testing only
+
public static void main(String[] a) {
System.setProperty("message.debug", "true");
@@ -732,7 +708,10 @@ public static void main(String[] a) {
// ((DccExCommandStationImpl) cs).pause(500L);
// cs.power(true);
// Logger.trace("Power is: " + (cs.isPower() ? "On" : "Off"));
- /////
+
+
+
+/////
//((DccExCommandStationImpl) cs).pause(500L);
// cs.changeFunctionValue(8, 0, true);
//
diff --git a/src/main/java/jcs/commandStation/dccex/connection/DccExMessageListener.java b/src/main/java/jcs/commandStation/dccex/connection/DccExMessageListener.java
index 0604e99d..7f3ad7c9 100644
--- a/src/main/java/jcs/commandStation/dccex/connection/DccExMessageListener.java
+++ b/src/main/java/jcs/commandStation/dccex/connection/DccExMessageListener.java
@@ -16,7 +16,7 @@
package jcs.commandStation.dccex.connection;
import jcs.commandStation.dccex.DccExMessage;
-import jcs.commandStation.events.DisconnectionEvent;
+import jcs.commandStation.events.ConnectionEvent;
/**
*
@@ -26,6 +26,6 @@ public interface DccExMessageListener {
void onMessage(DccExMessage message);
- void onDisconnect(DisconnectionEvent event);
+ void onDisconnect(ConnectionEvent event);
}
diff --git a/src/main/java/jcs/commandStation/dccex/connection/DccExSerialConnection.java b/src/main/java/jcs/commandStation/dccex/connection/DccExSerialConnection.java
index 5421884b..60cc2ca2 100644
--- a/src/main/java/jcs/commandStation/dccex/connection/DccExSerialConnection.java
+++ b/src/main/java/jcs/commandStation/dccex/connection/DccExSerialConnection.java
@@ -29,7 +29,7 @@
import static jcs.commandStation.dccex.DccExConnection.MESSAGE_DELIMITER;
import jcs.commandStation.dccex.DccExMessage;
import jcs.commandStation.dccex.DccExMessageFactory;
-import jcs.commandStation.events.DisconnectionEvent;
+import jcs.commandStation.events.ConnectionEvent;
import org.tinylog.Logger;
/**
@@ -157,7 +157,7 @@ private void disconnected() {
Logger.trace("Port " + commPort.getSystemPortName() + " is Disconnected");
String msg = commPort.getDescriptivePortName() + " [" + commPort.getSystemPortName() + "]";
- DisconnectionEvent de = new DisconnectionEvent(msg);
+ ConnectionEvent de = new ConnectionEvent(msg, false);
for (DccExMessageListener listener : dccExListeners) {
listener.onDisconnect(de);
diff --git a/src/main/java/jcs/commandStation/dccex/connection/DccExTCPConnection.java b/src/main/java/jcs/commandStation/dccex/connection/DccExTCPConnection.java
index ab5e2c25..c0fff96c 100644
--- a/src/main/java/jcs/commandStation/dccex/connection/DccExTCPConnection.java
+++ b/src/main/java/jcs/commandStation/dccex/connection/DccExTCPConnection.java
@@ -27,7 +27,7 @@
import jcs.commandStation.dccex.DccExConnection;
import jcs.commandStation.dccex.DccExMessage;
import jcs.commandStation.dccex.DccExMessageFactory;
-import jcs.commandStation.events.DisconnectionEvent;
+import jcs.commandStation.events.ConnectionEvent;
import org.tinylog.Logger;
/**
@@ -126,7 +126,7 @@ public synchronized String sendMessage(String message) {
} catch (IOException ex) {
Logger.error(ex);
String msg = "Host " + dccExAddress.getHostName();
- DisconnectionEvent de = new DisconnectionEvent(msg);
+ ConnectionEvent de = new ConnectionEvent(msg, false);
messageReceiver.messageListener.onDisconnect(de);
messageReceiver.quit();
@@ -216,7 +216,7 @@ public void run() {
} catch (SocketException se) {
Logger.error(se.getMessage());
String msg = "Host " + dccExAddress.getHostName();
- DisconnectionEvent de = new DisconnectionEvent(msg);
+ ConnectionEvent de = new ConnectionEvent(msg, false);
this.messageListener.onDisconnect(de);
quit();
} catch (IOException ioe) {
diff --git a/src/main/java/jcs/commandStation/entities/Device.java b/src/main/java/jcs/commandStation/entities/Device.java
new file mode 100644
index 00000000..a0cc2302
--- /dev/null
+++ b/src/main/java/jcs/commandStation/entities/Device.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2025 fransjacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.entities;
+
+import java.util.Objects;
+
+/**
+ * Command Station Device is an Object to be able to show the Devices that "live"
+ * inside a Command Station. Examples
+ * Marklin CS:
+ * - GFP internal booster device - Link S88 external feedback device ESU-ECoS:
+ * - Ecos device self / booster - Locomotive Manager - Accessories Manager - Feedback Manager
+ */
+public class Device {
+
+ private String id;
+ private String name;
+ private String hardwareVersion;
+ private String softwareVersion;
+ private String serialNumber;
+
+ private Integer size;
+ private Integer channels;
+ private boolean feedback;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getHardwareVersion() {
+ return hardwareVersion;
+ }
+
+ public void setHardwareVersion(String hardwareVersion) {
+ this.hardwareVersion = hardwareVersion;
+ }
+
+ public String getSoftwareVersion() {
+ return softwareVersion;
+ }
+
+ public void setSoftwareVersion(String softwareVersion) {
+ this.softwareVersion = softwareVersion;
+ }
+
+ public String getSerialNumber() {
+ return serialNumber;
+ }
+
+ public void setSerialNumber(String serialNumber) {
+ this.serialNumber = serialNumber;
+ }
+
+ public Integer getSize() {
+ return size;
+ }
+
+ public void setSize(Integer size) {
+ this.size = size;
+ }
+
+ public Integer getChannels() {
+ return channels;
+ }
+
+ public void setChannels(Integer channels) {
+ this.channels = channels;
+ }
+
+ public boolean isFeedback() {
+ return feedback;
+ }
+
+ public void setFeedback(boolean feedback) {
+ this.feedback = feedback;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 67 * hash + Objects.hashCode(this.id);
+ hash = 67 * hash + Objects.hashCode(this.name);
+ hash = 67 * hash + Objects.hashCode(this.hardwareVersion);
+ hash = 67 * hash + Objects.hashCode(this.softwareVersion);
+ hash = 67 * hash + Objects.hashCode(this.serialNumber);
+ hash = 67 * hash + Objects.hashCode(this.size);
+ hash = 67 * hash + Objects.hashCode(this.channels);
+ hash = 67 * hash + Objects.hashCode(this.feedback);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Device other = (Device) obj;
+ if (!Objects.equals(this.id, other.id)) {
+ return false;
+ }
+ if (!Objects.equals(this.name, other.name)) {
+ return false;
+ }
+ if (!Objects.equals(this.hardwareVersion, other.hardwareVersion)) {
+ return false;
+ }
+ if (!Objects.equals(this.softwareVersion, other.softwareVersion)) {
+ return false;
+ }
+ if (!Objects.equals(this.serialNumber, other.serialNumber)) {
+ return false;
+ }
+ if (!Objects.equals(this.size, other.size)) {
+ return false;
+ }
+ if (!Objects.equals(this.feedback, other.feedback)) {
+ return false;
+ }
+ return Objects.equals(this.channels, other.channels);
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/entities/DeviceBean.java b/src/main/java/jcs/commandStation/entities/DeviceBean.java
deleted file mode 100644
index c9175150..00000000
--- a/src/main/java/jcs/commandStation/entities/DeviceBean.java
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- * Copyright 2023 fransjacobs.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jcs.commandStation.entities;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import jcs.commandStation.marklin.cs.can.CanMessage;
-import jcs.entities.ChannelBean;
-import jcs.util.ByteUtil;
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.tinylog.Logger;
-
-/**
- * A Device is a Component which "lives" in side a Command Station.
- * It can be a Central Station (Marklin) ECos (ESU) etc. TODO: move away Command station specific code
- */
-public class DeviceBean {
-
- public static final String MAIN = "MAIN";
- public static final String PROG = "PROG";
- public static final String VOLT = "VOLT";
- public static final String TEMP = "TEMP";
-
- public static final String BUS0 = "Auswertung 1 - 16";
- public static final String BUS1 = "Bus 1 (RJ45-1)";
- public static final String BUS2 = "Bus 2 (RJ45-2)";
- public static final String BUS3 = "Bus 3 (6-Polig)";
-
- private String uid;
- private String name;
- private String typeName;
- private String identifier;
- private String type;
- private String articleNumber;
- private String serial;
- private Integer queryInteval;
-
- private String version;
- private Boolean present;
- private Integer available;
- private String config;
- private Boolean ready;
- private String path;
- private Boolean mounted;
-
- private final List channels;
- private final Map analogChannels;
- private final Map sensorBuses;
-
- public DeviceBean() {
- this((String) null);
- }
-
- public DeviceBean(String json) {
- channels = new LinkedList<>();
- analogChannels = new HashMap<>();
- sensorBuses = new HashMap<>();
-
- parse(json);
- }
-
- public DeviceBean(CanMessage message) {
- channels = new LinkedList<>();
- analogChannels = new HashMap<>();
- sensorBuses = new HashMap<>();
-
- if (message != null) {
- buildFromMessage(message);
- }
- }
-
- public String getUid() {
- return uid;
- }
-
- public void setUid(String uid) {
- this.uid = uid;
- }
-
- public Integer getUidAsInt() {
- String ui = this.uid.replace("0x", "");
- return Integer.parseUnsignedInt(ui, 16);
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getTypeName() {
- return typeName;
- }
-
- public void setTypeName(String typeName) {
- this.typeName = typeName;
- }
-
- public String getIdentifier() {
- return identifier;
- }
-
- public void setIdentifier(String identifier) {
- this.identifier = identifier;
- }
-
- @SuppressWarnings("UnnecessaryTemporaryOnConversionFromString")
- public Integer getIdentifierAsInt() {
- String id = this.identifier.replace("0x", "");
- return Integer.parseInt(id, 16);
- }
-
- public String getType() {
- return type;
- }
-
- public void setType(String type) {
- this.type = type;
- }
-
- public String getArticleNumber() {
- return articleNumber;
- }
-
- public void setArticleNumber(String articleNumber) {
- this.articleNumber = articleNumber;
- }
-
- public String getSerial() {
- return serial;
- }
-
- public void setSerial(String serial) {
- this.serial = serial;
- }
-
- public Integer getQueryInteval() {
- return queryInteval;
- }
-
- public void setQueryInteval(Integer queryInteval) {
- this.queryInteval = queryInteval;
- }
-
- public String getVersion() {
- return version;
- }
-
- public void setVersion(String version) {
- this.version = version;
- }
-
- public Boolean getPresent() {
- return present;
- }
-
- public void setPresent(Boolean present) {
- this.present = present;
- }
-
- public Integer getAvailable() {
- return available;
- }
-
- public void setAvailable(Integer available) {
- this.available = available;
- }
-
- public String getConfig() {
- return config;
- }
-
- public void setConfig(String config) {
- this.config = config;
- }
-
- public Boolean getReady() {
- return ready;
- }
-
- public void setReady(Boolean ready) {
- this.ready = ready;
- }
-
- public String getPath() {
- return path;
- }
-
- public void setPath(String path) {
- this.path = path;
- }
-
- public Boolean getMounted() {
- return mounted;
- }
-
- public void setMounted(Boolean mounted) {
- this.mounted = mounted;
- }
-
- private void parse(String json) {
- if (json == null) {
- return;
- }
- JSONObject device = new JSONObject(json);
-
- this.uid = device.optString("_uid");
- this.name = device.optString("_name");
- this.typeName = device.optString("_typname");
- this.identifier = device.optString("_kennung");
- this.type = device.optString("_typ");
- this.articleNumber = device.optString("_artikelnr");
- this.serial = device.optString("_seriennr");
- this.queryInteval = device.optInt("_queryInterval");
-
- JSONObject versionObj = device.optJSONObject("_version");
- if (versionObj != null) {
- String major = versionObj.optString("major");
- String minor = versionObj.optString("minor");
- this.version = (major != null ? major : "") + (major != null ? "." : "") + (minor != null ? minor : "");
- }
-
- this.present = device.optBoolean("isPresent");
- this.available = device.optInt("present");
- this.config = device.optString("config");
- this.ready = device.optBoolean("_ready");
- this.path = device.optString("path");
- this.mounted = device.optBoolean("isMounted");
-
- JSONArray channelsJA = device.optJSONArray("_kanal");
- if (channelsJA != null) {
- for (int i = 0; i < channelsJA.length(); i++) {
- JSONObject kanal = channelsJA.getJSONObject(i);
-
- ChannelBean cb = new ChannelBean(kanal.toString());
- this.channels.add(cb);
- String n = cb.getName();
- if (n != null) {
- switch (n) {
- case MAIN ->
- this.analogChannels.put(MAIN, cb);
- case PROG ->
- this.analogChannels.put(PROG, cb);
- case TEMP ->
- this.analogChannels.put(TEMP, cb);
- case VOLT ->
- this.analogChannels.put(VOLT, cb);
- }
- if ((n.contains(BUS0) || n.contains(BUS1) || n.contains(BUS2) || n.contains(BUS3)) && cb.isS88Bus()) {
- Integer busNr = cb.getNumber() - 1;
- this.sensorBuses.put(busNr, cb);
- }
-
- }
- }
- }
- }
-
- private void buildFromMessage(CanMessage message) {
- CanMessage resp;
- if (!message.isResponseMessage() && !message.getResponses().isEmpty()) {
- resp = message.getResponse();
- } else {
- resp = message;
- }
-
- if (CanMessage.PING_RESP == resp.getCommand() && CanMessage.DLC_8 == resp.getDlc()) {
- byte[] data = resp.getData();
-
- byte[] uida = new byte[4];
- System.arraycopy(data, 0, uida, 0, uida.length);
-
- byte[] vera = new byte[2];
- System.arraycopy(data, 4, vera, 0, vera.length);
-
- byte[] deva = new byte[2];
- System.arraycopy(data, 6, deva, 0, deva.length);
-
- int uidAsInt = resp.getDeviceUidNumberFromMessage();
-
- this.uid = "0x" + Integer.toHexString(uidAsInt);
-
- //this.uid = resp.getDeviceUidNumberFromMessage();
- //TODO: Version is not same displayed in the CS
- this.version = "" + CanMessage.toInt(vera);
- //TODO: in case of a Link S88 it is offset by 1 so device ID + 1
-
- int identifierAsInt = CanMessage.toInt(deva);
- this.identifier = "0x" + Integer.toHexString(identifierAsInt);
- //this.identifier = CanMessage.toInt(deva);
- }
- }
-
- public void updateFromMessage(CanMessage message) {
- //Filter the responses
- List responses = new ArrayList<>(message.getResponses().size());
- for (CanMessage resp : message.getResponses()) {
- if (CanMessage.STATUS_CONFIG_RESP == resp.getCommand()) {
- responses.add(resp);
- }
- }
-
-// Logger.trace(message);
-// for (CanMessage r : responses) {
-// Logger.trace(r);
-// }
- if (!responses.isEmpty()) {
- //The last response has the total response messages
- CanMessage last = responses.get(responses.size() - 1);
- int packets = 0;
-
- if (last.getDlc() == CanMessage.DLC_6) {
- packets = last.getDataByte(5);
- } else if (last.getDlc() == CanMessage.DLC_5) {
- //CS-2 lets assume the number packets to be the size
- packets = responses.size() - 1;
- }
- if (responses.size() - 1 != packets) {
- Logger.warn("Config Data might be invalid. Packages expected: " + packets + " received: " + (responses.size() - 1));
- Logger.trace(message);
- for (CanMessage m : responses) {
- Logger.trace(m);
- }
- } else {
- //Reset the device name
- name = "";
- }
-
- for (int i = 0; i < responses.size(); i++) {
- CanMessage msg = responses.get(i);
- byte[] data = msg.getData();
- int packageNr = msg.getPackageNumber();
-
- switch (i) {
- case 0 -> {
- if (CanMessage.DLC_5 == msg.getDlc()) {
- } else if (CanMessage.DLC_8 == msg.getDlc()) {
- //first packet?
- if (packageNr == 1) {
- //TODO!
- int measureChannels = data[0];
- int configChannels = data[1];
- byte[] sn = new byte[2];
- System.arraycopy(data, 6, sn, 0, sn.length);
- int serialnr = ((sn[0]) << 8) | (sn[1]);
- serial = serialnr + "";
- }
- }
- }
- case 1 -> {
- if (CanMessage.DLC_8 == msg.getDlc()) {
- if (packageNr == 2) {
- //Article
- articleNumber = ByteUtil.bytesToString(data);
- articleNumber = articleNumber.trim();
- }
- }
- }
- default -> {
- if (CanMessage.DLC_8 == msg.getDlc()) {
- String s = CanMessage.toString(data);
- if (s != null && s.length() > 0) {
- name = name + s;
- }
-
- if (packageNr == packets) {
- name = name.trim();
- }
- }
- }
- }
- }
- }
- }
-
- public boolean isDataComplete() {
- return name != null && name.length() > 2 && articleNumber != null && articleNumber.length() > 4;
- }
-
- public String getDevice() {
- return switch (getIdentifierAsInt()) {
- case 0x0000 ->
- "GFP";
- case 0x0010 ->
- "Gleisbox 60112 und 60113";
- case 0x0020 ->
- "Connect 6021 Art-Nr.60128";
- case 0x0030 ->
- "MS 2 60653, Txxxxx";
- case 0x0040 ->
- "Link-S88";
- case 0xffe0 ->
- "Wireless Devices";
- case 0xffff ->
- "CS2/3-GUI (Master)";
- default ->
- "Unknown " + this.name;
- };
- }
-
- public List getChannels() {
- return channels;
- }
-
- public void addChannel(ChannelBean channel) {
- this.channels.add(channel);
- }
-
- public Map getAnalogChannels() {
- return analogChannels;
- }
-
- public void setAnalogChannel(ChannelBean channel) {
- if (channel == null) {
- return;
- }
- switch (channel.getName()) {
- case MAIN ->
- analogChannels.put(MAIN, channel);
- case PROG ->
- analogChannels.put(PROG, channel);
- case VOLT ->
- analogChannels.put(VOLT, channel);
- case TEMP ->
- analogChannels.put(TEMP, channel);
- default -> {
- }
- }
- }
-
- public Map getSensorBuses() {
- return this.sensorBuses;
- }
-
- public void addSensorBus(Integer busNr, ChannelBean sensorBus) {
- this.sensorBuses.put(busNr, sensorBus);
- }
-
- public int getBusLength(Integer busNr) {
- if (this.isFeedbackDevice()) {
- ChannelBean cb = this.sensorBuses.get(busNr);
- if (cb != null) {
- if (busNr == 0) {
- return 1;
- } else {
- return cb.getValue();
- }
- } else {
- return 0;
- }
- } else {
- return -1;
- }
- }
-
- public Integer getLinkS88ContactIdOffset(int busNr) {
- return (busNr - 1) * 1000;
- }
-
- public boolean isFeedbackDevice() {
- return "Link S88".equals(typeName);
- }
-
- @Override
- public int hashCode() {
- int hash = 3;
- hash = 97 * hash + Objects.hashCode(this.uid);
- hash = 97 * hash + Objects.hashCode(this.name);
- hash = 97 * hash + Objects.hashCode(this.typeName);
- hash = 97 * hash + Objects.hashCode(this.identifier);
- hash = 97 * hash + Objects.hashCode(this.type);
- hash = 97 * hash + Objects.hashCode(this.articleNumber);
- hash = 97 * hash + Objects.hashCode(this.serial);
- hash = 97 * hash + Objects.hashCode(this.queryInteval);
- hash = 97 * hash + Objects.hashCode(this.version);
- hash = 97 * hash + Objects.hashCode(this.present);
- hash = 97 * hash + Objects.hashCode(this.available);
- hash = 97 * hash + Objects.hashCode(this.config);
- hash = 97 * hash + Objects.hashCode(this.ready);
- hash = 97 * hash + Objects.hashCode(this.path);
- hash = 97 * hash + Objects.hashCode(this.mounted);
- return hash;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- final DeviceBean other = (DeviceBean) obj;
- if (!Objects.equals(this.name, other.name)) {
- return false;
- }
- if (!Objects.equals(this.typeName, other.typeName)) {
- return false;
- }
- if (!Objects.equals(this.type, other.type)) {
- return false;
- }
- if (!Objects.equals(this.articleNumber, other.articleNumber)) {
- return false;
- }
- if (!Objects.equals(this.serial, other.serial)) {
- return false;
- }
- if (!Objects.equals(this.version, other.version)) {
- return false;
- }
- if (!Objects.equals(this.config, other.config)) {
- return false;
- }
- if (!Objects.equals(this.path, other.path)) {
- return false;
- }
- if (!Objects.equals(this.uid, other.uid)) {
- return false;
- }
- if (!Objects.equals(this.identifier, other.identifier)) {
- return false;
- }
- if (!Objects.equals(this.queryInteval, other.queryInteval)) {
- return false;
- }
- if (!Objects.equals(this.present, other.present)) {
- return false;
- }
- if (!Objects.equals(this.available, other.available)) {
- return false;
- }
- if (!Objects.equals(this.ready, other.ready)) {
- return false;
- }
- return Objects.equals(this.mounted, other.mounted);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("DeviceBean{");
- sb.append("uid=").append(uid);
- sb.append(", name=").append(name);
- sb.append(", typeName=").append(typeName);
- sb.append(", identifier=").append(identifier);
- sb.append(", type=").append(type);
- sb.append(", articleNumber=").append(articleNumber);
- sb.append(", serial=").append(serial);
- sb.append(", queryInteval=").append(queryInteval);
- sb.append(", version=").append(version);
- sb.append(", present=").append(present);
- sb.append(", available=").append(available);
- sb.append(", config=").append(config);
- sb.append(", ready=").append(ready);
- sb.append(", path=").append(path);
- sb.append(", mounted=").append(mounted);
- sb.append(", channels:").append(channels.size());
- sb.append("}");
- return sb.toString();
- }
-
-}
diff --git a/src/main/java/jcs/commandStation/entities/FeedbackModule.java b/src/main/java/jcs/commandStation/entities/FeedbackModule.java
new file mode 100644
index 00000000..9c4ca6cc
--- /dev/null
+++ b/src/main/java/jcs/commandStation/entities/FeedbackModule.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2024 frans.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.entities;
+
+import java.util.ArrayList;
+import java.util.List;
+import jcs.commandStation.events.SensorEvent;
+import jcs.entities.SensorBean;
+import org.tinylog.Logger;
+
+/**
+ * Represents 1 Feedback Module (S88) with a number of ports (usually 16)
+ */
+public class FeedbackModule implements Comparable {
+
+ private Integer id;
+ private Integer moduleNumber;
+ private Integer portCount;
+ private Integer addressOffset;
+ private Integer identifier;
+ private Integer busNumber;
+ private String commandStationId;
+ private Integer busSize;
+
+ private int[] ports;
+ private int[] prevPorts;
+
+ public static int DEFAULT_PORT_COUNT = 16;
+ public static int DEFAULT_ADDRESS_OFFSET = 0;
+ public static int DEFAULT_IDENTIFIER = 0;
+
+ public FeedbackModule() {
+ this(null, null, null);
+ }
+
+ public FeedbackModule(Integer id, Integer moduleNumber, String commandStationId) {
+ this(id, moduleNumber, DEFAULT_PORT_COUNT, DEFAULT_ADDRESS_OFFSET, DEFAULT_IDENTIFIER, commandStationId);
+ }
+
+ public FeedbackModule(Integer id, Integer moduleNumber, Integer portCount, Integer addressOffset, Integer identifier, String commandStationId) {
+ this.id = id;
+ this.moduleNumber = moduleNumber;
+ this.portCount = portCount;
+ this.addressOffset = addressOffset;
+ this.identifier = identifier;
+ this.commandStationId = commandStationId;
+
+ ports = new int[portCount];
+ prevPorts = new int[portCount];
+ }
+
+ @Override
+ public int compareTo(FeedbackModule o) {
+ int bn = 0;
+ if (busNumber != null) {
+ bn = busNumber;
+ }
+ int obn = 0;
+ if (o.busNumber != null) {
+ obn = o.busNumber;
+ }
+ int mn = 0;
+ if (moduleNumber != null) {
+ mn = moduleNumber;
+ }
+ int omn = 0;
+ if (o.moduleNumber != null) {
+ omn = o.moduleNumber;
+ }
+
+ if (Integer.compare(bn, obn) != 0) {
+ return Integer.compare(bn, obn);
+ }
+ return Integer.compare(mn, omn);
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public Integer getModuleNumber() {
+ return moduleNumber;
+ }
+
+ public void setModuleNumber(Integer moduleNumber) {
+ this.moduleNumber = moduleNumber;
+ }
+
+ public Integer getPortCount() {
+ return portCount;
+ }
+
+ public void setPortCount(Integer portCount) {
+ this.portCount = portCount;
+ if (portCount != null && portCount != ports.length) {
+ ports = new int[portCount];
+ prevPorts = new int[portCount];
+ }
+ }
+
+ public Integer getAddressOffset() {
+ return addressOffset;
+ }
+
+ public void setAddressOffset(Integer addressOffset) {
+ this.addressOffset = addressOffset;
+ }
+
+ public Integer getIdentifier() {
+ return identifier;
+ }
+
+ public void setIdentifier(Integer identifier) {
+ this.identifier = identifier;
+ }
+
+ public Integer getBusNumber() {
+ return busNumber;
+ }
+
+ public void setBusNumber(Integer busNumber) {
+ this.busNumber = busNumber;
+ }
+
+ public int[] getPorts() {
+ return ports;
+ }
+
+ public void setPorts(int[] ports) {
+ this.ports = ports;
+ }
+
+ public void setPortValue(int port, boolean active) {
+ //save current values
+ System.arraycopy(ports, 0, prevPorts, 0, ports.length);
+ ports[port] = active ? 1 : 0;
+ }
+
+ public boolean isPort(int port) {
+ if (ports != null && port < ports.length) {
+ return ports[port] == 1;
+ } else {
+ return false;
+ }
+ }
+
+ public int getAccumulatedPortsValue() {
+ int val = 0;
+ for (int i = 0; i < ports.length; i++) {
+ int portVal = 0;
+ if (ports[i] == 1) {
+ portVal = (int) Math.pow(2, i);
+ }
+ val = val + portVal;
+ }
+ return val;
+ }
+
+ public int[] getPrevPorts() {
+ return prevPorts;
+ }
+
+ public void setPrevPorts(int[] prevPorts) {
+ this.prevPorts = prevPorts;
+ }
+
+ public String getCommandStationId() {
+ return commandStationId;
+ }
+
+ public void setCommandStationId(String commandStationId) {
+ this.commandStationId = commandStationId;
+ }
+
+ public Integer getBusSize() {
+ return busSize;
+ }
+
+ public void setBusSize(Integer busSize) {
+ this.busSize = busSize;
+ }
+
+ public SensorBean getSensor(int port) {
+ int sid;
+ int offset = 0;
+ String name;
+ if (busNumber == null || busNumber < 0) {
+ //Not part of a Bus. Check the Address offset if it need an offset
+ if (addressOffset != null) {
+ offset = addressOffset;
+ }
+ sid = offset + (moduleNumber - 1) * portCount + port;
+ name = "M" + String.format("%02d", moduleNumber) + "-C" + String.format("%02d", port + 1);
+ } else {
+ //Part of a bus, there should be an offset...
+ if (addressOffset != null) {
+ offset = addressOffset;
+ } else {
+ Logger.warn("Module connected to bus " + busNumber + " but bus address offset is not specified!");
+ }
+ sid = offset + (moduleNumber - 1) * portCount + port;
+ name = "B" + busNumber.toString() + "-M" + String.format("%02d", moduleNumber) + "-C" + String.format("%02d", port + 1);
+ }
+
+ int status = ports[port];
+ int prevStatus = prevPorts[port];
+
+ int busNr = 0;
+ if (busNumber != null) {
+ busNr = busNumber;
+ }
+
+ SensorBean sb = new SensorBean(sid, moduleNumber, port + 1, identifier, status, prevStatus, commandStationId, busNr);
+ sb.setName(name);
+ return sb;
+ }
+
+ public List getSensors() {
+ List sensors = new ArrayList<>(ports.length);
+
+ for (int i = 0; i < ports.length; i++) {
+ SensorBean sb = getSensor(i);
+ sensors.add(sb);
+ }
+
+ return sensors;
+ }
+
+ public List getChangedSensors() {
+ List changedSensors = new ArrayList<>(ports.length);
+
+ for (int i = 0; i < ports.length; i++) {
+ if (ports[i] != prevPorts[i]) {
+ SensorBean sb = getSensor(i);
+ changedSensors.add(sb);
+ }
+ }
+ return changedSensors;
+ }
+
+ public List getChangedSensorEvents() {
+ List changedSensorEvents = new ArrayList<>(ports.length);
+
+ for (int i = 0; i < ports.length; i++) {
+ if (ports[i] != prevPorts[i]) {
+ SensorBean sb = getSensor(i);
+ SensorEvent se = new SensorEvent(sb);
+ changedSensorEvents.add(se);
+ }
+ }
+ return changedSensorEvents;
+ }
+
+ public String portToString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(" {");
+ for (int i = 0; i < ports.length; i++) {
+ sb.append(i + 1);
+ sb.append("[");
+ sb.append(ports[i]);
+ sb.append("] ");
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return "FeedbackModuleBean{" + "id=" + id + ", moduleNumber=" + moduleNumber + ", portCount=" + portCount + ", addressOffset=" + addressOffset + ", identifier=" + identifier + "}";
+ }
+
+}
+//
+// public static Integer calculateModuleNumber(int contactId) {
+// int module = (contactId - 1) / 16 + 1;
+// return module;
+// }
+// public static int calculatePortNumber(int contactId) {
+// int module = (contactId - 1) / 16 + 1;
+// int mport = contactId - (module - 1) * 16;
+// return mport;
+// }
+// public static int calculateContactId(int module, int port) {
+// //Bei einer CS2 errechnet sich der richtige Kontakt mit der Formel M - 1 * 16 + N
+// module = module - 1;
+// int contactId = module * 16;
+// return contactId + port;
+// }
diff --git a/src/main/java/jcs/commandStation/entities/InfoBean.java b/src/main/java/jcs/commandStation/entities/InfoBean.java
index f140c2cf..48e8c7ed 100644
--- a/src/main/java/jcs/commandStation/entities/InfoBean.java
+++ b/src/main/java/jcs/commandStation/entities/InfoBean.java
@@ -15,8 +15,8 @@
*/
package jcs.commandStation.entities;
-
import jakarta.persistence.Transient;
+import java.util.Objects;
import jcs.entities.CommandStationBean;
/**
@@ -32,41 +32,17 @@ public class InfoBean extends CommandStationBean {
private String hostname;
private String gfpUid;
private String guiUid;
+ private boolean supportMeasurements;
-//private String id;
-// private String description;
-// private String shortName;
-// private String className;
-// private String connectVia;
-// private String serialPort;
-// private String ipAddress;
-// private Integer networkPort;
-// private boolean ipAutoConfiguration;
-// private boolean decoderControlSupport;
-// private boolean accessoryControlSupport;
-// private boolean feedbackSupport;
-// private boolean locomotiveSynchronizationSupport;
-// private boolean accessorySynchronizationSupport;
-// private boolean locomotiveImageSynchronizationSupport;
-// private boolean locomotiveFunctionSynchronizationSupport;
-// private String protocols;
-// private boolean defaultCs;
-// private boolean enabled;
-// private String lastUsedSerial;
-// private String supConnTypesStr;
-// private boolean virtual;
-//
-// private String feedbackModuleIdentifier;
-// private Integer feedbackChannelCount;
-// private Integer feedbackBus0ModuleCount;
-// private Integer feedbackBus1ModuleCount;
-// private Integer feedbackBus2ModuleCount;
-// private Integer feedbackBus3ModuleCount;
public InfoBean() {
}
public InfoBean(CommandStationBean commandStationBean) {
+ copyInto(commandStationBean);
+ }
+
+ public final void copyInto(CommandStationBean commandStationBean) {
this.id = commandStationBean.getId();
this.description = commandStationBean.getDescription();
this.shortName = commandStationBean.getShortName();
@@ -169,9 +145,68 @@ public void setGuiUid(String guiUid) {
this.guiUid = guiUid;
}
+ @Transient
+ public boolean isSupportMeasurements() {
+ return supportMeasurements;
+ }
+
+ public void setSupportMeasurements(boolean supportMeasurements) {
+ this.supportMeasurements = supportMeasurements;
+ }
+
@Override
public String toString() {
return "InfoBean{" + "softwareVersion=" + softwareVersion + ", hardwareVersion=" + hardwareVersion + ", serialNumber=" + serialNumber + ", productName=" + productName + ", articleNumber=" + articleNumber + ", hostname=" + hostname + ", gfpUid=" + gfpUid + ", guiUid=" + guiUid + "}";
}
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 97 * hash + Objects.hashCode(this.softwareVersion);
+ hash = 97 * hash + Objects.hashCode(this.hardwareVersion);
+ hash = 97 * hash + Objects.hashCode(this.serialNumber);
+ hash = 97 * hash + Objects.hashCode(this.productName);
+ hash = 97 * hash + Objects.hashCode(this.articleNumber);
+ hash = 97 * hash + Objects.hashCode(this.hostname);
+ hash = 97 * hash + Objects.hashCode(this.gfpUid);
+ hash = 97 * hash + Objects.hashCode(this.guiUid);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final InfoBean other = (InfoBean) obj;
+ if (!Objects.equals(this.softwareVersion, other.softwareVersion)) {
+ return false;
+ }
+ if (!Objects.equals(this.hardwareVersion, other.hardwareVersion)) {
+ return false;
+ }
+ if (!Objects.equals(this.serialNumber, other.serialNumber)) {
+ return false;
+ }
+ if (!Objects.equals(this.productName, other.productName)) {
+ return false;
+ }
+ if (!Objects.equals(this.articleNumber, other.articleNumber)) {
+ return false;
+ }
+ if (!Objects.equals(this.hostname, other.hostname)) {
+ return false;
+ }
+ if (!Objects.equals(this.gfpUid, other.gfpUid)) {
+ return false;
+ }
+ return Objects.equals(this.guiUid, other.guiUid);
+ }
+
}
diff --git a/src/main/java/jcs/commandStation/entities/MeasuredChannels.java b/src/main/java/jcs/commandStation/entities/MeasuredChannels.java
new file mode 100644
index 00000000..d065c2d6
--- /dev/null
+++ b/src/main/java/jcs/commandStation/entities/MeasuredChannels.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2025 frans.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.entities;
+
+import java.util.Date;
+import java.util.Objects;
+import org.tinylog.Logger;
+
+/**
+ * Hold a group of measurements
+ */
+public class MeasuredChannels {
+
+ private long measurementTime;
+ private MeasurementBean main;
+ private MeasurementBean prog;
+ private MeasurementBean volt;
+ private MeasurementBean temp;
+
+ public MeasuredChannels() {
+
+ }
+
+ public MeasuredChannels(long measurementTime) {
+ this.measurementTime = measurementTime;
+ }
+
+ public long getMeasurementTime() {
+ return measurementTime;
+ }
+
+ public void addMeasurement(MeasurementBean measurement) {
+ switch (measurement.getName()) {
+ case "MAIN" ->
+ this.main = measurement;
+ case "PROG" ->
+ this.prog = measurement;
+ case "VOLT" ->
+ this.volt = measurement;
+ case "TEMP" ->
+ this.temp = measurement;
+ default ->
+ Logger.error("Unknown measurement " + measurement);
+ }
+ }
+
+ public MeasurementBean getMain() {
+ return main;
+ }
+
+ public MeasurementBean getProg() {
+ return prog;
+ }
+
+ public MeasurementBean getVolt() {
+ return volt;
+ }
+
+ public MeasurementBean getTemp() {
+ return temp;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 43 * hash + (int) (this.measurementTime ^ (this.measurementTime >>> 32));
+ hash = 43 * hash + Objects.hashCode(this.main);
+ hash = 43 * hash + Objects.hashCode(this.prog);
+ hash = 43 * hash + Objects.hashCode(this.volt);
+ hash = 43 * hash + Objects.hashCode(this.temp);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final MeasuredChannels other = (MeasuredChannels) obj;
+ if (this.measurementTime != other.measurementTime) {
+ return false;
+ }
+ if (!Objects.equals(this.main, other.main)) {
+ return false;
+ }
+ if (!Objects.equals(this.prog, other.prog)) {
+ return false;
+ }
+ if (!Objects.equals(this.volt, other.volt)) {
+ return false;
+ }
+ return Objects.equals(this.temp, other.temp);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("MeasuredChanels{measurementTime=");
+ sb.append(new Date(measurementTime));
+ if (main != null) {
+ sb.append(", MAIN=");
+ sb.append(main.getDisplayValue());
+ sb.append(" ");
+ sb.append(main.getUnit());
+ }
+ if (prog != null) {
+ sb.append(", PROG=");
+ sb.append(prog.getDisplayValue());
+ sb.append(" ");
+ sb.append(prog.getUnit());
+ }
+ if (volt != null) {
+ sb.append(", VOLT=");
+ sb.append(volt.getDisplayValue());
+ sb.append(" ");
+ sb.append(volt.getUnit());
+ }
+ if (temp != null) {
+ sb.append(", TEMP=");
+ sb.append(temp.getDisplayValue());
+ sb.append(" ");
+ sb.append(temp.getUnit());
+ }
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/entities/MeasurementBean.java b/src/main/java/jcs/commandStation/entities/MeasurementBean.java
new file mode 100644
index 00000000..722183e4
--- /dev/null
+++ b/src/main/java/jcs/commandStation/entities/MeasurementBean.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2025 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.entities;
+
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * A Bean which hold measured values
+ */
+public class MeasurementBean {
+
+ private Integer channelNumber;
+ private String name;
+ private boolean valid;
+ private Long measurementMillis;
+
+ private Integer measuredValue;
+ private String unit;
+
+ private Double displayValue;
+
+ public MeasurementBean() {
+ }
+
+ public MeasurementBean(Integer channelNumber, String name, boolean valid, Long measurementMillis) {
+ this(channelNumber, name, valid, measurementMillis, null, null, null);
+ }
+
+ public MeasurementBean(Integer channelNumber, String name, Long measurementMillis, Integer measuredValue, String unit, Double displayValue) {
+ this(channelNumber, name, true, measurementMillis, measuredValue, unit, displayValue);
+ }
+
+ public MeasurementBean(Integer channelNumber, String name, boolean valid, Long measurementMillis, Integer measuredValue, String unit, Double displayValue) {
+ this.channelNumber = channelNumber;
+ this.name = name;
+ this.valid = valid;
+ this.measurementMillis = measurementMillis;
+ this.measuredValue = measuredValue;
+ this.unit = unit;
+ this.displayValue = displayValue;
+ }
+
+ public Integer getChannelNumber() {
+ return channelNumber;
+ }
+
+ public void setChannelNumber(Integer channelNumber) {
+ this.channelNumber = channelNumber;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public boolean isValid() {
+ return valid;
+ }
+
+ public void setValid(boolean valid) {
+ this.valid = valid;
+ }
+
+ public Date getMeasurementTime() {
+ return new Date(measurementMillis);
+ }
+
+ public void setMeasurementTime(Date measurementTime) {
+ this.measurementMillis = measurementTime.getTime();
+ }
+
+ public Long getMeasurementMillis() {
+ return measurementMillis;
+ }
+
+ public void setMeasurementMillis(Long measurementMillis) {
+ this.measurementMillis = measurementMillis;
+ }
+
+ public Integer getMeasuredValue() {
+ return measuredValue;
+ }
+
+ public void setMeasuredValue(Integer measuredValue) {
+ this.measuredValue = measuredValue;
+ }
+
+ public String getUnit() {
+ return unit;
+ }
+
+ public void setUnit(String unit) {
+ this.unit = unit;
+ }
+
+ public Double getDisplayValue() {
+ return displayValue;
+ }
+
+ public void setDisplayValue(Double displayValue) {
+ this.displayValue = displayValue;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("MeasurementBean{");
+ if (channelNumber != null) {
+ sb.append("channelNumber=").append(channelNumber);
+ }
+ if (name != null) {
+ sb.append(", name=").append(name);
+ }
+ sb.append(", valid=").append(valid);
+ if (measurementMillis != null) {
+ sb.append(", measurementTime=").append(getMeasurementTime());
+ }
+ if (measuredValue != null) {
+ sb.append(", measuredValue=").append(measuredValue);
+ }
+ if (displayValue != null) {
+ sb.append(", humanValue=").append(displayValue);
+ }
+ if (unit != null) {
+ sb.append(", unit=").append(unit);
+ }
+
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 19 * hash + Objects.hashCode(this.channelNumber);
+ hash = 19 * hash + Objects.hashCode(this.name);
+ hash = 19 * hash + (this.valid ? 1 : 0);
+ hash = 19 * hash + Objects.hashCode(this.measurementMillis);
+ hash = 19 * hash + Objects.hashCode(this.measuredValue);
+ hash = 19 * hash + Objects.hashCode(this.unit);
+ hash = 19 * hash + Objects.hashCode(this.displayValue);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final MeasurementBean other = (MeasurementBean) obj;
+ if (this.valid != other.valid) {
+ return false;
+ }
+ if (!Objects.equals(this.name, other.name)) {
+ return false;
+ }
+ if (!Objects.equals(this.unit, other.unit)) {
+ return false;
+ }
+ if (!Objects.equals(this.channelNumber, other.channelNumber)) {
+ return false;
+ }
+ if (!Objects.equals(this.measurementMillis, other.measurementMillis)) {
+ return false;
+ }
+ if (!Objects.equals(this.measuredValue, other.measuredValue)) {
+ return false;
+ }
+ return Objects.equals(this.displayValue, other.displayValue);
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/esu/ecos/AccessoryManager.java b/src/main/java/jcs/commandStation/esu/ecos/AccessoryManager.java
index f3220b39..f4e09865 100644
--- a/src/main/java/jcs/commandStation/esu/ecos/AccessoryManager.java
+++ b/src/main/java/jcs/commandStation/esu/ecos/AccessoryManager.java
@@ -60,7 +60,7 @@ private void parse(EcosMessage message) {
//Details
accessory = parseValues(values, event);
}
- this.accessories.put(accessory.getId(), accessory);
+ accessories.put(accessory.getId(), accessory);
}
if (values.containsKey(Ecos.SIZE)) {
@@ -122,6 +122,8 @@ private AccessoryBean parseValues(Map values, boolean event) {
switch (protocol) {
case "MOT" ->
accessory.setProtocol(AccessoryBean.Protocol.MM);
+ case "MM" ->
+ accessory.setProtocol(AccessoryBean.Protocol.MM);
case "DCC" ->
accessory.setProtocol(AccessoryBean.Protocol.DCC);
default ->
@@ -216,6 +218,18 @@ Map getAccessories() {
return accessories;
}
+ AccessoryBean getAccessory(Integer address) {
+ AccessoryBean result = null;
+ for (AccessoryBean accessory : this.accessories.values()) {
+ if (address.equals(accessory.getAddress())) {
+ result = accessory;
+ break;
+ }
+ }
+
+ return result;
+ }
+
String findId(Integer address) {
String id = null;
for (AccessoryBean accessory : this.accessories.values()) {
@@ -230,7 +244,7 @@ String findId(Integer address) {
@Override
public void onAccessoryChange(AccessoryEvent accessoryEvent) {
AccessoryBean ab = accessoryEvent.getAccessoryBean();
- String id = accessoryEvent.getId();
+ String id = accessoryEvent.getIdString();
if (!this.accessories.containsKey(id)) {
id = findId(ab.getAddress());
}
@@ -398,6 +412,7 @@ static String deriveType(String symbol) {
//20005 name1["Sein mini"]20005 name2["artikel"]20005 name3[">0001<"]20005 addr[16]20005 protocol[MM]20005 mode[SWITCH]20005 symbol[13]20005
//state[0]20005 type[ACCESSORY]20005 addrext[16g,16r]20005 duration[500]20005 gates[2]20005 variant[0]20005 position[ok]20005 switching[0]
//
+
////
//20000 switching[1]
//20000 state[0]
diff --git a/src/main/java/jcs/commandStation/esu/ecos/Ecos.java b/src/main/java/jcs/commandStation/esu/ecos/Ecos.java
index fe4c626e..d7b5e69d 100644
--- a/src/main/java/jcs/commandStation/esu/ecos/Ecos.java
+++ b/src/main/java/jcs/commandStation/esu/ecos/Ecos.java
@@ -97,4 +97,5 @@ public interface Ecos {
public static final String VARIANT = "variant";
public static final String MSG = "msg";
+
}
diff --git a/src/main/java/jcs/commandStation/esu/ecos/EsuEcosCommandStationImpl.java b/src/main/java/jcs/commandStation/esu/ecos/EsuEcosCommandStationImpl.java
index 2e2b5766..4d609dc2 100644
--- a/src/main/java/jcs/commandStation/esu/ecos/EsuEcosCommandStationImpl.java
+++ b/src/main/java/jcs/commandStation/esu/ecos/EsuEcosCommandStationImpl.java
@@ -19,9 +19,9 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.TransferQueue;
import jcs.JCS;
@@ -36,10 +36,8 @@
import jcs.commandStation.events.PowerEventListener;
import jcs.commandStation.events.SensorEvent;
import jcs.entities.AccessoryBean;
-import jcs.entities.ChannelBean;
import jcs.entities.CommandStationBean;
-import jcs.commandStation.entities.DeviceBean;
-import jcs.entities.FeedbackModuleBean;
+import jcs.commandStation.entities.FeedbackModule;
import jcs.commandStation.entities.InfoBean;
import jcs.commandStation.esu.ecos.net.EcosHTTPConnection;
import jcs.commandStation.events.AccessoryEvent;
@@ -53,9 +51,11 @@
import jcs.commandStation.events.SensorEventListener;
import jcs.commandStation.autopilot.DriveSimulator;
import jcs.commandStation.VirtualConnection;
+import jcs.commandStation.entities.Device;
import static jcs.entities.AccessoryBean.AccessoryValue.GREEN;
import static jcs.entities.AccessoryBean.AccessoryValue.RED;
import jcs.entities.LocomotiveBean;
+import jcs.util.Ping;
import org.tinylog.Logger;
public class EsuEcosCommandStationImpl extends AbstractController implements DecoderController, AccessoryController, FeedbackController {
@@ -98,12 +98,10 @@ public void setVirtual(boolean flag) {
connect();
}
-
-
@Override
public boolean connect() {
if (!connected) {
- Logger.trace("Connecting to a " + (this.virtual ? "Virtual " : "") + "ESU ECoS Command Station...");
+ Logger.trace("Connecting to a " + (virtual ? "Virtual " : "") + "ESU ECoS Command Station...");
if (executor == null || executor.isShutdown()) {
executor = Executors.newCachedThreadPool();
}
@@ -117,72 +115,81 @@ public boolean connect() {
CommandStationBean.ConnectionType conType = commandStationBean.getConnectionType();
- boolean canConnect = true;
+ boolean canConnect;
if (conType == CommandStationBean.ConnectionType.NETWORK) {
- if (commandStationBean.getIpAddress() != null) {
- EcosConnectionFactory.writeLastUsedIpAddressProperty(commandStationBean.getIpAddress());
- } else {
- //try to discover the ECoS
- InetAddress ecosAddr = EcosConnectionFactory.discoverEcos();
- String ip = ecosAddr.getHostAddress();
- commandStationBean.setIpAddress(ip);
- EcosConnectionFactory.writeLastUsedIpAddressProperty(commandStationBean.getIpAddress());
- canConnect = ip != null;
- if (!canConnect) {
- Logger.error("Can't connect; IP Address not set");
+ try {
+ InetAddress ecosAddr;
+ if (virtual) {
+ ecosAddr = InetAddress.getLocalHost();
+ } else {
+ ecosAddr = InetAddress.getByName(commandStationBean.getIpAddress());
}
+ commandStationBean.setIpAddress(ecosAddr.getHostAddress());
+ canConnect = ecosAddr.getHostAddress() != null;
+ } catch (UnknownHostException ex) {
+ Logger.error("Invalid ip address : " + commandStationBean.getIpAddress());
+ return false;
+ }
+ } else {
+ if (virtual) {
+ canConnect = true;
+ } else {
+ canConnect = Ping.IsReachable(commandStationBean.getIpAddress());
}
}
- if (canConnect) {
- connection = EcosConnectionFactory.getConnection(commandStationBean.isVirtual());
+ if (!canConnect) {
+ Logger.error("Can't connect to " + (commandStationBean.getIpAddress() == null ? "ip Address not set" : "can't reach ip " + commandStationBean.getIpAddress()));
+ return false;
+ }
- if (connection != null) {
- long now = System.currentTimeMillis();
- long timeout = now + 5000L;
+ connection = EcosConnectionFactory.getConnection(commandStationBean);
- while (!connected && now < timeout) {
- connected = connection.isConnected();
- now = System.currentTimeMillis();
- }
- if (!connected && now > timeout) {
- Logger.error("Could not establish a connection");
- }
+ if (connection != null) {
+ long now = System.currentTimeMillis();
+ long timeout = now + 5000L;
- if (connected) {
- //Start the EventHandler
- eventMessageHandler = new EventHandler(this.connection);
- eventMessageHandler.start();
+ while (!connected && now < timeout) {
+ connected = connection.isConnected();
+ now = System.currentTimeMillis();
+ }
+ if (!connected && now > timeout) {
+ Logger.error("Could not establish a connection");
+ }
- //Obtain some info about the ECoS
- initBaseObject();
+ if (connected) {
+ //Start the EventHandler
+ eventMessageHandler = new EventHandler(this.connection);
+ eventMessageHandler.start();
- initLocomotiveManager();
- Logger.trace("There are " + this.locomotiveManager.getSize() + " locomotives");
+ //Obtain some info about the ECoS
+ initBaseObject();
- initAccessoryManager();
- Logger.trace("There are " + this.accessoryManager.getSize() + " accessories");
+ initLocomotiveManager();
+ Logger.trace("There are " + this.locomotiveManager.getSize() + " locomotives");
- initFeedbackManager();
- Logger.trace("There are " + this.feedbackManager.getSize() + " feedback modules");
+ initAccessoryManager();
+ Logger.trace("There are " + this.accessoryManager.getSize() + " accessories");
- if (isVirtual()) {
- simulator = new DriveSimulator();
- Logger.info("ECoS Virtual Mode Enabled!");
+ initFeedbackManager();
+ Logger.trace("There are " + this.feedbackManager.getSize() + " feedback modules");
- }
+ if (isVirtual()) {
+ simulator = new DriveSimulator();
+ Logger.info("ECoS Virtual Mode Enabled!");
- } else {
- Logger.warn("Can't connect with a ESU ECoS Command Station!");
- JCS.logProgress("Can't connect with ESU ECoS Command Station!");
}
+
+ } else {
+ Logger.warn("Can't connect with a ESU ECoS Command Station!");
+ JCS.logProgress("Can't connect with ESU ECoS Command Station!");
}
}
+
}
// Logger.trace("Connected with: " + (this.mainDevice != null ? this.mainDevice.getName() : "Unknown"));
// JCS.logProgress("Power is " + (this.power ? "On" : "Off"));
- return this.connected;
-
+ return connected;
}
private void initBaseObject() {
@@ -230,11 +237,11 @@ private void initFeedbackManager() {
for (int i = 0; i < feedbackManager.getSize(); i++) {
int moduleId = i + FeedbackManager.S88_OFFSET;
- reply = connection.sendMessage(EcosMessageFactory.getFeedbackModuleInfo(moduleId));
-
+ //reply =
+ //connection.sendMessage(EcosMessageFactory.getFeedbackModuleInfo(moduleId));
+
//TODO: Start of day...
//feedbackManager.update(reply);
-
connection.sendMessage(EcosMessageFactory.subscribeFeedbackModule(moduleId));
//Logger.trace("r: "+reply.getResponse());
}
@@ -243,18 +250,38 @@ private void initFeedbackManager() {
@Override
public void disconnect() {
try {
- if (this.connected) {
- this.connection.sendMessage(EcosMessageFactory.unSubscribeBaseObject());
- //TODO unsubscribe from all locomotives, accessories and sensors
+ if (connected) {
+ Logger.trace("Unsubsribe from " + feedbackManager.getSize() + " feedback modules...");
+ for (FeedbackModule fm : feedbackManager.getModules().values()) {
+ connection.sendMessage(EcosMessageFactory.unSubscribeFeedbackModule(fm.getId()));
+ }
+ Logger.trace("Unsubscribe from " + accessoryManager.getSize() + " accessories...");
+ for (AccessoryBean a : this.accessoryManager.getAccessories().values()) {
+ connection.sendMessage(EcosMessageFactory.unSubscribeAccessory(a.getId()));
+ }
+ Logger.trace("Unsubscribe from " + locomotiveManager.getSize() + " locomotives...");
+ for (LocomotiveBean l : this.locomotiveManager.getLocomotives().values()) {
+ connection.sendMessage(EcosMessageFactory.unSubscribeLocomotive(l.getId()));
+ }
+ connection.sendMessage(EcosMessageFactory.unSubscribeAccessoryManager());
+ connection.sendMessage(EcosMessageFactory.unSubscribeLokManager());
+ connection.sendMessage(EcosMessageFactory.unSubscribeFeedbackManager());
+ connection.sendMessage(EcosMessageFactory.unSubscribeBaseObject());
}
- if (this.eventMessageHandler != null) {
- this.eventMessageHandler.quit();
+ if (eventMessageHandler != null) {
+ Logger.trace("Stopping event handling...");
+ eventMessageHandler.quit();
+ eventMessageHandler.join();
+
+ eventMessageHandler = null;
}
- if (this.connected) {
- this.connection.close();
- this.connected = false;
+ if (connected) {
+ connection.close();
+ connected = false;
}
+
+ EcosConnectionFactory.disconnectAll();
} catch (Exception ex) {
Logger.error(ex);
}
@@ -262,16 +289,19 @@ public void disconnect() {
@Override
public InfoBean getCommandStationInfo() {
- InfoBean ib = new InfoBean(this.commandStationBean);
- if (this.ecosManager != null) {
-
- ib.setArticleNumber(this.ecosManager.getName().replace(this.ecosManager.getCommandStationType() + "-", ""));
- ib.setDescription(this.ecosManager.getName());
- ib.setArticleNumber(this.ecosManager.getName().replace(this.ecosManager.getCommandStationType() + "-", ""));
- ib.setSerialNumber(this.ecosManager.getSerialNumber());
- ib.setHardwareVersion(this.ecosManager.getHardwareVersion());
- ib.setSoftwareVersion(this.ecosManager.getApplicationVersion());
- ib.setHostname(this.getIp());
+ InfoBean ib = new InfoBean(commandStationBean);
+ if (ecosManager != null) {
+ ib.setArticleNumber(ecosManager.getName().replace(this.ecosManager.getCommandStationType() + "-", ""));
+ ib.setDescription(ecosManager.getName());
+ ib.setArticleNumber(ecosManager.getName().replace(this.ecosManager.getCommandStationType() + "-", ""));
+ ib.setSerialNumber(ecosManager.getSerialNumber());
+ ib.setProductName(ecosManager.getName());
+ ib.setHardwareVersion(ecosManager.getHardwareVersion());
+ ib.setSoftwareVersion(ecosManager.getApplicationVersion());
+ ib.setHostname(getIp());
+ if (ib.getIpAddress() == null) {
+ ib.setIpAddress(getIp());
+ }
} else {
ib.setDescription("Not Connected");
ib.setHostname("Not Connected");
@@ -279,26 +309,37 @@ public InfoBean getCommandStationInfo() {
return ib;
}
- //TODO: is the device in this form it is now really necessary?
@Override
- public DeviceBean getDevice() {
- DeviceBean d = new DeviceBean();
- if (ecosManager != null) {
- d.setName(ecosManager.getName());
- d.setVersion(ecosManager.getHardwareVersion());
- d.setTypeName(ecosManager.getCommandStationType());
- d.setSerial(ecosManager.getSerialNumber());
- } else {
- d.setName("Not Connected");
- }
- return d;
- }
+ public List getDevices() {
+ List devices = new ArrayList<>();
+ Device ecos = new Device();
+ ecos.setId(EcosManager.ID + "");
+ ecos.setName(ecosManager.getName());
+ ecos.setSerialNumber(ecosManager.getSerialNumber());
+ ecos.setHardwareVersion(ecosManager.getHardwareVersion());
+ ecos.setSoftwareVersion(ecosManager.getApplicationVersion());
+ devices.add(ecos);
+
+ Device locs = new Device();
+ locs.setId(LocomotiveManager.ID + "");
+ locs.setName("LocomotiveManager");
+ locs.setSize(locomotiveManager.getSize());
+ devices.add(locs);
+
+ Device acm = new Device();
+ acm.setId(AccessoryManager.ID + "");
+ acm.setName("AccessoryManager");
+ acm.setSize(accessoryManager.getSize());
+ devices.add(acm);
+
+ Device fbm = new Device();
+ fbm.setId(FeedbackManager.ID + "");
+ fbm.setName("FeedbackManager");
+ fbm.setSize(feedbackManager.getSize());
+ fbm.setChannels(feedbackManager.getModules().size());
+ fbm.setFeedback(true);
+ devices.add(fbm);
- //TODO: is the device in this form it is now really necessary?
- @Override
- public List getDevices() {
- List devices = new ArrayList<>();
- devices.add(getDevice());
return devices;
}
@@ -401,8 +442,7 @@ public void changeVelocity(int locUid, int speed, LocomotiveBean.Direction direc
//When a locomotive has a speed change (>0) check if Auto mode is on.
//When in Auto mode try to simulate the first sensor the locomotive is suppose to hit.
if (AutoPilot.isAutoModeActive() && speed > 0) {
- //simulateDriving(locUid, speed, direction);
- this.simulator.simulateDriving(locUid, speed, direction);
+ simulator.simulateDriving(locUid, speed, direction);
}
}
}
@@ -469,21 +509,9 @@ public boolean isSupportTrackMeasurements() {
}
@Override
- public Map getTrackMeasurements() {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
- @Override
- public void switchAccessory(Integer address, AccessoryBean.AccessoryValue value) {
- switchAccessory(address, value, this.defaultSwitchTime);
- }
-
- @Override
- public void switchAccessory(Integer address, AccessoryBean.AccessoryValue value, Integer switchTime) {
- //for now try to find the object id based on the address.
- //The protocol is not known so "accidents" can happen...
+ public void switchAccessory(Integer address, String protocol, AccessoryBean.AccessoryValue value, Integer switchTime) {
Logger.trace("Using Address " + address + " to find the AccessoryId...");
- String id = this.accessoryManager.findId(address);
+ String id = accessoryManager.findId(address);
if (id != null) {
switchAccessory(id, value);
} else {
@@ -491,8 +519,8 @@ public void switchAccessory(Integer address, AccessoryBean.AccessoryValue value,
}
}
- @Override
- public void switchAccessory(String id, AccessoryBean.AccessoryValue value) {
+ //@Override
+ void switchAccessory(String id, AccessoryBean.AccessoryValue value) {
//if (this.power && this.connected) {
Logger.trace("Changing Accessory " + id + " to " + value);
int state;
@@ -543,41 +571,24 @@ EcosConnection getConnection() {
}
@Override
- public DeviceBean getFeedbackDevice() {
- DeviceBean db = new DeviceBean();
- db.setArticleNumber(this.ecosManager.getName());
- db.setIdentifier("0x0");
- db.getBusLength(this.feedbackManager.getSize());
- db.setVersion(this.ecosManager.getApplicationVersion());
- db.setSerial(this.ecosManager.getSerialNumber());
- db.setTypeName("Link S88");
-
- ChannelBean cb = new ChannelBean();
- cb.setName(DeviceBean.BUS0);
- cb.setNumber(0);
-
- db.addSensorBus(0, cb);
-
- return db;
- }
-
- @Override
- public List getFeedbackModules() {
- List feedbackModules = new ArrayList<>(this.feedbackManager.getModules().values());
+ public List getFeedbackModules() {
+ List feedbackModules = new ArrayList<>(this.feedbackManager.getModules().values());
return feedbackModules;
}
@Override
public void fireSensorEventListeners(SensorEvent sensorEvent) {
Logger.trace("SensorEvent: " + sensorEvent);
- for (SensorEventListener listener : sensorEventListeners) {
- listener.onSensorChange(sensorEvent);
+ if (sensorEventListeners != null && !sensorEventListeners.isEmpty()) {
+ for (SensorEventListener listener : sensorEventListeners) {
+ listener.onSensorChange(sensorEvent);
+ }
}
}
@Override
public void simulateSensor(SensorEvent sensorEvent) {
- if (this.connection instanceof VirtualConnection virtualConnection) {
+ if (connection instanceof VirtualConnection virtualConnection) {
virtualConnection.sendEvent(sensorEvent);
}
}
@@ -607,8 +618,10 @@ void fireFunctionEventListeners(final LocomotiveFunctionEvent functionEvent) {
}
void fireAccessoryEventListeners(final AccessoryEvent accessoryEvent) {
- for (AccessoryEventListener listener : this.accessoryEventListeners) {
- listener.onAccessoryChange(accessoryEvent);
+ if (accessoryEventListeners != null && !accessoryEventListeners.isEmpty()) {
+ for (AccessoryEventListener listener : accessoryEventListeners) {
+ listener.onAccessoryChange(accessoryEvent);
+ }
}
}
@@ -622,7 +635,6 @@ void firePowerEventListeners(final PowerEvent powerEvent) {
//Communication from Ecos reply messages to JCS
private class EventHandler extends Thread {
- private boolean stop = false;
private boolean quit = true;
private BufferedReader reader;
@@ -632,16 +644,9 @@ public EventHandler(EcosConnection connection) {
eventQueue = connection.getEventQueue();
}
- void quit() {
+ synchronized void quit() {
this.quit = true;
- }
-
- boolean isRunning() {
- return !this.quit;
- }
-
- boolean isFinished() {
- return this.stop;
+ interrupt();
}
@Override
@@ -651,7 +656,7 @@ public void run() {
Logger.trace("Event Handler Started...");
- while (isRunning()) {
+ while (!quit) {
try {
EcosMessage eventMessage = eventQueue.take();
Logger.trace("# " + (eventMessage.isEvent() ? "-> " + eventMessage.getResponse() : eventMessage.getMessage() + " -> " + eventMessage.getResponse()));
@@ -684,21 +689,25 @@ public void run() {
}
}
} catch (InterruptedException ex) {
- Logger.error(ex);
+ if (!quit) {
+ Logger.error(ex);
+ }
}
}
- Logger.debug("Stop receiving");
try {
- reader.close();
+ if (reader != null) {
+ reader.close();
+ }
} catch (IOException ex) {
Logger.error(ex);
}
- stop = true;
+ Logger.debug("Stop receiving");
}
}
-//////////////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////////////
+ /// @param a
// For testing only
public static void main(String[] a) {
@@ -753,6 +762,13 @@ public static void main(String[] a) {
//
// cs.pause(1000);
//
+ List feedbackModules = cs.getFeedbackModules();
+ Logger.trace("There are " + feedbackModules + " Feedback Modules");
+ for (FeedbackModule fm : feedbackModules) {
+ Logger.trace("Module id: " + fm.getId() + " Module nr: " + fm.getModuleNumber() + " ports: " + fm.getPortCount() + " NodeId: " + fm.getIdentifier() + " BusNr: " + fm.getBusNumber());
+ Logger.trace("FBModule id: " + fm.getId() + " S 1 id:" + fm.getSensor(0).getId() + " contactId: " + fm.getSensor(0).getContactId() + " ModuleNr: " + fm.getSensor(0).getDeviceId() + " Name " + fm.getSensor(0).getName());
+ Logger.trace("FBModule id: " + fm.getId() + " S 15 id:" + fm.getSensor(15).getId() + " contactId: " + fm.getSensor(15).getContactId() + " ModuleNr: " + fm.getSensor(15).getDeviceId() + " Name " + fm.getSensor(15).getName());
+ }
// power = cs.power(true);
// Logger.trace("4 Power is " + (power ? "On" : "Off"));
//EcosMessage reply = cs.connection.sendMessage(new EcosMessage("queryObjects(26)"));
@@ -829,6 +845,7 @@ public static void main(String[] a) {
// //reply = cs.connection.sendMessage(new EcosMessage("help(65000,attribute)"));
// //Logger.trace(reply.getMessage() + " ->\n" + reply.getResponse());
//
+
//
// reply = cs.connection.sendMessage(new EcosMessage("request(65000,volt"));
// Logger.trace(reply.getMessage() + " ->\n" + reply.getResponse());
diff --git a/src/main/java/jcs/commandStation/esu/ecos/FeedbackManager.java b/src/main/java/jcs/commandStation/esu/ecos/FeedbackManager.java
index 87c97142..74c5dde7 100644
--- a/src/main/java/jcs/commandStation/esu/ecos/FeedbackManager.java
+++ b/src/main/java/jcs/commandStation/esu/ecos/FeedbackManager.java
@@ -20,7 +20,7 @@
import java.util.List;
import java.util.Map;
import jcs.commandStation.events.SensorEvent;
-import jcs.entities.FeedbackModuleBean;
+import jcs.commandStation.entities.FeedbackModule;
import org.tinylog.Logger;
/**
@@ -31,9 +31,12 @@ class FeedbackManager {
public static final int ID = Ecos.FEEDBACK_MANAGER_ID;
public static final int S88_OFFSET = 100;
+ public static final int S88_DEFAULT_PORT_COUNT = 16;
+
+ private static final String ESU_ECOS_CS = "esu-ecos";
private final EsuEcosCommandStationImpl ecosCommandStation;
- private final Map modules;
+ private final Map modules;
FeedbackManager(EsuEcosCommandStationImpl ecosCommandStation, EcosMessage message) {
this.ecosCommandStation = ecosCommandStation;
@@ -42,7 +45,6 @@ class FeedbackManager {
}
private List parse(EcosMessage message) {
- Logger.trace(message.getMessage());
Logger.trace(message.getResponse());
List changedSensors;
@@ -51,14 +53,22 @@ private List parse(EcosMessage message) {
int objectId = message.getObjectId();
if (ID != objectId) {
- FeedbackModuleBean feedbackModule;
- if (this.modules.containsKey(objectId)) {
- feedbackModule = this.modules.get(objectId);
+ FeedbackModule feedbackModule;
+ if (modules.containsKey(objectId)) {
+ feedbackModule = modules.get(objectId);
} else {
- feedbackModule = new FeedbackModuleBean();
+ feedbackModule = new FeedbackModule();
feedbackModule.setId(objectId);
- feedbackModule.setAddressOffset(S88_OFFSET);
- feedbackModule.setModuleNumber(objectId - S88_OFFSET);
+ feedbackModule.setAddressOffset(0);
+ feedbackModule.setModuleNumber(objectId - S88_OFFSET + 1);
+ //ESU ECoS has 1 bus
+ feedbackModule.setIdentifier(0);
+ //In Unit Testcase the command station is null
+ if (ecosCommandStation != null) {
+ feedbackModule.setCommandStationId(ecosCommandStation.getCommandStationBean().getId());
+ } else {
+ feedbackModule.setCommandStationId(ESU_ECOS_CS);
+ }
}
if (values.containsKey(Ecos.PORTS)) {
@@ -67,19 +77,21 @@ private List parse(EcosMessage message) {
int ports = Integer.parseInt(vports);
feedbackModule.setPortCount(ports);
}
+ } else {
+ feedbackModule.setPortCount(S88_DEFAULT_PORT_COUNT);
}
if (values.containsKey(Ecos.STATE)) {
String state = values.get(Ecos.STATE).toString();
updatePorts(state, feedbackModule);
}
- this.modules.put(objectId, feedbackModule);
- changedSensors = feedbackModule.getChangedSensors();
+ modules.put(objectId, feedbackModule);
+ changedSensors = feedbackModule.getChangedSensorEvents();
if (event) {
- if (this.ecosCommandStation != null) {
+ if (ecosCommandStation != null) {
for (SensorEvent sensorEvent : changedSensors) {
- this.ecosCommandStation.fireSensorEventListeners(sensorEvent);
+ ecosCommandStation.fireSensorEventListeners(sensorEvent);
}
}
}
@@ -88,14 +100,24 @@ private List parse(EcosMessage message) {
if (values.containsKey(Ecos.SIZE)) {
int size = Integer.parseInt(values.get(Ecos.SIZE).toString());
for (int i = 0; i < size; i++) {
- FeedbackModuleBean fbmb = new FeedbackModuleBean();
- fbmb.setAddressOffset(S88_OFFSET);
- fbmb.setModuleNumber(i);
- fbmb.setId(S88_OFFSET+i);
- this.modules.put(fbmb.getId(), fbmb);
+ FeedbackModule fbmb = new FeedbackModule();
+ fbmb.setAddressOffset(0);
+ fbmb.setModuleNumber(i + 1);
+ fbmb.setId(S88_OFFSET + i);
+ fbmb.setPortCount(S88_DEFAULT_PORT_COUNT);
+ fbmb.setIdentifier(0);
+ fbmb.setBusSize(size);
+
+ //In Unit Testcase the command station is null
+ if (ecosCommandStation != null) {
+ fbmb.setCommandStationId(ecosCommandStation.getCommandStationBean().getId());
+ } else {
+ fbmb.setCommandStationId(ESU_ECOS_CS);
+ }
+
+ modules.put(fbmb.getId(), fbmb);
}
}
-
}
changedSensors = Collections.EMPTY_LIST;
}
@@ -111,7 +133,7 @@ public int getSize() {
return this.modules.size();
}
- void updatePorts(String state, FeedbackModuleBean s88) {
+ void updatePorts(String state, FeedbackModule s88) {
String val = state.replace("0x", "");
int l = 4 - val.length();
for (int i = 0; i < l; i++) {
@@ -122,8 +144,8 @@ void updatePorts(String state, FeedbackModuleBean s88) {
int[] prevPorts = s88.getPrevPorts();
if (ports == null) {
- ports = new int[FeedbackModuleBean.DEFAULT_PORT_COUNT];
- prevPorts = new int[FeedbackModuleBean.DEFAULT_PORT_COUNT];
+ ports = new int[FeedbackModule.DEFAULT_PORT_COUNT];
+ prevPorts = new int[FeedbackModule.DEFAULT_PORT_COUNT];
}
//Set the previous ports State
System.arraycopy(ports, 0, prevPorts, 0, ports.length);
@@ -139,11 +161,11 @@ void updatePorts(String state, FeedbackModuleBean s88) {
s88.setPorts(ports);
}
- public Map getModules() {
+ public Map getModules() {
return modules;
}
- public FeedbackModuleBean getFeedbackModule(int id) {
- return this.modules.get(id);
+ public FeedbackModule getFeedbackModule(int id) {
+ return modules.get(id);
}
}
diff --git a/src/main/java/jcs/commandStation/esu/ecos/net/EcosConnectionFactory.java b/src/main/java/jcs/commandStation/esu/ecos/net/EcosConnectionFactory.java
index 897b06cb..389a6677 100644
--- a/src/main/java/jcs/commandStation/esu/ecos/net/EcosConnectionFactory.java
+++ b/src/main/java/jcs/commandStation/esu/ecos/net/EcosConnectionFactory.java
@@ -20,10 +20,8 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Set;
-import jcs.JCS;
+import jcs.entities.CommandStationBean;
import jcs.util.NetworkUtil;
-import jcs.util.Ping;
-import jcs.util.RunUtil;
import net.straylightlabs.hola.dns.Domain;
import net.straylightlabs.hola.sd.Instance;
import net.straylightlabs.hola.sd.Query;
@@ -34,120 +32,85 @@
* Try to connect with a ESU ECoS 50xxx.
*/
public class EcosConnectionFactory {
-
+
private static final String ESU_MRTP_SERVICE = "_esu-mrtp._tcp";
-
- private static EcosConnectionFactory instance;
-
- private EcosConnection controllerConnection;
- private EcosHTTPConnection httpConnection;
- private InetAddress controllerHost;
- private boolean forceVirtual = false;
-
- private static final String LAST_USED_IP_PROP_FILE = RunUtil.DEFAULT_PATH + "last-used-esu-ecos-ip.properties";
-
- private EcosConnectionFactory() {
+
+ private static EcosConnection controllerConnection;
+
+ private static EcosHTTPConnection httpConnection;
+
+ private static InetAddress controllerHost;
+ private static boolean forceVirtual = false;
+ private static boolean virtual;
+
+ static {
forceVirtual = "true".equals(System.getProperty("connection.always.virtual", "false"));
}
-
- public static EcosConnectionFactory getInstance() {
- if (instance == null) {
- instance = new EcosConnectionFactory();
- }
- return instance;
+
+ public static EcosConnection getConnection(CommandStationBean commandStation) {
+ return getConnection(commandStation, (virtual != commandStation.isVirtual()));
}
-
- EcosConnection getConnectionImpl(boolean flag) {
- boolean virtual = flag;
+
+ public static EcosConnection getConnection(CommandStationBean commandStation, boolean reconnect) {
+ if (reconnect) {
+ disconnectAll();
+ }
+
+ virtual = commandStation.isVirtual();
if (!virtual && forceVirtual) {
virtual = forceVirtual;
Logger.info("Forcing a virtual connection!");
}
- if (!virtual) {
- if (controllerConnection == null) {
- String lastUsedIp = RunUtil.readProperty(LAST_USED_IP_PROP_FILE, "ip-address");
- if (lastUsedIp != null) {
- try {
- if (Ping.IsReachable(lastUsedIp)) {
- this.controllerHost = InetAddress.getByName(lastUsedIp);
- Logger.trace("Using last used IP Address: " + lastUsedIp);
- } else {
- Logger.trace("Last used IP Address: " + lastUsedIp + " is not reachable");
- }
- } catch (UnknownHostException ex) {
- Logger.trace("Last used ESU ECoS IP: " + lastUsedIp + " is invalid!");
- lastUsedIp = null;
- }
- }
-
- if (this.controllerHost == null) {
- Logger.trace("Try to discover a ESU ECoS...");
- JCS.logProgress("Discovering a ESU ECoS...");
- controllerHost = discoverEcos();
- }
-
- if (controllerHost != null) {
- if (lastUsedIp == null) {
- //Write the last used IP Addres for faster discovery next time
- writeLastUsedIpAddressProperty(controllerHost.getHostAddress());
- }
- Logger.trace("ESU ECoS ip: " + controllerHost.getHostName());
-
- controllerConnection = new EcosTCPConnection(controllerHost);
- } else {
- Logger.warn("Can't find a ESU ECoS Controller host!");
- JCS.logProgress("Can't find a ESU ECoS Controller on the Network");
- }
+
+ try {
+ if (virtual) {
+ controllerHost = InetAddress.getLocalHost();
+ } else {
+ controllerHost = InetAddress.getByName(commandStation.getIpAddress());
}
- } else {
- //Virtual connection
- controllerConnection = new EcosVirtualConnection(NetworkUtil.getIPv4HostAddress());
+ } catch (UnknownHostException ex) {
+ Logger.error("Invalid ip address : " + commandStation.getIpAddress());
+ return null;
}
- return this.controllerConnection;
- }
-
- public static EcosConnection getConnection() {
- return getInstance().getConnectionImpl(false);
- }
-
- public static EcosConnection getConnection(boolean virtual) {
- return getInstance().getConnectionImpl(virtual);
+
+ if (controllerConnection == null) {
+ if (virtual) {
+ controllerConnection = new EcosVirtualConnection(controllerHost);
+ } else {
+ controllerConnection = new EcosTCPConnection(controllerHost);
+ }
+ }
+ return controllerConnection;
}
-
- EcosHTTPConnection getHttpConnectionImpl() {
+
+ public static EcosHTTPConnection getHttpConnection() {
if (httpConnection == null) {
httpConnection = new EcosHTTPConnection(controllerHost);
}
return httpConnection;
}
-
- public static EcosHTTPConnection getHttpConnection() {
- return getInstance().getHttpConnectionImpl();
- }
-
+
public static void disconnectAll() {
- if (instance.controllerConnection != null) {
+ httpConnection = null;
+
+ if (controllerConnection != null) {
try {
- instance.controllerConnection.close();
+ controllerConnection.close();
} catch (Exception ex) {
Logger.trace("Error during disconnect " + ex);
}
}
- instance.controllerConnection = null;
- instance.controllerHost = null;
+ controllerConnection = null;
+ controllerHost = null;
}
-
- String getControllerIpImpl() {
- if (this.controllerHost != null) {
- return this.controllerHost.getHostAddress();
+
+ public static String getControllerIp() {
+ if (controllerHost != null) {
+ return controllerHost.getHostAddress();
} else {
return "Unknown";
}
}
-
- public static String getControllerIp() {
- return getInstance().getControllerIpImpl();
- }
/**
* Try to Automatically discover the ESU ECoS IP Address on the local network.
@@ -157,23 +120,23 @@ public static String getControllerIp() {
*/
public static InetAddress discoverEcos() {
InetAddress ecosIp = null;
-
+
try {
Service ecosService = Service.fromName(ESU_MRTP_SERVICE);
Query ecosQuery = Query.createFor(ecosService, Domain.LOCAL);
-
+
Set ecosInstances = ecosQuery.runOnceOn(NetworkUtil.getIPv4HostAddress());
-
+
Logger.trace("Found " + ecosInstances.size());
-
+
if (ecosInstances.isEmpty()) {
Logger.warn("Could not find a ESU ECoS host on the local network!");
return null;
}
-
+
Instance ecos = ecosInstances.iterator().next();
Logger.trace("ESU ECoS: " + ecos);
-
+
Set addresses = ecos.getAddresses();
//Find the first ip4 address
@@ -188,11 +151,10 @@ public static InetAddress discoverEcos() {
}
return ecosIp;
}
-
- public static void writeLastUsedIpAddressProperty(String ipAddress) {
- if (ipAddress != null) {
- RunUtil.writeProperty(LAST_USED_IP_PROP_FILE, "ip-address", ipAddress);
- }
- }
-
+
+// public static void writeLastUsedIpAddressProperty(String ipAddress) {
+// if (ipAddress != null) {
+// RunUtil.writeProperty(LAST_USED_IP_PROP_FILE, "ip-address", ipAddress);
+// }
+// }
}
diff --git a/src/main/java/jcs/commandStation/esu/ecos/net/EcosMessageListener.java b/src/main/java/jcs/commandStation/esu/ecos/net/EcosMessageListener.java
index 6abdfe69..61b57712 100644
--- a/src/main/java/jcs/commandStation/esu/ecos/net/EcosMessageListener.java
+++ b/src/main/java/jcs/commandStation/esu/ecos/net/EcosMessageListener.java
@@ -16,7 +16,7 @@
package jcs.commandStation.esu.ecos.net;
import jcs.commandStation.esu.ecos.EcosMessage;
-import jcs.commandStation.events.DisconnectionEvent;
+import jcs.commandStation.events.ConnectionEvent;
/**
*
@@ -26,6 +26,6 @@ public interface EcosMessageListener {
void onMessage(EcosMessage message);
- void onDisconnect(DisconnectionEvent event);
+ void onDisconnect(ConnectionEvent event);
}
diff --git a/src/main/java/jcs/commandStation/esu/ecos/net/EcosTCPConnection.java b/src/main/java/jcs/commandStation/esu/ecos/net/EcosTCPConnection.java
index a7a613e9..a1cdd443 100644
--- a/src/main/java/jcs/commandStation/esu/ecos/net/EcosTCPConnection.java
+++ b/src/main/java/jcs/commandStation/esu/ecos/net/EcosTCPConnection.java
@@ -28,7 +28,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TransferQueue;
import jcs.commandStation.esu.ecos.EcosMessage;
-import jcs.commandStation.events.DisconnectionEvent;
+import jcs.commandStation.events.ConnectionEvent;
import org.tinylog.Logger;
/**
@@ -69,22 +69,23 @@ private void checkConnection() {
messageReceiver.start();
}
} catch (IOException ex) {
- this.clientSocket = null;
+ clientSocket = null;
Logger.error("Can't (re)connect with ESU Ecos " + ecosAddress.getHostAddress() + ". Cause: " + ex.getMessage());
Logger.trace(ex);
}
}
private void disconnect() {
- this.messageReceiver.quit();
+ messageReceiver.quit();
//wait until the messageReceiver has shut down
long now = System.currentTimeMillis();
long start = now;
- long timeout = now + TIMEOUT;
- boolean finished = this.messageReceiver.isFinished();
+ long timeout = now + TIMEOUT * 4;
+ boolean finished = messageReceiver.isFinished();
+
while (!finished && now < timeout) {
- finished = this.messageReceiver.isFinished();
+ finished = messageReceiver.isFinished();
now = System.currentTimeMillis();
}
@@ -144,7 +145,7 @@ public synchronized EcosMessage sendMessage(EcosMessage message) {
} catch (IOException | InterruptedException ex) {
Logger.error(ex);
String msg = "Host " + ecosAddress.getHostName();
- DisconnectionEvent de = new DisconnectionEvent(msg);
+ ConnectionEvent de = new ConnectionEvent(msg, false);
messageReceiver.messageListener.onDisconnect(de);
messageReceiver.quit();
@@ -159,12 +160,12 @@ public void close() {
@Override
public boolean isConnected() {
- return this.messageReceiver != null && this.messageReceiver.isRunning();
+ return messageReceiver != null && messageReceiver.isRunning();
}
@Override
public TransferQueue getEventQueue() {
- return this.eventQueue;
+ return eventQueue;
}
private class ClientMessageReceiver extends Thread {
@@ -182,22 +183,28 @@ public ClientMessageReceiver(Socket socket) {
}
}
- void quit() {
- this.quit = true;
+ synchronized void quit() {
+ quit = true;
+ interrupt();
//Shutdown the socket input otherwise the receving thread can't stop
try {
- clientSocket.shutdownInput();
+ if (!clientSocket.isClosed()) {
+ clientSocket.shutdownInput();
+ }
+ if (reader != null) {
+ reader.close();
+ }
} catch (IOException ex) {
Logger.error(ex);
}
}
boolean isRunning() {
- return !this.quit;
+ return !quit;
}
boolean isFinished() {
- return this.stop;
+ return stop;
}
void setMessageListener(EcosMessageListener messageListener) {
@@ -206,12 +213,12 @@ void setMessageListener(EcosMessageListener messageListener) {
@Override
public void run() {
- this.quit = false;
- this.setName("ESU-ECOS-RX");
+ quit = false;
+ setName("ESU-ECOS-RX");
Logger.trace("Started listening on port " + clientSocket.getLocalPort() + " ...");
- while (isRunning()) {
+ while (!quit) {
try {
String rx = reader.readLine();
Logger.trace("RX: " + rx);
@@ -247,7 +254,7 @@ public void run() {
sb.append(rx);
boolean complete = EcosMessage.isComplete(rx);
- while (!complete && now < timeout) {
+ while (!complete && now < timeout && !quit) {
rx = reader.readLine();
sb.append(rx);
complete = EcosMessage.isComplete(sb.toString());
@@ -259,29 +266,29 @@ public void run() {
EcosMessage emsg = new EcosMessage(sb.toString());
Logger.trace("Complete: " + emsg.isResponseComplete() + "\n" + emsg.getMessage() + "\n" + emsg.getResponse());
- eventQueue.put(emsg);
-
+ eventQueue.offer(emsg);
}
-
}
} catch (SocketException se) {
Logger.error(se.getMessage());
String msg = "Host " + ecosAddress.getHostName();
- DisconnectionEvent de = new DisconnectionEvent(msg);
- this.messageListener.onDisconnect(de);
+ ConnectionEvent de = new ConnectionEvent(msg, false);
+ messageListener.onDisconnect(de);
quit();
} catch (IOException | InterruptedException ex) {
Logger.error(ex);
}
}
- Logger.debug("Stop receiving");
try {
- reader.close();
+ if (reader != null) {
+ reader.close();
+ }
} catch (IOException ex) {
Logger.error(ex);
}
stop = true;
+ Logger.debug("Stopped receiving");
}
}
diff --git a/src/main/java/jcs/commandStation/esu/ecos/net/EcosVirtualConnection.java b/src/main/java/jcs/commandStation/esu/ecos/net/EcosVirtualConnection.java
index db9dad58..931b1c3b 100644
--- a/src/main/java/jcs/commandStation/esu/ecos/net/EcosVirtualConnection.java
+++ b/src/main/java/jcs/commandStation/esu/ecos/net/EcosVirtualConnection.java
@@ -26,7 +26,7 @@
import jcs.commandStation.events.SensorEvent;
import jcs.commandStation.VirtualConnection;
import jcs.entities.AccessoryBean;
-import jcs.entities.FeedbackModuleBean;
+import jcs.commandStation.entities.FeedbackModule;
import jcs.entities.FunctionBean;
import jcs.entities.LocomotiveBean;
import jcs.entities.SensorBean;
@@ -47,6 +47,8 @@ class EcosVirtualConnection implements EcosConnection, VirtualConnection {
private EcosMessageListener messageListener;
private boolean debug = false;
+
+ private static String ESU_ECOS_ID = "esu-ecos";
EcosVirtualConnection(InetAddress address) {
debug = System.getProperty("message.debug", "false").equalsIgnoreCase("true");
@@ -111,7 +113,7 @@ public synchronized EcosMessage sendMessage(EcosMessage message) {
}
case EcosMessageFactory.QUERY_LOCOMOTIVES -> {
//Query the locomotives from the database
- List locos = PersistenceFactory.getService().getLocomotives();
+ List locos = PersistenceFactory.getService().getLocomotivesByCommandStationId(ESU_ECOS_ID);
for (LocomotiveBean loco : locos) {
//name,addr,protocol
@@ -178,7 +180,7 @@ public synchronized EcosMessage sendMessage(EcosMessage message) {
default -> {
//Interpret the message
//Logger.trace(msg);
- //Logger.trace(message.getId() + ": " + message.getCommand());
+ //Logger.trace(message.getIdString() + ": " + message.getCommand());
String cmd = message.getCommand();
String id = message.getId();
int objId = message.getObjectId();
@@ -195,7 +197,7 @@ public synchronized EcosMessage sendMessage(EcosMessage message) {
}
} else if (objId >= 100 && objId < 999) {
if (Ecos.CMD_GET.equals(cmd)) {
- FeedbackModuleBean module = getFeedbackModule(objId);
+ FeedbackModule module = getFeedbackModule(objId);
replyBuilder.append(module.getAddressOffset() + module.getModuleNumber());
replyBuilder.append(" state[0x");
replyBuilder.append(module.getAccumulatedPortsValue());
@@ -332,6 +334,7 @@ public synchronized EcosMessage sendMessage(EcosMessage message) {
replyBuilder.append("]");
}
}
+
}
replyBuilder.append("");
@@ -400,11 +403,11 @@ static String getSymbol(String type) {
};
}
- FeedbackModuleBean getFeedbackModule(int moduleId) {
+ FeedbackModule getFeedbackModule(int moduleId) {
List sensors = PersistenceFactory.getService().getSensors();
int id = moduleId;
int moduleNr = id - 100;
- FeedbackModuleBean module = new FeedbackModuleBean();
+ FeedbackModule module = new FeedbackModule();
module.setId(id);
module.setModuleNumber(moduleNr);
module.setPortCount(16);
@@ -424,8 +427,8 @@ FeedbackModuleBean getFeedbackModule(int moduleId) {
@Override
public void sendEvent(SensorEvent sensorEvent) {
Logger.trace("Device: " + sensorEvent.getDeviceId() + " contact: " + sensorEvent.getContactId() + " -> " + sensorEvent.isActive());
- FeedbackModuleBean fbm = getFeedbackModule(100 + sensorEvent.getDeviceId());
- //Logger.trace(fbm.getId()+" nr: "+fbm.getModuleNumber() + " Current ports: " + fbm.portToString());
+ FeedbackModule fbm = getFeedbackModule(100 + sensorEvent.getDeviceId());
+ //Logger.trace(fbm.getIdString()+" nr: "+fbm.getModuleNumber() + " Current ports: " + fbm.portToString());
int port = sensorEvent.getContactId() - 1;
fbm.setPortValue(port, sensorEvent.isActive());
//Logger.trace(100 + fbm.getModuleNumber() + " changed ports: " + fbm.portToString());
diff --git a/src/main/java/jcs/commandStation/events/AccessoryEvent.java b/src/main/java/jcs/commandStation/events/AccessoryEvent.java
index 6da6b9b2..34f94a98 100755
--- a/src/main/java/jcs/commandStation/events/AccessoryEvent.java
+++ b/src/main/java/jcs/commandStation/events/AccessoryEvent.java
@@ -15,12 +15,11 @@
*/
package jcs.commandStation.events;
-import java.io.Serializable;
import jcs.entities.AccessoryBean;
import jcs.entities.AccessoryBean.AccessoryValue;
import jcs.entities.AccessoryBean.SignalValue;
-public class AccessoryEvent implements Serializable {
+public class AccessoryEvent implements JCSActionEvent {
private final AccessoryBean accessoryBean;
@@ -32,27 +31,40 @@ public AccessoryBean getAccessoryBean() {
return accessoryBean;
}
- public boolean isKnownAccessory() {
- return this.accessoryBean != null && (this.accessoryBean.getAddress() != null || this.accessoryBean.getId() != null);
+ public boolean isValid() {
+ return accessoryBean != null && (accessoryBean.getAddress() != null || accessoryBean.getId() != null);
}
public boolean isEventFor(AccessoryBean accessory) {
- boolean addressEquals = this.accessoryBean.getAddress().equals(accessory.getAddress());
- boolean idEquals = this.accessoryBean.getId().equals(accessory.getId());
+ boolean addressEquals = accessoryBean.getAddress().equals(accessory.getAddress());
+ boolean idEquals = accessoryBean.getId().equals(accessory.getId());
return addressEquals || idEquals;
}
public SignalValue getSignalValue() {
- return this.accessoryBean.getSignalValue();
+ return accessoryBean.getSignalValue();
}
public AccessoryValue getValue() {
- return this.accessoryBean.getAccessoryValue();
+ return accessoryBean.getAccessoryValue();
}
- public String getId() {
- return this.accessoryBean.getId();
+ public boolean isGreen() {
+ return AccessoryValue.GREEN == accessoryBean.getAccessoryValue();
+ }
+
+ public boolean isRed() {
+ return AccessoryValue.RED == accessoryBean.getAccessoryValue();
+ }
+
+ public Integer getAddress() {
+ return accessoryBean.getAddress();
+ }
+
+ @Override
+ public String getIdString() {
+ return accessoryBean.getId();
}
}
diff --git a/src/main/java/jcs/commandStation/events/DisconnectionEvent.java b/src/main/java/jcs/commandStation/events/ConnectionEvent.java
similarity index 74%
rename from src/main/java/jcs/commandStation/events/DisconnectionEvent.java
rename to src/main/java/jcs/commandStation/events/ConnectionEvent.java
index 3b3645bc..0c1994c1 100644
--- a/src/main/java/jcs/commandStation/events/DisconnectionEvent.java
+++ b/src/main/java/jcs/commandStation/events/ConnectionEvent.java
@@ -16,19 +16,24 @@
package jcs.commandStation.events;
/**
- *
- * @author frans
+ * Event to signal Connection and Disconnection
*/
-public class DisconnectionEvent {
+public class ConnectionEvent {
private final String source;
+ private final boolean connected;
- public DisconnectionEvent(String source) {
+ public ConnectionEvent(String source, boolean connected) {
this.source = source;
+ this.connected = connected;
}
public String getSource() {
return source;
}
+ public boolean isConnected() {
+ return connected;
+ }
+
}
diff --git a/src/main/java/jcs/commandStation/events/DisconnectionEventListener.java b/src/main/java/jcs/commandStation/events/ConnectionEventListener.java
similarity index 87%
rename from src/main/java/jcs/commandStation/events/DisconnectionEventListener.java
rename to src/main/java/jcs/commandStation/events/ConnectionEventListener.java
index aae616c5..ca11d8c9 100644
--- a/src/main/java/jcs/commandStation/events/DisconnectionEventListener.java
+++ b/src/main/java/jcs/commandStation/events/ConnectionEventListener.java
@@ -19,7 +19,7 @@
*
* @author frans
*/
-public interface DisconnectionEventListener {
+public interface ConnectionEventListener {
- void onDisconnect(DisconnectionEvent event);
+ void onConnectionChange(ConnectionEvent event);
}
diff --git a/src/main/java/jcs/commandStation/events/JCSActionEvent.java b/src/main/java/jcs/commandStation/events/JCSActionEvent.java
new file mode 100644
index 00000000..e353eee1
--- /dev/null
+++ b/src/main/java/jcs/commandStation/events/JCSActionEvent.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2025 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.events;
+
+/**
+ *
+ * An Action is required on the implemented delegate Event Object.
+ */
+public interface JCSActionEvent {
+
+ /**
+ *
+ * @return the id of the Object which requires an action
+ */
+ String getIdString();
+
+}
diff --git a/src/main/java/jcs/commandStation/events/MeasurementEvent.java b/src/main/java/jcs/commandStation/events/MeasurementEvent.java
index 9d1d33c6..7fc76fe9 100644
--- a/src/main/java/jcs/commandStation/events/MeasurementEvent.java
+++ b/src/main/java/jcs/commandStation/events/MeasurementEvent.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 frans.
+ * Copyright 2025 frans.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,30 +15,37 @@
*/
package jcs.commandStation.events;
-import jcs.entities.ChannelBean;
+import jcs.commandStation.entities.MeasuredChannels;
+import jcs.commandStation.entities.MeasurementBean;
/**
- *
- * @author frans
+ * Signals the lastMeasurment(s)
*/
public class MeasurementEvent {
- private final ChannelBean measurementChannel;
+ private final MeasuredChannels measuredChannels;
+
+ public MeasurementEvent(MeasuredChannels measuredChannels) {
+ this.measuredChannels = measuredChannels;
+ }
- public MeasurementEvent(ChannelBean measurementChannel) {
- this.measurementChannel = measurementChannel;
+ public MeasuredChannels getMeasuredChannels() {
+ return measuredChannels;
}
- public ChannelBean getMeasurementChannel() {
- return measurementChannel;
+ public MeasurementBean getMain() {
+ return measuredChannels.getMain();
}
- public Integer getCannel() {
- return this.measurementChannel.getNumber();
+ public MeasurementBean getProg() {
+ return measuredChannels.getProg();
}
- public String getFormattedValue() {
- return this.measurementChannel.getHumanValue() + " " + this.measurementChannel.getUnit();
+ public MeasurementBean getVolt() {
+ return measuredChannels.getVolt();
}
+ public MeasurementBean getTemp() {
+ return measuredChannels.getTemp();
+ }
}
diff --git a/src/main/java/jcs/commandStation/events/MeasurementEventListener.java b/src/main/java/jcs/commandStation/events/MeasurementEventListener.java
index ee36d981..189a8f7c 100644
--- a/src/main/java/jcs/commandStation/events/MeasurementEventListener.java
+++ b/src/main/java/jcs/commandStation/events/MeasurementEventListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 frans.
+ * Copyright 2025 frans.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/src/main/java/jcs/commandStation/events/SensorEvent.java b/src/main/java/jcs/commandStation/events/SensorEvent.java
index 2021fcfb..3cb14f62 100755
--- a/src/main/java/jcs/commandStation/events/SensorEvent.java
+++ b/src/main/java/jcs/commandStation/events/SensorEvent.java
@@ -18,59 +18,57 @@
import jcs.entities.SensorBean;
/**
- *
- * @author Frans Jacobs
+ * Value change happened on a Sensor.
*/
-public class SensorEvent {
+public class SensorEvent implements JCSActionEvent {
private final SensorBean sensorBean;
+ private final boolean newValue;
public SensorEvent(SensorBean sensorBean) {
+ this(sensorBean, sensorBean.isActive());
+ }
+
+ public SensorEvent(SensorBean sensorBean, boolean newValue) {
this.sensorBean = sensorBean;
+ this.newValue = newValue;
}
public SensorBean getSensorBean() {
return sensorBean;
}
- public String getId() {
- if (sensorBean.getId() != null) {
- return sensorBean.getId();
- } else {
- //TODO: Number format? check with both CS 3 and HSI 88 life sensors
- Integer deviceId = sensorBean.getDeviceId();
- Integer contactId = sensorBean.getContactId();
- String cn = ((contactId) > 9 ? "" : "0");
- if (cn.length() == 2) {
- cn = "00" + cn;
- } else if (cn.length() == 3) {
- cn = "0" + cn;
- }
- return deviceId + "-" + cn;
- }
+ public Integer getSensorId() {
+ return sensorBean.getId();
+ }
+
+ @Deprecated
+ @Override
+ public String getIdString() {
+ return sensorBean.getId().toString();
}
public Integer getDeviceId() {
- return this.sensorBean.getDeviceId();
+ return sensorBean.getDeviceId();
}
public Integer getContactId() {
- return this.sensorBean.getContactId();
+ return sensorBean.getContactId();
}
public boolean isChanged() {
- boolean active = sensorBean.isActive();
+ //boolean active = sensorBean.isActive();
boolean prevActive = sensorBean.isPreviousActive();
- return active != prevActive;
+ return newValue != prevActive;
}
public boolean isActive() {
- return sensorBean.isActive();
+ return newValue; //sensorBean.isActive();
}
@Override
public String toString() {
- return "SensorEvent{" + "id=" + getId() + ", active=" + (isActive() ? "1" : "0") + "}";
+ return "SensorEvent{" + "id=" + getIdString() + ", active=" + (isActive() ? "1" : "0") + "}";
}
}
diff --git a/src/main/java/jcs/commandStation/hsis88/HSIImpl.java b/src/main/java/jcs/commandStation/hsis88/HSIImpl.java
index 1be08b88..8dd0d3c0 100644
--- a/src/main/java/jcs/commandStation/hsis88/HSIImpl.java
+++ b/src/main/java/jcs/commandStation/hsis88/HSIImpl.java
@@ -15,6 +15,7 @@
*/
package jcs.commandStation.hsis88;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -23,20 +24,20 @@
import jcs.JCS;
import jcs.commandStation.AbstractController;
import jcs.commandStation.FeedbackController;
-import jcs.commandStation.events.DisconnectionEvent;
-import jcs.commandStation.events.DisconnectionEventListener;
+import jcs.commandStation.events.ConnectionEvent;
import jcs.commandStation.events.SensorEvent;
import jcs.commandStation.events.SensorEventListener;
import static jcs.commandStation.hsis88.HSIConnection.COMMAND_VERSION;
import jcs.entities.CommandStationBean;
import jcs.entities.CommandStationBean.ConnectionType;
-import jcs.commandStation.entities.DeviceBean;
-import jcs.entities.FeedbackModuleBean;
+import jcs.commandStation.entities.FeedbackModule;
import jcs.commandStation.entities.InfoBean;
import jcs.commandStation.VirtualConnection;
+import jcs.commandStation.entities.Device;
import jcs.entities.SensorBean;
import jcs.util.RunUtil;
import org.tinylog.Logger;
+import jcs.commandStation.events.ConnectionEventListener;
/**
*
@@ -47,9 +48,9 @@ public class HSIImpl extends AbstractController implements FeedbackController {
private HSIConnection connection;
private InfoBean infoBean;
- private final Map devices;
- private DeviceBean mainDevice;
- private DeviceBean feedbackDevice;
+ //private final Map devices;
+ //private DeviceBean mainDevice;
+ //private DeviceBean feedbackDevice;
private final Map sensors;
@@ -59,9 +60,9 @@ public HSIImpl(CommandStationBean commandStationBean) {
public HSIImpl(CommandStationBean commandStationBean, boolean autoConnect) {
super(autoConnect, commandStationBean);
- devices = new HashMap<>();
+ //devices = new HashMap<>();
sensors = new HashMap<>();
- this.executor = Executors.newCachedThreadPool();
+ //this.executor = Executors.newCachedThreadPool();
if (commandStationBean != null) {
if (autoConnect) {
@@ -123,24 +124,24 @@ public final synchronized boolean connect() {
this.infoBean.setHostname(this.commandStationBean.getSerialPort());
this.infoBean.setProductName(info);
- DeviceBean d = new DeviceBean();
+ //DeviceBean d = new DeviceBean();
String[] hsiinfo = info.split("/");
- d.setName(info);
- d.setUid("0");
- for (int i = 0; i < hsiinfo.length; i++) {
- switch (i) {
- case 0 ->
- d.setVersion(hsiinfo[i]);
- case 1 ->
- d.setSerial(hsiinfo[i]);
- case 2 ->
- d.setName(hsiinfo[i]);
- case 3 ->
- d.setTypeName(hsiinfo[i]);
- }
- }
- this.mainDevice = d;
- this.devices.put(0, d);
+ //d.setName(info);
+ //d.setUid("0");
+// for (int i = 0; i < hsiinfo.length; i++) {
+// switch (i) {
+// case 0 ->
+// //d.setVersion(hsiinfo[i]);
+// case 1 ->
+// //d.setSerial(hsiinfo[i]);
+// case 2 ->
+// //d.setName(hsiinfo[i]);
+// case 3 ->
+// //d.setTypeName(hsiinfo[i]);
+// }
+// }
+// //this.mainDevice = d;
+// this.devices.put(0, d);
//Query the S88 Modules ?
//connection.sendMessage("m\r");
@@ -156,28 +157,18 @@ public final synchronized boolean connect() {
return connected;
}
- @Override
- public DeviceBean getDevice() {
- return this.mainDevice;
- }
-
- @Override
- public List getDevices() {
- return null;//this.devices.values().stream().collect(Collectors.toList());
- }
-
@Override
public InfoBean getCommandStationInfo() {
return infoBean;
}
- @Override
- public DeviceBean getFeedbackDevice() {
- return this.feedbackDevice;
+ public List getDevices() {
+ List devices = new ArrayList<>();
+ return devices;
}
@Override
- public List getFeedbackModules() {
+ public List getFeedbackModules() {
return null;
}
@@ -202,9 +193,9 @@ public void disconnect() {
Logger.trace("Disconnected");
}
- private void fireAllDisconnectionEventListeners(final DisconnectionEvent disconnectionEvent) {
- for (DisconnectionEventListener listener : this.disconnectionEventListeners) {
- listener.onDisconnect(disconnectionEvent);
+ private void fireAllDisconnectionEventListeners(final ConnectionEvent disconnectionEvent) {
+ for (ConnectionEventListener listener : this.connectionEventListeners) {
+ listener.onConnectionChange(disconnectionEvent);
}
disconnect();
}
@@ -237,7 +228,7 @@ public void onMessage(final HSIMessage message) {
}
@Override
- public void onDisconnect(DisconnectionEvent event) {
+ public void onDisconnect(ConnectionEvent event) {
this.hsiImpl.fireAllDisconnectionEventListeners(event);
}
}
@@ -279,7 +270,8 @@ private void fireSensorEvents(HSIMessage message) {
//Logger.trace("U: " + sb.toLogString());
}
} else {
- sb = new SensorBean(0, key, contacts[i]);
+ //TODO: !!!!!!!
+ sb = null; //new SensorBean(0, key, contacts[i]);
this.sensors.put(sb.getContactId(), sb);
SensorEvent se = new SensorEvent(sb);
diff --git a/src/main/java/jcs/commandStation/hsis88/HSIMessageListener.java b/src/main/java/jcs/commandStation/hsis88/HSIMessageListener.java
index 0a7cf7df..acb94478 100644
--- a/src/main/java/jcs/commandStation/hsis88/HSIMessageListener.java
+++ b/src/main/java/jcs/commandStation/hsis88/HSIMessageListener.java
@@ -15,7 +15,7 @@
*/
package jcs.commandStation.hsis88;
-import jcs.commandStation.events.DisconnectionEvent;
+import jcs.commandStation.events.ConnectionEvent;
/**
*
@@ -25,6 +25,6 @@ public interface HSIMessageListener {
void onMessage(HSIMessage message);
- void onDisconnect(DisconnectionEvent event);
+ void onDisconnect(ConnectionEvent event);
}
diff --git a/src/main/java/jcs/commandStation/hsis88/HSISerialConnection.java b/src/main/java/jcs/commandStation/hsis88/HSISerialConnection.java
index 27e42871..1eade79d 100644
--- a/src/main/java/jcs/commandStation/hsis88/HSISerialConnection.java
+++ b/src/main/java/jcs/commandStation/hsis88/HSISerialConnection.java
@@ -25,7 +25,7 @@
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
-import jcs.commandStation.events.DisconnectionEvent;
+import jcs.commandStation.events.ConnectionEvent;
import org.tinylog.Logger;
/**
@@ -175,7 +175,7 @@ private void disconnected() {
Logger.trace("Port " + commPort.getSystemPortName() + " is Disconnected");
String msg = commPort.getDescriptivePortName() + " [" + commPort.getSystemPortName() + "]";
- DisconnectionEvent de = new DisconnectionEvent(msg);
+ ConnectionEvent de = new ConnectionEvent(msg, false);
for (HSIMessageListener listener : feedbackListeners) {
listener.onDisconnect(de);
diff --git a/src/main/java/jcs/commandStation/marklin/cs/MarklinCentralStationImpl.java b/src/main/java/jcs/commandStation/marklin/cs/MarklinCentralStationImpl.java
index 09c31a62..4184a799 100755
--- a/src/main/java/jcs/commandStation/marklin/cs/MarklinCentralStationImpl.java
+++ b/src/main/java/jcs/commandStation/marklin/cs/MarklinCentralStationImpl.java
@@ -15,13 +15,22 @@
*/
package jcs.commandStation.marklin.cs;
+import jcs.commandStation.marklin.cs.can.device.CanDevice;
+import jcs.commandStation.marklin.cs.can.parser.FeedbackEventMessage;
import java.awt.Image;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.SortedMap;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.Executors;
-import java.util.stream.Collectors;
+import java.util.concurrent.TransferQueue;
import jcs.JCS;
import jcs.commandStation.AbstractController;
import jcs.commandStation.AccessoryController;
@@ -44,61 +53,70 @@
import static jcs.commandStation.marklin.cs.can.CanMessageFactory.getStatusDataConfigResponse;
import jcs.commandStation.marklin.cs.can.parser.MessageInflator;
import jcs.commandStation.marklin.cs.can.parser.SystemStatus;
-import jcs.commandStation.marklin.cs.events.AccessoryListener;
-import jcs.commandStation.marklin.cs.events.CanPingListener;
-import jcs.commandStation.marklin.cs.events.FeedbackListener;
-import jcs.commandStation.marklin.cs.events.LocomotiveListener;
-import jcs.commandStation.marklin.cs.events.SystemListener;
import jcs.commandStation.marklin.cs.net.CSConnection;
import jcs.commandStation.marklin.cs.net.CSConnectionFactory;
-import jcs.commandStation.marklin.cs.net.HTTPConnection;
import jcs.commandStation.marklin.cs2.AccessoryBeanParser;
-import jcs.commandStation.marklin.cs2.ChannelDataParser;
import jcs.commandStation.marklin.cs2.LocomotiveBeanParser;
-import jcs.commandStation.marklin.cs3.DeviceJSONParser;
import jcs.commandStation.marklin.cs3.FunctionSvgToPngConverter;
import jcs.commandStation.marklin.cs3.LocomotiveBeanJSONParser;
import jcs.entities.AccessoryBean;
import jcs.entities.AccessoryBean.AccessoryValue;
-import jcs.entities.ChannelBean;
import jcs.entities.CommandStationBean;
-import jcs.commandStation.entities.DeviceBean;
import jcs.commandStation.entities.InfoBean;
-import jcs.commandStation.marklin.cs2.AccessoryEventParser;
-import jcs.entities.FeedbackModuleBean;
-import jcs.commandStation.marklin.cs2.InfoBeanParser;
+import jcs.commandStation.marklin.cs.can.parser.AccessoryMessage;
+import jcs.commandStation.entities.FeedbackModule;
import jcs.commandStation.marklin.cs2.LocomotiveDirectionEventParser;
import jcs.commandStation.marklin.cs2.LocomotiveFunctionEventParser;
-import jcs.commandStation.marklin.cs2.LocomotiveSpeedEventParser;
+import jcs.commandStation.marklin.cs.can.parser.LocomotiveVelocityMessage;
import jcs.commandStation.marklin.cs2.PowerEventParser;
-import jcs.commandStation.marklin.cs2.SensorMessageParser;
import jcs.commandStation.VirtualConnection;
+import jcs.commandStation.autopilot.AutoPilot;
+import jcs.commandStation.autopilot.DriveSimulator;
+import jcs.commandStation.entities.Device;
+import jcs.commandStation.entities.MeasuredChannels;
+import jcs.commandStation.entities.MeasurementBean;
+import jcs.commandStation.events.ConnectionEvent;
+import jcs.commandStation.marklin.cs.can.device.ConfigChannel;
+import jcs.commandStation.marklin.cs.can.device.MeasuringChannel;
+import jcs.commandStation.marklin.parser.CanDeviceParser;
+import jcs.commandStation.marklin.cs.can.parser.LocomotiveEmergencyStopMessage;
import jcs.entities.LocomotiveBean;
-import jcs.entities.LocomotiveBean.DecoderType;
-import static jcs.entities.LocomotiveBean.DecoderType.DCC;
-import static jcs.entities.LocomotiveBean.DecoderType.MFX;
-import static jcs.entities.LocomotiveBean.DecoderType.MFXP;
-import static jcs.entities.LocomotiveBean.DecoderType.SX1;
import jcs.entities.LocomotiveBean.Direction;
import jcs.entities.SensorBean;
import jcs.util.RunUtil;
import org.tinylog.Logger;
+import jcs.commandStation.marklin.cs.net.CSHTTPConnection;
+import jcs.commandStation.marklin.parser.GeraetParser;
+import jcs.commandStation.marklin.parser.SystemStatusMessage;
+import jcs.commandStation.events.ConnectionEventListener;
+import jcs.commandStation.events.MeasurementEvent;
+import jcs.commandStation.events.MeasurementEventListener;
+import jcs.commandStation.marklin.parser.CanDeviceJSONParser;
+import jcs.util.Ping;
/**
- *
- * @author Frans Jacobs
+ * Command Station Implementation for Marklin CS-2/3
*/
-public class MarklinCentralStationImpl extends AbstractController implements DecoderController, AccessoryController, FeedbackController {
+public class MarklinCentralStationImpl extends AbstractController implements DecoderController, AccessoryController, FeedbackController, ConnectionEventListener {
private CSConnection connection;
private InfoBean infoBean;
- private final Map devices;
- private DeviceBean mainDevice;
- private DeviceBean feedbackDevice;
+ private Map canDevices;
+
private int csUid;
+ private EventMessageHandler eventMessageHandler;
+
+ private DriveSimulator simulator;
+
+ private Long canBootLoaderLastCallMillis;
+ private WatchdogTask watchdogTask;
+ private Timer watchDogTimer;
+
+ private MeasurementTask measurementTask;
+ private Timer measurementTimer;
- Map analogChannels;
+ private SortedMap measuredValues;
public MarklinCentralStationImpl(CommandStationBean commandStationBean) {
this(commandStationBean, false);
@@ -106,8 +124,8 @@ public MarklinCentralStationImpl(CommandStationBean commandStationBean) {
public MarklinCentralStationImpl(CommandStationBean commandStationBean, boolean autoConnect) {
super(autoConnect, commandStationBean);
- devices = new HashMap<>();
- analogChannels = new HashMap<>();
+ canDevices = new HashMap<>();
+ measuredValues = new ConcurrentSkipListMap<>();
if (commandStationBean != null) {
if (autoConnect) {
@@ -123,14 +141,9 @@ int getCsUid() {
return csUid;
}
- private boolean isCS3(String articleNumber) {
- return "60216".equals(articleNumber) || "60226".equals(articleNumber);
- }
-
- public boolean isCS3() {
- if (mainDevice != null) {
- String articleNumber = mainDevice.getArticleNumber();
- return "60216".equals(articleNumber) || "60226".equals(articleNumber);
+ boolean isCS3() {
+ if (infoBean != null && infoBean.getArticleNumber() != null) {
+ return "60216".equals(infoBean.getArticleNumber()) || "60226".equals(infoBean.getArticleNumber());
} else {
return false;
}
@@ -141,24 +154,77 @@ public String getIp() {
return CSConnectionFactory.getControllerIp();
}
+ @Override
+ public void setVirtual(boolean flag) {
+ this.virtual = flag;
+ Logger.info("Switching Virtual Mode " + (flag ? "On" : "Off"));
+ disconnect();
+ connect();
+ }
+
+ CanDevice getCanDevice(String name) {
+ for (CanDevice d : canDevices.values()) {
+ if (name.equals(d.getName())) {
+ return d;
+ }
+ }
+ return null;
+ }
+
@Override
public final synchronized boolean connect() {
if (!connected) {
- Logger.trace("Connecting to a Central Station " + (commandStationBean != null ? commandStationBean.getDescription() : "Unknown"));
+ Logger.trace("Connecting to a " + (virtual ? "Virtual " : "") + "Central Station " + (commandStationBean != null ? commandStationBean.getDescription() : "Unknown"));
+
if (executor == null || executor.isShutdown()) {
executor = Executors.newCachedThreadPool();
}
- CSConnection csConnection = CSConnectionFactory.getConnection();
- connection = csConnection;
+ if (commandStationBean == null) {
+ Logger.error("Marklin Command Station Configuration NOT set!");
+ return false;
+ } else {
+ Logger.trace("Connect using " + commandStationBean.getConnectionType());
+ }
+
+ CommandStationBean.ConnectionType conType = commandStationBean.getConnectionType();
+
+ boolean canConnect;
+ if (conType == CommandStationBean.ConnectionType.NETWORK) {
+ try {
+ InetAddress csAddr;
+ if (virtual) {
+ csAddr = InetAddress.getLocalHost();
+ } else {
+ csAddr = InetAddress.getByName(commandStationBean.getIpAddress());
+ }
+ commandStationBean.setIpAddress(csAddr.getHostAddress());
+ canConnect = csAddr.getHostAddress() != null;
+ } catch (UnknownHostException ex) {
+ Logger.error("Invalid ip address : " + commandStationBean.getIpAddress());
+ return false;
+ }
+ } else {
+ if (virtual) {
+ canConnect = true;
+ } else {
+ canConnect = Ping.IsReachable(commandStationBean.getIpAddress());
+ }
+ }
+
+ if (!canConnect) {
+ Logger.error("Can't connect to " + (commandStationBean.getIpAddress() == null ? "ip Address not set" : "can't reach ip " + commandStationBean.getIpAddress()));
+ return false;
+ }
+
+ connection = CSConnectionFactory.getConnection(commandStationBean);
if (connection != null) {
- //Wait, if needed until the receiver thread has started
long now = System.currentTimeMillis();
- long timeout = now + 1000L;
+ long timeout = now + 5000L;
while (!connected && now < timeout) {
- connected = csConnection.isConnected();
+ connected = connection.isConnected();
now = System.currentTimeMillis();
}
if (!connected && now > timeout) {
@@ -166,142 +232,352 @@ public final synchronized boolean connect() {
}
if (connected) {
- //Prepare the observers (listeners) which need to react on message events from the Central Station
- CanPingMessageListener pingListener = new CanPingMessageListener(this);
- CanFeedbackMessageListener feedbackListener = new CanFeedbackMessageListener(this);
- CanSystemMessageListener systemEventListener = new CanSystemMessageListener(this);
- CanAccessoryMessageListener accessoryListener = new CanAccessoryMessageListener(this);
- CanLocomotiveMessageListener locomotiveListener = new CanLocomotiveMessageListener(this);
-
- this.connection.setCanPingListener(pingListener);
- this.connection.setFeedbackListener(feedbackListener);
- this.connection.setSystemListener(systemEventListener);
- this.connection.setAccessoryListener(accessoryListener);
- this.connection.setLocomotiveListener(locomotiveListener);
+ CanDevice gfp = getGFP();
+ canDevices.put(gfp.getUidInt(), gfp);
+ csUid = gfp.getUidInt();
+
+ canBootLoaderLastCallMillis = System.currentTimeMillis();
JCS.logProgress("Obtaining Device information...");
+ if (virtual) {
+ List devices = getCS3Devices();
+ for (CanDevice device : devices) {
+ if (CanDeviceJSONParser.GFP.equals(device.getName())) {
+ canDevices.put(device.getUidInt(), device);
+ }
+ }
+ } else {
+ csUid = Integer.parseUnsignedInt(gfp.getUid(), 16);
+ obtainDevices();
+ }
- infoBean = getCSInfo();
+ //The eventMessageHandler Thread is in charge to handle all event messages which are send from the CS to JCS
+ eventMessageHandler = new EventMessageHandler(connection);
+ eventMessageHandler.start();
- //request all members on the Marklin CAN bus to give a response
- long start = System.currentTimeMillis();
- now = System.currentTimeMillis();
- timeout = now + 30000L;
+ connection.addDisconnectionEventListener(this);
+ startWatchdog();
- if (isCS3(infoBean.getArticleNumber())) {
- Logger.trace("Connected to CS3");
- getAppDevicesCs3();
- } else {
- Logger.trace("Connected to CS2");
- getMembers();
- while (mainDevice == null && mainDevice.getName() == null && mainDevice.getArticleNumber() == null && now < timeout) {
- pause(100);
- now = System.currentTimeMillis();
- }
- }
+ power = isPower();
+ JCS.logProgress("Power is " + (power ? "On" : "Off"));
- if (mainDevice != null) {
- Logger.trace("Found " + mainDevice.getName() + ", " + mainDevice.getArticleNumber() + " SerialNumber: " + mainDevice.getSerial() + " in " + (now - start) + " ms");
- JCS.logProgress("Connected with " + infoBean.getProductName());
+// if (gfp.getMeasureChannelCount() != null && gfp.getMeasureChannelCount() > 0) {
+// Logger.trace("Measurements are possible...");
+// }
+// else {
+// queryDevice(gfp);
+// Logger.trace("GFP Measurement Count: " + gfp.getMeasureChannelCount());
+// }
+ startMeasurements();
- power = isPower();
- JCS.logProgress("Power is " + (power ? "On" : "Off"));
- } else {
- Logger.warn("No main Device found yet...");
- }
+ Logger.trace("Connected to " + gfp.getName() + ", " + gfp.getArticleNumber() + " SerialNumber: " + gfp.getSerial());
}
- Logger.trace("Connected: " + connected + " Default Accessory SwitchTime: " + this.defaultSwitchTime);
} else {
Logger.warn("Can't connect with Central Station!");
JCS.logProgress("Can't connect with Central Station!");
}
}
- if (isCS3()) {
- getMembers();
+ if (isVirtual()) {
+ simulator = new DriveSimulator();
+ Logger.info("Marklin Central Station Virtual Mode Enabled!");
}
return connected;
}
- //The Central station has a "geraet.vrs" files which can be retrieved via HTTP.
+ //The device information can be retrieved via CAN, but using a shortcut via http goes much faster.
+ //The Central station has a "geraet.vrs" file which can be retrieved via HTTP.
//Based on the info in this file it is quicker to know whether the CS is a version 2 or 3.
- //In case of a 3 the data can ve retreived via JSON else use CAN
- private InfoBean getCSInfo() {
- HTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
+ //In case of a CS-3 the information can be retrieved via JSON else use CAN
+ CanDevice getGFP() {
+ CSHTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
String geraet = httpCon.getInfoFile();
- InfoBean ib = InfoBeanParser.parseFile(geraet);
+ CanDevice gfp = GeraetParser.parseFile(geraet);
+ return gfp;
+ }
+
+ InfoBean createInfoBean(Map canDevices) {
+ InfoBean ib = new InfoBean(commandStationBean);
+ if (connection != null && connection.getControllerAddress() != null) {
+ ib.setIpAddress(connection.getControllerAddress().getHostAddress());
+ }
- if ("60126".equals(ib.getArticleNumber()) || "60226".equals(ib.getArticleNumber())) {
- //CS3
- String json = httpCon.getInfoJSON();
- ib = InfoBeanParser.parseJson(json);
- httpCon.setCs3(true);
+ for (CanDevice d : canDevices.values()) {
+ Logger.trace("Checking device: " + d);
+ String name = d.getName();
+ if (name == null) {
+ name = "null";
+ }
+ switch (name) {
+ case "Central Station 3" -> {
+ ib.setGfpUid(d.getUid());
+ //String uid = d.getUid();
+ //uid = uid.replace("0x", "");
+ //csUid = Integer.parseUnsignedInt(uid, 16);
+ Logger.trace("GFP uid: " + d.getUid() + " -> " + csUid);
+
+ ib.setArticleNumber(d.getArticleNumber());
+ ib.setProductName(d.getName());
+ ib.setSerialNumber(d.getSerial());
+ ib.setHardwareVersion(d.getVersion());
+ //ib.setSupportMeasurements(d.getMeasureChannelCount() > 0);
+
+ //TODO: Only CS 2 and CS3 plus...?
+ //ib.setFeedbackBus0ModuleCount(0);
+ //ib.setFeedbackSupport(true);
+ //Is the System property still needed?
+ //if (System.getProperty("cs.article") == null) {
+ //System.setProperty("cs.article", mainDevice.getArticleNumber());
+ //System.setProperty("cs.serial", mainDevice.getSerial());
+ //System.setProperty("cs.name", mainDevice.getName());
+ //System.setProperty("cs.cs3", (isCS3() ? "true" : "false"));
+ //}
+ }
+ case "GFP3-1" -> {
+ //Virtual
+ ib.setGfpUid(d.getUid());
+ Logger.trace("GFP uid: " + d.getUid() + " -> " + csUid);
+
+ ib.setArticleNumber(d.getArticleNumber());
+ ib.setProductName(d.getName());
+ ib.setSerialNumber(d.getSerial());
+ ib.setHardwareVersion(d.getVersion());
+ //ib.setSupportMeasurements(d.getMeasureChannelCount() > 0);
+ }
+ case "Link S88" -> {
+ ib.setFeedbackSupport(true);
+ ConfigChannel bus1 = d.getConfigChannel(2);
+ ConfigChannel bus2 = d.getConfigChannel(3);
+ ConfigChannel bus3 = d.getConfigChannel(4);
+
+ ib.setFeedbackBus1ModuleCount(bus1.getActualValue());
+ ib.setFeedbackBus2ModuleCount(bus2.getActualValue());
+ ib.setFeedbackBus3ModuleCount(bus3.getActualValue());
+ }
+ case "CS2/3-GUI (Master)" -> {
+ ib.setSoftwareVersion(d.getVersion());
+ }
+ default -> {
+ Logger.info("A yet unknown CAN Device " + d);
+ }
+ }
}
return ib;
}
/**
- * The CS3 has a Web App API which is used for the Web GUI.
- * The Internal devices can be obtained calling this API which returns a JSON string.
- * From this JSON all devices are found.
- * Most important is the GFP which is the heart of the CS 3 most CAN Commands need the GFP UID.
- * This data can also be obtained using the CAN Member PING command, but The JSON gives a little more detail.
+ * Obtain information about the connected CAN Device in the Central Station
*/
- private void getAppDevicesCs3() {
- HTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
+ void obtainDevices() {
+ CanMessage msg = CanMessageFactory.getMembersPing();
+ connection.sendCanMessage(msg);
- String devJson = httpCon.getDevicesJSON();
- List devs = DeviceJSONParser.parse(devJson);
- //Update the devices
- for (DeviceBean d : devs) {
- if (devices.containsKey(d.getUidAsInt())) {
- //Logger.trace("Updating " + d.getUid() + ", " + d.getName());
- devices.put(d.getUidAsInt(), d);
+ List devices = CanDeviceParser.parse(msg);
+ Logger.trace("Found " + devices.size() + " CANDevices");
+ for (CanDevice d : devices) {
+ if (!canDevices.containsKey(d.getUidInt())) {
+ canDevices.put(d.getUidInt(), d);
} else {
- //Logger.trace("Adding " + d.getUid() + ", " + d.getName());
- devices.put(d.getUidAsInt(), d);
- }
- String an = d.getArticleNumber();
- if ("60213".equals(an) || "60214".equals(an) || "60215".equals(an) || "60126".equals(an) || "60226".equals(an)) {
- this.csUid = d.getUidAsInt();
- this.mainDevice = d;
- Logger.trace("MainDevice: " + d);
+ CanDevice ed = canDevices.get(d.getUidInt());
+ if (d.getSerial() != null) {
+ ed.setSerial(d.getSerial());
+ }
+ if (d.getVersion() != null) {
+ ed.setVersion(d.getVersion());
+ }
+ if (d.getIdentifier() != null) {
+ ed.setIdentifier(d.getIdentifier());
+ }
+ if (d.getMeasureChannelCount() != null) {
+ ed.setMeasureChannelCount(d.getMeasureChannelCount());
+ }
+ if (d.getConfigChannelCount() != null) {
+ ed.setConfigChannelCount(d.getConfigChannelCount());
+ }
+ if (d.getArticleNumber() != null) {
+ ed.setArticleNumber(d.getArticleNumber());
+ }
+ if (d.getName() != null) {
+ ed.setName(d.getName());
+ }
+ Logger.trace("Updated: " + ed);
}
+ }
+
+ //Lets get some info about these members
+ for (CanDevice device : canDevices.values()) {
+ queryDevice(device);
+ }
+ }
+
+ void queryDevice(CanDevice device) {
+ Logger.trace("Query for information about device " + device);
+ CanMessage updateMessage = sendMessage(CanMessageFactory.statusDataConfig(device.getUidInt(), 0));
- if ("60883".equals(an)) {
- this.feedbackDevice = d;
- Logger.trace("FeedbackDevice: " + d);
+ if (!updateMessage.hasValidResponse() && device.getGuiUid() != null) {
+ Logger.trace("Trying fallback " + device.getGuiUid());
+ updateMessage = sendMessage(CanMessageFactory.statusDataConfig(device.getGuiUidInt(), 0));
+ }
+
+ if (updateMessage.hasValidResponse()) {
+ CanDeviceParser.parse(device, updateMessage);
+
+ int measurementChannels;
+ if (device.getMeasureChannelCount() == null) {
+ measurementChannels = 0;
+ } else {
+ measurementChannels = device.getMeasureChannelCount();
}
+ int configChannels;
+ if (device.getConfigChannelCount() == null) {
+ configChannels = 0;
+ } else {
+ configChannels = device.getConfigChannelCount();
+ }
+
+ int channels = measurementChannels + configChannels;
+ if (channels > 0) {
+ Logger.trace("Quering " + channels + " channels for device " + device);
+ for (int index = 1; index <= channels; index++) {
+ Logger.trace("Query channel " + index);
+ updateMessage = sendMessage(CanMessageFactory.statusDataConfig(device.getUidInt(), index));
+ CanDeviceParser.parse(device, updateMessage);
+ if (index <= measurementChannels) {
+ Logger.trace("M#" + index + "; " + device.getMeasuringChannel(index));
+ } else {
+ int configChannelNumber = index - measurementChannels;
+ Logger.trace("C#" + configChannelNumber + "; " + device.getConfigChannel(configChannelNumber));
+ }
+ }
+ }
+ } else {
+ Logger.trace("No response data in query for " + device);
}
- Logger.trace("Found " + devices.size() + " devices");
}
- @Override
- public DeviceBean getDevice() {
- return this.mainDevice;
- }
+ /**
+ * The CS3 has a Web App API which is used for the Web GUI.
+ * The Internal devices can be obtained calling this API which returns a JSON string.
+ * From this JSON all devices are found.
+ * Most important is the GFP which is the heart of the CS 3 most CAN Commands need the GFP UID.
+ * This data can also be obtained using the CAN Member PING command, but The JSON gives a little more detail.
+ */
+ private List getCS3Devices() {
+ CSHTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
- @Override
- public List getDevices() {
- return this.devices.values().stream().collect(Collectors.toList());
+ String devJson = httpCon.getDevicesJSON();
+ List devices = CanDeviceJSONParser.parse(devJson);
+ return devices;
}
@Override
public InfoBean getCommandStationInfo() {
+ if (infoBean == null) {
+ infoBean = createInfoBean(canDevices);
+ }
return infoBean;
}
@Override
- public DeviceBean getFeedbackDevice() {
- return this.feedbackDevice;
+ public List getDevices() {
+ List devices = new ArrayList<>();
+ for (CanDevice cd : canDevices.values()) {
+ Device d = new Device();
+ d.setId(cd.getUid());
+ d.setName(cd.getName());
+ d.setSerialNumber(cd.getSerial());
+ d.setSoftwareVersion(cd.getVersion());
+ d.setHardwareVersion(cd.getHwVersion());
+ d.setChannels(cd.getConfigChannelCount());
+ d.setFeedback(CanDevice.FEEDBACK_DEVICE_NAME.equals(cd.getName()));
+ devices.add(d);
+ }
+
+ return devices;
}
- //TODO!
@Override
- public List getFeedbackModules() {
- return null;
+ public List getFeedbackModules() {
+ //Feedbackmodules can be queried from the Link S88 if available.
+ //In case of a CS 3 Plus or CS 2 there should be a node "0" which could have max 32 S88 modules.
+ //TODO: Test with CS-3Plus and CS2.
+ //Link S88
+ List feedbackModules = new ArrayList<>();
+ CanDevice links88 = getCanDevice(CanDevice.FEEDBACK_DEVICE_NAME);
+ int bus1Len = 0, bus2Len = 0, bus3Len = 0, nodeId = 0;
+ if (links88 != null) {
+ nodeId = links88.getIdentifierInt() + 1;
+ for (ConfigChannel cc : links88.getConfigChannels()) {
+ if (cc.getNumber() == 2) {
+ bus1Len = cc.getActualValue();
+ }
+ if (cc.getNumber() == 3) {
+ bus2Len = cc.getActualValue();
+ }
+ if (cc.getNumber() == 4) {
+ bus3Len = cc.getActualValue();
+ }
+ }
+ Logger.trace("nodeId: " + nodeId + ", bus1Len: " + bus1Len + ", bus2Len: " + bus2Len + ", bus3Len: " + bus3Len);
+
+ //Link S88 has 16 sensors starting from 0
+ //Bus 1 offset 1000, Bus 2 offset 2000 and Bus 3 offset 3000
+ FeedbackModule l = new FeedbackModule();
+ l.setId(0);
+
+ l.setAddressOffset(0);
+ l.setModuleNumber(1);
+ l.setPortCount(16);
+ l.setIdentifier(nodeId);
+ l.setBusNumber(0);
+ l.setCommandStationId(commandStationBean.getId());
+ l.setBusSize(1);
+ feedbackModules.add(l);
+
+ for (int i = 0; i < bus1Len; i++) {
+ FeedbackModule b1 = new FeedbackModule();
+ //Use the offset plus module nr as the id
+ b1.setId(1000 + i);
+ b1.setAddressOffset(1000);
+ b1.setModuleNumber(i + 1);
+ b1.setPortCount(16);
+ b1.setIdentifier(nodeId);
+ b1.setBusNumber(1);
+ b1.setCommandStationId(commandStationBean.getId());
+ b1.setBusSize(bus1Len);
+ feedbackModules.add(b1);
+ }
+ for (int i = 0; i < bus2Len; i++) {
+ FeedbackModule b2 = new FeedbackModule();
+ b2.setId(2000 + i);
+ b2.setAddressOffset(2000);
+ b2.setModuleNumber(i + 1);
+ b2.setPortCount(16);
+ b2.setIdentifier(nodeId);
+ b2.setBusNumber(2);
+ b2.setCommandStationId(commandStationBean.getId());
+ b2.setBusSize(bus2Len);
+
+ feedbackModules.add(b2);
+ }
+ for (int i = 0; i < bus3Len; i++) {
+ FeedbackModule b3 = new FeedbackModule();
+ b3.setId(3000 + i);
+ b3.setAddressOffset(3000);
+ b3.setModuleNumber(i + 1);
+ b3.setPortCount(16);
+ b3.setIdentifier(nodeId);
+ b3.setBusNumber(3);
+ b3.setCommandStationId(commandStationBean.getId());
+ b3.setBusSize(bus3Len);
+
+ feedbackModules.add(b3);
+ }
+ }
+
+ return feedbackModules;
}
/**
@@ -311,36 +587,31 @@ public List getFeedbackModules() {
*/
@Override
public boolean isPower() {
- if (this.connected) {
- CanMessage m = sendMessage(CanMessageFactory.querySystem(this.csUid));
- if (debug) {
- Logger.trace("Received " + m.getResponses().size() + " responses. RX: " + m.getResponse());
- }
- SystemStatus ss = new SystemStatus(m);
- this.power = ss.isPower();
+ if (connected) {
+ power = SystemStatus.parseSystemPowerMessage(sendMessage(CanMessageFactory.querySystem(csUid)));
} else {
- this.power = false;
+ power = false;
}
- return this.power;
+ return power;
}
/**
- * System Stop and GO When on = true then the GO command is issued: The track format processor activates the operation and supplies electrical energy. Any speed levels/functions that may still exist
- * or have been saved will be sent again. when false the Stop command is issued: Track format processor stops operation on main and programming track. Electrical energy is no longer supplied. All
- * speed levels/function values and settings are retained.
+ * System Stop and GO When on = true then the GO command is issued:
+ * The track format processor activates the operation and supplies electrical energy.
+ * Any speed levels/functions that may still exist or have been saved will be sent again.
+ * When false the Stop command is issued: Track format processor stops operation on main and programming track.
+ * Electrical energy is no longer supplied. All speed levels/function values and settings are retained.
*
* @param on true Track power On else Off
* @return true the Track power is On else Off
*/
@Override
public boolean power(boolean on) {
- if (this.connected) {
- SystemStatus ss = new SystemStatus(sendMessage(CanMessageFactory.systemStopGo(on, csUid)));
- this.power = ss.isPower();
-
- PowerEvent pe = new PowerEvent(this.power);
+ if (connected) {
+ Logger.trace("Switch Track Power " + (on ? "On" : "Off"));
+ power = SystemStatus.parseSystemPowerMessage(sendMessage(CanMessageFactory.systemStopGo(on, csUid)));
+ PowerEvent pe = new PowerEvent(power);
notifyPowerEventListeners(pe);
-
return power;
} else {
return false;
@@ -349,15 +620,36 @@ public boolean power(boolean on) {
@Override
public void disconnect() {
+ Logger.trace("Start disconnecting...");
+ //Stop all schedules
+ if (measurementTimer != null) {
+ measurementTimer.cancel();
+ }
+ if (watchDogTimer != null) {
+ watchDogTimer.cancel();
+ }
+ //Stop Threads
+ if (executor != null) {
+ executor.shutdown();
+ }
+
+ //Signal listeners that there are no measurements
+ MeasuredChannels measuredChannels = new MeasuredChannels(System.currentTimeMillis());
+ MeasurementEvent me = new MeasurementEvent(measuredChannels);
+ for (MeasurementEventListener listener : measurementEventListeners) {
+ listener.onMeasurement(me);
+ }
+
try {
if (connection != null) {
+ if (eventMessageHandler != null) {
+ eventMessageHandler.quit();
+ }
+ eventMessageHandler.join(2000);
connection.close();
connected = false;
}
- if (executor != null) {
- executor.shutdown();
- }
executor = null;
connection = null;
@@ -368,164 +660,67 @@ public void disconnect() {
Logger.trace("Disconnected");
}
- void getMembers() {
- CanMessage msg = CanMessageFactory.getMembersPing();
- this.connection.sendCanMessage(msg);
-
- if (debug) {
- Logger.trace(msg);
- for (CanMessage r : msg.getResponses()) {
- Logger.trace(r);
- }
- }
-
- for (CanMessage r : msg.getResponses()) {
- DeviceBean d = new DeviceBean(r);
-
- if (!devices.containsKey(d.getUidAsInt())) {
- devices.put(d.getUidAsInt(), d);
- }
- if (debug) {
- Logger.trace("Found uid: " + d.getUid() + " deviceId: " + d.getIdentifier() + " Device Type: " + d.getDevice());
- }
- }
- if (debug) {
- Logger.trace("Found " + this.devices.size() + " devices");
+ @Override
+ public void onConnectionChange(ConnectionEvent event) {
+ String s = event.getSource();
+ boolean con = event.isConnected();
+ if (con) {
+ Logger.trace(s + " has re-connected");
+ } else {
+ disconnect();
}
- while (mainDevice == null) {
- for (DeviceBean d : this.getDevices()) {
- if (!d.isDataComplete()) {
- if (debug) {
- Logger.trace("Requesting more info for uid: " + d.getUid());
- }
- CanMessage updateMessage = sendMessage(CanMessageFactory.statusDataConfig(d.getUidAsInt(), 0));
- if (debug) {
- Logger.trace(updateMessage);
- for (CanMessage r : updateMessage.getResponses()) {
- Logger.trace(r);
- }
- }
- d.updateFromMessage(updateMessage);
- if (debug) {
- if (d.isDataComplete()) {
- Logger.trace("Updated: " + d);
- } else {
- Logger.trace("No data received for Device uid: " + d.getUid());
- }
- }
- }
- }
-
- for (DeviceBean d : this.getDevices()) {
- if (d.isDataComplete() && ("60213".equals(d.getArticleNumber()) || "60214".equals(d.getArticleNumber()) || "60215".equals(d.getArticleNumber()) || "60126".equals(d.getArticleNumber()) || "60226".equals(d.getArticleNumber()))) {
- csUid = d.getUidAsInt();
- mainDevice = d;
- if (debug) {
- Logger.trace("Main Device: " + d);
- }
- if (System.getProperty("cs.article") == null) {
- System.setProperty("cs.article", this.mainDevice.getArticleNumber());
- System.setProperty("cs.serial", this.mainDevice.getSerial());
- System.setProperty("cs.name", this.mainDevice.getName());
- System.setProperty("cs.cs3", (isCS3() ? "true" : "false"));
- }
- } else {
- if (debug) {
- Logger.trace(d);
- }
- }
- }
+ for (ConnectionEventListener listener : connectionEventListeners) {
+ listener.onConnectionChange(event);
}
}
- private void updateMember(CanMessage message) {
- executor.execute(() -> updateDevice(message));
+ @Override
+ public boolean isSupportTrackMeasurements() {
+ if (!virtual) {
+ CanDevice gfp = canDevices.get(csUid);
+ return gfp != null && gfp.getMeasureChannelCount() != null && gfp.getMeasureChannelCount() > 0;
+ } else {
+ return false;
+ }
}
- private void updateDevice(final CanMessage message) {
- if (CanMessage.PING_RESP == message.getCommand()) {
- int uid = message.getDeviceUidNumberFromMessage();
-
- DeviceBean device;
- if (this.devices.containsKey(uid)) {
- device = this.devices.get(uid);
- } else {
- device = new DeviceBean(message);
- this.devices.put(device.getUidAsInt(), device);
- }
-
- if (!device.isDataComplete()) {
- CanMessage msg = sendMessage(CanMessageFactory.statusDataConfig(device.getUidAsInt(), 0));
- device.updateFromMessage(msg);
- if (debug) {
- Logger.trace("Updated: " + device);
+ void performMeasurements() {
+ //The measurable channels are in the GFP.
+ CanDevice gfp = canDevices.get(csUid);
+ if (gfp != null) {
+ List channels = gfp.getMeasuringChannels();
+ long now = System.currentTimeMillis();
+ MeasuredChannels measuredChannels = new MeasuredChannels(now);
+ for (MeasuringChannel channel : channels) {
+ int channelNumber = channel.getNumber();
+
+ CanMessage message = sendMessage(CanMessageFactory.systemStatus(csUid, channelNumber));
+ MeasurementBean measurement = SystemStatusMessage.parse(channel, message, now);
+ measuredChannels.addMeasurement(measurement);
+ //Logger.trace(measurement);
+ measuredValues.put(now, measuredChannels);
+ if (measuredValues.size() > 100) {
+ long first = measuredValues.firstKey();
+ measuredValues.remove(first);
}
- //Can the main device be set from the avaliable data
- for (DeviceBean d : this.devices.values()) {
- if (d.isDataComplete() && ("60214".equals(d.getArticleNumber()) || "60226".equals(d.getArticleNumber()) || "60126".equals(d.getArticleNumber()))) {
- this.csUid = d.getUidAsInt();
- this.mainDevice = d;
- if (debug) {
- Logger.trace("Main Device: " + d);
- }
- }
- }
- }
- if (this.mainDevice == null) {
- //Lets send a ping again
- getMembers();
- } else {
- if (this.mainDevice != null && this.mainDevice.isDataComplete()) {
- if (System.getProperty("cs.article") == null) {
- System.setProperty("cs.article", this.mainDevice.getArticleNumber());
- System.setProperty("cs.serial", this.mainDevice.getSerial());
- System.setProperty("cs.name", this.mainDevice.getName());
- System.setProperty("cs.cs3", (isCS3() ? "true" : "false"));
- if (debug) {
- Logger.trace("CS " + (isCS3() ? "3" : "2") + " Device: " + device);
- }
- }
+ MeasurementEvent me = new MeasurementEvent(measuredChannels);
+ for (MeasurementEventListener listener : measurementEventListeners) {
+ listener.onMeasurement(me);
}
}
+ } else {
+ Logger.warn("No measurable channels available");
}
}
- @Override
- public boolean isSupportTrackMeasurements() {
- return true;
+ public List getMeasurements() {
+ return new ArrayList<>(measuredValues.values());
}
- @Override
- public synchronized Map getTrackMeasurements() {
- if (this.connected && this.mainDevice != null) {
- //main device
- int nrOfChannels = this.mainDevice.getAnalogChannels().size();
-
- ChannelDataParser parser = new ChannelDataParser();
-
- if (this.analogChannels.isEmpty()) {
- //No channels configured so let do this first
- for (int c = 1; c <= nrOfChannels; c++) {
- Logger.trace("Quering config for channel " + c);
- CanMessage message = sendMessage(CanMessageFactory.statusDataConfig(csUid, c));
-
- ChannelBean ch = parser.parseConfigMessage(message);
- analogChannels.put(c, ch);
- }
- }
-
- for (int c = 1; c <= nrOfChannels; c++) {
- ChannelBean ch = this.analogChannels.get(c);
- if (ch != null) {
- CanMessage message = sendMessage(CanMessageFactory.systemStatus(c, csUid));
- ch = parser.parseUpdateMessage(message, ch);
- analogChannels.put(c, ch);
- }
- }
- }
- return this.analogChannels;
+ public MeasuredChannels getLastMeasurment() {
+ return measuredValues.firstEntry().getValue();
}
/**
@@ -537,8 +732,8 @@ public synchronized Map getTrackMeasurements() {
* @return the CanMessage with responses
*/
private CanMessage sendMessage(CanMessage canMessage) {
- if (this.connection != null) {
- this.connection.sendCanMessage(canMessage);
+ if (connection != null && connected) {
+ connection.sendCanMessage(canMessage);
} else {
Logger.warn("NOT connected!");
Logger.trace("Message: " + canMessage + " NOT Send!");
@@ -546,58 +741,49 @@ private CanMessage sendMessage(CanMessage canMessage) {
return canMessage;
}
- private int getLocoAddres(int address, DecoderType decoderType) {
- int locoAddress;
- locoAddress = switch (decoderType) {
- case MFX ->
- 0x4000 + address;
- case MFXP ->
- 0x4000 + address;
- case DCC ->
- 0xC000 + address;
- case SX1 ->
- 0x0800 + address;
- default ->
- address;
- };
-
- return locoAddress;
- }
-
@Override
public void changeDirection(int locUid, Direction direction) {
- if (this.power && this.connected) {
+ if (power && connected) {
+ Logger.trace("Change direction to " + direction + " CS val " + direction.getMarklinValue());
CanMessage message = sendMessage(CanMessageFactory.setDirection(locUid, direction.getMarklinValue(), this.csUid));
+ //query velocity of give a not halt
LocomotiveDirectionEvent dme = LocomotiveDirectionEventParser.parseMessage(message);
- this.notifyLocomotiveDirectionEventListeners(dme);
+ notifyLocomotiveDirectionEventListeners(dme);
}
}
@Override
public void changeVelocity(int locUid, int speed, Direction direction) {
- if (this.power && this.connected) {
- CanMessage message = sendMessage(CanMessageFactory.setLocSpeed(locUid, speed, this.csUid));
- LocomotiveSpeedEvent vme = LocomotiveSpeedEventParser.parseMessage(message);
- this.notifyLocomotiveSpeedEventListeners(vme);
+ if (power && connected) {
+ CanMessage message = CanMessageFactory.setLocSpeed(locUid, speed, csUid);
+ Logger.trace("Ch Velocity for uid: " + locUid + " -> " + message);
+ message = sendMessage(message);
+
+ LocomotiveSpeedEvent vme = LocomotiveVelocityMessage.parse(message);
+ notifyLocomotiveSpeedEventListeners(vme);
+
+ if (isVirtual()) {
+ //When a locomotive has a speed change (>0) check if Auto mode is on.
+ //When in Auto mode try to simulate the first sensor the locomotive is suppose to hit.
+ if (AutoPilot.isAutoModeActive() && speed > 0) {
+ //simulateDriving(locUid, speed, direction);
+ simulator.simulateDriving(locUid, speed, direction);
+ }
+ }
}
}
@Override
public void changeFunctionValue(int locUid, int functionNumber, boolean flag) {
- if (this.power && this.connected) {
+ if (power && connected) {
CanMessage message = sendMessage(CanMessageFactory.setFunction(locUid, functionNumber, flag, this.csUid));
- this.notifyLocomotiveFunctionEventListeners(LocomotiveFunctionEventParser.parseMessage(message));
+ notifyLocomotiveFunctionEventListeners(LocomotiveFunctionEventParser.parseMessage(message));
}
}
@Override
- public void switchAccessory(Integer address, AccessoryValue value) {
- switchAccessory(address, value, defaultSwitchTime);
- }
-
- @Override
- public void switchAccessory(Integer address, AccessoryValue value, Integer switchTime) {
- if (this.power && this.connected) {
+ public void switchAccessory(Integer address, String protocol, AccessoryValue value, Integer switchTime) {
+ if (power && connected) {
//make sure a time is set!
int st;
if (switchTime == null || switchTime == 0) {
@@ -605,36 +791,30 @@ public void switchAccessory(Integer address, AccessoryValue value, Integer switc
} else {
st = switchTime / 10;
}
- //CS Switchtime is in 10 ms increments
+
+ int adr; // zero based!
+ if ("dcc".equals(protocol)) {
+ adr = address - 1;
+ adr = adr + CanMessage.DCC_ACCESSORY_OFFSET;
+ } else {
+ adr = address - 1;
+ }
+ //CS 2/3 Switchtime is in 10 ms increments!
st = st / 10;
- CanMessage message = sendMessage(CanMessageFactory.switchAccessory(address, value, true, st, this.csUid));
+ CanMessage message = sendMessage(CanMessageFactory.switchAccessory(adr, value, true, st, this.csUid));
//Notify listeners
- AccessoryEvent ae = AccessoryEventParser.parseMessage(message);
-
+ AccessoryEvent ae = AccessoryMessage.parse(message);
notifyAccessoryEventListeners(ae);
} else {
Logger.trace("Trackpower is OFF! Can't switch Accessory: " + address + " to: " + value + "!");
}
}
- @Override
- public void switchAccessory(String id, AccessoryValue value) {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
- private void sendJCSUID() {
- executor.execute(() -> sendJCSUIDMessage());
- }
-
- private void sendJCSUIDMessage() {
+ void sendJCSUIDMessage() {
sendMessage(CanMessageFactory.getMemberPingResponse(CanMessage.JCS_UID, 1, CanMessage.JCS_DEVICE_ID));
}
- private void sendJCSInformation() {
- executor.execute(() -> sentJCSInformationMessage());
- }
-
- private void sentJCSInformationMessage() {
+ void sentJCSInformationMessage() {
List messages = getStatusDataConfigResponse(CanMessage.JCS_SERIAL, 0, 0, "JCS", "Java Central Station", CanMessage.JCS_UID);
for (CanMessage msg : messages) {
sendMessage(msg);
@@ -643,7 +823,7 @@ private void sentJCSInformationMessage() {
List getLocomotivesViaCAN() {
CanMessage message = CanMessageFactory.requestConfigData(csUid, "loks");
- this.connection.sendCanMessage(message);
+ connection.sendCanMessage(message);
String lokomotive = MessageInflator.inflateConfigDataStream(message, "locomotive");
LocomotiveBeanParser lp = new LocomotiveBeanParser();
@@ -651,14 +831,14 @@ List getLocomotivesViaCAN() {
}
List getLocomotivesViaHttp() {
- HTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
+ CSHTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
String csLocos = httpCon.getLocomotivesFile();
LocomotiveBeanParser lp = new LocomotiveBeanParser();
return lp.parseLocomotivesFile(csLocos);
}
List getLocomotivesViaJSON() {
- HTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
+ CSHTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
String json = httpCon.getLocomotivesJSON();
LocomotiveBeanJSONParser lp = new LocomotiveBeanJSONParser();
return lp.parseLocomotives(json);
@@ -686,7 +866,7 @@ public List getLocomotives() {
}
List getAccessoriesViaHttp() {
- HTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
+ CSHTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
if (isCS3() && System.getProperty("accessory.list.via", "JSON").equalsIgnoreCase("JSON")) {
String json = httpCon.getAccessoriesJSON();
return AccessoryBeanParser.parseAccessoryJSON(json, commandStationBean.getId(), commandStationBean.getShortName());
@@ -716,14 +896,14 @@ public List getAccessories() {
@Override
public Image getLocomotiveImage(String icon) {
- HTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
+ CSHTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
Image locIcon = httpCon.getLocomotiveImage(icon);
return locIcon;
}
@Override
public Image getLocomotiveFunctionImage(String icon) {
- HTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
+ CSHTTPConnection httpCon = CSConnectionFactory.getHTTPConnection();
if (this.isCS3()) {
if (!FunctionSvgToPngConverter.isSvgCacheLoaded()) {
Logger.trace("Loading SVG Cache");
@@ -737,12 +917,7 @@ public Image getLocomotiveFunctionImage(String icon) {
}
private void notifyPowerEventListeners(final PowerEvent powerEvent) {
- this.power = powerEvent.isPower();
- executor.execute(() -> fireAllPowerEventListeners(powerEvent));
- }
-
- private void fireAllPowerEventListeners(final PowerEvent powerEvent) {
- this.power = powerEvent.isPower();
+ power = powerEvent.isPower();
for (PowerEventListener listener : powerEventListeners) {
listener.onPowerChange(powerEvent);
}
@@ -757,26 +932,18 @@ public void fireSensorEventListeners(final SensorEvent sensorEvent) {
@Override
public void simulateSensor(SensorEvent sensorEvent) {
- if (this.connection instanceof VirtualConnection virtualConnection) {
+ if (connection instanceof VirtualConnection virtualConnection) {
virtualConnection.sendEvent(sensorEvent);
}
}
- private void notifySensorEventListeners(final SensorEvent sensorEvent) {
- executor.execute(() -> fireSensorEventListeners(sensorEvent));
- }
-
- private void fireAllAccessoryEventListeners(final AccessoryEvent accessoryEvent) {
+ private void notifyAccessoryEventListeners(final AccessoryEvent accessoryEvent) {
for (AccessoryEventListener listener : this.accessoryEventListeners) {
listener.onAccessoryChange(accessoryEvent);
}
}
- private void notifyAccessoryEventListeners(final AccessoryEvent accessoryEvent) {
- executor.execute(() -> fireAllAccessoryEventListeners(accessoryEvent));
- }
-
- private void fireAllFunctionEventListeners(final LocomotiveFunctionEvent functionEvent) {
+ private void notifyLocomotiveFunctionEventListeners(final LocomotiveFunctionEvent functionEvent) {
if (functionEvent.isValid()) {
for (LocomotiveFunctionEventListener listener : this.locomotiveFunctionEventListeners) {
listener.onFunctionChange(functionEvent);
@@ -784,11 +951,7 @@ private void fireAllFunctionEventListeners(final LocomotiveFunctionEvent functio
}
}
- private void notifyLocomotiveFunctionEventListeners(final LocomotiveFunctionEvent functionEvent) {
- executor.execute(() -> fireAllFunctionEventListeners(functionEvent));
- }
-
- private void fireAllDirectionEventListeners(final LocomotiveDirectionEvent directionEvent) {
+ private void notifyLocomotiveDirectionEventListeners(final LocomotiveDirectionEvent directionEvent) {
if (directionEvent.isValid()) {
for (LocomotiveDirectionEventListener listener : this.locomotiveDirectionEventListeners) {
listener.onDirectionChange(directionEvent);
@@ -796,11 +959,7 @@ private void fireAllDirectionEventListeners(final LocomotiveDirectionEvent direc
}
}
- private void notifyLocomotiveDirectionEventListeners(final LocomotiveDirectionEvent directionEvent) {
- executor.execute(() -> fireAllDirectionEventListeners(directionEvent));
- }
-
- private void fireAllLocomotiveSpeedEventListeners(final LocomotiveSpeedEvent speedEvent) {
+ private void notifyLocomotiveSpeedEventListeners(final LocomotiveSpeedEvent speedEvent) {
if (speedEvent.isValid()) {
for (LocomotiveSpeedEventListener listener : this.locomotiveSpeedEventListeners) {
listener.onSpeedChange(speedEvent);
@@ -808,178 +967,265 @@ private void fireAllLocomotiveSpeedEventListeners(final LocomotiveSpeedEvent spe
}
}
- private void notifyLocomotiveSpeedEventListeners(final LocomotiveSpeedEvent locomotiveEvent) {
- executor.execute(() -> fireAllLocomotiveSpeedEventListeners(locomotiveEvent));
- }
-
- private class CanFeedbackMessageListener implements FeedbackListener {
+ /**
+ * Handle Event Message, which are unsolicited messages from the CS.
+ */
+ private class EventMessageHandler extends Thread {
- private final MarklinCentralStationImpl controller;
+ @SuppressWarnings("FieldMayBeFinal")
+ private boolean stop = false;
+ private boolean quit = true;
- CanFeedbackMessageListener(MarklinCentralStationImpl controller) {
- this.controller = controller;
- }
+ private final TransferQueue eventMessageQueue;
- @Override
- public void onFeedbackMessage(final CanMessage message) {
- int cmd = message.getCommand();
- switch (cmd) {
- case CanMessage.S88_EVENT_RESPONSE -> {
- if (CanMessage.DLC_8 == message.getDlc()) {
- SensorBean sb = SensorMessageParser.parseMessage(message, new Date());
- SensorEvent sme = new SensorEvent(sb);
- if (sme.getSensorBean() != null) {
- controller.notifySensorEventListeners(sme);
- }
- }
- }
- }
+ public EventMessageHandler(CSConnection csConnection) {
+ eventMessageQueue = csConnection.getEventQueue();
}
- }
-
- private class CanPingMessageListener implements CanPingListener {
- private final MarklinCentralStationImpl controller;
-
- CanPingMessageListener(MarklinCentralStationImpl controller) {
- this.controller = controller;
+ void quit() {
+ this.quit = true;
}
- @Override
- public void onCanPingRequestMessage(final CanMessage message) {
- int cmd = message.getCommand();
- int dlc = message.getDlc();
- //int uid = message.getDeviceUidNumberFromMessage();
- switch (cmd) {
- case CanMessage.PING_REQ -> {
- //Lets do this the when we know all of the CS...
- if (controller.mainDevice != null) {
- if (CanMessage.DLC_0 == dlc) {
- controller.sendJCSUID();
- }
- }
- }
- }
+ boolean isRunning() {
+ return !this.quit;
}
- @Override
- public void onCanPingResponseMessage(final CanMessage message
- ) {
- int cmd = message.getCommand();
- int dlc = message.getDlc();
- switch (cmd) {
- case CanMessage.PING_RESP -> {
- if (CanMessage.DLC_8 == dlc) {
- controller.updateMember(message);
- }
- }
- }
+ boolean isFinished() {
+ return this.stop;
}
@Override
- public void onCanStatusConfigRequestMessage(final CanMessage message) {
- int cmd = message.getCommand();
- int dlc = message.getDlc();
- int uid = message.getDeviceUidNumberFromMessage();
- switch (cmd) {
- case CanMessage.STATUS_CONFIG -> {
- if (CanMessage.JCS_UID == uid && CanMessage.DLC_5 == dlc) {
- controller.sendJCSInformation();
- }
- }
- }
- }
- }
+ public void run() {
+ quit = false;
+ Thread.currentThread().setName("CS-EVENT-MESSAGE-HANDLER");
+
+ Logger.trace("Event Handler Started...");
+
+ while (isRunning()) {
+ try {
+ CanMessage eventMessage = eventMessageQueue.take();
+ //Logger.trace("# " + eventMessage);
+
+ int command = eventMessage.getCommand();
+ int dlc = eventMessage.getDlc();
+ int uid = eventMessage.getDeviceUidNumberFromMessage();
+ int subcmd = eventMessage.getSubCommand();
+
+ switch (command) {
+ case CanMessage.PING_REQ -> {
+ //Lets do this the when we know all of the CS...
+ if (CanMessage.DLC_0 == dlc) {
+ //Logger.trace("Answering Ping RQ: " + eventMessage);
+ //sendJCSUIDMessage();
+ }
+ }
+ case CanMessage.PING_RESP -> {
+ if (CanMessage.DLC_8 == dlc) {
+// Logger.trace("Ping Response RX: " + eventMessage);
+// List devices = CanDeviceParser.parse(eventMessage);
+// if (!devices.isEmpty()) {
+// CanDevice deviceU = devices.get(0);
+// CanDevice device = canDevices.get(deviceU.getUidInt());
+// Logger.trace("Found " + device+" GFP "+(csUid==deviceU.getUidInt()?"yes":"no"));
+// }
+ //updateDevice(eventMessage);
+ }
+ }
+ case CanMessage.STATUS_CONFIG -> {
+ if (CanMessage.JCS_UID == uid && CanMessage.DLC_5 == dlc) {
+ Logger.trace("StatusConfig RQ: " + eventMessage);
+ //sentJCSInformationMessage();
+ }
+ }
+ case CanMessage.STATUS_CONFIG_RESP -> {
+ Logger.trace("StatusConfigResponse RX: " + eventMessage);
+ //add to an list of resposne check
- private class CanSystemMessageListener implements SystemListener {
+ }
+ case CanMessage.S88_EVENT_RESPONSE -> {
+ if (CanMessage.DLC_8 == dlc) {
+ Logger.trace("FeedbackSensorEvent RX: " + eventMessage);
+
+ SensorBean sb = FeedbackEventMessage.parse(eventMessage, new Date());
+ SensorEvent sme = new SensorEvent(sb);
+ if (sme.getSensorBean() != null) {
+ fireSensorEventListeners(sme);
+ }
+ }
+ }
+ case CanMessage.SX1_EVENT -> {
+ if (CanMessage.DLC_8 == dlc) {
+ SensorBean sb = FeedbackEventMessage.parse(eventMessage, new Date());
+ SensorEvent sme = new SensorEvent(sb);
+ if (sme.getSensorBean() != null) {
+ fireSensorEventListeners(sme);
+ }
+ }
+ }
+ case CanMessage.SYSTEM_COMMAND -> {
+ //Logger.trace("SystemConfigCommand RX: " + eventMessage);
+ }
+ case CanMessage.SYSTEM_COMMAND_RESP -> {
+ switch (subcmd) {
+ case CanMessage.STOP_SUB_CMD -> {
+ PowerEvent spe = PowerEventParser.parseMessage(eventMessage);
+ notifyPowerEventListeners(spe);
+ }
+ case CanMessage.GO_SUB_CMD -> {
+ PowerEvent gpe = PowerEventParser.parseMessage(eventMessage);
+ notifyPowerEventListeners(gpe);
+ }
+ case CanMessage.HALT_SUB_CMD -> {
+ PowerEvent gpe = PowerEventParser.parseMessage(eventMessage);
+ notifyPowerEventListeners(gpe);
+ }
+ case CanMessage.LOC_STOP_SUB_CMD -> {
+ //stop specific loc
+ LocomotiveSpeedEvent lse = LocomotiveEmergencyStopMessage.parse(eventMessage);
+ notifyLocomotiveSpeedEventListeners(lse);
+ }
+ case CanMessage.OVERLOAD_SUB_CMD -> {
+ PowerEvent gpe = PowerEventParser.parseMessage(eventMessage);
+ notifyPowerEventListeners(gpe);
+ }
+ }
+ }
+ case CanMessage.ACCESSORY_SWITCHING -> {
+ Logger.trace("AccessorySwitching RX: " + eventMessage);
+ }
+ case CanMessage.ACCESSORY_SWITCHING_RESP -> {
+ AccessoryEvent ae = AccessoryMessage.parse(eventMessage);
+ if (ae.isValid()) {
+ notifyAccessoryEventListeners(ae);
+ }
+ }
+ case CanMessage.LOC_VELOCITY -> {
+ Logger.trace("VelocityChange# " + eventMessage);
- private final MarklinCentralStationImpl controller;
+ }
+ case CanMessage.LOC_VELOCITY_RESP -> {
+ Logger.trace("VelocityChange " + eventMessage);
- CanSystemMessageListener(MarklinCentralStationImpl controller) {
- this.controller = controller;
- }
+ notifyLocomotiveSpeedEventListeners(LocomotiveVelocityMessage.parse(eventMessage));
+ }
+ case CanMessage.LOC_DIRECTION -> {
+ Logger.trace("DirectionChange# " + eventMessage);
- @Override
- public void onSystemMessage(CanMessage message) {
- int cmd = message.getCommand();
- int subcmd = message.getSubCommand();
-
- switch (cmd) {
- case CanMessage.SYSTEM_COMMAND_RESP -> {
- switch (subcmd) {
- case CanMessage.STOP_SUB_CMD -> {
- PowerEvent spe = PowerEventParser.parseMessage(message);
- controller.notifyPowerEventListeners(spe);
}
- case CanMessage.GO_SUB_CMD -> {
- PowerEvent gpe = PowerEventParser.parseMessage(message);
- controller.notifyPowerEventListeners(gpe);
+ case CanMessage.LOC_DIRECTION_RESP -> {
+ Logger.trace("DirectionChange " + eventMessage);
+ notifyLocomotiveDirectionEventListeners(LocomotiveDirectionEventParser.parseMessage(eventMessage));
}
- case CanMessage.HALT_SUB_CMD -> {
- PowerEvent gpe = PowerEventParser.parseMessage(message);
- controller.notifyPowerEventListeners(gpe);
+ case CanMessage.LOC_FUNCTION -> {
+
}
- case CanMessage.LOC_STOP_SUB_CMD -> {
- PowerEvent gpe = PowerEventParser.parseMessage(message);
- controller.notifyPowerEventListeners(gpe);
+ case CanMessage.LOC_FUNCTION_RESP -> {
+ notifyLocomotiveFunctionEventListeners(LocomotiveFunctionEventParser.parseMessage(eventMessage));
}
- case CanMessage.OVERLOAD_SUB_CMD -> {
- PowerEvent gpe = PowerEventParser.parseMessage(message);
- controller.notifyPowerEventListeners(gpe);
+ case CanMessage.BOOTLOADER_CAN -> {
+ //Update the last time millis. Used for the watchdog timer.
+ canBootLoaderLastCallMillis = System.currentTimeMillis();
}
default -> {
}
}
+
+ } catch (InterruptedException ex) {
+ Logger.error(ex);
}
}
+
+ Logger.debug("Stop Event handling");
}
}
- private class CanAccessoryMessageListener implements AccessoryListener {
+ private void startWatchdog() {
+ long checkInterval = Long.parseLong(System.getProperty("connection.watchdog.interval", "10"));
+ checkInterval = checkInterval * 1000;
- private final MarklinCentralStationImpl controller;
+ if (checkInterval > 0 && !virtual) {
+ watchdogTask = new WatchdogTask(this);
+ watchDogTimer = new Timer("WatchDogTimer");
+ watchDogTimer.schedule(watchdogTask, 0, checkInterval);
+ Logger.debug("Started Watchdog Timer with an interval of " + checkInterval + "ms");
+ } else {
+ Logger.debug("Skipping Watchdog Timer");
+ }
+ }
- CanAccessoryMessageListener(MarklinCentralStationImpl controller) {
- this.controller = controller;
+ private class WatchdogTask extends TimerTask {
+
+ private final MarklinCentralStationImpl commandStation;
+ private final long checkInterval;
+
+ WatchdogTask(MarklinCentralStationImpl commandStation) {
+ this.commandStation = commandStation;
+ checkInterval = Long.parseLong(System.getProperty("connection.watchdog.interval", "10")) * 1000;
}
@Override
- public void onAccessoryMessage(CanMessage message) {
- int cmd = message.getCommand();
- switch (cmd) {
- case CanMessage.ACCESSORY_SWITCHING_RESP -> {
- AccessoryEvent ae = AccessoryEventParser.parseMessage(message);
- if (ae.isKnownAccessory()) {
- controller.notifyAccessoryEventListeners(ae);
+ public void run() {
+ if (commandStation.isConnected() && !virtual) {
+ Long now = System.currentTimeMillis();
+ long diff = now - commandStation.canBootLoaderLastCallMillis;
+ boolean connectionLost = checkInterval < diff;
+
+ if (connectionLost) {
+ Logger.trace("The last CANBootLoader request is received more than " + (checkInterval / 1000) + "s ago!");
+ ConnectionEvent de = new ConnectionEvent("Marklin Central Station", false);
+ commandStation.onConnectionChange(de);
+ }
+ } else {
+ //Try to reconnect
+ if (!virtual && "true".equalsIgnoreCase(System.getProperty("controller.autoconnect", "true"))) {
+ boolean con = commandStation.connect();
+ if (con) {
+ ConnectionEvent de = new ConnectionEvent("Marklin Central Station", true);
+ commandStation.onConnectionChange(de);
}
}
}
}
}
- private class CanLocomotiveMessageListener implements LocomotiveListener {
+ private void startMeasurements() {
+ long measureInterval = Long.parseLong(System.getProperty("measurement.interval", "5"));
+ measureInterval = measureInterval * 1000;
+
+ if (measureInterval > 0 && !virtual) {
+ measurementTask = new MeasurementTask(this);
+ measurementTimer = new Timer("MeasurementsTimer");
+ measurementTimer.schedule(measurementTask, 10, measureInterval);
+ Logger.debug("Started Measurements Timer with an interval of " + measureInterval + "ms");
+ } else {
+ Logger.debug("Skipping Measurements Timer");
+ }
+ }
+
+ private class MeasurementTask extends TimerTask {
- private final MarklinCentralStationImpl controller;
+ private final MarklinCentralStationImpl commandStation;
- CanLocomotiveMessageListener(MarklinCentralStationImpl controller) {
- this.controller = controller;
+ MeasurementTask(MarklinCentralStationImpl commandStation) {
+ this.commandStation = commandStation;
}
@Override
- public void onLocomotiveMessage(CanMessage message) {
- int cmd = message.getCommand();
- switch (cmd) {
- case CanMessage.LOC_FUNCTION_RESP ->
- controller.notifyLocomotiveFunctionEventListeners(LocomotiveFunctionEventParser.parseMessage(message));
- case CanMessage.LOC_DIRECTION_RESP ->
- controller.notifyLocomotiveDirectionEventListeners(LocomotiveDirectionEventParser.parseMessage(message));
- case CanMessage.LOC_VELOCITY_RESP ->
- controller.notifyLocomotiveSpeedEventListeners(LocomotiveSpeedEventParser.parseMessage(message));
+ public void run() {
+ if (commandStation.isConnected() && !virtual) {
+ if (commandStation.isSupportTrackMeasurements()) {
+ commandStation.performMeasurements();
+ } else {
+ Logger.debug("Track Measurement are not supported. Cancelling the Measurements schedule...");
+ measurementTimer.cancel();
+ }
}
}
}
-//////////// For Testing only.....//////
+ //////////// For Testing only.....//////
+ /// @param a
+ ///
public static void main(String[] a) {
RunUtil.loadExternalProperties();
@@ -999,26 +1245,45 @@ public static void main(String[] a) {
csb.setProtocols("DCC,MFX,MM");
csb.setDefault(true);
csb.setEnabled(true);
+ csb.setVirtual(false);
MarklinCentralStationImpl cs = new MarklinCentralStationImpl(csb, false);
+ cs.debug = true;
+
Logger.debug((cs.connect() ? "Connected" : "NOT Connected"));
if (cs.isConnected()) {
- Logger.debug("Power is " + (cs.isPower() ? "ON" : "Off"));
+// Logger.debug("Power is " + (cs.isPower() ? "ON" : "Off"));
+// cs.power(false);
+// Logger.debug("Power is " + (cs.isPower() ? "ON" : "Off"));
+// cs.power(true);
+//
+// Logger.debug("Switch Accessory 2 to Red");
+// //cs.switchAccessory(2, AccessoryValue.RED, 250);
+//
+// Logger.debug("Switch Accessory 2 to Green");
+ //cs.switchAccessory(2, AccessoryValue.GREEN, 250);
+ List feedbackModules = cs.getFeedbackModules();
+ Logger.trace("There are " + feedbackModules + " Feedback Modules");
+ for (FeedbackModule fm : feedbackModules) {
+ Logger.trace("Module id: " + fm.getId() + " Module nr: " + fm.getModuleNumber() + " ports: " + fm.getPortCount() + " NodeId: " + fm.getIdentifier() + " BusNr: " + fm.getBusNumber());
+ Logger.trace("FBModule id: " + fm.getId() + " S 1 id:" + fm.getSensor(0).getId() + " contactId: " + fm.getSensor(0).getContactId() + " ModuleNr: " + fm.getSensor(0).getDeviceId() + " Name " + fm.getSensor(0).getName());
+ Logger.trace("FBModule id: " + fm.getId() + " S 15 id:" + fm.getSensor(15).getId() + " contactId: " + fm.getSensor(15).getContactId() + " ModuleNr: " + fm.getSensor(15).getDeviceId() + " Name " + fm.getSensor(15).getName());
+ }
//cs.getLocomotivesViaCAN();
//cs.getAccessoriesViaCan();
- //cs3.pause(2000);
+ //cs.pause(2000);
+ //cs.getMembers();
+ //cs.memberPing();
//Logger.trace("getStatusDataConfig CS3");
- //cs3.getStatusDataConfigCS3();
- //cs3.pause(2000);
+ //cs.getStatusDataConfigCS3();
+ //cs.pause(2000);
//Logger.trace("getStatusDataConfig");
//cs3.getStatusDataConfig();
//cs3.pause(1000);
//cs3.pause(4000);
- //cs3.power(false);
- //Logger.debug("Power is " + (cs.isPower() ? "ON" : "Off"));
- //cs3.power(true);
+ //
//cs3.pause(500);
//Logger.debug("Power is " + (cs.isPower() ? "ON" : "Off"));
//cs3.sendJCSInfo();
@@ -1029,63 +1294,7 @@ public static void main(String[] a) {
//Logger.debug("Power is " + (cs.isPower() ? "ON" : "Off"));
//cs3.sendJCSInfo();
//SystemConfiguration data
- Map measurements = cs.getTrackMeasurements();
-
- for (ChannelBean ch : measurements.values()) {
- Logger.trace("Channel " + ch.getNumber() + ": " + ch.getHumanValue() + " " + ch.getUnit());
- }
-
-// Logger.debug("Channel 1: " + cs.channelData1.getChannel().getHumanValue() + " " + cs.channelData1.getChannel().getUnit());
-// Logger.debug("Channel 2: " + cs.channelData2.getChannel().getHumanValue() + " " + cs.channelData2.getChannel().getUnit());
-// Logger.debug("Channel 3: " + cs.channelData3.getChannel().getHumanValue() + " " + cs.channelData3.getChannel().getUnit());
-// Logger.debug("Channel 4: " + cs.channelData4.getChannel().getHumanValue() + " " + cs.channelData4.getChannel().getUnit());
- //cs.getSystemStatus(1);
-//
-// Logger.debug("Channel 4....");
-// cs.getSystemStatus(4);
-//Now get the systemstatus for all devices
-//First the status data config must be called to get the channels
- //cs3.getSystemStatus()
- // SystemStatus ss = cs.getSystemStatus();
- // Logger.debug("1: "+ss);
- //
- //
- // ss = cs.power(true);
- // Logger.debug("3: "+ss);
- //
- // cs.pause(1000);
- // ss = cs.power(false);
- // Logger.debug("4: "+ss);
- // List sml = cs.querySensors(48);
- // for (SensorEvent sme : sml) {
- // Sensor s = new Sensor(sme.getContactId(), sme.isNewValue() ? 1 : 0, sme.isOldValue() ? 1 : 0, sme.getDeviceIdBytes(), sme.getMillis(), new Date());
- // Logger.debug(s.toLogString());
- // }
- //List asl = cs.getAccessoryStatuses();
- //for (AccessoryStatus as : asl) {
- // Logger.debug(as.toString());
- //}
- // for (int i = 0; i < 30; i++) {
- // cs.sendIdle();
- // pause(500);
- // }
- // Logger.debug("Sending member ping\n");
- // List prl = cs.membersPing();
- // //Logger.info("Query direction of loc 12");
- // //DirectionInfo info = cs.getDirectionMarkin(12, DecoderType.MM);
- // Logger.debug("got " + prl.size() + " responses");
- // for (PingResponseParser device : prl) {
- // Logger.debug(device);
- // }
- // List sel = cs.querySensors(48);
- //
- // for (SensorEvent se : sel) {
- // Logger.debug(se.toString());
- // }
- // FeedbackModule fm2 = new FeedbackModule(2);
- // cs.queryAllPorts(fm2);
- // Logger.debug(fm2.toLogString());
- //cs2.querySensor(1);
+// cs.performMeasurements();
}
//PingResponse pr2 = cs.memberPing();
@@ -1102,11 +1311,12 @@ public static void main(String[] a) {
// for (LocomotiveBean loc : locs) {
// Logger.trace(loc);
// }
+ //cs.pause(40000);
cs.pause(40000);
cs.disconnect();
- cs.pause(100L);
+// cs.pause(100L);
Logger.debug("DONE");
- //System.exit(0);
+ System.exit(0);
}
//for (int i = 0; i < 16; i++) {
// cs.requestFeedbackEvents(i + 1);
diff --git a/src/main/java/jcs/commandStation/marklin/cs/can/CanMessage.java b/src/main/java/jcs/commandStation/marklin/cs/can/CanMessage.java
index 6566133e..b70d8898 100755
--- a/src/main/java/jcs/commandStation/marklin/cs/can/CanMessage.java
+++ b/src/main/java/jcs/commandStation/marklin/cs/can/CanMessage.java
@@ -15,7 +15,6 @@
*/
package jcs.commandStation.marklin.cs.can;
-import java.io.Serializable;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
@@ -24,7 +23,7 @@
/**
* CS 2/3 CAN message.
*/
-public class CanMessage implements MarklinCan, Serializable {
+public class CanMessage implements MarklinCan {
private int priority;
private int command;
@@ -92,10 +91,32 @@ public static final int toInt(byte[] value) {
return val;
}
+ public static int getStringLength(byte[] data) {
+ //A string is terminated with 0x00. Return the lengt before the termination
+ for (int l = 0; l < data.length; l++) {
+ if (data[l] == 0) {
+ //found terminator
+ return l;
+ }
+ }
+ return data.length;
+ }
+
public static String toString(byte[] data) {
return new String(data);
}
+ public static String toString(byte[] data, int length) {
+ byte[] stringData = new byte[length];
+ System.arraycopy(data, 0, stringData, 0, stringData.length);
+ return new String(stringData);
+ }
+
+ public static final byte toByte(int value) {
+ byte[] bts = to2Bytes(value);
+ return bts[1];
+ }
+
public static final byte[] to2Bytes(int value) {
byte[] bts = new byte[]{
(byte) ((value >> 8) & 0xFF),
@@ -274,44 +295,6 @@ public boolean isResponseFor(CanMessage other) {
}
}
- public boolean isPingResponse() {
- return this.command == PING_RESP;
- }
-
- public boolean isPingRequest() {
- return this.command == PING_REQ;
- }
-
- public boolean isStatusConfigRequest() {
- return this.command == STATUS_CONFIG;
- }
-
- public boolean isSensorResponse() {
- return this.command == S88_EVENT_RESP;
- }
-
- public boolean isStatusDataConfigMessage() {
- return this.command == STATUS_CONFIG | this.command == STATUS_CONFIG + 1;
- }
-
- public boolean isSensorMessage() {
- return this.command == S88_EVENT || this.command == SX1_EVENT;
- }
-
- public boolean isAccessoryMessage() {
- return this.command == ACCESSORY_SWITCHING || this.command == ACCESSORY_SWITCHING_RESP;
- }
-
- public boolean isLocomotiveMessage() {
- return this.command == LOC_VELOCITY || this.command == LOC_VELOCITY_RESP
- || this.command == LOC_DIRECTION || this.command == LOC_DIRECTION_RESP
- || this.command == LOC_FUNCTION || this.command == LOC_FUNCTION_RESP;
- }
-
- public boolean isSystemMessage() {
- return this.command == SYSTEM_COMMAND || this.command == SYSTEM_COMMAND_RESP;
- }
-
public boolean hasValidResponse() {
if (this.responses == null || this.responses.isEmpty()) {
return false;
@@ -392,9 +375,9 @@ public boolean isResponseComplete() {
return this.responses.size() >= 2;
}
default -> {
- if (this.expectsResponse()) {
- CanMessage r0 = this.responses.get(0);
- return this.command == r0.getCommand() - 1;
+ if (expectsResponse() && !responses.isEmpty()) {
+ CanMessage r0 = responses.get(0);
+ return command == r0.getCommand() - 1;
} else {
return true;
}
@@ -408,7 +391,7 @@ public boolean isResponseComplete() {
@Override
public String toString() {
- return ByteUtil.toHexString(this.getMessage());
+ return ByteUtil.toHexString(getMessage());
}
public int getNumberOfMeasurementValues() {
@@ -468,6 +451,43 @@ public static final byte[] generateHash(int gfpUid) {
return to2Bytes(hash);
}
+ /**
+ * Create a CanMessage from a String. This method is used to ease testing.
+ * The message should be in the format:
+ * 0x00 0x00 0x07 0x69 0x04 0x00 0x00 0x00 0x00 0x00 0xab 0x00 0xfc
+ *
+ * @param message a CAN message with content as in the given String
+ * @return
+ */
+ public static CanMessage parse(String message) {
+ return parse(message, false);
+ }
+
+ /**
+ * Create a CanMessage from a String. This method is used to ease testing.
+ * The message should be in the format:
+ * 0x00 0x00 0x07 0x69 0x04 0x00 0x00 0x00 0x00 0x00 0xab 0x00 0xfc
+ *
+ * @param message a CAN message with content as in the given String
+ * @param response force the response bit set
+ */
+ public static CanMessage parse(String message, boolean response) {
+ String[] sa = message.split(" ");
+ byte[] ba = new byte[MESSAGE_SIZE];
+ for (int i = 0; i < ba.length; i++) {
+ String bs = sa[i];
+ bs = bs.replace("0x", "");
+ ba[i] = toByte(Integer.parseUnsignedInt(bs, 16));
+
+ if (i == 1 && response) {
+ ba[i]++;
+ }
+
+ }
+
+ return new CanMessage(ba);
+ }
+
@Override
public int hashCode() {
int h = 5;
@@ -506,8 +526,6 @@ public boolean equals(Object obj) {
return Arrays.equals(this.data, other.data);
}
-}
-
// public String print() {
// StringBuilder sb = new StringBuilder();
// sb.append(getMessageName());
@@ -611,3 +629,4 @@ public boolean equals(Object obj) {
// return "Unknown: " + cmd;
// }
// }
+}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/can/CanMessageFactory.java b/src/main/java/jcs/commandStation/marklin/cs/can/CanMessageFactory.java
index 73008318..acce43e0 100755
--- a/src/main/java/jcs/commandStation/marklin/cs/can/CanMessageFactory.java
+++ b/src/main/java/jcs/commandStation/marklin/cs/can/CanMessageFactory.java
@@ -196,13 +196,13 @@ public static CanMessage getMobileAppPingRequest() {
* @param gfpUid the GFP UID
* @return
*/
- public static CanMessage systemStatus(int channel, int gfpUid) {
+ public static CanMessage systemStatus(int uid, int channel) {
byte[] data = new byte[CanMessage.DATA_SIZE];
byte[] hash;
- if (gfpUid > 0) {
- byte[] uid = CanMessage.to4Bytes(gfpUid);
- System.arraycopy(uid, 0, data, 0, uid.length);
- hash = CanMessage.generateHash(gfpUid);
+ if (uid > 0) {
+ byte[] uidb = CanMessage.to4Bytes(uid);
+ System.arraycopy(uidb, 0, data, 0, uidb.length);
+ hash = CanMessage.generateHash(uid);
} else {
hash = MAGIC_HASH;
}
@@ -214,27 +214,26 @@ public static CanMessage systemStatus(int channel, int gfpUid) {
return cm;
}
- public static CanMessage switchAccessory(int address, AccessoryValue value, boolean on, int gfpUid) {
- byte[] data = new byte[CanMessage.DATA_SIZE];
- //TODO support for DCC
- //localID = address - 1; // GUI-address is 1-based, protocol-address is 0-based
- //if (protocol == ProtocolDCC) { localID |= 0x3800; } else { localID |= 0x3000;}
- byte[] hash;
- if (gfpUid > 0) {
- hash = CanMessage.generateHash(gfpUid);
- } else {
- hash = MAGIC_HASH;
- }
-
- data[ACCESSORY_CAN_ADDRESS_IDX] = ACCESSORY_CAN_ADDRESS;
- data[ACCESSORY_ADDRESS_IDX] = (byte) (address - 1);
- data[ACCESSORY_VALUE_IDX] = (byte) (AccessoryValue.GREEN.equals(value) ? 1 : 0);
- data[ACCESSORY_ACTIVE_IDX] = (byte) (on ? 1 : 0);
-
- CanMessage cm = new CanMessage(PRIO_1, ACCESSORY_SWITCHING, hash, DLC_6, data);
- return cm;
- }
-
+// public static CanMessage switchAccessory(int address, AccessoryValue value, boolean on, int gfpUid) {
+// byte[] data = new byte[CanMessage.DATA_SIZE];
+// //TODO support for DCC
+// //localID = address - 1; // GUI-address is 1-based, protocol-address is 0-based
+// //if (protocol == ProtocolDCC) { localID |= 0x3800; } else { localID |= 0x3000;}
+// byte[] hash;
+// if (gfpUid > 0) {
+// hash = CanMessage.generateHash(gfpUid);
+// } else {
+// hash = MAGIC_HASH;
+// }
+//
+// data[ACCESSORY_CAN_ADDRESS_IDX] = ACCESSORY_CAN_ADDRESS;
+// data[ACCESSORY_ADDRESS_IDX] = (byte) (address - 1);
+// data[ACCESSORY_VALUE_IDX] = (byte) (AccessoryValue.GREEN.equals(value) ? 1 : 0);
+// data[ACCESSORY_ACTIVE_IDX] = (byte) (on ? 1 : 0);
+//
+// CanMessage cm = new CanMessage(PRIO_1, ACCESSORY_SWITCHING, hash, DLC_6, data);
+// return cm;
+// }
public static CanMessage switchAccessory(int address, AccessoryValue value, boolean on, int switchTime, int gfpUid) {
byte[] data = new byte[CanMessage.DATA_SIZE];
byte[] hash;
@@ -244,8 +243,9 @@ public static CanMessage switchAccessory(int address, AccessoryValue value, bool
hash = MAGIC_HASH;
}
- data[ACCESSORY_CAN_ADDRESS_IDX] = ACCESSORY_CAN_ADDRESS;
- data[ACCESSORY_ADDRESS_IDX] = (byte) (address - 1);
+ byte[] addressBytes = CanMessage.to2Bytes(address);
+ System.arraycopy(addressBytes, 0, data, 2, addressBytes.length);
+ //TODO for signal other values are also supported
data[ACCESSORY_VALUE_IDX] = (byte) (AccessoryValue.GREEN.equals(value) ? 1 : 0);
data[ACCESSORY_ACTIVE_IDX] = (byte) (on ? 1 : 0);
@@ -328,6 +328,25 @@ public static CanMessage queryDirection(int address, int gfpUid) {
return cm;
}
+
+ // private int getLocoAddres(int address, DecoderType decoderType) {
+// int locoAddress;
+// locoAddress = switch (decoderType) {
+// case MFX ->
+// 0x4000 + address;
+// case MFXP ->
+// 0x4000 + address;
+// case DCC ->
+// 0xC000 + address;
+// case SX1 ->
+// 0x0800 + address;
+// default ->
+// address;
+// };
+//
+// return locoAddress;
+// }
+
public static CanMessage setDirection(int address, int csdirection, int gfpUid) {
byte[] data = new byte[CanMessage.DATA_SIZE];
byte[] hash;
@@ -402,6 +421,32 @@ public static CanMessage requestConfigData(int gfpUid, String dataName) {
return cm;
}
+ /**
+ * Create a CanMessage for an (virtual) event. Used for the Virtual drive simulator
+ *
+ * @param sensorbean
+ * @return
+ */
+ public static CanMessage sensorEventMessage(int nodeId, int contactId, int value, int previousValue, int millis, int uid) {
+ byte[] data = new byte[CanMessage.DATA_SIZE];
+
+ byte[] nId = CanMessage.to2Bytes(nodeId);
+ byte[] cId = CanMessage.to2Bytes(contactId);
+ byte val = (byte) value;
+ byte prev = (byte) previousValue;
+ byte[] time = CanMessage.to2Bytes(millis / 10);
+ byte[] hash = CanMessage.generateHash(uid);
+
+ System.arraycopy(nId, 0, data, 0, nId.length);
+ System.arraycopy(cId, 0, data, 2, cId.length);
+ data[4] = prev;
+ data[5] = val;
+ System.arraycopy(time, 0, data, 6, time.length);
+
+ CanMessage sensorMessage = new CanMessage(PRIO_1, S88_EVENT_RESPONSE, hash, DLC_8, data);
+ return sensorMessage;
+ }
+
//Mainly for testing....
public static void main(String[] a) {
System.out.println("getMobAppPingReq: " + getMobileAppPingRequest());
@@ -415,15 +460,14 @@ public static void main(String[] a) {
System.out.println("systemStatus ch 1: " + systemStatus(1, 1668498828));
System.out.println("systemStatus ch 4: " + systemStatus(4, 1668498828));
- System.out.println("switchAccessory 1g: " + switchAccessory(1, AccessoryValue.GREEN, true, 1668498828));
- System.out.println("switchAccessory 1g: " + switchAccessory(1, AccessoryValue.GREEN, false, 1668498828));
+ System.out.println("switchAccessory 1g: " + switchAccessory(1, AccessoryValue.GREEN, true, 20, 1668498828));
+ System.out.println("switchAccessory 1g: " + switchAccessory(1, AccessoryValue.GREEN, false, 30, 1668498828));
- System.out.println("switchAccessory 1g: " + switchAccessory(1, AccessoryValue.RED, true, 1668498828));
+ System.out.println("switchAccessory dcc 1: " + switchAccessory(14336, AccessoryValue.RED, true, 200, 1668498828));
- System.out.println("switchAccessory 2g: " + switchAccessory(2, AccessoryValue.GREEN, true,50, 1668498828));
- System.out.println("switchAccessory 2r: " + switchAccessory(2, AccessoryValue.RED, true,50, 1668498828));
+ System.out.println("switchAccessory 2g: " + switchAccessory(2, AccessoryValue.GREEN, true, 50, 1668498828));
+ System.out.println("switchAccessory 2r: " + switchAccessory(2, AccessoryValue.RED, true, 50, 1668498828));
-
System.out.println("requestConfigData: " + requestConfigData(1668498828, "loks"));
System.out.println("");
diff --git a/src/main/java/jcs/commandStation/marklin/cs/can/MarklinCan.java b/src/main/java/jcs/commandStation/marklin/cs/can/MarklinCan.java
index 0a668163..7ceaec50 100755
--- a/src/main/java/jcs/commandStation/marklin/cs/can/MarklinCan.java
+++ b/src/main/java/jcs/commandStation/marklin/cs/can/MarklinCan.java
@@ -107,7 +107,7 @@ interface MarklinCan {
//Sensor messages
public final static int S88_EVENT = 0x22;
- public final static int S88_EVENT_RESP = 0x23;
+ //public final static int S88_EVENT_RESP = 0x23;
public final static int SX1_EVENT = 0x24;
@@ -180,4 +180,6 @@ interface MarklinCan {
//Magic hash which will result is getting device ids
public static final byte[] MAGIC_HASH = new byte[]{0x07, 0x69};
+ public static final int DCC_ACCESSORY_OFFSET = 14336;
+
}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/can/device/CanDevice.java b/src/main/java/jcs/commandStation/marklin/cs/can/device/CanDevice.java
new file mode 100644
index 00000000..5b358d8c
--- /dev/null
+++ b/src/main/java/jcs/commandStation/marklin/cs/can/device/CanDevice.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2025 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.marklin.cs.can.device;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import jcs.util.ByteUtil;
+
+/**
+ * A CanDevice is a Device inside or connected to the Marklin Central Station
+ * Example devices are:
+ * - Central Station self
+ * - GFP (Gleis Format Prozessor)
+ * - Link S88
+ */
+public class CanDevice {
+
+ private String uid;
+ private String guiUid;
+ private String version;
+ private String hwVersion;
+ private String identifier;
+ private Integer measureChannelCount;
+ private Integer configChannelCount;
+ private String serial;
+ private String articleNumber;
+ private String name;
+ private String shortName;
+
+ private final Map measuringChannels;
+ private final Map configChannels;
+
+ public static final String FEEDBACK_DEVICE_NAME = "Link S88";
+
+ public CanDevice() {
+ measuringChannels = new HashMap<>();
+ configChannels = new HashMap<>();
+ }
+
+ public String getUid() {
+ return uid;
+ }
+
+ public Integer getUidInt() {
+ String ui = uid.replace("0x", "");
+ return Integer.parseUnsignedInt(ui, 16);
+ }
+
+ public void setUid(String uid) {
+ this.uid = uid;
+ }
+
+ public void setUid(Integer uid) {
+ this.uid = "0x" + uid;
+ }
+
+ public String getGuiUid() {
+ return guiUid;
+ }
+
+ public Integer getGuiUidInt() {
+ String ui = guiUid.replace("0x", "");
+ return Integer.parseUnsignedInt(ui, 16);
+ }
+
+ public void setGuiUid(String guiUid) {
+ this.guiUid = guiUid;
+ }
+
+ public String getName() {
+ if (name == null && this.identifier != null && this.identifier.equals("0xffff")) {
+ return getDeviceType();
+ }
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getShortName() {
+ return shortName;
+ }
+
+ public void setShortName(String shortName) {
+ this.shortName = shortName;
+ }
+
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ public void setIdentifier(String identifier) {
+ this.identifier = identifier;
+ }
+
+ public void setIdentifier(Integer identifier) {
+ this.identifier = ByteUtil.toHexString(identifier);
+ }
+
+ public Integer getIdentifierInt() {
+ String id = identifier.replace("0x", "");
+ return Integer.parseUnsignedInt(id, 16);
+ }
+
+ public String getArticleNumber() {
+ return articleNumber;
+ }
+
+ public void setArticleNumber(String articleNumber) {
+ this.articleNumber = articleNumber;
+ }
+
+ public String getSerial() {
+ return serial;
+ }
+
+ public void setSerial(String serial) {
+ this.serial = serial;
+ }
+
+ public void setSerial(Integer serial) {
+ this.serial = serial.toString();
+ }
+
+ public Integer getMeasureChannelCount() {
+ return measureChannelCount;
+ }
+
+ public void setMeasureChannelCount(Integer measureChannelCount) {
+ this.measureChannelCount = measureChannelCount;
+ }
+
+ public Integer getConfigChannelCount() {
+ return configChannelCount;
+ }
+
+ public void setConfigChannelCount(Integer configChannelCount) {
+ this.configChannelCount = configChannelCount;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getHwVersion() {
+ return hwVersion;
+ }
+
+ public void setHwVersion(String HwVersion) {
+ this.hwVersion = HwVersion;
+ }
+
+ public String getDeviceType() {
+ return switch (getIdentifierInt()) {
+ case 0x0000 ->
+ "GFP";
+ case 0x0010 ->
+ "Gleisbox 60112 und 60113";
+ case 0x0020 ->
+ "Connect 6021 Art-Nr.60128";
+ case 0x0030 ->
+ "MS 2 60653, Txxxxx";
+ case 0x0040 ->
+ "Link-S88";
+ case 0x0050 ->
+ "CS2/3-GFP";
+ case 0xffe0 ->
+ "Wireless Devices";
+ case 0xffff ->
+ "CS2/3-GUI (Master)";
+ default ->
+ "Unknown " + name;
+ };
+ }
+
+ public MeasuringChannel getMeasuringChannel(Integer number) {
+ return measuringChannels.get(number);
+ }
+
+ public void addMeasuringChannel(MeasuringChannel measuringChannel) {
+ measuringChannels.put(measuringChannel.getNumber(), measuringChannel);
+ }
+
+ public List getMeasuringChannels() {
+ return new ArrayList<>(measuringChannels.values());
+ }
+
+ public void addConfigChannel(ConfigChannel configChannel) {
+ configChannels.put(configChannel.getNumber(), configChannel);
+ }
+
+ public ConfigChannel getConfigChannel(Integer number) {
+ return configChannels.get(number);
+ }
+
+ public List getConfigChannels() {
+ return new ArrayList<>(configChannels.values());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("CanDevice{");
+ if (uid != null) {
+ sb.append("uid=").append(uid);
+ }
+ if (name != null) {
+ sb.append(", name=").append(name);
+ } else if (getName() != null) {
+ sb.append(", derivedname=").append(getName());
+ }
+ if (identifier != null) {
+ sb.append(", identifier=").append(identifier);
+ }
+ if (articleNumber != null) {
+ sb.append(", articleNumber=").append(articleNumber);
+ }
+ if (serial != null) {
+ sb.append(", serial=").append(serial);
+ }
+ if (measureChannelCount != null) {
+ sb.append(", measureChannelCount=").append(measureChannelCount);
+ }
+ if (configChannelCount != null) {
+ sb.append(", configChannelCount=").append(configChannelCount);
+ }
+ if (version != null) {
+ sb.append(", version=").append(version);
+ }
+ if (hwVersion != null) {
+ sb.append(", hwVersion=").append(hwVersion);
+ }
+ if (!measuringChannels.isEmpty()) {
+ sb.append(", measuringChannels=").append(measuringChannels);
+ }
+ if (!configChannels.isEmpty()) {
+ sb.append(", configChannels=").append(configChannels);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 79 * hash + Objects.hashCode(this.uid);
+ hash = 79 * hash + Objects.hashCode(this.version);
+ hash = 79 * hash + Objects.hashCode(this.hwVersion);
+ hash = 79 * hash + Objects.hashCode(this.identifier);
+ hash = 79 * hash + Objects.hashCode(this.measureChannelCount);
+ hash = 79 * hash + Objects.hashCode(this.configChannelCount);
+ hash = 79 * hash + Objects.hashCode(this.serial);
+ hash = 79 * hash + Objects.hashCode(this.articleNumber);
+ hash = 79 * hash + Objects.hashCode(this.name);
+ hash = 79 * hash + Objects.hashCode(this.measuringChannels);
+ hash = 79 * hash + Objects.hashCode(this.configChannels);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final CanDevice other = (CanDevice) obj;
+ if (!Objects.equals(this.uid, other.uid)) {
+ return false;
+ }
+ if (!Objects.equals(this.version, other.version)) {
+ return false;
+ }
+ if (!Objects.equals(this.hwVersion, other.hwVersion)) {
+ return false;
+ }
+ if (!Objects.equals(this.identifier, other.identifier)) {
+ return false;
+ }
+ if (!Objects.equals(this.serial, other.serial)) {
+ return false;
+ }
+ if (!Objects.equals(this.articleNumber, other.articleNumber)) {
+ return false;
+ }
+ if (!Objects.equals(this.measureChannelCount, other.measureChannelCount)) {
+ return false;
+ }
+ if (!Objects.equals(this.configChannelCount, other.configChannelCount)) {
+ return false;
+ }
+ if (!Objects.equals(this.measuringChannels, other.measuringChannels)) {
+ return false;
+ }
+ if (!Objects.equals(this.configChannels, other.configChannels)) {
+ return false;
+ }
+ return Objects.equals(this.name, other.name);
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/can/device/ConfigChannel.java b/src/main/java/jcs/commandStation/marklin/cs/can/device/ConfigChannel.java
new file mode 100644
index 00000000..e6b5686d
--- /dev/null
+++ b/src/main/java/jcs/commandStation/marklin/cs/can/device/ConfigChannel.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2025 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.marklin.cs.can.device;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import org.json.JSONObject;
+
+/**
+ * Represents a Configuration Channel of a Marklin Central Station CanDevice
+ * There are 2 formatConfig Channel, main difference is de use of a choice list.
+ * Quotes from the documentation:
+ * Format eines Datenblocks mit der Möglichkeit eine Auswahl zu treffen:
+ * - Konfigkanalnummer
+ * - Kenner Auswahlliste
+ * - Anzahl der Auswahlpunkte
+ * - Jetzige (Default) Einstellung
+ * - Auswahlbezeichnung
+ * - Auswahl 1
+ * - Auswahl 2
+ * - Auswahl 3
+ *
+ * Format eines Datenblocks mit der Möglichkeit einen Wert einzustellen:
+ * - Konfigirationskanalnummer
+ * - Kenner Slider
+ * - Unterer Wert
+ * - Oberer Wert
+ * - Aktuelle Einstellung
+ * - Auswahlbezeichnung
+ * - Bezeichnung Start
+ * - Bezeichnung Ende
+ * - Einheit
+ *
+ */
+public class ConfigChannel {
+
+ private Integer number;
+ private Integer valueId;
+ private Integer choicesCount;
+ private String choiceDescription;
+ private final List choices;
+ private Integer lowValue;
+ private Integer highValue;
+ private Integer actualValue;
+ private String startName;
+ private String endName;
+ private String unit;
+
+ public ConfigChannel() {
+ this(null);
+ }
+
+ public ConfigChannel(String json) {
+ choices = new ArrayList<>();
+ if (json != null) {
+ parseJson(json);
+ }
+ }
+
+ public Integer getNumber() {
+ return number;
+ }
+
+ public void setNumber(Integer number) {
+ this.number = number;
+ }
+
+ public Integer getValueId() {
+ return valueId;
+ }
+
+ public void setValueId(Integer valueId) {
+ this.valueId = valueId;
+ }
+
+ public Integer getChoicesCount() {
+ return choicesCount;
+ }
+
+ public void setChoicesCount(Integer choicesCount) {
+ this.choicesCount = choicesCount;
+ }
+
+ public String getChoiceDescription() {
+ return choiceDescription;
+ }
+
+ public void setChoiceDescription(String choiceDescription) {
+ this.choiceDescription = choiceDescription;
+ }
+
+ public void addChoice(String choice) {
+ this.choices.add(choice);
+ }
+
+ public List getChoices() {
+ return choices;
+ }
+
+ public Integer getLowValue() {
+ return lowValue;
+ }
+
+ public void setLowValue(Integer lowValue) {
+ this.lowValue = lowValue;
+ }
+
+ public Integer getHighValue() {
+ return highValue;
+ }
+
+ public void setHighValue(Integer highValue) {
+ this.highValue = highValue;
+ }
+
+ public Integer getActualValue() {
+ return actualValue;
+ }
+
+ public void setActualValue(Integer actualValue) {
+ this.actualValue = actualValue;
+ }
+
+ public String getStartName() {
+ return startName;
+ }
+
+ public void setStartName(String startName) {
+ this.startName = startName;
+ }
+
+ public String getEndName() {
+ return endName;
+ }
+
+ public void setEndName(String endName) {
+ this.endName = endName;
+ }
+
+ public String getUnit() {
+ return unit;
+ }
+
+ public void setUnit(String unit) {
+ this.unit = unit;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("ConfigChannel{");
+ if (number != null) {
+ sb.append("number=").append(number);
+ }
+ if (choicesCount != null) {
+ sb.append(", choicesCount=").append(choicesCount);
+ }
+ if (valueId != null) {
+ sb.append(", valueId=").append(valueId);
+ }
+ if (choiceDescription != null) {
+ sb.append(", choiceDescription=").append(choiceDescription);
+ }
+ if (!choices.isEmpty()) {
+ sb.append(", choices=").append(choices);
+ }
+ if (lowValue != null) {
+ sb.append(", lowValue=").append(lowValue);
+ }
+ if (highValue != null) {
+ sb.append(", highValue=").append(highValue);
+ }
+ if (actualValue != null) {
+ sb.append(", actualValue=").append(actualValue);
+ }
+ if (startName != null) {
+ sb.append(", startName=").append(startName);
+ }
+ if (endName != null) {
+ sb.append(", endName=").append(endName);
+ }
+ if (unit != null) {
+ sb.append(", unit=").append(unit);
+ }
+
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 83 * hash + Objects.hashCode(this.number);
+ hash = 83 * hash + Objects.hashCode(this.valueId);
+ hash = 83 * hash + Objects.hashCode(this.choicesCount);
+ hash = 83 * hash + Objects.hashCode(this.choiceDescription);
+ hash = 83 * hash + Objects.hashCode(this.choices);
+ hash = 83 * hash + Objects.hashCode(this.lowValue);
+ hash = 83 * hash + Objects.hashCode(this.highValue);
+ hash = 83 * hash + Objects.hashCode(this.actualValue);
+ hash = 83 * hash + Objects.hashCode(this.startName);
+ hash = 83 * hash + Objects.hashCode(this.endName);
+ hash = 83 * hash + Objects.hashCode(this.unit);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final ConfigChannel other = (ConfigChannel) obj;
+ if (!Objects.equals(this.choiceDescription, other.choiceDescription)) {
+ return false;
+ }
+ if (!Objects.equals(this.startName, other.startName)) {
+ return false;
+ }
+ if (!Objects.equals(this.endName, other.endName)) {
+ return false;
+ }
+ if (!Objects.equals(this.unit, other.unit)) {
+ return false;
+ }
+ if (!Objects.equals(this.number, other.number)) {
+ return false;
+ }
+ if (!Objects.equals(this.valueId, other.valueId)) {
+ return false;
+ }
+ if (!Objects.equals(this.choicesCount, other.choicesCount)) {
+ return false;
+ }
+ if (!Objects.equals(this.choices, other.choices)) {
+ return false;
+ }
+ if (!Objects.equals(this.lowValue, other.lowValue)) {
+ return false;
+ }
+ if (!Objects.equals(this.highValue, other.highValue)) {
+ return false;
+ }
+ return Objects.equals(this.actualValue, other.actualValue);
+ }
+
+ private void parseJson(String json) {
+ JSONObject kanal = new JSONObject(json);
+
+ //this.defaultValueId = kanal.optInt("auswahl");
+ this.actualValue = kanal.optInt("auswahl");
+ this.unit = kanal.optString("einheit");
+ //this.index = kanal.optInt("index");
+ this.lowValue = kanal.optInt("min");
+ this.highValue = kanal.optInt("max");
+ this.choiceDescription = kanal.optString("name");
+ this.number = kanal.optInt("nr");
+ this.startName = kanal.optString("startWert");
+ //this.type = kanal.optInt("typ");
+ this.actualValue = kanal.optInt("wert");
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/can/device/MeasuringChannel.java b/src/main/java/jcs/commandStation/marklin/cs/can/device/MeasuringChannel.java
new file mode 100644
index 00000000..c2ddf901
--- /dev/null
+++ b/src/main/java/jcs/commandStation/marklin/cs/can/device/MeasuringChannel.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2025 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.marklin.cs.can.device;
+
+import java.util.Objects;
+import org.json.JSONObject;
+
+/**
+ * Represents a Measurement Channel in the Marklin Central Station
+ */
+public class MeasuringChannel {
+
+ private Integer number;
+ private Integer scale;
+ private Integer colorGreen;
+ private Integer colorYellow;
+ private Integer colorRed;
+ private Integer colorMax;
+ private Integer zeroPoint;
+ private Integer rangeGreen;
+ private Integer rangeYellow;
+ private Integer rangeRed;
+ private Integer rangeMax;
+ private String name;
+ private Double startValue;
+ private Double endValue;
+ private String unit;
+
+ public MeasuringChannel() {
+ }
+
+ public MeasuringChannel(String json) {
+ parseJson(json);
+ }
+
+ public String getUnit() {
+ return unit;
+ }
+
+ public void setUnit(String unit) {
+ this.unit = unit;
+ }
+
+ public Double getEndValue() {
+ return endValue;
+ }
+
+ public void setEndValue(Double endValue) {
+ this.endValue = endValue;
+ }
+
+ public Integer getColorYellow() {
+ return colorYellow;
+ }
+
+ public void setColorYellow(Integer colorYellow) {
+ this.colorYellow = colorYellow;
+ }
+
+ public Integer getColorGreen() {
+ return colorGreen;
+ }
+
+ public void setColorGreen(Integer colorGreen) {
+ this.colorGreen = colorGreen;
+ }
+
+ public Integer getColorMax() {
+ return colorMax;
+ }
+
+ public void setColorMax(Integer colorMax) {
+ this.colorMax = colorMax;
+ }
+
+ public Integer getColorRed() {
+ return colorRed;
+ }
+
+ public void setColorRed(Integer colorRed) {
+ this.colorRed = colorRed;
+ }
+
+ public Integer getZeroPoint() {
+ return zeroPoint;
+ }
+
+ public void setZeroPoint(Integer zeroPoint) {
+ this.zeroPoint = zeroPoint;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getNumber() {
+ return number;
+ }
+
+ public void setNumber(Integer number) {
+ this.number = number;
+ }
+
+ public Integer getRangeYellow() {
+ return rangeYellow;
+ }
+
+ public void setRangeYellow(Integer rangeYellow) {
+ this.rangeYellow = rangeYellow;
+ }
+
+ public Integer getRangeGreen() {
+ return rangeGreen;
+ }
+
+ public void setRangeGreen(Integer rangeGreen) {
+ this.rangeGreen = rangeGreen;
+ }
+
+ public Integer getRangeMax() {
+ return rangeMax;
+ }
+
+ public void setRangeMax(Integer rangeMax) {
+ this.rangeMax = rangeMax;
+ }
+
+ public Integer getRangeRed() {
+ return rangeRed;
+ }
+
+ public void setRangeRed(Integer rangeRed) {
+ this.rangeRed = rangeRed;
+ }
+
+ public Integer getScale() {
+ return scale;
+ }
+
+ public void setScale(Integer scale) {
+ this.scale = scale;
+ }
+
+ public Double getStartValue() {
+ return startValue;
+ }
+
+ public void setStartValue(Double startValue) {
+ this.startValue = startValue;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("MeasuringChannel{");
+ if (number != null) {
+ sb.append("number=").append(number);
+ }
+ if (name != null) {
+ sb.append(", name=").append(name);
+ }
+ if (scale != null) {
+ sb.append(", scale=").append(scale);
+ }
+ if (colorGreen != null) {
+ sb.append(", colorGreen=").append(colorGreen);
+ }
+ if (colorYellow != null) {
+ sb.append(", colorYellow=").append(colorYellow);
+ }
+ if (colorRed != null) {
+ sb.append(", colorRed=").append(colorRed);
+ }
+ if (colorMax != null) {
+ sb.append(", colorMax=").append(colorMax);
+ }
+ if (zeroPoint != null) {
+ sb.append(", zero=").append(zeroPoint);
+ }
+ if (rangeGreen != null) {
+ sb.append(", rangeGreen=").append(rangeGreen);
+ }
+ if (rangeYellow != null) {
+ sb.append(", rangeYellow=").append(rangeYellow);
+ }
+ if (rangeRed != null) {
+ sb.append(", rangeRed=").append(rangeRed);
+ }
+ if (rangeMax != null) {
+ sb.append(", rangeMax=").append(rangeMax);
+ }
+ if (startValue != null) {
+ sb.append(", startValue=").append(startValue);
+ }
+ if (endValue != null) {
+ sb.append(", endValue=").append(endValue);
+ }
+ if (unit != null) {
+ sb.append(", unit=").append(unit);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 23 * hash + Objects.hashCode(this.number);
+ hash = 23 * hash + Objects.hashCode(this.scale);
+ hash = 23 * hash + Objects.hashCode(this.colorGreen);
+ hash = 23 * hash + Objects.hashCode(this.colorYellow);
+ hash = 23 * hash + Objects.hashCode(this.colorRed);
+ hash = 23 * hash + Objects.hashCode(this.colorMax);
+ hash = 23 * hash + Objects.hashCode(this.zeroPoint);
+ hash = 23 * hash + Objects.hashCode(this.rangeGreen);
+ hash = 23 * hash + Objects.hashCode(this.rangeYellow);
+ hash = 23 * hash + Objects.hashCode(this.rangeRed);
+ hash = 23 * hash + Objects.hashCode(this.rangeMax);
+ hash = 23 * hash + Objects.hashCode(this.name);
+ hash = 23 * hash + Objects.hashCode(this.startValue);
+ hash = 23 * hash + Objects.hashCode(this.endValue);
+ hash = 23 * hash + Objects.hashCode(this.unit);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final MeasuringChannel other = (MeasuringChannel) obj;
+ if (!Objects.equals(this.name, other.name)) {
+ return false;
+ }
+ if (!Objects.equals(this.unit, other.unit)) {
+ return false;
+ }
+ if (!Objects.equals(this.number, other.number)) {
+ return false;
+ }
+ if (!Objects.equals(this.scale, other.scale)) {
+ return false;
+ }
+ if (!Objects.equals(this.colorGreen, other.colorGreen)) {
+ return false;
+ }
+ if (!Objects.equals(this.colorYellow, other.colorYellow)) {
+ return false;
+ }
+ if (!Objects.equals(this.colorRed, other.colorRed)) {
+ return false;
+ }
+ if (!Objects.equals(this.colorMax, other.colorMax)) {
+ return false;
+ }
+ if (!Objects.equals(this.zeroPoint, other.zeroPoint)) {
+ return false;
+ }
+ if (!Objects.equals(this.rangeGreen, other.rangeGreen)) {
+ return false;
+ }
+ if (!Objects.equals(this.rangeYellow, other.rangeYellow)) {
+ return false;
+ }
+ if (!Objects.equals(this.rangeRed, other.rangeRed)) {
+ return false;
+ }
+ if (!Objects.equals(this.rangeMax, other.rangeMax)) {
+ return false;
+ }
+ if (!Objects.equals(this.startValue, other.startValue)) {
+ return false;
+ }
+ return Objects.equals(this.endValue, other.endValue);
+ }
+
+ private void parseJson(String json) {
+ JSONObject kanal = new JSONObject(json);
+
+// this.selection = kanal.optString("auswahl");
+// this.config = kanal.optString("auswahl");
+ this.unit = kanal.optString("einheit");
+ this.colorYellow = kanal.optInt("farbeGelb");
+ this.colorGreen = kanal.optInt("farbeGruen");
+ this.colorMax = kanal.optInt("farbeMax");
+ this.colorRed = kanal.optInt("farbeRot");
+// this.index = kanal.optInt("index");
+// this.present = kanal.optBoolean("isPresent");
+// this.min = kanal.optInt("min");
+// this.max = kanal.optInt("max");
+ this.name = kanal.optString("name");
+ this.number = kanal.optInt("nr");
+// this.ready = kanal.optBoolean("_ready");
+ this.rangeYellow = kanal.optInt("rangeGelb");
+ this.rangeGreen = kanal.optInt("rangeGruen");
+ this.rangeMax = kanal.optInt("rangeMax");
+ this.rangeRed = kanal.optInt("rangeRot");
+ this.scale = kanal.optInt("potenz");
+ this.startValue = kanal.optDouble("startWert");
+// this.type = kanal.optInt("typ");
+// this.value = kanal.optInt("wert");
+ //this.previousValue;
+// this.humanValue = kanal.optDouble("valueHuman");
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/can/parser/AccessoryMessage.java b/src/main/java/jcs/commandStation/marklin/cs/can/parser/AccessoryMessage.java
new file mode 100644
index 00000000..210b664e
--- /dev/null
+++ b/src/main/java/jcs/commandStation/marklin/cs/can/parser/AccessoryMessage.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2024 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.marklin.cs.can.parser;
+
+import jcs.commandStation.events.AccessoryEvent;
+import jcs.commandStation.marklin.cs.can.CanMessage;
+import jcs.entities.AccessoryBean;
+import org.tinylog.Logger;
+
+public class AccessoryMessage {
+
+ private AccessoryMessage() {
+
+ }
+
+ public static AccessoryEvent parse(CanMessage message) {
+ if(message == null) {
+ return null;
+ }
+ CanMessage msg;
+ if (!message.isResponseMessage()) {
+ msg = message.getResponse();
+ } else {
+ msg = message;
+ }
+
+ int cmd = msg.getCommand();
+ int dlc = msg.getDlc();
+ byte[] data = msg.getData();
+
+ if (CanMessage.ACCESSORY_SWITCHING_RESP == cmd || CanMessage.ACCESSORY_SWITCHING == cmd) {
+ byte[] addressData = new byte[]{data[2], data[3]};
+ int address = CanMessage.toInt(addressData);
+ String protocol;
+ //CS is zero based
+ if (address >= CanMessage.DCC_ACCESSORY_OFFSET) {
+ protocol = "dcc";
+ address = address - CanMessage.DCC_ACCESSORY_OFFSET + 1;
+ } else {
+ protocol = "mm";
+ address = address + 1;
+ }
+
+ //int address = data[3];
+ int position = data[4];
+
+ String id = Integer.toString(address);
+ AccessoryBean accessoryBean = new AccessoryBean(id, address, null, null, position, null, protocol, null, CanMessage.MARKLIN_COMMANDSTATION_ID);
+
+ //TODO DCC support
+ ///RX: 0x00 0x16 0x37 0x7e 0x06 0x00 0x00 0x38 0x00 0x01 0x00 0x00 0x00
+ //RX: 0x00 0x16 0x37 0x7e 0x06 0x00 0x00 0x38 0x01 0x00 0x01 0x00 0x00
+
+
+ if (CanMessage.DLC_8 == dlc) {
+ int switchTime = CanMessage.toInt(new byte[]{data[6], data[7]});
+ switchTime = switchTime * 10;
+ accessoryBean.setSwitchTime(switchTime);
+ }
+ return new AccessoryEvent(accessoryBean);
+ } else {
+ Logger.warn("Can't parse message, not an Accessory Message! " + message);
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/can/parser/DirectionInfo.java b/src/main/java/jcs/commandStation/marklin/cs/can/parser/DirectionInfo.java
deleted file mode 100755
index 676eaafa..00000000
--- a/src/main/java/jcs/commandStation/marklin/cs/can/parser/DirectionInfo.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2023 Frans Jacobs.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jcs.commandStation.marklin.cs.can.parser;
-
-import java.io.Serializable;
-import jcs.commandStation.marklin.cs.can.CanMessage;
-import jcs.entities.LocomotiveBean.Direction;
-import org.tinylog.Logger;
-
-public class DirectionInfo implements Serializable {
-
- private Direction direction;
-
- public DirectionInfo(Direction direction) {
- this.direction = direction;
- }
-
- public DirectionInfo(CanMessage locDirection) {
- parseMessage(locDirection);
- }
-
- private void parseMessage(CanMessage locDirection) {
- Logger.debug(locDirection);
- byte[] data = locDirection.getResponse(0).getData();
- int dir = data[4];
-
- this.direction = Direction.getDirectionMarkin(dir);
- }
-
- @Override
- public String toString() {
- return "DirectionInfo{" + "direction=" + direction + '}';
- }
-
- public Direction getDirection() {
- return direction;
- }
-
-}
diff --git a/src/main/java/jcs/commandStation/marklin/cs2/SensorMessageParser.java b/src/main/java/jcs/commandStation/marklin/cs/can/parser/FeedbackEventMessage.java
similarity index 63%
rename from src/main/java/jcs/commandStation/marklin/cs2/SensorMessageParser.java
rename to src/main/java/jcs/commandStation/marklin/cs/can/parser/FeedbackEventMessage.java
index e3b29c82..f8938fe6 100644
--- a/src/main/java/jcs/commandStation/marklin/cs2/SensorMessageParser.java
+++ b/src/main/java/jcs/commandStation/marklin/cs/can/parser/FeedbackEventMessage.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package jcs.commandStation.marklin.cs2;
+package jcs.commandStation.marklin.cs.can.parser;
import java.util.Date;
import jcs.commandStation.marklin.cs.can.CanMessage;
@@ -23,11 +23,13 @@
/**
*
- * @author Frans Jacobs
+ * Parse Sensor messages
*/
-public class SensorMessageParser {
+public class FeedbackEventMessage {
- public static SensorBean parseMessage(CanMessage message, Date eventDate) {
+ private static final String MARKLIN_CS = "marklin.cs";
+
+ public static SensorBean parse(CanMessage message, Date eventDate) {
CanMessage resp;
if (!message.isResponseMessage()) {
resp = message.getResponse();
@@ -38,18 +40,32 @@ public static SensorBean parseMessage(CanMessage message, Date eventDate) {
if (resp.isResponseMessage() && CanMessage.S88_EVENT_RESPONSE == resp.getCommand()) {
byte[] data = resp.getData();
- Integer deviceId = ByteUtil.toInt(new byte[]{data[0], data[1]});
+ Integer identifier = ByteUtil.toInt(new byte[]{data[0], data[1]});
Integer contactId = ByteUtil.toInt(new byte[]{data[2], data[3]});
int previousStatus = data[4];
int status = data[5];
Integer millis = ByteUtil.toInt(new byte[]{data[6], data[7]}) * 10;
- SensorBean sensorBean = new SensorBean(deviceId, contactId, status, previousStatus, millis, eventDate);
+
+ //Derive the busNumber
+ int busNumber;
+ if (contactId < 1000) {
+ busNumber = 0;
+ } else if (contactId >= 1000 && contactId < 2000) {
+ busNumber = 1;
+ } else if (contactId >= 2000 && contactId < 3000) {
+ busNumber = 2;
+ } else {
+ busNumber = 3;
+ }
+
+ SensorBean sensorBean = new SensorBean(contactId, null, null, null, identifier, status, previousStatus, millis, System.currentTimeMillis(), MARKLIN_CS, busNumber);
return sensorBean;
} else {
Logger.warn("Can't parse message, not a Sensor Response! " + resp);
return null;
}
}
+
}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/can/parser/LocomotiveEmergencyStopMessage.java b/src/main/java/jcs/commandStation/marklin/cs/can/parser/LocomotiveEmergencyStopMessage.java
new file mode 100644
index 00000000..6ef33c73
--- /dev/null
+++ b/src/main/java/jcs/commandStation/marklin/cs/can/parser/LocomotiveEmergencyStopMessage.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2025 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.marklin.cs.can.parser;
+
+import jcs.commandStation.events.LocomotiveSpeedEvent;
+import jcs.commandStation.marklin.cs.can.CanMessage;
+import jcs.entities.LocomotiveBean;
+import org.tinylog.Logger;
+
+/**
+ * Emergency Stop for a specific locomotive
+ */
+public class LocomotiveEmergencyStopMessage {
+
+ public static LocomotiveSpeedEvent parse(CanMessage message) {
+ CanMessage resp;
+ if (!message.isResponseMessage()) {
+ resp = message.getResponse();
+ } else {
+ resp = message;
+ }
+ int cmd = resp.getCommand();
+ int subCmd = resp.getSubCommand();
+ int dlc = resp.getDlc();
+ byte[] data = resp.getData();
+
+ if ((cmd == CanMessage.SYSTEM_COMMAND_RESP || cmd == CanMessage.SYSTEM_COMMAND) && subCmd == CanMessage.LOC_STOP_SUB_CMD && dlc == CanMessage.DLC_5) {
+ long id = CanMessage.toInt(new byte[]{data[0], data[1], data[2], data[3]});
+
+ LocomotiveBean locomotiveBean = new LocomotiveBean();
+ locomotiveBean.setCommandStationId(CanMessage.MARKLIN_COMMANDSTATION_ID);
+ locomotiveBean.setId(id);
+ locomotiveBean.setVelocity(0);
+ return new LocomotiveSpeedEvent(locomotiveBean);
+ } else {
+ Logger.warn("Can't parse message, not a Locomotive Emergency Stop Message! " + resp);
+ return null;
+ }
+
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/marklin/cs2/LocomotiveSpeedEventParser.java b/src/main/java/jcs/commandStation/marklin/cs/can/parser/LocomotiveVelocityMessage.java
similarity index 64%
rename from src/main/java/jcs/commandStation/marklin/cs2/LocomotiveSpeedEventParser.java
rename to src/main/java/jcs/commandStation/marklin/cs/can/parser/LocomotiveVelocityMessage.java
index f636a4c7..9f727a42 100644
--- a/src/main/java/jcs/commandStation/marklin/cs2/LocomotiveSpeedEventParser.java
+++ b/src/main/java/jcs/commandStation/marklin/cs/can/parser/LocomotiveVelocityMessage.java
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package jcs.commandStation.marklin.cs2;
+package jcs.commandStation.marklin.cs.can.parser;
-import jcs.commandStation.events.*;
+import jcs.commandStation.events.LocomotiveSpeedEvent;
import jcs.commandStation.marklin.cs.can.CanMessage;
import jcs.entities.LocomotiveBean;
import org.tinylog.Logger;
@@ -23,9 +23,9 @@
/**
*
*/
-public class LocomotiveSpeedEventParser {
-
- public static LocomotiveSpeedEvent parseMessage(CanMessage message) {
+public class LocomotiveVelocityMessage {
+
+ public static LocomotiveSpeedEvent parse(CanMessage message) {
LocomotiveBean locomotiveBean = new LocomotiveBean();
locomotiveBean.setCommandStationId(CanMessage.MARKLIN_COMMANDSTATION_ID);
@@ -36,14 +36,7 @@ public static LocomotiveSpeedEvent parseMessage(CanMessage message) {
resp = message;
}
- if (resp.isResponseMessage() && CanMessage.SYSTEM_COMMAND == resp.getCommand() && CanMessage.LOC_STOP_SUB_CMD == resp.getSubCommand() && CanMessage.DLC_5 == resp.getDlc()) {
- //Loco halt command could be issued due to a direction change.
- byte[] data = resp.getData();
- long id = CanMessage.toInt(new byte[]{data[0], data[1], data[2], data[3]});
-
- locomotiveBean.setId(id);
- locomotiveBean.setVelocity(0);
- } else if (resp.isResponseMessage() && CanMessage.LOC_VELOCITY_RESP == resp.getCommand()) {
+ if (resp.isResponseMessage() && CanMessage.LOC_VELOCITY_RESP == resp.getCommand()) {
byte[] data = resp.getData();
long id = CanMessage.toInt(new byte[]{data[0], data[1], data[2], data[3]});
@@ -53,7 +46,7 @@ public static LocomotiveSpeedEvent parseMessage(CanMessage message) {
locomotiveBean.setVelocity(velocity);
} else {
- Logger.warn("Can't parse message, not a Locomotive Velocity or a Locomotive Emergency Stop Message! " + resp);
+ Logger.warn("Can't parse message, not a Locomotive Velocity Message! " + resp);
return null;
}
return new LocomotiveSpeedEvent(locomotiveBean);
diff --git a/src/main/java/jcs/commandStation/marklin/cs/can/parser/SystemStatus.java b/src/main/java/jcs/commandStation/marklin/cs/can/parser/SystemStatus.java
index a255d97d..5bbd29b3 100755
--- a/src/main/java/jcs/commandStation/marklin/cs/can/parser/SystemStatus.java
+++ b/src/main/java/jcs/commandStation/marklin/cs/can/parser/SystemStatus.java
@@ -15,69 +15,29 @@
*/
package jcs.commandStation.marklin.cs.can.parser;
-import java.io.Serializable;
import java.util.List;
import jcs.commandStation.marklin.cs.can.CanMessage;
-import jcs.util.ByteUtil;
-import org.tinylog.Logger;
/**
- *
- * @author Frans Jacobs
+ * SystemStatus parser
*/
-public class SystemStatus implements Serializable {
-
- private boolean power;
- private byte[] gfpUid;
-
- public SystemStatus(CanMessage message) {
- parseMessage(message);
- }
+public class SystemStatus {
- //There might be more than 1 responses.
- //when there are more use the one which contains a valid gfp uid
- private void parseMessage(CanMessage message) {
- if (message != null) {
- CanMessage resp = null;
- List respList = message.getResponses();
- if (respList.isEmpty()) {
- Logger.warn("No response for: " + message);
- gfpUid = message.getDeviceUidFromMessage();
- int status = message.getData()[4];
- power = status == 1;
- } else {
- for (CanMessage cm : respList) {
- if (cm.isResponseMessage() && cm.isDeviceUidValid()) {
- resp = cm;
- }
- }
- if (resp == null) {
- resp = message;
- }
-
- if (CanMessage.SYSTEM_COMMAND_RESP == resp.getCommand() && resp.isDeviceUidValid()) {
- byte[] data = resp.getData();
- gfpUid = resp.getDeviceUidFromMessage();
- int status = data[4];
- power = status == 1;
+ public static boolean parseSystemPowerMessage(CanMessage message) {
+ if (message == null) {
+ return false;
+ }
+ List respList = message.getResponses();
+ if (respList.isEmpty()) {
+ return message.getData()[4] == 1;
+ } else {
+ for (CanMessage cm : respList) {
+ if (CanMessage.SYSTEM_COMMAND_RESP == cm.getCommand() && cm.getDlc() == CanMessage.DLC_5) {
+ int powerVal = cm.getData()[4];
+ return powerVal == 1;
}
}
- } else {
- power = false;
- gfpUid = new byte[]{0, 0, 0, 0};
}
- }
-
- @Override
- public String toString() {
- return "SystemStatus{" + " power: " + (power ? "On" : "Off") + " GFP UID: " + ByteUtil.toHexString(gfpUid) + " }";
- }
-
- public boolean isPower() {
- return power;
- }
-
- public byte[] getGfpUid() {
- return gfpUid;
+ return false;
}
}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/net/CSConnection.java b/src/main/java/jcs/commandStation/marklin/cs/net/CSConnection.java
index c49d47d5..2048f354 100755
--- a/src/main/java/jcs/commandStation/marklin/cs/net/CSConnection.java
+++ b/src/main/java/jcs/commandStation/marklin/cs/net/CSConnection.java
@@ -16,17 +16,10 @@
package jcs.commandStation.marklin.cs.net;
import java.net.InetAddress;
+import java.util.concurrent.TransferQueue;
import jcs.commandStation.marklin.cs.can.CanMessage;
-import jcs.commandStation.marklin.cs.events.CanPingListener;
-import jcs.commandStation.marklin.cs.events.AccessoryListener;
-import jcs.commandStation.marklin.cs.events.FeedbackListener;
-import jcs.commandStation.marklin.cs.events.LocomotiveListener;
-import jcs.commandStation.marklin.cs.events.SystemListener;
+import jcs.commandStation.events.ConnectionEventListener;
-/**
- *
- * @author Frans Jacobs
- */
public interface CSConnection extends AutoCloseable {
static final int MAX_ERRORS = 15;
@@ -37,18 +30,13 @@ public interface CSConnection extends AutoCloseable {
CanMessage sendCanMessage(CanMessage message);
- void setCanPingListener(CanPingListener canPingListener);
-
- void setFeedbackListener(FeedbackListener feedbackListener);
-
- void setSystemListener(SystemListener systemListener);
-
- void setAccessoryListener(AccessoryListener accessoryListener);
-
- void setLocomotiveListener(LocomotiveListener locomotiveListener);
-
InetAddress getControllerAddress();
boolean isConnected();
+ TransferQueue getEventQueue();
+
+ void addDisconnectionEventListener(ConnectionEventListener listener);
+
+
}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/net/CSConnectionFactory.java b/src/main/java/jcs/commandStation/marklin/cs/net/CSConnectionFactory.java
index 7228197a..816e36e0 100755
--- a/src/main/java/jcs/commandStation/marklin/cs/net/CSConnectionFactory.java
+++ b/src/main/java/jcs/commandStation/marklin/cs/net/CSConnectionFactory.java
@@ -26,8 +26,8 @@
import jcs.JCS;
import jcs.commandStation.marklin.cs.can.CanMessage;
import jcs.commandStation.marklin.cs.can.CanMessageFactory;
+import jcs.entities.CommandStationBean;
import jcs.util.NetworkUtil;
-import jcs.util.Ping;
import jcs.util.RunUtil;
import net.straylightlabs.hola.dns.Domain;
import net.straylightlabs.hola.sd.Instance;
@@ -36,107 +36,147 @@
import org.tinylog.Logger;
/**
- * Try to connect with a Marklin CS 2/3. A "ping" is send to the broadcast address like the mobile app does. The CS 2/3 response reveals the IP address.
+ * Try to connect with a Marklin CS 2/3.
+ * The Latest software version of the CS-3 support mDNS, so mDNS is first used to discover the CS.
+ * When mDNS does not work the "old" manner the mobile app uses is used.
+ * A "magic" ping is send to the broadcast address the CS 2/3 response reveals the IP address.
*
* @author Frans Jacobs
*/
public class CSConnectionFactory {
private static final String MARKLIN_CS_SERVICE = "_workstation._tcp";
+ private static final String BROADCAST_ADDRESS = "255.255.255.255";
- private static CSConnectionFactory instance;
-
- private CSConnection controllerConnection;
- private HTTPConnection httpConnection;
- private InetAddress controllerHost;
+ private static InetAddress controllerHost;
+ private static boolean forceVirtual = false;
+ private static boolean virtual;
- private static final String BROADCAST_ADDRESS = "255.255.255.255";
+ private static CSConnection controllerConnection;
+ private static CSHTTPConnection httpConnection;
- private static final String LAST_USED_IP_PROP_FILE = RunUtil.DEFAULT_PATH + "last-used-marklin-cs-ip.properties";
+ static {
+ forceVirtual = "true".equals(System.getProperty("connection.always.virtual", "false"));
+ }
- private CSConnectionFactory() {
+ public static CSConnection getConnection(CommandStationBean commandStation) {
+ return getConnection(commandStation, (virtual != commandStation.isVirtual()));
}
- public static CSConnectionFactory getInstance() {
- if (instance == null) {
- instance = new CSConnectionFactory();
+ public static CSConnection getConnection(CommandStationBean commandStation, boolean reconnect) {
+ if (reconnect) {
+ disconnectAll();
}
- return instance;
- }
- CSConnection getConnectionImpl() {
- if (controllerConnection == null) {
+ virtual = commandStation.isVirtual();
+ if (!virtual && forceVirtual) {
+ virtual = forceVirtual;
+ Logger.info("Forcing a virtual connection!");
+ }
- String lastUsedIp = RunUtil.readProperty(LAST_USED_IP_PROP_FILE, "ip-address");
-
- if (lastUsedIp != null) {
- try {
- if (Ping.IsReachable(lastUsedIp)) {
- this.controllerHost = InetAddress.getByName(lastUsedIp);
- Logger.trace("Using last used IP Address: " + lastUsedIp);
- } else {
- Logger.trace("Last used IP Address: " + lastUsedIp + " is not reachable");
- }
- } catch (UnknownHostException ex) {
- Logger.trace("Last used CS IP: " + lastUsedIp + " is invalid!");
- lastUsedIp = null;
- }
+ try {
+ if (virtual) {
+ controllerHost = InetAddress.getLocalHost();
+ } else {
+ controllerHost = InetAddress.getByName(commandStation.getIpAddress());
}
+ } catch (UnknownHostException ex) {
+ Logger.error("Invalid ip address : " + commandStation.getIpAddress());
+ return null;
+ }
- if (this.controllerHost == null) {
- Logger.trace("Try to discover a Marklin CS...");
- JCS.logProgress("Discovering a Marklin Central Station...");
- sendMobileAppPing();
+ if (controllerConnection == null) {
+ if (virtual) {
+ controllerConnection = new CSVirtualConnection(controllerHost);
+ } else {
+ controllerConnection = new CSTCPConnection(controllerHost);
}
+ }
- if (controllerHost != null) {
- if (lastUsedIp == null) {
- //Write the last used IP Addres for faster discovery next time
- RunUtil.writeProperty(LAST_USED_IP_PROP_FILE, "ip-address", controllerHost.getHostAddress());
- }
- Logger.trace("CS ip: " + controllerHost.getHostName());
+ return controllerConnection;
+ }
- controllerConnection = new TCPConnection(controllerHost);
+ public static CSHTTPConnection getHTTPConnection() {
+ if (httpConnection == null) {
+ if (virtual) {
+ httpConnection = new CSHTTPConnectionVirt(controllerHost);
} else {
- Logger.warn("Can't find a Marklin Controller host!");
- JCS.logProgress("Can't find a Marklin Central Station on the Network");
+ httpConnection = new CSHTTPConnectionImpl(controllerHost);
}
}
- return this.controllerConnection;
- }
-
- public static CSConnection getConnection() {
- return getInstance().getConnectionImpl();
+ return httpConnection;
}
public static void disconnectAll() {
- if (instance.controllerConnection != null) {
+ if (controllerConnection != null) {
try {
- instance.controllerConnection.close();
+ controllerConnection.close();
} catch (Exception ex) {
Logger.trace("Error during disconnect " + ex);
}
}
- instance.controllerConnection = null;
- instance.httpConnection = null;
- instance.controllerHost = null;
+ controllerConnection = null;
+ httpConnection = null;
+ controllerHost = null;
}
- HTTPConnection getHTTPConnectionImpl() {
- if (controllerConnection == null) {
- getConnectionImpl();
- }
- if (httpConnection == null) {
- httpConnection = new HTTPConnection(controllerHost);
+ public static String getControllerIp() {
+ if (controllerHost != null) {
+ return controllerHost.getHostAddress();
+ } else {
+ return "Unknown";
}
- return httpConnection;
}
- public static HTTPConnection getHTTPConnection() {
- return getInstance().getHTTPConnectionImpl();
+ /**
+ * Try to Automatically discover the Marklin Central Station IP Address on the local network.
+ * mDNS is now supported by the CS-3, not sure whether the CS-2 also supports it.
+ *
+ * @return the IP Address of the Marklin Central Station of null if not discovered.
+ */
+ public static InetAddress discoverCs() {
+ InetAddress csIp = null;
+
+ try {
+ Service marklinService = Service.fromName(MARKLIN_CS_SERVICE);
+ Query marklinQuery = Query.createFor(marklinService, Domain.LOCAL);
+
+ Set marklinInstances = marklinQuery.runOnceOn(NetworkUtil.getIPv4HostAddress());
+
+ Logger.trace("Found " + marklinInstances.size());
+
+ if (marklinInstances.isEmpty()) {
+ Logger.warn("Could not find a Marklin Central Station host on the local network!");
+ return null;
+ }
+
+ Instance cs = marklinInstances.iterator().next();
+ Logger.trace("Marklin Central Station: " + cs);
+
+ Set addresses = cs.getAddresses();
+
+ //Find the first ip4 address
+ for (InetAddress ia : addresses) {
+ if (ia instanceof Inet4Address) {
+ csIp = ia;
+ break;
+ }
+ }
+ } catch (IOException ex) {
+ Logger.error(ex.getMessage());
+ }
+ return csIp;
}
- void sendMobileAppPing() {
+ /**
+ * Try to Automatically discover the Marklin CS 2/3 IP Address on the local network.
+ * A "magic" CAN message is send as broadcast on the network on the CS RX port.
+ * This method was used by the "old" mobile app to find the Central station.
+ *
+ * @return the IP Address of the Marklin Central Station or null if not discovered.
+ */
+ public static InetAddress discoverCsUsingMobileAppPing() {
+ InetAddress csIp = null;
try {
InetAddress localAddress;
if (RunUtil.isLinux()) {
@@ -168,9 +208,7 @@ void sendMobileAppPing() {
Logger.trace("Received: " + response + " from: " + replyHost.getHostAddress());
if (response.getCommand() == CanMessage.PING_REQ) {
- if (this.controllerHost == null) {
- this.controllerHost = replyHost;
- }
+ csIp = replyHost;
JCS.logProgress("Found a Central Station in the network with IP: " + replyHost.getHostAddress());
} else {
Logger.debug("Received wrong command: " + response.getCommand() + " != " + CanMessage.PING_REQ + "...");
@@ -181,57 +219,6 @@ void sendMobileAppPing() {
} catch (IOException ex) {
Logger.error(ex);
}
- }
-
- String getControllerIpImpl() {
- if (this.controllerHost != null) {
- return this.controllerHost.getHostAddress();
- } else {
- return "Unknown";
- }
- }
-
- public static String getControllerIp() {
- return getInstance().getControllerIpImpl();
- }
-
- /**
- * Try to Automatically discover the ESU ECoS IP Address on the local network.
- * mDNS is used to discover the ECoS
- *
- * @return the IP Address of the ECoS of null if not discovered.
- */
- public static InetAddress discoverCs() {
- InetAddress csIp = null;
-
- try {
- Service marklinService = Service.fromName(MARKLIN_CS_SERVICE);
- Query marklinQuery = Query.createFor(marklinService, Domain.LOCAL);
-
- Set marklinInstances = marklinQuery.runOnceOn(NetworkUtil.getIPv4HostAddress());
-
- Logger.trace("Found " + marklinInstances.size());
-
- if (marklinInstances.isEmpty()) {
- Logger.warn("Could not find a Marklin Central Station host on the local network!");
- return null;
- }
-
- Instance cs = marklinInstances.iterator().next();
- Logger.trace("Marklin Central Station: " + cs);
-
- Set addresses = cs.getAddresses();
-
- //Find the first ip4 address
- for (InetAddress ia : addresses) {
- if (ia instanceof Inet4Address) {
- csIp = ia;
- break;
- }
- }
- } catch (IOException ex) {
- Logger.error(ex.getMessage());
- }
return csIp;
}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/net/CSHTTPConnection.java b/src/main/java/jcs/commandStation/marklin/cs/net/CSHTTPConnection.java
new file mode 100644
index 00000000..3a9bd622
--- /dev/null
+++ b/src/main/java/jcs/commandStation/marklin/cs/net/CSHTTPConnection.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2025 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.marklin.cs.net;
+
+import java.awt.Image;
+
+public interface CSHTTPConnection extends AutoCloseable {
+
+ boolean isConnected();
+
+ void setCs3(boolean cs3);
+
+ String getLocomotivesFile();
+
+ String getLocomotivesJSON();
+
+ String getAccessoriesFile();
+
+ String getFunctionsSvgJSON();
+
+ String getAccessoriesSvgJSON();
+
+ String getAccessoriesJSON();
+
+ String getDevicesJSON();
+
+ String getInfoFile();
+
+ String getInfoJSON();
+
+ Image getLocomotiveImage(String imageName);
+
+ Image getFunctionImageCS2(String imageName);
+
+}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/net/HTTPConnection.java b/src/main/java/jcs/commandStation/marklin/cs/net/CSHTTPConnectionImpl.java
similarity index 70%
rename from src/main/java/jcs/commandStation/marklin/cs/net/HTTPConnection.java
rename to src/main/java/jcs/commandStation/marklin/cs/net/CSHTTPConnectionImpl.java
index 2e771de7..53e2d47c 100755
--- a/src/main/java/jcs/commandStation/marklin/cs/net/HTTPConnection.java
+++ b/src/main/java/jcs/commandStation/marklin/cs/net/CSHTTPConnectionImpl.java
@@ -18,16 +18,13 @@
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
-import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.MalformedURLException;
+import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import org.tinylog.Logger;
@@ -36,28 +33,28 @@
*
* @author Frans Jacobs
*/
-public class HTTPConnection {
-
+public class CSHTTPConnectionImpl implements CSHTTPConnection {
+
private final InetAddress csAddress;
private boolean cs3;
-
+
private final static String HTTP = "http://";
private final static String CONFIG = "/config/";
private final static String LOCOMOTIVE = "lokomotive.cs2";
private final static String LOCOMOTIVE_JSON = "/app/api/loks";
-
+
private final static String MAGNETARTIKEL = "magnetartikel.cs2";
private final static String ACCESSORIES_URL = "/app/api/mags";
-
+
private final static String DEVICE = "geraet.vrs";
-
+
private final static String IMAGE_FOLDER_CS3 = "/app/assets/lok/";
private final static String IMAGE_FOLDER_CS2 = "/icons/";
private final static String FUNCTION_IMAGE_FOLDER = "/fcticons/";
-
+
private final static String FUNCTION_SVG_URL = "/images/svgSprites/fcticons.json";
private final static String ACCESSORIES_SVG_URL = "/images/svgSprites/magicons.json";
-
+
private final static String CS3_INFO_JSON = "/app/api/info";
private final static String DEVICES = "/app/api/devs";
@@ -67,29 +64,32 @@ public class HTTPConnection {
// http://cs3host/app/api/mags
// http://cs3host/app/api/info
//
- HTTPConnection(InetAddress csAddress) {
+ CSHTTPConnectionImpl(InetAddress csAddress) {
this.csAddress = csAddress;
//Assume a CS2
this.cs3 = false;
}
-
+
+ @Override
public boolean isConnected() {
return csAddress != null && csAddress.getHostAddress() != null;
}
-
+
+ @Override
public void setCs3(boolean cs3) {
this.cs3 = cs3;
Logger.trace("Changed Connection settings for a " + (cs3 ? "CS3" : "CS2"));
}
-
+
private static String fixURL(String url) {
return url.replace(" ", "%20");
}
-
+
+ @Override
public String getLocomotivesFile() {
StringBuilder locs = new StringBuilder();
try {
- URL cs = new URL(HTTP + csAddress.getHostAddress() + CONFIG + LOCOMOTIVE);
+ URL cs = URI.create(HTTP + csAddress.getHostAddress() + CONFIG + LOCOMOTIVE).toURL();
URLConnection lc = cs.openConnection();
try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
String inputLine;
@@ -105,11 +105,12 @@ public String getLocomotivesFile() {
}
return locs.toString();
}
-
+
+ @Override
public String getLocomotivesJSON() {
StringBuilder loks = new StringBuilder();
try {
- URL url = new URL(HTTP + csAddress.getHostAddress() + LOCOMOTIVE_JSON);
+ URL url = URI.create(HTTP + csAddress.getHostAddress() + LOCOMOTIVE_JSON).toURL();
URLConnection lc = url.openConnection();
try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
String inputLine;
@@ -125,11 +126,12 @@ public String getLocomotivesJSON() {
}
return loks.toString();
}
-
+
+ @Override
public String getAccessoriesFile() {
StringBuilder locs = new StringBuilder();
try {
- URL url = new URL(HTTP + csAddress.getHostAddress() + CONFIG + MAGNETARTIKEL);
+ URL url = URI.create(HTTP + csAddress.getHostAddress() + CONFIG + MAGNETARTIKEL).toURL();
URLConnection lc = url.openConnection();
try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
String inputLine;
@@ -145,11 +147,12 @@ public String getAccessoriesFile() {
}
return locs.toString();
}
-
+
+ @Override
public String getFunctionsSvgJSON() {
StringBuilder json = new StringBuilder();
try {
- URL url = new URL(HTTP + csAddress.getHostAddress() + FUNCTION_SVG_URL);
+ URL url = URI.create(HTTP + csAddress.getHostAddress() + FUNCTION_SVG_URL).toURL();
URLConnection lc = url.openConnection();
try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
String inputLine;
@@ -165,11 +168,12 @@ public String getFunctionsSvgJSON() {
}
return json.toString();
}
-
+
+ @Override
public String getAccessoriesSvgJSON() {
StringBuilder json = new StringBuilder();
try {
- URL url = new URL(HTTP + csAddress.getHostAddress() + ACCESSORIES_SVG_URL);
+ URL url = URI.create(HTTP + csAddress.getHostAddress() + ACCESSORIES_SVG_URL).toURL();
URLConnection lc = url.openConnection();
try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
String inputLine;
@@ -185,11 +189,12 @@ public String getAccessoriesSvgJSON() {
}
return json.toString();
}
-
+
+ @Override
public String getAccessoriesJSON() {
StringBuilder json = new StringBuilder();
try {
- URL url = new URL(HTTP + csAddress.getHostAddress() + ACCESSORIES_URL);
+ URL url = URI.create(HTTP + csAddress.getHostAddress() + ACCESSORIES_URL).toURL();
URLConnection lc = url.openConnection();
try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
String inputLine;
@@ -205,12 +210,13 @@ public String getAccessoriesJSON() {
}
return json.toString();
}
-
+
+ @Override
public String getDevicesJSON() {
StringBuilder device = new StringBuilder();
if (this.csAddress != null && csAddress.getHostAddress() != null) {
try {
- URL url = new URL(HTTP + csAddress.getHostAddress() + DEVICES);
+ URL url = URI.create(HTTP + csAddress.getHostAddress() + DEVICES).toURL();
URLConnection lc = url.openConnection();
try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
String inputLine;
@@ -229,11 +235,12 @@ public String getDevicesJSON() {
}
return device.toString();
}
-
+
+ @Override
public String getInfoFile() {
StringBuilder device = new StringBuilder();
try {
- URL url = new URL(HTTP + csAddress.getHostAddress() + CONFIG + DEVICE);
+ URL url = URI.create(HTTP + csAddress.getHostAddress() + CONFIG + DEVICE).toURL();
URLConnection lc = url.openConnection();
try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
String inputLine;
@@ -249,12 +256,13 @@ public String getInfoFile() {
}
return device.toString();
}
-
+
+ @Override
public String getInfoJSON() {
StringBuilder device = new StringBuilder();
if (this.csAddress != null && csAddress.getHostAddress() != null) {
try {
- URL url = new URL(HTTP + csAddress.getHostAddress() + CS3_INFO_JSON);
+ URL url = URI.create(HTTP + csAddress.getHostAddress() + CS3_INFO_JSON).toURL();
URLConnection lc = url.openConnection();
try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
String inputLine;
@@ -273,17 +281,18 @@ public String getInfoJSON() {
}
return device.toString();
}
-
+
+ @Override
public Image getLocomotiveImage(String imageName) {
BufferedImage image = null;
try {
URL url;
if (cs3) {
- url = new URL(fixURL(HTTP + csAddress.getHostAddress() + IMAGE_FOLDER_CS3 + imageName + ".png"));
+ url = URI.create(fixURL(HTTP + csAddress.getHostAddress() + IMAGE_FOLDER_CS3 + imageName + ".png")).toURL();
} else {
- url = new URL(fixURL(HTTP + csAddress.getHostAddress() + IMAGE_FOLDER_CS2 + imageName + ".png"));
+ url = URI.create(fixURL(HTTP + csAddress.getHostAddress() + IMAGE_FOLDER_CS2 + imageName + ".png")).toURL();
}
-
+
Logger.trace("image URL: " + url);
image = ImageIO.read(url);
} catch (MalformedURLException ex) {
@@ -293,13 +302,14 @@ public Image getLocomotiveImage(String imageName) {
}
return image;
}
-
+
+ @Override
public Image getFunctionImageCS2(String imageName) {
BufferedImage image = null;
String iurl = fixURL(HTTP + csAddress.getHostAddress() + FUNCTION_IMAGE_FOLDER + imageName + ".png");
-
+
try {
- URL url = new URL(iurl);
+ URL url = URI.create(iurl).toURL();
image = ImageIO.read(url);
} catch (IIOException iio) {
//Image not avalable
@@ -311,65 +321,9 @@ public Image getFunctionImageCS2(String imageName) {
}
return image;
}
-
- public static void main(String[] args) throws Exception {
- boolean cs3 = true;
-
- InetAddress inetAddr;
- if (cs3) {
- inetAddr = InetAddress.getByName("192.168.178.180");
- } else {
- inetAddr = InetAddress.getByName("192.168.178.86");
- }
- HTTPConnection hc = new HTTPConnection(inetAddr);
- hc.setCs3(cs3);
-
- String serial;
- if (cs3) {
- serial = "2374";
- } else {
- serial = "13344";
- }
-// Path fPath = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "cache" + File.separator + "zfunctions");
-// if (!Files.exists(fPath)) {
-// Files.createDirectories(fPath);
-// Logger.trace("Created new directory " + fPath);
-// }
-//
-// Path aPath = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "cache" + File.separator + "zaccessories");
-// if (!Files.exists(aPath)) {
-// Files.createDirectories(aPath);
-// Logger.trace("Created new directory " + aPath);
-// }
- Path info = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "info.json");
- String json = hc.getInfoJSON();
- Files.writeString(info, json);
-
- Path devices = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "devices.json");
- json = hc.getDevicesJSON();
- Files.writeString(devices, json);
-
- Path locomotives = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "locomotives.json");
- json = hc.getLocomotivesJSON();
- Files.writeString(locomotives, json);
-
- Path accessories = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "mags.json");
- json = hc.getAccessoriesJSON();
- Files.writeString(accessories, json);
-
- Path fcticons = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "fcticons.json");
- json = hc.getFunctionsSvgJSON();
- Files.writeString(fcticons, json);
-
- Path magicons = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "magicons.json");
- json = hc.getAccessoriesSvgJSON();
- Files.writeString(magicons, json);
-
- Path accessoryFile = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "magnetartikel.cs2");
- String file = hc.getAccessoriesFile();
- Logger.trace(file);
- Files.writeString(accessoryFile, file);
-
+ @Override
+ public void close() throws Exception {
}
+
}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/net/CSHTTPConnectionVirt.java b/src/main/java/jcs/commandStation/marklin/cs/net/CSHTTPConnectionVirt.java
new file mode 100644
index 00000000..e18a0080
--- /dev/null
+++ b/src/main/java/jcs/commandStation/marklin/cs/net/CSHTTPConnectionVirt.java
@@ -0,0 +1,591 @@
+/*
+ * Copyright 2025 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.marklin.cs.net;
+
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+import javax.imageio.IIOException;
+import javax.imageio.ImageIO;
+import org.tinylog.Logger;
+
+/**
+ *
+ * Virtual HTTP Connection for Simulator mode
+ */
+public class CSHTTPConnectionVirt implements CSHTTPConnection {
+
+ private final InetAddress csAddress;
+ private boolean cs3;
+
+ private final static String HTTP = "http://";
+ private final static String CONFIG = "/config/";
+ private final static String LOCOMOTIVE = "lokomotive.cs2";
+ private final static String LOCOMOTIVE_JSON = "/app/api/loks";
+
+ private final static String MAGNETARTIKEL = "magnetartikel.cs2";
+ private final static String ACCESSORIES_URL = "/app/api/mags";
+
+ //private final static String DEVICE = "geraet.vrs";
+ private final static String IMAGE_FOLDER_CS3 = "/app/assets/lok/";
+ private final static String IMAGE_FOLDER_CS2 = "/icons/";
+ private final static String FUNCTION_IMAGE_FOLDER = "/fcticons/";
+
+ private final static String FUNCTION_SVG_URL = "/images/svgSprites/fcticons.json";
+ private final static String ACCESSORIES_SVG_URL = "/images/svgSprites/magicons.json";
+
+ //private final static String CS3_INFO_JSON = "/app/api/info";
+ //private final static String DEVICES = "/app/api/devs";
+ public CSHTTPConnectionVirt() throws UnknownHostException {
+ this(InetAddress.getLocalHost());
+ }
+
+ public CSHTTPConnectionVirt(InetAddress csAddress) {
+ this.csAddress = csAddress;
+ //Assume a CS2
+ this.cs3 = false;
+ }
+
+ @Override
+ public boolean isConnected() {
+ return csAddress != null && csAddress.getHostAddress() != null;
+ }
+
+ @Override
+ public void setCs3(boolean cs3) {
+ this.cs3 = cs3;
+ Logger.trace("Changed Connection settings for a " + (cs3 ? "CS3" : "CS2"));
+ }
+
+ private static String fixURL(String url) {
+ return url.replace(" ", "%20");
+ }
+
+ @Override
+ public String getLocomotivesFile() {
+ StringBuilder locs = new StringBuilder();
+ try {
+ URL cs = URI.create(HTTP + csAddress.getHostAddress() + CONFIG + LOCOMOTIVE).toURL();
+ URLConnection lc = cs.openConnection();
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
+ String inputLine;
+ while ((inputLine = in.readLine()) != null) {
+ locs.append(inputLine.strip());
+ locs.append("\n");
+ }
+ }
+ } catch (MalformedURLException ex) {
+ Logger.error(ex);
+ } catch (IOException ex) {
+ Logger.error(ex);
+ }
+ return locs.toString();
+ }
+
+ @Override
+ public String getLocomotivesJSON() {
+ StringBuilder loks = new StringBuilder();
+ try {
+ URL url = URI.create(HTTP + csAddress.getHostAddress() + LOCOMOTIVE_JSON).toURL();
+ URLConnection lc = url.openConnection();
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
+ String inputLine;
+ while ((inputLine = in.readLine()) != null) {
+ loks.append(inputLine.strip());
+ loks.append("\n");
+ }
+ }
+ } catch (MalformedURLException ex) {
+ Logger.error(ex);
+ } catch (IOException ex) {
+ Logger.error(ex);
+ }
+ return loks.toString();
+ }
+
+ @Override
+ public String getAccessoriesFile() {
+ StringBuilder locs = new StringBuilder();
+ try {
+ URL url = URI.create(HTTP + csAddress.getHostAddress() + CONFIG + MAGNETARTIKEL).toURL();
+ URLConnection lc = url.openConnection();
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
+ String inputLine;
+ while ((inputLine = in.readLine()) != null) {
+ locs.append(inputLine.strip());
+ locs.append("\n");
+ }
+ }
+ } catch (MalformedURLException ex) {
+ Logger.error(ex);
+ } catch (IOException ex) {
+ Logger.error(ex);
+ }
+ return locs.toString();
+ }
+
+ @Override
+ public String getFunctionsSvgJSON() {
+ StringBuilder json = new StringBuilder();
+ try {
+ URL url = URI.create(HTTP + csAddress.getHostAddress() + FUNCTION_SVG_URL).toURL();
+ URLConnection lc = url.openConnection();
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
+ String inputLine;
+ while ((inputLine = in.readLine()) != null) {
+ json.append(inputLine.strip());
+ json.append("\n");
+ }
+ }
+ } catch (MalformedURLException ex) {
+ Logger.error(ex);
+ } catch (IOException ex) {
+ Logger.error(ex);
+ }
+ return json.toString();
+ }
+
+ @Override
+ public String getAccessoriesSvgJSON() {
+ StringBuilder json = new StringBuilder();
+ try {
+ URL url = URI.create(HTTP + csAddress.getHostAddress() + ACCESSORIES_SVG_URL).toURL();
+ URLConnection lc = url.openConnection();
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
+ String inputLine;
+ while ((inputLine = in.readLine()) != null) {
+ json.append(inputLine.strip());
+ json.append("\n");
+ }
+ }
+ } catch (MalformedURLException ex) {
+ Logger.error(ex);
+ } catch (IOException ex) {
+ Logger.error(ex);
+ }
+ return json.toString();
+ }
+
+ @Override
+ public String getAccessoriesJSON() {
+ StringBuilder json = new StringBuilder();
+ try {
+ URL url = URI.create(HTTP + csAddress.getHostAddress() + ACCESSORIES_URL).toURL();
+ URLConnection lc = url.openConnection();
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(lc.getInputStream()))) {
+ String inputLine;
+ while ((inputLine = in.readLine()) != null) {
+ json.append(inputLine.strip());
+ json.append("\n");
+ }
+ }
+ } catch (MalformedURLException ex) {
+ Logger.error(ex);
+ } catch (IOException ex) {
+ Logger.error(ex);
+ }
+ return json.toString();
+ }
+
+ @Override
+ public String getDevicesJSON() {
+ StringBuilder json = new StringBuilder();
+ json.append("{");
+ json.append("\"0x8\": {");
+ json.append("\"_uid\": \"0x8\",");
+ json.append("\"_name\": \"USB 0\",");
+ json.append("\"_kennung\": \"0xffff\",");
+ json.append("\"_typ\": \"65280\",");
+ json.append("\"_queryInterval\": \"5\",");
+ json.append("\"_version\": {");
+ json.append("\"major\": \"16\"");
+ json.append("},");
+ json.append("\"_kanal\": [],");
+ json.append("\"_ready\": true,");
+ json.append("\"path\": \"/media/usb0\",");
+ json.append("\"isPresent\": true,");
+ json.append("\"isMounted\": true");
+ json.append("},");
+ json.append("\"0x9\": {");
+ json.append("\"_uid\": \"0x9\",");
+ json.append("\"_name\": \"USB 1\",");
+ json.append("\"_kennung\": \"0xffff\",");
+ json.append("\"_typ\": \"65280\",");
+ json.append("\"_queryInterval\": \"5\",");
+ json.append("\"_version\": {");
+ json.append("\"major\": \"77\"");
+ json.append("},");
+ json.append("\"_kanal\": [],");
+ json.append("\"_ready\": true,");
+ json.append("\"path\": \"/media/usb1\",");
+ json.append("\"isPresent\": true,");
+ json.append("\"isMounted\": true");
+ json.append("},");
+ json.append("\"0x53385c41\": {");
+ json.append("\"_uid\": \"0x53385c41\",");
+ json.append("\"_name\": \"LinkS88-1\",");
+ json.append("\"_typname\": \"Link S88\",");
+ json.append("\"_kennung\": \"0x41\",");
+ json.append("\"_typ\": \"64\",");
+ json.append("\"_artikelnr\": \"60883\",");
+ json.append("\"_seriennr\": \"9281\",");
+ json.append("\"_queryInterval\": \"5\",");
+ json.append("\"_version\": {");
+ json.append("\"major\": \"1\",");
+ json.append("\"minor\": \"1\"");
+ json.append("},");
+ json.append("\"_kanal\": [");
+ json.append("{");
+ json.append("\"endWert\": \"8\",");
+ json.append("\"max\": \"8\",");
+ json.append("\"name\": \"Spalten Tastatur\",");
+ json.append("\"nr\": \"11\",");
+ json.append("\"startWert\": \"0\",");
+ json.append("\"typ\": \"2\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"endWert\": \"15\",");
+ json.append("\"max\": \"15\",");
+ json.append("\"name\": \"Zeilen Tastatur\",");
+ json.append("\"nr\": \"12\",");
+ json.append("\"startWert\": \"0\",");
+ json.append("\"typ\": \"2\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"auswahl\": \"Einzeln:Tastaturmatrix\",");
+ json.append("\"name\": \"Auswertung 1 - 16\",");
+ json.append("\"nr\": \"1\",");
+ json.append("\"typ\": \"1\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"endWert\": \"31\",");
+ json.append("\"max\": \"31\",");
+ json.append("\"name\": \"Länge Bus 1 (RJ45-1)\",");
+ json.append("\"nr\": \"2\",");
+ json.append("\"startWert\": \"0\",");
+ json.append("\"typ\": \"2\",");
+ json.append("\"wert\": \"1\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"endWert\": \"31\",");
+ json.append("\"max\": \"31\",");
+ json.append("\"name\": \"Länge Bus 2 (RJ45-2)\",");
+ json.append("\"nr\": \"3\",");
+ json.append("\"startWert\": \"0\",");
+ json.append("\"typ\": \"2\",");
+ json.append("\"wert\": \"2\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"endWert\": \"31\",");
+ json.append("\"max\": \"31\",");
+ json.append("\"name\": \"Länge Bus 3 (6-Polig)\",");
+ json.append("\"nr\": \"4\",");
+ json.append("\"startWert\": \"0\",");
+ json.append("\"typ\": \"2\",");
+ json.append("\"wert\": \"1\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"einheit\": \"ms\",");
+ json.append("\"endWert\": \"1000\",");
+ json.append("\"max\": \"1000\",");
+ json.append("\"min\": \"10\",");
+ json.append("\"name\": \"Zykluszeit Bus 1 (RJ45-1)\",");
+ json.append("\"nr\": \"5\",");
+ json.append("\"startWert\": \"10\",");
+ json.append("\"typ\": \"2\",");
+ json.append("\"wert\": \"100\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"einheit\": \"ms\",");
+ json.append("\"endWert\": \"1000\",");
+ json.append("\"max\": \"1000\",");
+ json.append("\"min\": \"10\",");
+ json.append("\"name\": \"Zykluszeit Bus 2 (RJ45-2)\",");
+ json.append("\"nr\": \"6\",");
+ json.append("\"startWert\": \"10\",");
+ json.append("\"typ\": \"2\",");
+ json.append("\"wert\": \"100\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"einheit\": \"ms\",");
+ json.append("\"endWert\": \"1000\",");
+ json.append("\"max\": \"1000\",");
+ json.append("\"min\": \"10\",");
+ json.append("\"name\": \"Zykluszeit Bus 3 (6-Polig)\",");
+ json.append("\"nr\": \"7\",");
+ json.append("\"startWert\": \"10\",");
+ json.append("\"typ\": \"2\",");
+ json.append("\"wert\": \"100\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"einheit\": \"µs\",");
+ json.append("\"endWert\": \"1000\",");
+ json.append("\"max\": \"1000\",");
+ json.append("\"min\": \"100\",");
+ json.append("\"name\": \"Bitzeit S88\",");
+ json.append("\"nr\": \"8\",");
+ json.append("\"startWert\": \"100\",");
+ json.append("\"typ\": \"2\",");
+ json.append("\"wert\": \"167\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"einheit\": \"ms\",");
+ json.append("\"endWert\": \"1000\",");
+ json.append("\"max\": \"1000\",");
+ json.append("\"min\": \"10\",");
+ json.append("\"name\": \"Zykluszeit 1 - 16\",");
+ json.append("\"nr\": \"9\",");
+ json.append("\"startWert\": \"10\",");
+ json.append("\"typ\": \"2\",");
+ json.append("\"wert\": \"100\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"einheit\": \"ms\",");
+ json.append("\"endWert\": \"100\",");
+ json.append("\"max\": \"100\",");
+ json.append("\"min\": \"10\",");
+ json.append("\"name\": \"Zykluszeit Tastatur\",");
+ json.append("\"nr\": \"10\",");
+ json.append("\"startWert\": \"10\",");
+ json.append("\"typ\": \"2\",");
+ json.append("\"wert\": \"37\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("}");
+ json.append("],");
+ json.append("\"_ready\": false,");
+ json.append("\"isPresent\": true,");
+ json.append("\"present\": \"1\",");
+ json.append("\"config\": \"12;ok\"");
+ json.append("},");
+ json.append("\"0x6373458c\": {");
+ json.append("\"_uid\": \"0x6373458c\",");
+ json.append("\"_name\": \"GFP3-1\",");
+ json.append("\"_typname\": \"Central Station 3\",");
+ json.append("\"_kennung\": \"0xffff\",");
+ json.append("\"_typ\": \"80\",");
+ json.append("\"_artikelnr\": \"60226\",");
+ json.append("\"_seriennr\": \"0000\",");
+ json.append("\"_queryInterval\": \"5\",");
+ json.append("\"_version\": {");
+ json.append("\"major\": \"12\",");
+ json.append("\"minor\": \"113\"");
+ json.append("},");
+ json.append("\"_kanal\": [");
+ json.append("{");
+ json.append("\"einheit\": \"A\",");
+ json.append("\"endWert\": \"5.50\",");
+ json.append("\"farbeGelb\": \"240\",");
+ json.append("\"farbeGruen\": \"48\",");
+ json.append("\"farbeMax\": \"192\",");
+ json.append("\"farbeRot\": \"224\",");
+ json.append("\"name\": \"MAIN\",");
+ json.append("\"nr\": \"1\",");
+ json.append("\"potenz\": \"253\",");
+ json.append("\"rangeGelb\": \"576\",");
+ json.append("\"rangeGruen\": \"552\",");
+ json.append("\"rangeMax\": \"660\",");
+ json.append("\"rangeRot\": \"600\",");
+ json.append("\"startWert\": \"0.00\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"einheit\": \"A\",");
+ json.append("\"endWert\": \"2.30\",");
+ json.append("\"farbeGelb\": \"240\",");
+ json.append("\"farbeGruen\": \"48\",");
+ json.append("\"farbeMax\": \"192\",");
+ json.append("\"farbeRot\": \"224\",");
+ json.append("\"name\": \"PROG\",");
+ json.append("\"nr\": \"2\",");
+ json.append("\"potenz\": \"253\",");
+ json.append("\"rangeGelb\": \"363\",");
+ json.append("\"rangeGruen\": \"330\",");
+ json.append("\"rangeMax\": \"759\",");
+ json.append("\"rangeRot\": \"561\",");
+ json.append("\"startWert\": \"0.00\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"einheit\": \"C\",");
+ json.append("\"endWert\": \"80.0\",");
+ json.append("\"farbeGelb\": \"8\",");
+ json.append("\"farbeGruen\": \"12\",");
+ json.append("\"farbeMax\": \"192\",");
+ json.append("\"farbeRot\": \"240\",");
+ json.append("\"name\": \"TEMP\",");
+ json.append("\"nr\": \"4\",");
+ json.append("\"rangeGelb\": \"145\",");
+ json.append("\"rangeGruen\": \"121\",");
+ json.append("\"rangeMax\": \"193\",");
+ json.append("\"rangeRot\": \"169\",");
+ json.append("\"startWert\": \"0.0\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("},");
+ json.append("{");
+ json.append("\"auswahl\": \"60061:60101:L51095\",");
+ json.append("\"index\": \"1\",");
+ json.append("\"name\": \"Netzteil:\",");
+ json.append("\"nr\": \"1\",");
+ json.append("\"typ\": \"1\",");
+ json.append("\"valueHuman\": \"0\"");
+ json.append("}");
+ json.append("],");
+ json.append("\"_ready\": true,");
+ json.append("\"isPresent\": true");
+ json.append("},");
+ json.append("\"0x6373458d\": {");
+ json.append("\"_uid\": \"0x6373458d\",");
+ json.append("\"_name\": \"CS3 0000\",");
+ json.append("\"_typ\": \"65504\",");
+ json.append("\"_kanal\": [],");
+ json.append("\"_ready\": true,");
+ json.append("\"ip\": \"127.0.0.1\",");
+ json.append("\"isPresent\": true");
+ json.append("},");
+ json.append("}");
+
+ return json.toString();
+ }
+
+ @Override
+ public String getInfoFile() {
+ StringBuilder geraet = new StringBuilder();
+ geraet.append("[geraet]\n");
+ geraet.append("version\n");
+ geraet.append(".major=0\n");
+ geraet.append(".minor=1\n");
+ geraet.append("geraet\n");
+ geraet.append(".sernum=0000\n");
+ geraet.append(".gfpuid=6373458c\n");
+ geraet.append(".guiuid=6373458d\n");
+ geraet.append(".hardvers=HW:03.04\n");
+ geraet.append(".articleno=60226\n");
+ geraet.append(".producer=Frans Jacobs.\n");
+ geraet.append(".produkt=Virtual Central Station 3\n");
+
+ return geraet.toString();
+ }
+
+ @Override
+ public String getInfoJSON() {
+ StringBuilder json = new StringBuilder();
+ json.append("{");
+ json.append("\"softwareVersion\"");
+ json.append(":");
+ json.append("\"2.5.1 (0)\"");
+ json.append(",");
+
+ json.append("\"hardwareVersion\"");
+ json.append(":");
+ json.append("\"HW:03.04\"");
+ json.append(",");
+
+ json.append("\"serialNumber\"");
+ json.append(":");
+ json.append("\"0000\"");
+ json.append(",");
+
+ json.append("\"productName\"");
+ json.append(":");
+ json.append("\"Virtual Central Station 3\"");
+ json.append(",");
+
+ json.append("\"articleNumber\"");
+ json.append(":");
+ json.append("\"60226\"");
+ json.append(",");
+
+ json.append("\"hostname\"");
+ json.append(":");
+ json.append("\"CS3-00000\"");
+ json.append(",");
+
+ json.append("\"gfpUid\"");
+ json.append(":");
+ json.append("\"6373458c\"");
+ json.append(",");
+
+ json.append("\"guiUid\"");
+ json.append(":");
+ json.append("\"6373458d\"");
+ json.append("}");
+ return json.toString();
+ }
+
+ @Override
+ public Image getLocomotiveImage(String imageName) {
+ BufferedImage image = null;
+ try {
+ URL url;
+ if (cs3) {
+ url = URI.create(fixURL(HTTP + csAddress.getHostAddress() + IMAGE_FOLDER_CS3 + imageName + ".png")).toURL();
+ } else {
+ url = URI.create(fixURL(HTTP + csAddress.getHostAddress() + IMAGE_FOLDER_CS2 + imageName + ".png")).toURL();
+ }
+
+ Logger.trace("image URL: " + url);
+ image = ImageIO.read(url);
+ } catch (MalformedURLException ex) {
+ Logger.error(ex);
+ } catch (IOException ex) {
+ Logger.error(ex);
+ }
+ return image;
+ }
+
+ @Override
+ public Image getFunctionImageCS2(String imageName) {
+ BufferedImage image = null;
+ String iurl = fixURL(HTTP + csAddress.getHostAddress() + FUNCTION_IMAGE_FOLDER + imageName + ".png");
+
+ try {
+ URL url = URI.create(iurl).toURL();
+ image = ImageIO.read(url);
+ } catch (IIOException iio) {
+ //Image not avalable
+ Logger.warn("Image: " + iurl + " is not available");
+ } catch (MalformedURLException ex) {
+ Logger.error(ex);
+ } catch (IOException ex) {
+ Logger.error(ex);
+ }
+ return image;
+ }
+
+ @Override
+ public void close() throws Exception {
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/net/CSTCPConnection.java b/src/main/java/jcs/commandStation/marklin/cs/net/CSTCPConnection.java
new file mode 100755
index 00000000..41bcda64
--- /dev/null
+++ b/src/main/java/jcs/commandStation/marklin/cs/net/CSTCPConnection.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2023 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.marklin.cs.net;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedTransferQueue;
+import java.util.concurrent.TransferQueue;
+import jcs.commandStation.events.ConnectionEvent;
+import jcs.commandStation.marklin.cs.can.CanMessage;
+import org.tinylog.Logger;
+import jcs.commandStation.events.ConnectionEventListener;
+
+/**
+ *
+ * @author Frans Jacobs
+ */
+class CSTCPConnection implements CSConnection {
+
+ private final InetAddress centralStationAddress;
+
+ private Socket clientSocket;
+ private DataOutputStream dos;
+
+ private ClientMessageReceiver messageReceiver;
+ private final List disconnectionEventListeners;
+
+ private static final long SHORT_TIMEOUT = 1000L;
+ private static final long LONG_TIMEOUT = 5000L;
+
+ private boolean debug = false;
+
+ private final TransferQueue eventQueue;
+
+ CSTCPConnection(InetAddress csAddress) {
+ centralStationAddress = csAddress;
+ debug = System.getProperty("message.debug", "false").equalsIgnoreCase("true");
+ eventQueue = new LinkedTransferQueue<>();
+ disconnectionEventListeners = new ArrayList<>();
+ checkConnection();
+ }
+
+ private void checkConnection() {
+ try {
+ if (clientSocket == null || !clientSocket.isConnected()) {
+ clientSocket = new Socket(centralStationAddress, CSConnection.CS_RX_PORT);
+ clientSocket.setKeepAlive(true);
+ clientSocket.setTcpNoDelay(true);
+ dos = new DataOutputStream(clientSocket.getOutputStream());
+
+ messageReceiver = new ClientMessageReceiver(clientSocket);
+ messageReceiver.setDaemon(true);
+ messageReceiver.start();
+ }
+ } catch (IOException ex) {
+ this.clientSocket = null;
+ Logger.error("Can't (re)connect with Central Station " + centralStationAddress.getHostAddress() + ". Cause: " + ex.getMessage());
+ Logger.trace(ex);
+ }
+ }
+
+ @Override
+ public TransferQueue getEventQueue() {
+ return this.eventQueue;
+ }
+
+ @Override
+ public void addDisconnectionEventListener(ConnectionEventListener listener) {
+ this.disconnectionEventListeners.add(listener);
+ }
+
+ private void disconnect() {
+ messageReceiver.quit();
+ if (dos != null) {
+ try {
+ dos.close();
+ } catch (IOException ex) {
+ Logger.error("Can't close output stream. Cause: " + ex.getMessage());
+ Logger.trace(ex);
+ }
+ }
+
+ disconnectionEventListeners.clear();
+ if (clientSocket != null) {
+ try {
+ clientSocket.close();
+ } catch (IOException ex) {
+ Logger.error("Can't close socket. Cause: " + ex.getMessage());
+ Logger.trace(ex);
+ }
+ }
+ }
+
+ private class ResponseCallback {
+
+ private final CanMessage tx;
+ private boolean done = false;
+
+ ResponseCallback(final CanMessage tx) {
+ this.tx = tx;
+ }
+
+ public boolean isSubscribedfor(int command) {
+ int txCmd = this.tx.getCommand();
+ if (CanMessage.REQUEST_CONFIG_DATA == txCmd) {
+ //Special case so valid are +1 and +2
+ return txCmd == (command - 1) || txCmd == (command - 2);
+ } else {
+ return txCmd == (command - 1);
+ }
+ }
+
+ public void addResponse(CanMessage rx, int moreAvailable) {
+ this.tx.addResponse(rx);
+ this.done = moreAvailable == 0;
+ }
+
+ public boolean isResponseComplete() {
+ //Most of the messages will have just one response but there are some which have more
+ return tx.isResponseComplete() && this.done;
+ }
+ }
+
+ @Override
+ public synchronized CanMessage sendCanMessage(CanMessage message) {
+ if (message == null) {
+ Logger.warn("Message is NULL?");
+ return null;
+ }
+
+ ResponseCallback callback = null;
+
+ if (message.expectsResponse() || message.expectsLongResponse()) {
+ //Message is expecting response so lets register for response
+ callback = new ResponseCallback(message);
+ messageReceiver.registerResponseCallback(callback);
+ }
+
+ try {
+ byte[] bytes = message.getMessage();
+ //Send the message
+ dos.write(bytes);
+ dos.flush();
+ } catch (IOException ex) {
+ Logger.error(ex.getMessage());
+ }
+
+ if (CanMessage.PING_RESP != message.getCommand()) {
+ //Do not log the ping response as this message is send every 5 seconds or so as a response to the CS ping request.
+ if (debug) {
+ Logger.trace("TX: " + message);
+ }
+ }
+
+ long now = System.currentTimeMillis();
+ long start = now;
+ long timeout;
+
+ if (message.expectsLongResponse()) {
+ timeout = now + LONG_TIMEOUT;
+ } else if (message.expectsResponse()) {
+ timeout = now + SHORT_TIMEOUT;
+ } else {
+ timeout = now;
+ }
+
+ if (callback != null) {
+ //Wait for the response
+ boolean responseComplete = callback.isResponseComplete();
+
+ //When querying the CS CAN Bus sometimes the responses have a little delay. This could sometimes lead to missing responses.
+ //Therefor just wait for 10 milliseconds to be sure with queries where this has been observed...
+ if (message.getCommand() == CanMessage.STATUS_CONFIG || message.getCommand() == CanMessage.PING_REQ) {
+ pause10Millis();
+ }
+
+ while (!responseComplete && now < timeout) {
+ responseComplete = callback.isResponseComplete();
+ now = System.currentTimeMillis();
+ }
+
+ if (debug) {
+ if (responseComplete) {
+ Logger.trace("Got Response in " + (now - start) + " ms");
+ } else {
+ Logger.trace("No Response for " + message + " in " + (now - start) + " ms");
+ }
+ }
+
+ //Remove the callback
+ messageReceiver.unRegisterResponseCallback();
+ }
+
+ //Capture messages for now to be able to develop the virtual mode
+ Logger.trace("#TX: " + message + (message.isResponseMessage() ? " response msg" : ""));
+ if (!message.isResponseMessage()) {
+ if (message.getResponses().size() > 1) {
+ List responses = message.getResponses();
+ for (int i = 0; i < responses.size(); i++) {
+ Logger.trace("#RX " + i + ": " + message.getResponse(i));
+ }
+ } else {
+ Logger.trace("#RX: " + message.getResponse());
+ }
+ }
+
+ return message;
+ }
+
+ @Override
+ public void close() throws Exception {
+ disconnect();
+ }
+
+ @Override
+ public InetAddress getControllerAddress() {
+ return centralStationAddress;
+ }
+
+ @Override
+ public boolean isConnected() {
+ return messageReceiver != null && messageReceiver.isRunning();
+ }
+
+ private void pause10Millis() {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException ex) {
+ }
+ }
+
+ private class ClientMessageReceiver extends Thread {
+
+ private boolean quit = true;
+ private DataInputStream din;
+
+ private ResponseCallback callBack;
+
+ public ClientMessageReceiver(Socket socket) {
+ try {
+ BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
+ din = new DataInputStream(bis);
+ } catch (IOException ex) {
+ Logger.error(ex);
+ }
+ }
+
+ void registerResponseCallback(ResponseCallback callBack) {
+ this.callBack = callBack;
+ }
+
+ void unRegisterResponseCallback() {
+ callBack = null;
+ }
+
+ synchronized void quit() {
+ quit = true;
+ }
+
+ synchronized boolean isRunning() {
+ return !quit;
+ }
+
+ @Override
+ public void run() {
+ Thread.currentThread().setName("CS-CAN-RX");
+
+ quit = false;
+ Logger.trace("Started listening on port " + clientSocket.getLocalPort() + "...");
+
+ while (isRunning()) {
+ try {
+ int prio = din.readUnsignedByte();
+ int cmd = din.readUnsignedByte();
+ int hash = din.readUnsignedShort();
+ int dlc = din.readUnsignedByte();
+ //read the data
+ int dataIdx = 0;
+ byte[] data = new byte[CanMessage.DATA_SIZE];
+ while (dataIdx < CanMessage.DATA_SIZE) {
+ data[dataIdx] = din.readByte();
+ dataIdx++;
+ }
+ CanMessage rx = new CanMessage(prio, cmd, hash, dlc, data);
+
+ //Logger.trace("RX: "+rx +"; "+ din.available());
+ if (this.callBack != null && this.callBack.isSubscribedfor(cmd)) {
+ this.callBack.addResponse(rx, din.available());
+
+ } else {
+ eventQueue.offer(rx);
+ //Logger.trace("Enqueued: " + rx + " QueueSize: " + eventQueue.size());
+ }
+
+ } catch (SocketException se) {
+ if (!quit) {
+ String msg = "Host " + centralStationAddress.getHostName();
+ ConnectionEvent de = new ConnectionEvent(msg, false);
+ for (ConnectionEventListener listener : disconnectionEventListeners) {
+ listener.onConnectionChange(de);
+ }
+ }
+
+ quit();
+ } catch (IOException ex) {
+ Logger.error(ex);
+ }
+ }
+
+ Logger.debug("Stop receiving");
+ try {
+ din.close();
+ } catch (IOException ex) {
+ Logger.error(ex);
+ }
+ }
+ }
+}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/net/CSVirtualConnection.java b/src/main/java/jcs/commandStation/marklin/cs/net/CSVirtualConnection.java
new file mode 100644
index 00000000..4802eb57
--- /dev/null
+++ b/src/main/java/jcs/commandStation/marklin/cs/net/CSVirtualConnection.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2025 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.marklin.cs.net;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.LinkedTransferQueue;
+import java.util.concurrent.TransferQueue;
+import jcs.commandStation.VirtualConnection;
+import jcs.commandStation.events.ConnectionEvent;
+import jcs.commandStation.events.SensorEvent;
+import jcs.commandStation.marklin.cs.can.CanMessage;
+import jcs.commandStation.marklin.cs.can.CanMessageFactory;
+import jcs.commandStation.marklin.cs.can.parser.SystemStatus;
+import org.tinylog.Logger;
+import jcs.commandStation.events.ConnectionEventListener;
+
+/**
+ * Virtual Marklin CS Connection
+ */
+class CSVirtualConnection implements CSConnection, VirtualConnection {
+
+ final static int LINK_S88_UID = 1396202561;
+
+ private boolean connected;
+
+ private final InetAddress centralStationAddress;
+
+ private PeriodicCSMessageSender periodicMessageSender;
+ private final List disconnectionEventListeners;
+
+ private final TransferQueue eventQueue;
+
+ CSVirtualConnection(InetAddress csAddress) {
+ centralStationAddress = csAddress;
+ eventQueue = new LinkedTransferQueue<>();
+ //disconnectionEventListeners = new ArrayList<>();
+ disconnectionEventListeners = Collections.synchronizedList(new ArrayList<>());
+ this.connected = true;
+
+ //initMessageSender();
+ }
+
+ @Override
+ public void sendEvent(SensorEvent sensorEvent) {
+ int deviceId = sensorEvent.getDeviceId();
+ int contactId = sensorEvent.getContactId();
+ int value = sensorEvent.isActive() ? 1 : 0;
+ int prevVal;
+ if (sensorEvent.isChanged() && sensorEvent.isActive()) {
+ prevVal = 1;
+ } else {
+ prevVal = 0;
+ }
+
+ Logger.trace("Device: " + deviceId + " contact: " + contactId + " -> " + value);
+
+ CanMessage eventMessage = CanMessageFactory.sensorEventMessage(deviceId, contactId, value, prevVal, 100, LINK_S88_UID);
+
+ try {
+ eventQueue.put(eventMessage);
+ } catch (InterruptedException ex) {
+ Logger.error(ex);
+ }
+
+ }
+
+ private void initMessageSender() {
+ periodicMessageSender = new PeriodicCSMessageSender();
+ periodicMessageSender.start();
+ //periodicMessageSender.setDaemon(true);
+ }
+
+ @Override
+ public TransferQueue getEventQueue() {
+ return this.eventQueue;
+ }
+
+ @Override
+ public void addDisconnectionEventListener(ConnectionEventListener listener) {
+ this.disconnectionEventListeners.add(listener);
+ }
+
+ private void disconnect() {
+ this.connected = false;
+ if (periodicMessageSender != null) {
+ periodicMessageSender.quit();
+ }
+ disconnectionEventListeners.clear();
+ }
+
+ @Override
+ public synchronized CanMessage sendCanMessage(CanMessage message) {
+ if (message == null) {
+ Logger.warn("Message is NULL?");
+ return null;
+ }
+
+ int command = message.getCommand();
+ int dlc = message.getDlc();
+ int uid = message.getDeviceUidNumberFromMessage();
+ int subcmd = message.getSubCommand();
+
+ Logger.trace("TX: " + message);
+
+ switch (command) {
+ case CanMessage.SYSTEM_COMMAND -> {
+ switch (subcmd) {
+ case CanMessage.STOP_SUB_CMD -> {
+ if (dlc == 4) {
+ //Power status Query, reply with On
+ message.addResponse(CanMessage.parse("0x00 0x01 0x03 0x26 0x05 0x63 0x73 0x45 0x8c 0x01 0x00 0x00 0x00"));
+ message.addResponse(CanMessage.parse("0x00 0x01 0x03 0x26 0x05 0x00 0x00 0x00 0x00 0x01 0x00 0x00 0x00"));
+ } else if (dlc == 5) {
+ if (SystemStatus.parseSystemPowerMessage(message)) {
+ message.addResponse(CanMessage.parse("0x00 0x01 0x03 0x26 0x05 0x63 0x73 0x45 0x8c 0x01 0x00 0x00 0x00"));
+ message.addResponse(CanMessage.parse("0x00 0x01 0x03 0x26 0x05 0x00 0x00 0x00 0x00 0x01 0x00 0x00 0x00"));
+ } else {
+ //Switch Power Off
+ message.addResponse(CanMessage.parse("0x00 0x01 0x03 0x26 0x05 0x63 0x73 0x45 0x8c 0x00 0x00 0x00 0x00"));
+ message.addResponse(CanMessage.parse("0x00 0x01 0x03 0x26 0x05 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00"));
+ }
+ }
+ }
+ case CanMessage.GO_SUB_CMD -> {
+ }
+ case CanMessage.HALT_SUB_CMD -> {
+ }
+ case CanMessage.LOC_STOP_SUB_CMD -> {
+ }
+ case CanMessage.OVERLOAD_SUB_CMD -> {
+ }
+ }
+
+ }
+ case CanMessage.PING_REQ -> {
+ //Lets do this the when we know all of the CS...
+// if (mainDevice != null) {
+// if (CanMessage.DLC_0 == dlc) {
+// Logger.trace("Ping RQ: " + eventMessage);
+// sendJCSUIDMessage();
+// }
+// }
+ }
+ case CanMessage.PING_RESP -> {
+// if (CanMessage.DLC_8 == dlc) {
+// Logger.trace("Ping Response RX: " + eventMessage);
+//
+// updateDevice(eventMessage);
+// }
+ }
+ case CanMessage.STATUS_CONFIG -> {
+// if (CanMessage.JCS_UID == uid && CanMessage.DLC_5 == dlc) {
+// Logger.trace("StatusConfig RQ: " + eventMessage);
+// sentJCSInformationMessage();
+// }
+ }
+ case CanMessage.STATUS_CONFIG_RESP -> {
+ }
+ case CanMessage.S88_EVENT_RESPONSE -> {
+// if (CanMessage.DLC_8 == dlc) {
+// SensorBean sb = SensorMessageParser.parseMessage(eventMessage, new Date());
+// SensorEvent sme = new SensorEvent(sb);
+// if (sme.getSensorBean() != null) {
+// fireSensorEventListeners(sme);
+// }
+// }
+
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x00 0x01 0x00 0x01 0xe5 0x10
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x00 0x01 0x01 0x00 0x00 0x32
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x00 0x02 0x00 0x01 0xab 0x68
+//
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x00 0x02 0x00 0x01 0xab 0x68 0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x00 0x0f 0x00 0x01 0xff 0xff
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x00 0x0f 0x01 0x00 0x01 0x90
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x00 0x10 0x00 0x01 0xe3 0x3a
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x00 0x10 0x01 0x00 0x00 0x8c
+//
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x07 0xd1 0x01 0x00 0x00 0xb4
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x07 0xe0 0x00 0x01 0xdc 0xf0
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x07 0xe0 0x01 0x00 0x00 0x50
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x03 0xee 0x00 0x01 0x40 0xf6
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x03 0xee 0x01 0x00 0x00 0x0a
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x03 0xf0 0x00 0x01 0x42 0x5d
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x03 0xf0 0x01 0x00 0x00 0x0a
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x03 0xf0 0x00 0x01 0x00 0x82
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x03 0xf0 0x01 0x00 0x00 0x0a
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x03 0xf0 0x00 0x01 0x00 0x46
+//0x00 0x23 0x7b 0x79 0x08 0x00 0x41 0x03 0xf0 0x01 0x00 0x00 0x0a
+ }
+ case CanMessage.SYSTEM_COMMAND_RESP -> {
+ }
+ case CanMessage.ACCESSORY_SWITCHING -> {
+ message.addResponse(CanMessage.parse(message.toString(), true));
+ }
+ case CanMessage.ACCESSORY_SWITCHING_RESP -> {
+//DCC Accessories
+//RX: 0x00 0x16 0x37 0x7e 0x06 0x00 0x00 0x38 0x00 0x00 0x01 0x00 0x00
+//RX: 0x00 0x16 0x37 0x7e 0x06 0x00 0x00 0x38 0x00 0x00 0x00 0x00 0x00
+//RX: 0x00 0x16 0x37 0x7e 0x06 0x00 0x00 0x38 0x00 0x01 0x01 0x00 0x00
+//RX: 0x00 0x16 0x37 0x7e 0x06 0x00 0x00 0x38 0x00 0x01 0x00 0x00 0x00
+//RX: 0x00 0x16 0x37 0x7e 0x06 0x00 0x00 0x38 0x01 0x00 0x01 0x00 0x00
+//RX: 0x00 0x16 0x37 0x7e 0x06 0x00 0x00 0x38 0x01 0x00 0x00 0x00 0x00
+//RX: 0x00 0x16 0x37 0x7e 0x06 0x00 0x00 0x38 0x01 0x01 0x01 0x00 0x00
+//RX: 0x00 0x16 0x37 0x7e 0x06 0x00 0x00 0x38 0x01 0x01 0x00 0x00 0x00
+
+ }
+ case CanMessage.LOC_VELOCITY -> {
+ message.addResponse(CanMessage.parse(message.toString(), true));
+ }
+ case CanMessage.LOC_VELOCITY_RESP -> {
+ }
+ case CanMessage.LOC_DIRECTION -> {
+ message.addResponse(CanMessage.parse(message.toString(), true));
+ }
+ case CanMessage.LOC_DIRECTION_RESP -> {
+ }
+ case CanMessage.LOC_FUNCTION -> {
+ message.addResponse(CanMessage.parse(message.toString(), true));
+ }
+ case CanMessage.LOC_FUNCTION_RESP -> {
+ }
+ default -> {
+ }
+ }
+ return message;
+ }
+
+ @Override
+ public void close() throws Exception {
+ disconnect();
+ }
+
+ @Override
+ public InetAddress getControllerAddress() {
+ return centralStationAddress;
+ }
+
+ @Override
+ public boolean isConnected() {
+ return this.connected;
+ }
+
+ private class PeriodicCSMessageSender extends Thread {
+
+ private boolean quit = true;
+
+ public PeriodicCSMessageSender() {
+ }
+
+ synchronized void quit() {
+ quit = true;
+ }
+
+ synchronized boolean isRunning() {
+ return !quit;
+ }
+
+ @Override
+ public void run() {
+ Thread.currentThread().setName("CS-VIRT-CAN-TX");
+
+ quit = false;
+ Logger.trace("Started sending periodic messages...");
+ while (isRunning()) {
+ //Send a ping requests once and a while
+ CanMessage ping = CanMessage.parse("0x00 0x30 0x37 0x7e 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00");
+ eventQueue.offer(ping);
+ Logger.trace("Enqueued: " + ping + " QueueSize: " + eventQueue.size());
+
+ synchronized (this) {
+ try {
+ wait(5000);
+ } catch (InterruptedException ex) {
+ Logger.trace(ex);
+ }
+ }
+ }
+
+ String msg = "Host " + centralStationAddress.getHostName();
+ ConnectionEvent de = new ConnectionEvent(msg, false);
+ for (ConnectionEventListener listener : disconnectionEventListeners) {
+ listener.onConnectionChange(de);
+ }
+
+ Logger.trace("Stop sending");
+
+ }
+ }
+}
diff --git a/src/main/java/jcs/commandStation/marklin/cs/net/TCPConnection.java b/src/main/java/jcs/commandStation/marklin/cs/net/TCPConnection.java
deleted file mode 100755
index 9a3a8a0c..00000000
--- a/src/main/java/jcs/commandStation/marklin/cs/net/TCPConnection.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * Copyright 2023 Frans Jacobs.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jcs.commandStation.marklin.cs.net;
-
-import java.io.BufferedInputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.SocketException;
-import jcs.commandStation.marklin.cs.can.CanMessage;
-import org.tinylog.Logger;
-import jcs.commandStation.marklin.cs.events.CanPingListener;
-import jcs.commandStation.marklin.cs.events.AccessoryListener;
-import jcs.commandStation.marklin.cs.events.FeedbackListener;
-import jcs.commandStation.marklin.cs.events.LocomotiveListener;
-import jcs.commandStation.marklin.cs.events.SystemListener;
-
-/**
- *
- * @author Frans Jacobs
- */
-class TCPConnection implements CSConnection {
-
- private final InetAddress centralStationAddress;
-
- private Socket clientSocket;
- private DataOutputStream dos;
-
- private ClientMessageReceiver messageReceiver;
-
- private static final long SHORT_TIMEOUT = 1000L;
- private static final long LONG_TIMEOUT = 5000L;
-
- private boolean debug = false;
-
- TCPConnection(InetAddress csAddress) {
- centralStationAddress = csAddress;
- debug = System.getProperty("message.debug", "false").equalsIgnoreCase("true");
- checkConnection();
- }
-
- private void checkConnection() {
- try {
- if (clientSocket == null || !clientSocket.isConnected()) {
- clientSocket = new Socket(centralStationAddress, CSConnection.CS_RX_PORT);
- clientSocket.setKeepAlive(true);
- clientSocket.setTcpNoDelay(true);
- dos = new DataOutputStream(clientSocket.getOutputStream());
-
- messageReceiver = new ClientMessageReceiver(clientSocket);
- messageReceiver.setDaemon(true);
- messageReceiver.start();
- }
- } catch (IOException ex) {
- this.clientSocket = null;
- Logger.error("Can't (re)connect with Central Station " + centralStationAddress.getHostAddress() + ". Cause: " + ex.getMessage());
- Logger.trace(ex);
- }
- }
-
- private void disconnect() {
- this.messageReceiver.quit();
- if (dos != null) {
- try {
- dos.close();
- } catch (IOException ex) {
- Logger.error("Can't close output stream. Cause: " + ex.getMessage());
- Logger.trace(ex);
- }
- }
- if (clientSocket != null) {
- try {
- clientSocket.close();
- } catch (IOException ex) {
- Logger.error("Can't close socket. Cause: " + ex.getMessage());
- Logger.trace(ex);
- }
- }
- }
-
- private class ResponseCallback {
-
- private final CanMessage tx;
- private boolean done = false;
-
- ResponseCallback(final CanMessage tx) {
- this.tx = tx;
- }
-
- public boolean isSubscribedfor(int command) {
- int txCmd = this.tx.getCommand();
- if (CanMessage.REQUEST_CONFIG_DATA == txCmd) {
- //Special case so valid are +1 and +2
- return txCmd == (command - 1) || txCmd == (command - 2);
- } else {
- return txCmd == (command - 1);
- }
- }
-
- public void addResponse(CanMessage rx, int moreAvailable) {
- this.tx.addResponse(rx);
- this.done = moreAvailable == 0;
- }
-
- public boolean isResponseComplete() {
- //Most of the messages will have just one response but there are some which have more
- return tx.isResponseComplete() && this.done;
- }
- }
-
- @Override
- public synchronized CanMessage sendCanMessage(CanMessage message) {
- ResponseCallback callback = null;
-
- if (message != null) {
- if (message.expectsResponse() || message.expectsLongResponse()) {
- //Message is expecting response so lets register for response
- callback = new ResponseCallback(message);
- this.messageReceiver.registerResponseCallback(callback);
- }
-
- try {
- byte[] bytes = message.getMessage();
- //Send the message
- dos.write(bytes);
- dos.flush();
- } catch (IOException ex) {
- Logger.error(ex);
- }
-
- if (CanMessage.PING_RESP != message.getCommand()) {
- //Do not log the ping response as this message is send every 5 seconds or so as a response to the CS ping request.
- if (debug) {
- Logger.trace("TX: " + message);
- }
- }
-
- long now = System.currentTimeMillis();
- long start = now;
- long timeout;
-
- if (message.expectsLongResponse()) {
- timeout = now + LONG_TIMEOUT;
- } else if (message.expectsResponse()) {
- timeout = now + SHORT_TIMEOUT;
- } else {
- timeout = now;
- }
-
- if (callback != null) {
- //Wait for the response
- boolean responseComplete = callback.isResponseComplete();
- while (!responseComplete && now < timeout) {
- responseComplete = callback.isResponseComplete();
- now = System.currentTimeMillis();
- }
-
- if (debug) {
- if (responseComplete) {
- Logger.trace("Got Response in " + (now - start) + " ms");
- } else {
- Logger.trace("No Response for " + message + " in " + (now - start) + " ms");
- }
- }
-
- //Remove the callback
- this.messageReceiver.unRegisterResponseCallback();
- }
- }
- return message;
- }
-
- @Override
- public void setCanPingListener(CanPingListener pingListener) {
- if (messageReceiver != null) {
- this.messageReceiver.registerCanPingListener(pingListener);
- }
- }
-
- @Override
- public void setFeedbackListener(FeedbackListener feedbackListener) {
- if (messageReceiver != null) {
- this.messageReceiver.registerFeedbackListener(feedbackListener);
- }
- }
-
- @Override
- public void setSystemListener(SystemListener systemEventListener) {
- if (messageReceiver != null) {
- this.messageReceiver.registerSystemListener(systemEventListener);
- }
- }
-
- @Override
- public void setAccessoryListener(AccessoryListener accessoryEventListener) {
- if (messageReceiver != null) {
- this.messageReceiver.registerAccessoryListener(accessoryEventListener);
- }
- }
-
- @Override
- public void setLocomotiveListener(LocomotiveListener locomotiveListener) {
- if (messageReceiver != null) {
- this.messageReceiver.registerLocomotiveListener(locomotiveListener);
- }
- }
-
- @Override
- public void close() throws Exception {
- disconnect();
- }
-
- @Override
- public InetAddress getControllerAddress() {
- return centralStationAddress;
- }
-
- @Override
- public boolean isConnected() {
- return this.messageReceiver != null && this.messageReceiver.isRunning();
- }
-
- private class ClientMessageReceiver extends Thread {
-
- private boolean quit = true;
- private DataInputStream din;
-
- private CanPingListener pingListener;
- private FeedbackListener feedbackListener;
- private SystemListener systemListener;
- private AccessoryListener accessoryListener;
- private LocomotiveListener locomotiveListener;
-
- private ResponseCallback callBack;
-
- public ClientMessageReceiver(Socket socket) {
- try {
- BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
- din = new DataInputStream(bis);
- } catch (IOException ex) {
- Logger.error(ex);
- }
- }
-
- void registerResponseCallback(ResponseCallback callBack) {
- this.callBack = callBack;
- }
-
- void unRegisterResponseCallback() {
- this.callBack = null;
- }
-
- void registerCanPingListener(CanPingListener pingListener) {
- this.pingListener = pingListener;
- }
-
- void registerFeedbackListener(FeedbackListener feedbackListener) {
- this.feedbackListener = feedbackListener;
- }
-
- void registerSystemListener(SystemListener systemListener) {
- this.systemListener = systemListener;
- }
-
- void registerAccessoryListener(AccessoryListener accessoryListener) {
- this.accessoryListener = accessoryListener;
- }
-
- void registerLocomotiveListener(LocomotiveListener locomotiveListener) {
- this.locomotiveListener = locomotiveListener;
- }
-
- synchronized void quit() {
- this.quit = true;
- }
-
- synchronized boolean isRunning() {
- return !this.quit;
- }
-
- @Override
- public void run() {
- Thread.currentThread().setName("CAN-RX");
-
- this.quit = false;
- Logger.trace("Started listening on port " + clientSocket.getLocalPort() + "...");
-
- while (isRunning()) {
- try {
- int prio = din.readUnsignedByte();
- int cmd = din.readUnsignedByte();
- int hash = din.readUnsignedShort();
- int dlc = din.readUnsignedByte();
- //read the data
- int dataIdx = 0;
- byte[] data = new byte[CanMessage.DATA_SIZE];
- while (dataIdx < CanMessage.DATA_SIZE) {
- data[dataIdx] = din.readByte();
- dataIdx++;
- }
- CanMessage rx = new CanMessage(prio, cmd, hash, dlc, data);
-
- //Logger.trace("RX: "+rx +"; "+ din.available());
- if (this.callBack != null && this.callBack.isSubscribedfor(cmd)) {
- this.callBack.addResponse(rx, din.available());
- } else if (rx.isPingResponse() && pingListener != null) {
- this.pingListener.onCanPingResponseMessage(rx);
- } else if (rx.isPingRequest() && pingListener != null) {
- this.pingListener.onCanPingRequestMessage(rx);
- } else if (rx.isStatusConfigRequest() && pingListener != null) {
- this.pingListener.onCanStatusConfigRequestMessage(rx);
- } else if (rx.isSensorResponse() && feedbackListener != null) {
- this.feedbackListener.onFeedbackMessage(rx);
- } else if (rx.isSystemMessage() && systemListener != null) {
- this.systemListener.onSystemMessage(rx);
- } else if (rx.isAccessoryMessage() && accessoryListener != null) {
- this.accessoryListener.onAccessoryMessage(rx);
- } else if (rx.isLocomotiveMessage() && locomotiveListener != null) {
- this.locomotiveListener.onLocomotiveMessage(rx);
- } else {
- if (CanMessage.BOOTLOADER_CAN != 0x36) {
- //Do not log the bootloader message. it is not used in JCS. No idea what this message is for.
- if (debug) {
- Logger.trace("#RX: " + rx);
- }
- }
- }
- } catch (SocketException se) {
- quit();
- } catch (IOException ioe) {
- Logger.error(ioe);
- }
- }
-
- Logger.debug("Stop receiving");
- try {
- din.close();
- } catch (IOException ex) {
- Logger.error(ex);
- }
- }
- }
-
-// public static void main(String[] a) throws UnknownHostException {
-// boolean cs3 = true;
-//
-// InetAddress inetAddr;
-// if (cs3) {
-// inetAddr = InetAddress.getByName("192.168.178.180");
-// } else {
-// inetAddr = InetAddress.getByName("192.168.178.86");
-// }
-//
-// int uid;
-// if (cs3) {
-// uid = 1668498828;
-// } else {
-// uid = 1129552448;
-// }
-//
-// TCPConnection c = new TCPConnection(inetAddr);
-//
-// while (!c.messageReceiver.isRunning()) {
-//
-// }
-//
-// //CanMessage m = c.sendCanMessage(CanMessageFactory.getMembersPing());
-// CanMessage m = c.sendCanMessage(CanMessageFactory.querySystem(uid));
-//
-// Logger.trace("TX: " + m);
-// for (CanMessage r : m.getResponses()) {
-// Logger.trace("RSP: " + r);
-// }
-//
-// CanMessage m2 = c.sendCanMessage(CanMessageFactory.querySystem(uid));
-//
-// Logger.trace("TX: " + m2);
-// for (CanMessage r : m2.getResponses()) {
-// Logger.trace("RSP: " + r);
-// }
-//
-// }
-
-}
diff --git a/src/main/java/jcs/commandStation/marklin/cs2/AccessoryBeanParser.java b/src/main/java/jcs/commandStation/marklin/cs2/AccessoryBeanParser.java
index 945f8576..723648ef 100755
--- a/src/main/java/jcs/commandStation/marklin/cs2/AccessoryBeanParser.java
+++ b/src/main/java/jcs/commandStation/marklin/cs2/AccessoryBeanParser.java
@@ -67,7 +67,7 @@ public static List parseAccessoryFile(String file, String command
}
ab = new AccessoryBean();
ab.setSynchronize(true);
- ab.setSource(source + ":"+MAGNETARTIKEL);
+ ab.setSource(source + ":" + MAGNETARTIKEL);
ab.setCommandStationId(commandStationId);
}
case ".name" -> {
@@ -165,7 +165,7 @@ public static List parseAccessoryJSON(String json, String command
for (int i = 0; i < aa.length(); i++) {
AccessoryBean ab = new AccessoryBean();
ab.setSynchronize(true);
- ab.setSource(source + ":"+MAGS_JSON);
+ ab.setSource(source + ":" + MAGS_JSON);
ab.setCommandStationId(commandStationId);
JSONObject ajo = aa.getJSONObject(i);
@@ -199,26 +199,25 @@ public static List parseAccessoryJSON(String json, String command
return accessories;
}
- public static void main(String[] a) throws Exception {
-
- Path accessoryFile = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "magnetartikel.cs2");
- String file = Files.readString(accessoryFile);
- List accessories = AccessoryBeanParser.parseAccessoryFile(file, "marklin.cs", "CS");
- for (AccessoryBean acc : accessories) {
- Logger.trace(acc.toLogString());
- }
- Logger.trace("Total " + accessories.size());
-
- Path accessoryJson = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "mags.json");
-
- String json = Files.readString(accessoryJson);
- accessories = AccessoryBeanParser.parseAccessoryJSON(json, "marklin.cs", "CS");
-
- for (AccessoryBean acc : accessories) {
- Logger.trace(acc.toLogString());
- }
- Logger.trace("Total " + accessories.size());
-
- }
-
+// public static void main(String[] a) throws Exception {
+//
+// Path accessoryFile = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "magnetartikel.cs2");
+// String file = Files.readString(accessoryFile);
+// List accessories = AccessoryBeanParser.parseAccessoryFile(file, "marklin.cs", "CS");
+// for (AccessoryBean acc : accessories) {
+// Logger.trace(acc.toLogString());
+// }
+// Logger.trace("Total " + accessories.size());
+//
+// Path accessoryJson = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "mags.json");
+//
+// String json = Files.readString(accessoryJson);
+// accessories = AccessoryBeanParser.parseAccessoryJSON(json, "marklin.cs", "CS");
+//
+// for (AccessoryBean acc : accessories) {
+// Logger.trace(acc.toLogString());
+// }
+// Logger.trace("Total " + accessories.size());
+//
+// }
}
diff --git a/src/main/java/jcs/commandStation/marklin/cs2/AccessoryEventParser.java b/src/main/java/jcs/commandStation/marklin/cs2/AccessoryEventParser.java
deleted file mode 100644
index ead78225..00000000
--- a/src/main/java/jcs/commandStation/marklin/cs2/AccessoryEventParser.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2024 Frans Jacobs.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jcs.commandStation.marklin.cs2;
-
-import jcs.commandStation.events.AccessoryEvent;
-import jcs.commandStation.marklin.cs.can.CanMessage;
-import jcs.entities.AccessoryBean;
-import org.tinylog.Logger;
-
-public class AccessoryEventParser {
-
- public static AccessoryEvent parseMessage(CanMessage message) {
- CanMessage resp;
- if (!message.isResponseMessage()) {
- resp = message.getResponse();
- } else {
- resp = message;
- }
-
- if (resp.isResponseMessage() && CanMessage.ACCESSORY_SWITCHING_RESP == resp.getCommand()) {
- byte[] data = resp.getData();
- int address = data[3];
- int position = data[4];
- //CS is zero based
- address = address + 1;
- String id = address + "";
-
- AccessoryBean accessoryBean = new AccessoryBean(id, address, null, null, position, null, null, null, CanMessage.MARKLIN_COMMANDSTATION_ID);
- if (resp.getDlc() == CanMessage.DLC_8) {
- int switchTime = CanMessage.toInt(new byte[]{data[6], data[7]});
- accessoryBean.setSwitchTime(switchTime);
- }
-
- return new AccessoryEvent(accessoryBean);
- } else {
- Logger.warn("Can't parse message, not an Accessory Response! " + resp);
- return null;
- }
- }
-
-}
diff --git a/src/main/java/jcs/commandStation/marklin/cs2/ChannelDataParser.java b/src/main/java/jcs/commandStation/marklin/cs2/ChannelDataParser.java
deleted file mode 100644
index c5c34816..00000000
--- a/src/main/java/jcs/commandStation/marklin/cs2/ChannelDataParser.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright 2023 Frans Jacobs.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jcs.commandStation.marklin.cs2;
-
-import java.io.Serializable;
-import java.util.List;
-import jcs.commandStation.marklin.cs.can.CanMessage;
-import jcs.entities.ChannelBean;
-import jcs.util.ByteUtil;
-import org.tinylog.Logger;
-
-/**
- *
- * @author Frans Jacobs
- */
-public class ChannelDataParser implements Serializable {
-
- public ChannelDataParser() {
-
- }
-
- private int getStringLength(byte[] data) {
- for (int i = 0; i < data.length; i++) {
- if (data[i] == 0x00) {
- return i;
- }
- }
- return data.length;
- }
-
- private int getNumberOfPackets(CanMessage message) {
- int packets = -1;
- List responses = message.getResponses();
-
- int lastIdx = responses.size();
- if (lastIdx > 0) {
- lastIdx = lastIdx - 1;
- } else {
- return -1;
- }
- CanMessage last = responses.get(lastIdx);
-
- if (last.getDlc() == CanMessage.DLC_6) {
- packets = last.getDataByte(5);
- } else if (last.getDlc() == CanMessage.DLC_5) {
- //CS-2 lets assume the number packets to be the size
- packets = responses.size() - 1;
- }
- return packets;
- }
-
- public ChannelBean parseConfigMessage(CanMessage message) {
- ChannelBean channel = new ChannelBean();
- if (message.getCommand() == CanMessage.STATUS_CONFIG) {
- List responses = message.getResponses();
- int packets = getNumberOfPackets(message);
-
- //Create one array with data
- byte[] data = new byte[8 * packets];
-
- if (packets > 0) {
- for (int i = 0; i < packets; i++) {
- byte[] d = responses.get(i).getData();
- System.arraycopy(d, 0, data, (i * d.length), d.length);
- }
- int number = Byte.toUnsignedInt(data[0]);
- int scale = data[1];
-
- int colorMax = Byte.toUnsignedInt(data[2]);
- int colorGreen = Byte.toUnsignedInt(data[3]);
- int colorYellow = Byte.toUnsignedInt(data[4]);
- int colorRed = Byte.toUnsignedInt(data[5]);
- double startValue = ((double) ByteUtil.toInt(new byte[]{data[6], data[7]}));
-
- channel.setNumber(number);
- channel.setScale(scale);
- channel.setColorMax(colorMax);
- channel.setColorGreen(colorGreen);
- channel.setColorYellow(colorYellow);
- channel.setColorRed(colorRed);
- channel.setStartValue(startValue);
-
- //1
- int rangeMax = ByteUtil.toInt(new byte[]{data[8], data[9]});
- int rangeGreen = ByteUtil.toInt(new byte[]{data[10], data[11]});
- int rangeYellow = ByteUtil.toInt(new byte[]{data[12], data[13]});
- int rangeRed = ByteUtil.toInt(new byte[]{data[14], data[15]});
-
- channel.setRangeMax(rangeMax);
- channel.setRangeGreen(rangeGreen);
- channel.setRangeYellow(rangeYellow);
- channel.setRangeRed(rangeRed);
-
- //2,3,4
- //parse the strings
- int idx = 16; //we are now @ byte 16; get all remaining bytes from here
- int fullLen = data.length - idx;
- byte[] stringdata = new byte[fullLen];
- System.arraycopy(data, idx, stringdata, 0, stringdata.length);
- //get the lenght util \0
- int len = this.getStringLength(stringdata);
- byte[] strArr = new byte[len];
- System.arraycopy(data, idx, strArr, 0, strArr.length);
-
- String name = ByteUtil.bytesToString(strArr);
- channel.setName(name);
-
- //next string
- idx = idx + len + 1;
- fullLen = data.length - idx;
- stringdata = new byte[fullLen];
- System.arraycopy(data, idx, stringdata, 0, stringdata.length);
- len = this.getStringLength(stringdata);
- strArr = new byte[len];
- System.arraycopy(data, idx, strArr, 0, strArr.length);
- String startVal = ByteUtil.bytesToString(strArr);
- if(startVal == null) {
- startVal = "0.0";
- }
- double startValDouble = Double.parseDouble(startVal);
- channel.setStartValue(startValDouble);
-
- //next string
- idx = idx + len + 1;
- fullLen = data.length - idx;
- stringdata = new byte[fullLen];
- System.arraycopy(data, idx, stringdata, 0, stringdata.length);
- len = this.getStringLength(stringdata);
- strArr = new byte[len];
- System.arraycopy(data, idx, strArr, 0, strArr.length);
- String endVal = ByteUtil.bytesToString(strArr);
-
- double endValue = Double.parseDouble(endVal);
- channel.setEndValue(endValue);
-
- //next string
- idx = idx + len + 1;
- fullLen = data.length - idx;
- stringdata = new byte[fullLen];
- System.arraycopy(data, idx, stringdata, 0, stringdata.length);
- len = this.getStringLength(stringdata);
- strArr = new byte[len];
- System.arraycopy(data, idx, strArr, 0, strArr.length);
- String uom = ByteUtil.bytesToString(strArr);
- channel.setUnit(uom);
-
- //last string??
- idx = idx + len + 1;
- fullLen = data.length - idx;
- if (fullLen > 0) {
- stringdata = new byte[fullLen];
- System.arraycopy(data, idx, stringdata, 0, stringdata.length);
- len = this.getStringLength(stringdata);
- strArr = new byte[len];
- System.arraycopy(data, idx, strArr, 0, strArr.length);
- String xxx = ByteUtil.bytesToString(strArr);
- Logger.trace("Found string part: " + xxx);
- }
- } else {
- Logger.warn("Config packet data Invalid");
- }
- } else {
- Logger.trace("Command: " + ByteUtil.toHexString(message.getCommand()) + " Sub Command: " + ByteUtil.toHexString(message.getSubCommand()));
- }
- return channel;
- }
-
- public ChannelBean parseUpdateMessage(CanMessage message, ChannelBean channel) {
- if (message.getCommand() == CanMessage.SYSTEM_COMMAND && message.getSubCommand() == CanMessage.SYSTEM_SUB_STATUS) {
- CanMessage response = message.getResponse();
- byte[] data = response.getData();
- int number = data[5];
- int value = CanMessage.toInt(new byte[]{data[6], data[7]});
-
- if (channel.getNumber() == number) {
- channel.setValue(value);
- } else {
- Logger.warn("Can't set value for " + channel.getNumber() + " as the response is for channel " + number);
- }
- } else {
- Logger.trace("Command: " + ByteUtil.toHexString(message.getCommand()) + " Sub Command: " + ByteUtil.toHexString(message.getSubCommand()));
- }
- return channel;
- }
-
-}
diff --git a/src/main/java/jcs/commandStation/marklin/cs2/LocomotiveBeanParser.java b/src/main/java/jcs/commandStation/marklin/cs2/LocomotiveBeanParser.java
index 89f5d049..ee365a6e 100755
--- a/src/main/java/jcs/commandStation/marklin/cs2/LocomotiveBeanParser.java
+++ b/src/main/java/jcs/commandStation/marklin/cs2/LocomotiveBeanParser.java
@@ -15,10 +15,6 @@
*/
package jcs.commandStation.marklin.cs2;
-import java.io.File;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
@@ -269,18 +265,4 @@ private LocomotiveBean createLoco(Map locoProps, Map locs = lp.parseLocomotivesFile(loksFile);
-
- for (LocomotiveBean loc : locs) {
- Logger.trace(loc);
- }
-
- }
-
}
diff --git a/src/main/java/jcs/commandStation/marklin/cs3/DeviceJSONParser.java b/src/main/java/jcs/commandStation/marklin/cs3/DeviceJSONParser.java
deleted file mode 100644
index 5b85c61d..00000000
--- a/src/main/java/jcs/commandStation/marklin/cs3/DeviceJSONParser.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2023 Frans Jacobs.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jcs.commandStation.marklin.cs3;
-
-import java.io.File;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.List;
-import jcs.entities.ChannelBean;
-import jcs.commandStation.entities.DeviceBean;
-import org.json.JSONObject;
-import org.tinylog.Logger;
-
-/**
- *
- * @author Frans Jacobs
- */
-public class DeviceJSONParser {
-
- public DeviceJSONParser() {
- }
-
- public static List parse(String json) {
- JSONObject devicesJO = new JSONObject(json);
- String[] names = JSONObject.getNames(devicesJO);
- List devices = new ArrayList<>(names.length);
- for (String n : names) {
- JSONObject dev = devicesJO.getJSONObject(n);
- DeviceBean db = new DeviceBean(dev.toString());
- devices.add(db);
- }
-
- return devices;
- }
-
- public static void main(String[] a) throws Exception {
- Path path = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "devices.json");
-
- String devicesFile = Files.readString(path);
-
- List devices = DeviceJSONParser.parse(devicesFile);
-
-// for (DeviceBean dev : devices) {
-// Logger.trace(dev);
-// }
-
- for (DeviceBean dev : devices) {
- if (dev.isFeedbackDevice()) {
- Logger.trace(dev);
-
- List cbl = dev.getChannels();
- for (ChannelBean cb : cbl) {
- if (cb.isS88Bus()) {
- Logger.debug(cb);
- }
- }
-
- }
- }
-
- }
-}
diff --git a/src/main/java/jcs/commandStation/marklin/cs3/LocomotiveBeanJSONParser.java b/src/main/java/jcs/commandStation/marklin/cs3/LocomotiveBeanJSONParser.java
index d67ea1d8..5bba0e09 100644
--- a/src/main/java/jcs/commandStation/marklin/cs3/LocomotiveBeanJSONParser.java
+++ b/src/main/java/jcs/commandStation/marklin/cs3/LocomotiveBeanJSONParser.java
@@ -15,10 +15,6 @@
*/
package jcs.commandStation.marklin.cs3;
-import java.io.File;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.LinkedList;
import java.util.List;
import jcs.entities.FunctionBean;
@@ -144,17 +140,17 @@ public List parseLocomotives(String json) {
return this.locomotives;
}
- public static void main(String[] a) throws Exception {
- Path path = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "locomotives.json");
-
- String loksFile = Files.readString(path);
-
- LocomotiveBeanJSONParser lp = new LocomotiveBeanJSONParser();
- List locs = lp.parseLocomotives(loksFile);
-
- for (LocomotiveBean loc : locs) {
- Logger.trace(loc);
- }
- }
+// public static void main(String[] a) throws Exception {
+// Path path = Paths.get(System.getProperty("user.home") + File.separator + "jcs" + File.separator + "locomotives.json");
+//
+// String loksFile = Files.readString(path);
+//
+// LocomotiveBeanJSONParser lp = new LocomotiveBeanJSONParser();
+// List locs = lp.parseLocomotives(loksFile);
+//
+// for (LocomotiveBean loc : locs) {
+// Logger.trace(loc);
+// }
+// }
}
diff --git a/src/main/java/jcs/commandStation/marklin/parser/CanDeviceJSONParser.java b/src/main/java/jcs/commandStation/marklin/parser/CanDeviceJSONParser.java
new file mode 100644
index 00000000..10704670
--- /dev/null
+++ b/src/main/java/jcs/commandStation/marklin/parser/CanDeviceJSONParser.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2025 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.marklin.parser;
+
+import java.util.ArrayList;
+import java.util.List;
+import jcs.commandStation.marklin.cs.can.device.CanDevice;
+import jcs.commandStation.marklin.cs.can.device.ConfigChannel;
+import jcs.commandStation.marklin.cs.can.device.MeasuringChannel;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+/**
+ * CS 3 supports JSON
+ */
+public class CanDeviceJSONParser {
+
+ public static final String LINK_S88 = "LinkS88-1";
+ public static final String GFP = "GFP3-1";
+ public static final String CS3 = "CS3 0000";
+
+ public CanDeviceJSONParser() {
+ }
+
+ public static List parse(String json) {
+ JSONObject devicesJO = new JSONObject(json);
+ String[] ids = JSONObject.getNames(devicesJO);
+ List devices = new ArrayList<>();
+ for (String id : ids) {
+ JSONObject jo = devicesJO.getJSONObject(id);
+ String name = jo.optString("_name");
+ if (name.equals(LINK_S88) || name.equals(GFP) || name.equals(CS3)) {
+ CanDevice d = parseDevice(devicesJO.getJSONObject(id));
+ devices.add(d);
+ }
+ }
+
+ return devices;
+ }
+
+ private static CanDevice parseDevice(JSONObject jo) {
+ CanDevice d = new CanDevice();
+ d.setUid(jo.optString("_uid"));
+ d.setName(jo.optString("_name"));
+
+ //Links S88 JSON appears to have 1 more, CAN bus is zero base and has 1 less.
+ //To come to the same node id as can subtract 1
+ if (LINK_S88.equals(jo.optString("_name"))) {
+ String id = jo.optString("_kennung");
+ id = id.replaceAll("0x", "");
+ int idi = Integer.parseUnsignedInt(id, 16);
+ d.setIdentifier(idi - 1);
+ } else {
+ d.setIdentifier(jo.optString("_kennung"));
+ }
+
+ d.setArticleNumber(jo.optString("_artikelnr"));
+ d.setSerial(jo.optString("_seriennr"));
+
+ JSONObject versionObj = jo.optJSONObject("_version");
+ if (versionObj != null) {
+ String major = versionObj.optString("major");
+ String minor = versionObj.optString("minor");
+ d.setVersion((major != null ? major : "") + (major != null ? "." : "") + (minor != null ? minor : ""));
+ }
+
+ JSONArray channelsJA = jo.optJSONArray("_kanal");
+ if (channelsJA != null) {
+ for (int i = 0; i < channelsJA.length(); i++) {
+ JSONObject cjo = channelsJA.getJSONObject(i);
+
+ if (cjo.has("auswahl")) {
+ ConfigChannel cc = parseConfigChannel(cjo);
+ d.addConfigChannel(cc);
+ } else {
+ MeasuringChannel mc = parseMeasuringChannel(cjo);
+ d.addMeasuringChannel(mc);
+ }
+ }
+ d.setMeasureChannelCount(d.getMeasuringChannels().size());
+ d.setConfigChannelCount(d.getConfigChannels().size());
+ }
+ return d;
+ }
+
+ private static MeasuringChannel parseMeasuringChannel(JSONObject jo) {
+ MeasuringChannel mc = new MeasuringChannel();
+ mc.setName(jo.optString("name"));
+ mc.setNumber(jo.optInt("nr"));
+ Integer scale = jo.optInt("potenz");
+ if (scale > 0) {
+ scale = scale - 256;
+ }
+ mc.setScale(scale);
+ mc.setUnit(jo.optString("einheit"));
+ mc.setEndValue(jo.optDouble("endWert"));
+ mc.setStartValue(jo.optDouble("startWert"));
+ mc.setColorGreen(jo.optInt("farbeGruen"));
+ mc.setColorYellow(jo.optInt("farbeGelb"));
+ mc.setColorRed(jo.optInt("farbeRot"));
+ mc.setColorMax(jo.optInt("farbeMax"));
+ mc.setRangeGreen(jo.optInt("rangeGruen"));
+ mc.setRangeYellow(jo.optInt("rangeGelb"));
+ mc.setRangeRed(jo.optInt("rangeRot"));
+ mc.setRangeMax(jo.optInt("rangeMax"));
+ mc.setZeroPoint(jo.optInt("valueHuman"));
+ return mc;
+ }
+
+ private static ConfigChannel parseConfigChannel(JSONObject jo) {
+ ConfigChannel cc = new ConfigChannel();
+
+ String choice = jo.optString("auswahl");
+ String choices[] = choice.split(":");
+ for (String c : choices) {
+ cc.addChoice(c);
+ }
+ cc.setChoicesCount(choices.length);
+ cc.setValueId(jo.optInt("index"));
+
+ cc.setNumber(jo.optInt("nr"));
+ cc.setChoiceDescription(jo.optString("name"));
+
+ String unit = jo.optString("einheit");
+ if (!"".equals(unit)) {
+ cc.setUnit(unit);
+ }
+ cc.setHighValue(jo.optInt("endWert"));
+ cc.setLowValue(jo.optInt("startWert"));
+ cc.setActualValue(jo.optInt("wert"));
+ return cc;
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/marklin/parser/CanDeviceParser.java b/src/main/java/jcs/commandStation/marklin/parser/CanDeviceParser.java
new file mode 100644
index 00000000..0053a204
--- /dev/null
+++ b/src/main/java/jcs/commandStation/marklin/parser/CanDeviceParser.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2025 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.marklin.parser;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+import jcs.commandStation.marklin.cs.can.device.MeasuringChannel;
+import jcs.commandStation.marklin.cs.can.device.CanDevice;
+import jcs.commandStation.marklin.cs.can.CanMessage;
+import jcs.commandStation.marklin.cs.can.device.ConfigChannel;
+import org.tinylog.Logger;
+
+/**
+ * Parse the CS CAN Bus devices from the
+ * "Softwarestand Anfrage / Teilnehmer Ping" and "Statusdaten Konfiguration" messages
+ */
+public class CanDeviceParser {
+
+ public static List parse(CanMessage memberPingmessage) {
+ List devices = new ArrayList<>();
+ List responses = memberPingmessage.getResponses();
+ if (responses.isEmpty() && memberPingmessage.isResponseMessage()) {
+ responses.add(memberPingmessage);
+ }
+
+ for (CanMessage response : responses) {
+ CanDevice device = parseResponse(response);
+ if (device != null) {
+ devices.add(device);
+ }
+ }
+
+ return devices;
+ }
+
+ private static CanDevice parseResponse(CanMessage response) {
+ if (CanMessage.PING_RESP == response.getCommand() && CanMessage.DLC_8 == response.getDlc()) {
+ byte[] data = response.getData();
+
+ byte[] uida = new byte[4];
+ System.arraycopy(data, 0, uida, 0, uida.length);
+
+ byte[] vera = new byte[2];
+ System.arraycopy(data, 4, vera, 0, vera.length);
+
+ byte[] deva = new byte[2];
+ System.arraycopy(data, 6, deva, 0, deva.length);
+
+ int uidAsInt = response.getDeviceUidNumberFromMessage();
+ String uid = "0x" + Integer.toHexString(uidAsInt);
+ int major = Byte.toUnsignedInt(vera[0]);
+ int minor = Byte.toUnsignedInt(vera[1]);
+ String version = major + "." + minor;
+
+ int identifierAsInt = CanMessage.toInt(deva);
+ String identifier = "0x" + Integer.toHexString(identifierAsInt);
+
+ CanDevice device = new CanDevice();
+
+ device.setUid(uid);
+ device.setVersion(version);
+ device.setIdentifier(identifier);
+ return device;
+ }
+ return null;
+ }
+
+ public static void parse(CanDevice canDevice, CanMessage statusConfigmessage) {
+ //Filter the responses
+ List responses = new ArrayList<>(statusConfigmessage.getResponses().size());
+ for (CanMessage resp : statusConfigmessage.getResponses()) {
+ if (CanMessage.STATUS_CONFIG_RESP == resp.getCommand()) {
+ responses.add(resp);
+ }
+ }
+ if (responses.isEmpty()) {
+ return;
+ }
+
+ //The last response has the total response messages, aka the package number
+ CanMessage last = responses.get(responses.size() - 1);
+ int packets = 0;
+ int index = 0;
+
+ if (last.getDlc() == CanMessage.DLC_6) {
+ index = last.getDataByte(4);
+ packets = last.getDataByte(5);
+ } else if (last.getDlc() == CanMessage.DLC_5) {
+ //CS-2 lets assume the number packets to be the size
+ packets = responses.size() - 1;
+ }
+ if (responses.size() - 1 != packets) {
+ Logger.warn("Config Data might be invalid. Packages expected: " + packets + " received: " + (responses.size() - 1));
+ Logger.trace(statusConfigmessage);
+ for (CanMessage m : responses) {
+ Logger.trace(m);
+ }
+ }
+
+ if (index == 0) {
+ parseDeviceDescription(canDevice, responses);
+ } else {
+ int measurementChannels;
+ if (canDevice.getMeasureChannelCount() == null) {
+ measurementChannels = 0;
+ } else {
+ measurementChannels = canDevice.getMeasureChannelCount();
+ }
+ if (index <= measurementChannels) {
+ parseMeasurementChannel(canDevice, responses);
+ } else {
+ parseConfigurationChannel(canDevice, responses);
+ }
+ }
+ }
+
+ /**
+ * In case the index equals zero (0) the responses contain a CAN Device Description.
+ *
+ * @param responses
+ * @return
+ */
+ private static void parseDeviceDescription(CanDevice canDevice, List responses) {
+ List stringDataList = new ArrayList<>();
+ for (int i = 0; i < responses.size(); i++) {
+ CanMessage msg = responses.get(i);
+ byte[] data = msg.getData();
+ int packageNr = msg.getPackageNumber();
+
+ switch (packageNr) {
+ case 1 -> {
+ if (CanMessage.DLC_8 == msg.getDlc()) {
+ int measureChannels = Byte.toUnsignedInt(data[0]);
+ int configChannels = Byte.toUnsignedInt(data[1]);
+ canDevice.setMeasureChannelCount(measureChannels);
+ canDevice.setConfigChannelCount(configChannels);
+
+ byte[] free = new byte[2];
+ System.arraycopy(data, 2, free, 0, free.length);
+
+ byte[] sn = new byte[4];
+ System.arraycopy(data, 4, sn, 0, sn.length);
+ int serial = CanMessage.toInt(sn);
+ canDevice.setSerial(serial);
+ } else {
+ Logger.trace("Invalid DLC " + msg.getDlc() + " Package " + packageNr + " " + msg);
+ }
+ }
+ case 2 -> {
+ if (CanMessage.DLC_8 == msg.getDlc()) {
+ //Article is defined in the full 8 bytes of the 2nd package
+ String articleNumber = CanMessage.toString(data);
+ canDevice.setArticleNumber(articleNumber.trim());
+ } else {
+ Logger.trace("Invalid DLC " + msg.getDlc() + " Package " + packageNr + " " + msg);
+ }
+ }
+ default -> {
+ switch (msg.getDlc()) {
+ case CanMessage.DLC_8 -> {
+ for (int j = 0; j < data.length; j++) {
+ stringDataList.add(data[j]);
+ }
+ }
+ case CanMessage.DLC_6 -> {
+ //Got the last response
+ List strings = splitIntoStrings(stringDataList);
+ if (!strings.isEmpty()) {
+ canDevice.setName(strings.get(0));
+ }
+ if (strings.size() > 1) {
+ Logger.warn("There are more name strings than expected " + strings);
+ }
+ }
+ default ->
+ Logger.trace("Invalid DLC " + msg.getDlc() + " Package " + packageNr + " " + msg);
+ }
+ }
+ }
+ }
+ }
+
+ private static void parseMeasurementChannel(CanDevice canDevice, List responses) {
+ List stringDataList = new ArrayList<>();
+ MeasuringChannel channel = new MeasuringChannel();
+
+ for (int i = 0; i < responses.size(); i++) {
+ CanMessage msg = responses.get(i);
+ byte[] data = msg.getData();
+ int packageNr = msg.getPackageNumber();
+
+ switch (packageNr) {
+ case 1 -> {
+ if (CanMessage.DLC_8 == msg.getDlc()) {
+ int channelNumber = Byte.toUnsignedInt(data[0]);
+ int measurementScale = (int) data[1];
+ int colorRange1 = Byte.toUnsignedInt(data[2]);
+ int colorRange2 = Byte.toUnsignedInt(data[3]);
+ int colorRange3 = Byte.toUnsignedInt(data[4]);
+ int colorRange4 = Byte.toUnsignedInt(data[5]);
+
+ byte[] zeroPoint = new byte[2];
+ System.arraycopy(data, 6, zeroPoint, 0, zeroPoint.length);
+ int zero = CanMessage.toInt(zeroPoint);
+
+ //channel.setIndex(index);
+ channel.setNumber(channelNumber);
+ channel.setScale(measurementScale);
+ channel.setColorGreen(colorRange1);
+ channel.setColorYellow(colorRange2);
+ channel.setColorRed(colorRange3);
+ channel.setColorMax(colorRange4);
+
+ channel.setZeroPoint(zero);
+ } else {
+ Logger.trace("Invalid DLC " + msg.getDlc() + " Package " + packageNr + " " + msg);
+ }
+ }
+ case 2 -> {
+ if (CanMessage.DLC_8 == msg.getDlc()) {
+ byte[] brange1 = new byte[2];
+ System.arraycopy(data, 0, brange1, 0, brange1.length);
+ byte[] brange2 = new byte[2];
+ System.arraycopy(data, 2, brange2, 0, brange2.length);
+ byte[] brange3 = new byte[2];
+ System.arraycopy(data, 4, brange3, 0, brange3.length);
+ byte[] brange4 = new byte[2];
+ System.arraycopy(data, 6, brange4, 0, brange4.length);
+
+ int range1 = CanMessage.toInt(brange1);
+ int range2 = CanMessage.toInt(brange2);
+ int range3 = CanMessage.toInt(brange2);
+ int range4 = CanMessage.toInt(brange4);
+
+ channel.setRangeGreen(range1);
+ channel.setRangeYellow(range2);
+ channel.setRangeRed(range3);
+ channel.setRangeMax(range4);
+ } else {
+ Logger.trace("Invalid DLC " + msg.getDlc() + " Package " + packageNr + " " + msg);
+ }
+ }
+ default -> {
+ switch (msg.getDlc()) {
+ case CanMessage.DLC_8 -> {
+ //The last part of the measurement data are strings, so first concat all data packets
+ for (int ii = 0; ii < data.length; ii++) {
+ stringDataList.add(data[ii]);
+ }
+ }
+ case CanMessage.DLC_6 -> {
+ //Last message in this response
+ if (!stringDataList.isEmpty()) {
+ List strings = splitIntoStrings(stringDataList);
+
+ if (!strings.isEmpty()) {
+ for (int j = 0; j < strings.size(); j++) {
+ switch (j) {
+ case 0 ->
+ channel.setName(strings.get(0));
+ case 1 ->
+ channel.setStartValue(Double.valueOf(strings.get(1)));
+ case 2 ->
+ channel.setEndValue(Double.valueOf(strings.get(2)));
+ case 3 ->
+ channel.setUnit(strings.get(3));
+ default ->
+ Logger.trace("Remaining: " + strings.get(j));
+ }
+ }
+ }
+ }
+ }
+ default ->
+ Logger.trace("Invalid DLC " + msg.getDlc() + " Package " + packageNr + " " + msg);
+ }
+ }
+ }
+ }
+ canDevice.addMeasuringChannel(channel);
+ }
+
+ private static void parseConfigurationChannel(CanDevice canDevice, List responses) {
+ List stringDataList = new ArrayList<>();
+ ConfigChannel channel = new ConfigChannel();
+
+ for (int i = 0; i < responses.size(); i++) {
+ CanMessage msg = responses.get(i);
+ byte[] data = msg.getData();
+ int packageNr = msg.getPackageNumber();
+
+ //There are 2 possible formats; one with choice lists and one with values.
+ //Not clear how the recognice the one or the other...
+ switch (packageNr) {
+ case 1 -> {
+ if (CanMessage.DLC_8 == msg.getDlc()) {
+ int channelConfigNumber = Byte.toUnsignedInt(data[0]);
+ int valueId = Byte.toUnsignedInt(data[1]);
+ channel.setNumber(channelConfigNumber);
+ channel.setValueId(valueId);
+ //Here it is a bit unclear. For choice list
+ int choicesCount = Byte.toUnsignedInt(data[2]);
+ int defaultValueId = Byte.toUnsignedInt(data[3]);
+
+ //next 4 byte are reserved aka 0
+ int res1 = Byte.toUnsignedInt(data[4]);
+ int res2 = Byte.toUnsignedInt(data[5]);
+ int res3 = Byte.toUnsignedInt(data[6]);
+ int res4 = Byte.toUnsignedInt(data[7]);
+
+ if (res1 == 0 & res2 == 0 && res3 == 0 && res4 == 0) {
+ channel.setChoicesCount(choicesCount);
+ channel.setValueId(defaultValueId);
+ } else {
+ //for the other format:
+ byte[] lowval = new byte[2];
+ System.arraycopy(data, 2, lowval, 0, lowval.length);
+ int lowValue = CanMessage.toInt(lowval);
+ channel.setLowValue(lowValue);
+ byte[] upperval = new byte[2];
+ System.arraycopy(data, 4, upperval, 0, upperval.length);
+ int upperValue = CanMessage.toInt(lowval);
+ channel.setHighValue(upperValue);
+ byte[] setval = new byte[2];
+ System.arraycopy(data, 6, setval, 0, setval.length);
+ int actualValue = CanMessage.toInt(setval);
+ channel.setActualValue(actualValue);
+ }
+ } else {
+ Logger.trace("Invalid DLC " + msg.getDlc() + " Package " + packageNr + " " + msg);
+ }
+ }
+ default -> {
+ int dlc = msg.getDlc();
+ switch (dlc) {
+ case CanMessage.DLC_8 -> {
+ //The last part of the config channel data are strings, so first concat all data packets
+ for (int ii = 0; ii < data.length; ii++) {
+ stringDataList.add(data[ii]);
+ }
+ }
+ case CanMessage.DLC_6 -> {
+ //Last message in this response
+ if (!stringDataList.isEmpty()) {
+ List strings = splitIntoStrings(stringDataList);
+ //for (String s : strings) {
+ // Logger.trace(s);
+ //}
+ int choicesCount = 0;
+ if (channel.getChoicesCount() != null) {
+ choicesCount = channel.getChoicesCount();
+ }
+
+ //first string the the description
+ channel.setChoiceDescription(strings.get(0));
+
+ if (choicesCount > 0) {
+ //next are the choices
+ for (int j = 1; j <= choicesCount; j++) {
+ if (strings.size() > j) {
+ channel.addChoice(strings.get(j));
+ }
+ }
+ } else {
+ //No choice list must be single values
+ for (int j = 1; j < strings.size(); j++) {
+ switch (j) {
+ case 1 ->
+ channel.setStartName(strings.get(j));
+ case 2 ->
+ channel.setEndName(strings.get(j));
+ case 3 ->
+ channel.setUnit(strings.get(j));
+ default ->
+ Logger.trace("Remaining: " + strings.get(j));
+ }
+ }
+ }
+ canDevice.addConfigChannel(channel);
+ }
+ }
+ default ->
+ Logger.trace("Invalid DLC " + msg.getDlc() + " Package " + packageNr + " " + msg);
+ }
+ }
+ }
+ }
+ }
+
+ private static List splitIntoStrings(List byteList) {
+ List strings = new ArrayList<>();
+ int index = 0;
+ byte[] d = new byte[byteList.size()];
+ for (int j = 0; j < d.length; j++) {
+ d[j] = byteList.get(j);
+
+ if (d[j] == 0) {
+ //Terminator
+ int len = j - index;
+
+ byte[] tmp = new byte[len];
+ System.arraycopy(d, index, tmp, 0, tmp.length);
+ String s;
+ try {
+ s = new String(tmp, "UTF-8");
+
+ if (s.length() >= 1) {
+ strings.add(s);
+ }
+ } catch (UnsupportedEncodingException ex) {
+ Logger.error(ex);
+ }
+ index = j + 1;
+ }
+ }
+ return strings;
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/marklin/cs2/InfoBeanParser.java b/src/main/java/jcs/commandStation/marklin/parser/GeraetParser.java
similarity index 58%
rename from src/main/java/jcs/commandStation/marklin/cs2/InfoBeanParser.java
rename to src/main/java/jcs/commandStation/marklin/parser/GeraetParser.java
index 16f61e39..1af28f9f 100644
--- a/src/main/java/jcs/commandStation/marklin/cs2/InfoBeanParser.java
+++ b/src/main/java/jcs/commandStation/marklin/parser/GeraetParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 Frans Jacobs.
+ * Copyright 2025 Frans Jacobs.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,24 +13,34 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package jcs.commandStation.marklin.cs2;
+package jcs.commandStation.marklin.parser;
import jcs.commandStation.entities.InfoBean;
+import jcs.commandStation.marklin.cs.can.device.CanDevice;
import org.json.JSONObject;
/**
- * Parse an InfoBean from Marklin CS2 file or CS 3 JSON.
+ * Parse an InfoBean from Marklin CS2/3 file.
*/
-public class InfoBeanParser {
+public class GeraetParser {
- public static InfoBean parseFile(String file) {
- if (file == null) {
+ /**
+ * The quickest method to obtain basic information of the Central Station is to query the "geraet" file via the http interface.
+ *
+ * @param commandStationBean
+ * @param geraetFile
+ * @return
+ */
+ public static CanDevice parseFile(String geraetFile) {
+ if (geraetFile == null) {
return null;
}
+
+ CanDevice gfp = new CanDevice();
+ //InfoBean ib = new InfoBean();
+ //ib.copyInto(commandStationBean);
- InfoBean ib = new InfoBean();
-
- String[] lines = file.split("\n");
+ String[] lines = geraetFile.split("\n");
String minor = "";
String major = "";
for (String line : lines) {
@@ -40,10 +50,10 @@ public static InfoBean parseFile(String file) {
}
String key = line.substring(0, eqidx).trim();
String value = line.substring(eqidx).replace("=", "").trim();
-
+
switch (key) {
case "[geraet]" -> {
-
+
}
case ".major" -> {
major = value;
@@ -52,57 +62,65 @@ public static InfoBean parseFile(String file) {
minor = value;
}
case ".sernum" -> {
- //this.serialNumber = value;
- ib.setSerialNumber(value);
+ //ib.setSerialNumber(value);
+ gfp.setSerial(value);
}
case ".gfpuid" -> {
- //this.gfpUid = value;
- ib.setGfpUid(value);
+ //ib.setGfpUid(value);
+ gfp.setUid(value);
}
case ".guiuid" -> {
- //this.guiUid = value;
- ib.setGuiUid(value);
+ gfp.setGuiUid(value);
}
case ".hardvers" -> {
- //this.hardwareVersion = value;
- ib.setHardwareVersion(value);
+ //ib.setHardwareVersion(value);
+ gfp.setHwVersion(value);
}
case ".articleno" -> {
- //this.articleNumber = value;
- ib.setArticleNumber(value);
+ //ib.setArticleNumber(value);
+ gfp.setArticleNumber(value);
}
case ".produkt" -> {
- //this.productName = value;
- ib.setProductName(value);
+ //ib.setProductName(value);
+ gfp.setName(value);
}
}
}
-
+
String softwareVersion = (major != null ? major : "") + (major != null ? "." : "") + (minor != null ? minor : "");
- ib.setSoftwareVersion(softwareVersion);
-
+ //ib.setSoftwareVersion(softwareVersion);
+ gfp.setVersion(softwareVersion);
+
+ if (gfp.getSerial() != null & gfp.getSerial().length() < 5) {
+ gfp.setSerial("0" + gfp.getSerial());
+ }
+
String shortName;
- //String sn = ib.getSerialNumber();
- if (ib.getProductName() != null && ib.getProductName().contains("Central Station 3")) {
+ if (gfp.getName() != null && gfp.getName().contains("Central Station 3")) {
shortName = "CS3";
} else {
shortName = "CS2";
}
- if (ib.getSerialNumber().length() < 5) {
- ib.setSerialNumber("0" + ib.getSerialNumber());
- }
- ib.setHostname(shortName + "-" + ib.getSerialNumber());
-
- return ib;
+
+ gfp.setShortName(shortName);
+
+ gfp.setIdentifier("0x00");
+ return gfp;
}
+ /**
+ * The CS 3 has JSON files accessible via the web interface which contains lots of info about the CS
+ *
+ * @param json
+ * @return
+ */
public static InfoBean parseJson(String json) {
if (json == null) {
return null;
}
-
+
InfoBean ib = new InfoBean();
-
+
JSONObject infoObject = new JSONObject(json);
ib.setSoftwareVersion(infoObject.optString("softwareVersion"));
ib.setHardwareVersion(infoObject.optString("hardwareVersion"));
@@ -112,8 +130,8 @@ public static InfoBean parseJson(String json) {
ib.setHostname(infoObject.optString("hostname"));
ib.setGfpUid(infoObject.optString("gfpUid"));
ib.setGuiUid(infoObject.optString("guiUid"));
-
+
return ib;
}
-
+
}
diff --git a/src/main/java/jcs/commandStation/marklin/parser/SystemStatusMessage.java b/src/main/java/jcs/commandStation/marklin/parser/SystemStatusMessage.java
new file mode 100644
index 00000000..16746441
--- /dev/null
+++ b/src/main/java/jcs/commandStation/marklin/parser/SystemStatusMessage.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2025 Frans Jacobs.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jcs.commandStation.marklin.parser;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import jcs.commandStation.entities.MeasurementBean;
+import jcs.commandStation.marklin.cs.can.CanMessage;
+import jcs.commandStation.marklin.cs.can.device.MeasuringChannel;
+import org.tinylog.Logger;
+
+/**
+ * Convert the SystemStatus Message measured values into a MeasurementBean
+ */
+public class SystemStatusMessage {
+
+ public SystemStatusMessage() {
+
+ }
+
+ public static MeasurementBean parse(MeasuringChannel channel, CanMessage systemStatusmessage) {
+ return parse(channel, systemStatusmessage, System.currentTimeMillis());
+ }
+
+ public static MeasurementBean parse(MeasuringChannel channel, CanMessage systemStatusmessage, long measurementMillis) {
+
+ MeasurementBean measurement = null;
+ if (systemStatusmessage.getCommand() == CanMessage.SYSTEM_COMMAND && systemStatusmessage.getSubCommand() == CanMessage.SYSTEM_SUB_STATUS) {
+ CanMessage response = systemStatusmessage.getResponse();
+ byte[] data = response.getData();
+
+ switch (response.getDlc()) {
+ case CanMessage.DLC_7 -> {
+ int channelNumber = data[5];
+ int valid = data[6];
+ measurement = new MeasurementBean(channelNumber, channel.getName(), (valid == 1), System.currentTimeMillis());
+ }
+ case CanMessage.DLC_8 -> {
+ int channelNumber = data[5];
+ int measuredValue = CanMessage.toInt(new byte[]{data[6], data[7]});
+
+ Double displayValue = calculateDisplayValue(measuredValue, channel);
+ measurement = new MeasurementBean(channelNumber, channel.getName(), measurementMillis, measuredValue, channel.getUnit(), displayValue);
+ }
+ default ->
+ Logger.error("Invalid DLC " + response.getDlc() + " response " + response);
+ }
+ } else {
+ Logger.error("Unexpected message " + systemStatusmessage);
+ }
+ return measurement;
+ }
+
+ private static Double calculateDisplayValue(Integer measuredValue, MeasuringChannel channel) {
+ Double startVal = channel.getStartValue();
+ Double endVal = channel.getEndValue();
+ Integer rangeMax = channel.getRangeMax();
+ //Logger.trace("Ch: "+channel);
+
+ if (startVal != null && endVal != null && rangeMax != null) {
+ //Logger.trace("endVal: "+endVal+" startVal: "+startVal+" rangeMax: "+rangeMax+" measuredValue: "+measuredValue);
+
+ Double displayValue = ((endVal - startVal) / rangeMax * measuredValue) + startVal;
+ return round(displayValue, getDigits(channel.getName()));
+ }
+ return null;
+ }
+
+ private static Double round(Double value, int digits) {
+ if (digits < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ BigDecimal bd = new BigDecimal(Double.toString(value));
+ bd = bd.setScale(digits, RoundingMode.HALF_UP);
+ return bd.doubleValue();
+ }
+
+ private static int getDigits(String channelName) {
+
+ return switch (channelName) {
+ case "MAIN" ->
+ 3;
+ case "PROG" ->
+ 3;
+ case "VOLT" ->
+ 1;
+ case "TEMP" ->
+ 1;
+ default ->
+ 0;
+ };
+ }
+
+}
diff --git a/src/main/java/jcs/commandStation/virtual/VirtualCommandStationImpl.java b/src/main/java/jcs/commandStation/virtual/VirtualCommandStationImpl.java
index 7dedcfc9..56adfb87 100644
--- a/src/main/java/jcs/commandStation/virtual/VirtualCommandStationImpl.java
+++ b/src/main/java/jcs/commandStation/virtual/VirtualCommandStationImpl.java
@@ -19,13 +19,13 @@
import java.awt.Image;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import jcs.JCS;
import jcs.commandStation.AbstractController;
import jcs.commandStation.AccessoryController;
import jcs.commandStation.DecoderController;
import jcs.commandStation.FeedbackController;
import jcs.commandStation.autopilot.AutoPilot;
+import jcs.commandStation.entities.Device;
import jcs.commandStation.events.AccessoryEvent;
import jcs.commandStation.events.AccessoryEventListener;
import jcs.commandStation.events.LocomotiveDirectionEvent;
@@ -39,14 +39,11 @@
import jcs.commandStation.events.SensorEvent;
import jcs.commandStation.events.SensorEventListener;
import jcs.entities.AccessoryBean;
-import jcs.entities.ChannelBean;
import jcs.entities.CommandStationBean;
-import jcs.commandStation.entities.DeviceBean;
-import jcs.entities.FeedbackModuleBean;
+import jcs.commandStation.entities.FeedbackModule;
import jcs.commandStation.entities.InfoBean;
import jcs.entities.LocomotiveBean;
import jcs.util.NetworkUtil;
-import jcs.util.VersionInfo;
import org.tinylog.Logger;
/**
@@ -55,7 +52,8 @@
*/
public class VirtualCommandStationImpl extends AbstractController implements DecoderController, AccessoryController, FeedbackController {
- private DeviceBean mainDevice;
+ public static final String VIRTUAL_CS = "virtual";
+
private InfoBean infoBean;
private DriveSimulator simulator;
@@ -82,18 +80,17 @@ private void autoConnect() {
public synchronized boolean connect() {
this.connected = true;
- mainDevice = new DeviceBean();
- mainDevice.setArticleNumber("JCS Virtual CS");
- mainDevice.setVersion(VersionInfo.getVersion());
-
- mainDevice.setSerial("1");
- mainDevice.setIdentifier(this.commandStationBean.getId());
- mainDevice.setName(this.commandStationBean.getDescription());
-
+// mainDevice = new DeviceBean();
+// mainDevice.setArticleNumber("JCS Virtual CS");
+// mainDevice.setVersion(VersionInfo.getVersion());
+//
+// mainDevice.setSerial("1");
+// mainDevice.setIdentifier(this.commandStationBean.getIdString());
+// mainDevice.setName(this.commandStationBean.getDescription());
infoBean = new InfoBean();
infoBean.setProductName(commandStationBean.getDescription());
infoBean.setArticleNumber(commandStationBean.getShortName());
- infoBean.setHostname(this.getIp());
+ infoBean.setHostname(getIp());
power(true);
@@ -104,7 +101,7 @@ public synchronized boolean connect() {
public void disconnect() {
this.connected = false;
this.infoBean = null;
- this.mainDevice = null;
+ //this.mainDevice = null;
}
@Override
@@ -118,19 +115,12 @@ public InfoBean getCommandStationInfo() {
}
@Override
- public DeviceBean getDevice() {
- return this.mainDevice;
- }
-
- @Override
- public List getDevices() {
- List devices = new ArrayList<>();
- if (mainDevice != null) {
- devices.add(this.mainDevice);
- }
+ public List getDevices() {
+ List devices = new ArrayList<>();
+
return devices;
}
-
+
@Override
public String getIp() {
return NetworkUtil.getIPv4HostAddress().getHostAddress();
@@ -261,25 +251,11 @@ public boolean isSupportTrackMeasurements() {
}
@Override
- public Map getTrackMeasurements() {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
- @Override
- public void switchAccessory(Integer address, AccessoryBean.AccessoryValue value) {
- switchAccessory(address, value, 200);
- }
-
- @Override
- public void switchAccessory(String id, AccessoryBean.AccessoryValue value) {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
- @Override
- public void switchAccessory(Integer address, AccessoryBean.AccessoryValue value, Integer switchTime) {
+ public void switchAccessory(Integer address, String protocol, AccessoryBean.AccessoryValue value, Integer switchTime) {
if (this.power && connected) {
AccessoryBean ab = new AccessoryBean();
ab.setAddress(address);
+ ab.setProtocol(AccessoryBean.Protocol.get(protocol));
ab.setAccessoryValue(value);
String id = address + "";
if (id.length() == 1) {
@@ -291,7 +267,9 @@ public void switchAccessory(Integer address, AccessoryBean.AccessoryValue value,
ab.setCommandStationId(commandStationBean.getId());
AccessoryEvent ae = new AccessoryEvent(ab);
- executor.execute(() -> fireAllAccessoryEventListeners(ae));
+ //executor.execute(() -> fireAllAccessoryEventListeners(ae));
+ Logger.trace("Switched accessory " + id + " to " + value.getValue());
+ fireAllAccessoryEventListeners(ae);
} else {
if (!this.power) {
Logger.warn("Can't switch accessory " + address + " to: " + value + " Power is OFF!");
@@ -302,6 +280,7 @@ public void switchAccessory(Integer address, AccessoryBean.AccessoryValue value,
private void fireAllAccessoryEventListeners(final AccessoryEvent accessoryEvent) {
for (AccessoryEventListener listener : this.accessoryEventListeners) {
listener.onAccessoryChange(accessoryEvent);
+ Logger.trace("Fired accessory listener " + accessoryEvent.getIdString());
}
}
@@ -311,12 +290,7 @@ public List getAccessories() {
}
@Override
- public DeviceBean getFeedbackDevice() {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
- @Override
- public List getFeedbackModules() {
+ public List getFeedbackModules() {
throw new UnsupportedOperationException("Not supported yet.");
}
diff --git a/src/main/java/jcs/entities/BlockBean.java b/src/main/java/jcs/entities/BlockBean.java
index edc9223c..a014ff74 100644
--- a/src/main/java/jcs/entities/BlockBean.java
+++ b/src/main/java/jcs/entities/BlockBean.java
@@ -31,8 +31,8 @@ public class BlockBean {
private String id;
private String tileId;
private String description;
- private String plusSensorId;
- private String minSensorId;
+ private Integer plusSensorId;
+ private Integer minSensorId;
private String plusSignalId;
private String minSignalId;
private Long locomotiveId;
@@ -110,11 +110,11 @@ public void setDescription(String description) {
}
@Column(name = "plus_sensor_id")
- public String getPlusSensorId() {
+ public Integer getPlusSensorId() {
return plusSensorId;
}
- public void setPlusSensorId(String plusSensorId) {
+ public void setPlusSensorId(Integer plusSensorId) {
this.plusSensorId = plusSensorId;
}
@@ -133,11 +133,11 @@ public void setPlusSensorBean(SensorBean plusSensorBean) {
}
@Column(name = "min_sensor_id")
- public String getMinSensorId() {
+ public Integer getMinSensorId() {
return minSensorId;
}
- public void setMinSensorId(String minSensorId) {
+ public void setMinSensorId(Integer minSensorId) {
this.minSensorId = minSensorId;
}
diff --git a/src/main/java/jcs/entities/CommandStationBean.java b/src/main/java/jcs/entities/CommandStationBean.java
index bee8946d..05087540 100644
--- a/src/main/java/jcs/entities/CommandStationBean.java
+++ b/src/main/java/jcs/entities/CommandStationBean.java
@@ -31,8 +31,7 @@
import java.util.concurrent.ConcurrentHashMap;
/**
- *
- * @author frans
+ * Represents a Command Station
*/
@Table(name = "command_stations")
public class CommandStationBean {
@@ -67,6 +66,11 @@ public class CommandStationBean {
protected Integer feedbackBus2ModuleCount;
protected Integer feedbackBus3ModuleCount;
+ public static final String MARKLIN_CS = "marklin.cs";
+ public static final String ESU_ECOS = "esu-ecos";
+ public static final String DCC_EX = "dcc-ex";
+ public static final String HSI_S88 = "hsi-s88";
+
@Id
@Column(name = "id")
public String getId() {
@@ -116,7 +120,11 @@ public void setConnectVia(String connectVia) {
@Transient
public ConnectionType getConnectionType() {
- return ConnectionType.get(connectVia);
+ if (connectVia != null) {
+ return ConnectionType.get(connectVia);
+ } else {
+ return null;
+ }
}
@Transient
diff --git a/src/main/java/jcs/entities/FeedbackModuleBean.java b/src/main/java/jcs/entities/FeedbackModuleBean.java
deleted file mode 100644
index af710dd7..00000000
--- a/src/main/java/jcs/entities/FeedbackModuleBean.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright 2024 frans.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jcs.entities;
-
-import java.util.ArrayList;
-import java.util.List;
-import jcs.commandStation.events.SensorEvent;
-
-/**
- * Represents 1 Feedback Module (S88) with a number of ports (usually 16)
- */
-public class FeedbackModuleBean {
-
- private Integer id;
- private Integer moduleNumber;
- private Integer portCount;
- private Integer addressOffset;
- private Integer identifier;
-
- private int[] ports;
- private int[] prevPorts;
-
- public static int DEFAULT_PORT_COUNT = 16;
-
- public FeedbackModuleBean() {
- }
-
- public Integer getId() {
- return id;
- }
-
- public void setId(Integer id) {
- this.id = id;
- }
-
- public Integer getModuleNumber() {
- return moduleNumber;
- }
-
- public void setModuleNumber(Integer moduleNumber) {
- this.moduleNumber = moduleNumber;
- }
-
- public Integer getPortCount() {
- return portCount;
- }
-
- public void setPortCount(Integer portCount) {
- this.portCount = portCount;
- if (portCount != null) {
- if (this.ports == null) {
- ports = new int[portCount];
- prevPorts = new int[portCount];
- } else {
- if (ports.length != portCount) {
- ports = new int[portCount];
- prevPorts = new int[portCount];
- }
- }
- }
- }
-
- public Integer getAddressOffset() {
- return addressOffset;
- }
-
- public void setAddressOffset(Integer addressOffset) {
- this.addressOffset = addressOffset;
- }
-
- public Integer getIdentifier() {
- return identifier;
- }
-
- public void setIdentifier(Integer identifier) {
- this.identifier = identifier;
- }
-
- public int[] getPorts() {
- return ports;
- }
-
- public void setPorts(int[] ports) {
- this.ports = ports;
- }
-
- public void setPortValue(int port, boolean active) {
- //save current values
- System.arraycopy(this.ports, 0, this.prevPorts, 0, this.ports.length);
- this.ports[port] = active ? 1 : 0;
- }
-
- public int getAccumulatedPortsValue() {
- int val = 0;
- for (int i = 0; i < ports.length; i++) {
- int portVal = 0;
- if (ports[i] == 1) {
- portVal = (int) Math.pow(2, i);
- }
- val = val + portVal;
- }
- return val;
- }
-
- public int[] getPrevPorts() {
- return prevPorts;
- }
-
- public void setPrevPorts(int[] prevPorts) {
- this.prevPorts = prevPorts;
- }
-
- public SensorBean getSensor(int port) {
- SensorBean sb = new SensorBean(id, port, ports[port]);
- return sb;
- }
-
- public boolean isPort(int port) {
- if (ports != null && port < ports.length) {
- return this.ports[port] == 1;
- } else {
- return false;
- }
- }
-
- public List getChangedSensors() {
- List changedSensors = new ArrayList<>(ports.length);
-
- for (int i = 0; i < ports.length; i++) {
- if (ports[i] != prevPorts[i]) {
- SensorBean sb = new SensorBean(moduleNumber, i + 1, ports[i]);
- SensorEvent se = new SensorEvent(sb);
- changedSensors.add(se);
- }
- }
- return changedSensors;
- }
-
- public List getSensors() {
- List sensors = new ArrayList<>(ports.length);
-
- for (int i = 0; i < ports.length; i++) {
- if (ports[i] != prevPorts[i]) {
- SensorBean sb = new SensorBean(moduleNumber, i + 1, ports[i]);
- sensors.add(sb);
- }
- }
- return sensors;
- }
-
- public String portToString() {
- StringBuilder sb = new StringBuilder();
- sb.append(" {");
- for (int i = 0; i < ports.length; i++) {
- sb.append(i + 1);
- sb.append("[");
- sb.append(ports[i]);
- sb.append("] ");
- }
- sb.append("}");
- return sb.toString();
- }
-
- @Override
- public String toString() {
- return "FeedbackModuleBean{" + "id=" + id + ", moduleNumber=" + moduleNumber + ", portCount=" + portCount + ", addressOffset=" + addressOffset + ", identifier=" + identifier + "}";
- }
-
-}
diff --git a/src/main/java/jcs/entities/LocomotiveBean.java b/src/main/java/jcs/entities/LocomotiveBean.java
index a11b8c59..74f66cb8 100644
--- a/src/main/java/jcs/entities/LocomotiveBean.java
+++ b/src/main/java/jcs/entities/LocomotiveBean.java
@@ -525,7 +525,11 @@ public String getDirection() {
}
public static Direction get(String direction) {
- return ENUM_MAP.get(direction);
+ if (direction != null) {
+ return ENUM_MAP.get(direction);
+ } else {
+ return null;
+ }
}
private static int translate2MarklinValue(String value) {
diff --git a/src/main/java/jcs/entities/SensorBean.java b/src/main/java/jcs/entities/SensorBean.java
index 74a76cf2..a9d69ca8 100755
--- a/src/main/java/jcs/entities/SensorBean.java
+++ b/src/main/java/jcs/entities/SensorBean.java
@@ -21,83 +21,96 @@
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
-import java.io.Serializable;
import java.util.Date;
import java.util.Objects;
@Table(name = "sensors", indexes = {
@Index(name = "sens_devi_cont_idx", columnList = "device_id, contact_id", unique = true)})
-public class SensorBean implements Serializable {
+public class SensorBean {
- private String id;
+ private Integer id;
private String name;
private Integer deviceId;
private Integer contactId;
private Integer status;
private Integer previousStatus;
private Integer millis;
- private Date lastUpdated;
+ private Long lastUpdated;
+ private Integer nodeId;
+ private String commandStationId;
+ private Integer busNr;
public SensorBean() {
- this(null, null, null, null, null, null, null, null);
+ this(null, null, null, null, null, null, null, 0);
}
- public SensorBean(Integer deviceId, Integer contactId, Integer status) {
- this(null, null, deviceId, contactId, status, null, null, null);
+ public SensorBean(Integer id, Integer deviceId, Integer contactId, Integer nodeId, Integer status, Integer previousStatus, String commandStationId, Integer busNr) {
+ this(id, null, deviceId, contactId, nodeId, status, previousStatus, (Integer) null, (Long) null, commandStationId, busNr);
}
- public SensorBean(Integer deviceId, Integer contactId, Integer status, Integer previousStatus, Integer millis, Date lastUpdated) {
- this(null, null, deviceId, contactId, status, previousStatus, millis, lastUpdated);
+ public SensorBean(Integer id, String name, Integer deviceId, Integer contactId, Integer nodeId, Integer status, Integer previousStatus, Integer millis, String commandStationId, Integer busNr) {
+ this(id, name, deviceId, contactId, nodeId, status, previousStatus, millis, (Long) null, commandStationId, busNr);
}
- public SensorBean(String name, Integer deviceId, Integer contactId) {
- this(null, name, deviceId, contactId, null, null, null, null);
+ public SensorBean(Integer id, String name, Integer deviceId, Integer contactId, Integer nodeId, Integer status, Integer previousStatus, Integer millis, Date lastUpdated, String commandStationId, Integer busNr) {
+ this(id, name, deviceId, contactId, nodeId, status, previousStatus, millis, (lastUpdated != null ? lastUpdated.getTime() : null), commandStationId, busNr);
}
- public SensorBean(String name, Integer deviceId, Integer contactId, Integer status, Integer previousStatus, Integer millis, Date lastUpdated) {
- this(null, name, deviceId, contactId, status, previousStatus, millis, lastUpdated);
- }
-
- public SensorBean(String id, String name, Integer deviceId, Integer contactId, Integer status, Integer previousStatus, Integer millis, Date lastUpdated) {
+ public SensorBean(Integer id, String name, Integer deviceId, Integer contactId, Integer nodeId, Integer status, Integer previousStatus, Integer millis, Long lastUpdated, String commandStationId, Integer busNr) {
this.id = id;
this.name = name;
this.status = status;
this.previousStatus = previousStatus;
this.deviceId = deviceId;
this.contactId = contactId;
+ this.nodeId = nodeId;
this.millis = millis;
this.lastUpdated = lastUpdated;
- }
+ this.commandStationId = commandStationId;
+ this.busNr = busNr;
- @Id
- @Column(name = "id", nullable = false)
- public String getId() {
- if (id == null) {
- id = generateId();
+ //TODO!
+ if (name == null) {
+ //this.name = generateName();
}
- return id;
}
- private String generateId() {
- //Format the id start with the device then "-"
- //than a 4 char contact id
- if (contactId == null) {
+ private String generateNameOld() {
+ if (deviceId != null && contactId != null && nodeId != null) {
+
+ String dn = deviceId.toString();
+ int dnl = dn.length();
+ for (int x = 0; x < 2 - dnl; x++) {
+ dn = "0" + dn;
+ }
+
+ String cn = contactId.toString();
+ int cnl = cn.length();
+ for (int x = 0; x < 4 - cnl; x++) {
+ cn = "0" + cn;
+ }
+
+ return dn + "-" + cn;
+ } else {
return null;
}
- String cn = contactId.toString();
- int cnl = cn.length();
- for (int x = 0; x < 4 - cnl; x++) {
- cn = "0" + cn;
- }
- return deviceId + "-" + cn;
}
- public void setId(String id) {
+ @Id
+ @Column(name = "id", nullable = false)
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
this.id = id;
}
@Column(name = "name", length = 255, nullable = false)
public String getName() {
+ if (name == null) {
+ //name = generateName();
+ }
return name;
}
@@ -123,6 +136,15 @@ public void setContactId(Integer contactId) {
this.contactId = contactId;
}
+ @Column(name = "node_id")
+ public Integer getNodeId() {
+ return nodeId;
+ }
+
+ public void setNodeId(Integer nodeId) {
+ this.nodeId = nodeId;
+ }
+
@Column(name = "status")
public Integer getStatus() {
return status;
@@ -141,25 +163,41 @@ public void setPreviousStatus(Integer previousStatus) {
this.previousStatus = previousStatus;
}
+ @Column(name = "command_station_id", length = 255, nullable = false)
+ public String getCommandStationId() {
+ return commandStationId;
+ }
+
+ public void setCommandStationId(String commandStationId) {
+ this.commandStationId = commandStationId;
+ }
+
+ @Column(name = "bus_nr", nullable = false)
+ public Integer getBusNr() {
+ return busNr;
+ }
+
+ public void setBusNr(Integer busNr) {
+ this.busNr = busNr;
+ }
+
@Transient
public void toggle() {
if (status == null) {
status = 0;
}
previousStatus = status;
- Date lastChanged = this.lastUpdated;
+ Long lastChanged = lastUpdated;
if (lastChanged == null) {
- lastChanged = new Date();
+ lastChanged = System.currentTimeMillis();
}
if (status == 0) {
status = 1;
} else {
status = 0;
}
- lastUpdated = new Date();
- long prev = lastChanged.getTime();
- long now = lastUpdated.getTime();
- Long m = (now - prev) / 10;
+ lastUpdated = System.currentTimeMillis();
+ Long m = (lastUpdated - lastChanged) / 10;
this.millis = m.intValue();
}
@@ -172,13 +210,34 @@ public void setMillis(Integer millis) {
this.millis = millis;
}
+ @Transient
+ public Long getLastUpdatedMillis() {
+ return this.lastUpdated;
+ }
+
+ public void setLastUpdatedMillis(Long updatedOn) {
+ this.lastUpdated = updatedOn;
+ }
+
@Column(name = "last_updated")
public Date getLastUpdated() {
- return lastUpdated;
+ if (lastUpdated != null) {
+ return new Date(lastUpdated);
+ } else {
+ return null;
+ }
}
- public void setLastUpdated(Date lastUpdated) {
- this.lastUpdated = lastUpdated;
+ public void setLastUpdated(Date updatedOn) {
+ Long prevUpdated = lastUpdated;
+ if (updatedOn != null) {
+ lastUpdated = updatedOn.getTime();
+ }
+
+ if (lastUpdated != null && prevUpdated != null) {
+ Long m = (lastUpdated - prevUpdated) / 10;
+ millis = m.intValue();
+ }
}
@Transient
@@ -191,22 +250,28 @@ public boolean isActive() {
}
public void setActive(boolean active) {
- this.status = active ? 1 : 0;
+ previousStatus = status;
+ status = active ? 1 : 0;
}
public void setPreviousActive(boolean active) {
- this.previousStatus = active ? 1 : 0;
+ previousStatus = active ? 1 : 0;
}
@Transient
public boolean isPreviousActive() {
- if (this.previousStatus != null) {
- return this.previousStatus > 0;
+ if (previousStatus != null) {
+ return previousStatus > 0;
} else {
return false;
}
}
+ @Transient
+ public boolean hasChanged() {
+ return !status.equals(previousStatus);
+ }
+
@Override
public int hashCode() {
int hash = 3;
@@ -214,10 +279,13 @@ public int hashCode() {
hash = 41 * hash + Objects.hashCode(this.name);
hash = 41 * hash + Objects.hashCode(this.deviceId);
hash = 41 * hash + Objects.hashCode(this.contactId);
+ hash = 41 * hash + Objects.hashCode(this.nodeId);
hash = 41 * hash + Objects.hashCode(this.status);
hash = 41 * hash + Objects.hashCode(this.previousStatus);
hash = 41 * hash + Objects.hashCode(this.millis);
hash = 41 * hash + Objects.hashCode(this.lastUpdated);
+ hash = 41 * hash + Objects.hashCode(this.commandStationId);
+ hash = 41 * hash + Objects.hashCode(this.busNr);
return hash;
}
@@ -245,6 +313,9 @@ public boolean equals(Object obj) {
if (!Objects.equals(this.contactId, other.contactId)) {
return false;
}
+ if (!Objects.equals(this.nodeId, other.nodeId)) {
+ return false;
+ }
if (!Objects.equals(this.status, other.status)) {
return false;
}
@@ -254,9 +325,16 @@ public boolean equals(Object obj) {
if (!Objects.equals(this.millis, other.millis)) {
return false;
}
+ if (!Objects.equals(this.commandStationId, other.commandStationId)) {
+ return false;
+ }
+ if (!Objects.equals(this.busNr, other.busNr)) {
+ return false;
+ }
return Objects.equals(this.lastUpdated, other.lastUpdated);
}
+ @Deprecated
public boolean equalsDeviceIdAndContactId(Object obj) {
if (this == obj) {
return true;
@@ -268,18 +346,43 @@ public boolean equalsDeviceIdAndContactId(Object obj) {
return false;
}
final SensorBean other = (SensorBean) obj;
+ if (!Objects.equals(this.commandStationId, other.commandStationId)) {
+ return false;
+ }
if (!Objects.equals(this.deviceId, other.deviceId)) {
return false;
}
return Objects.equals(this.contactId, other.contactId);
}
+ public boolean equalsId(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final SensorBean other = (SensorBean) obj;
+ return Objects.equals(this.id, other.id);
+ }
+
@Override
public String toString() {
- return name;
+ //return name;
+ return toLogString();
}
public String toLogString() {
+// String ids;
+// if (id == null) {
+// ids = "(" + generateId() + ")";
+// } else {
+// ids = id;
+// }
+
return "SensorBean{"
+ "id="
+ id
@@ -289,6 +392,8 @@ public String toLogString() {
+ deviceId
+ ", contactId="
+ contactId
+ + ", nodeId="
+ + nodeId
+ ", status="
+ status
+ ", previousStatus="
@@ -297,22 +402,10 @@ public String toLogString() {
+ millis
+ ", lastUpdated="
+ lastUpdated
- + '}';
+ + ", commandStationId="
+ + commandStationId
+ + ", busNr="
+ + busNr
+ + "}";
}
}
-
-// public static Integer calculateModuleNumber(int contactId) {
-// int module = (contactId - 1) / 16 + 1;
-// return module;
-// }
-// public static int calculatePortNumber(int contactId) {
-// int module = (contactId - 1) / 16 + 1;
-// int mport = contactId - (module - 1) * 16;
-// return mport;
-// }
-// public static int calculateContactId(int module, int port) {
-// //Bei einer CS2 errechnet sich der richtige Kontakt mit der Formel M - 1 * 16 + N
-// module = module - 1;
-// int contactId = module * 16;
-// return contactId + port;
- // }
diff --git a/src/main/java/jcs/entities/TileBean.java b/src/main/java/jcs/entities/TileBean.java
index 8976511b..21384242 100644
--- a/src/main/java/jcs/entities/TileBean.java
+++ b/src/main/java/jcs/entities/TileBean.java
@@ -22,7 +22,6 @@
import jakarta.persistence.Transient;
import java.awt.Point;
-import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -33,7 +32,7 @@
@Table(name = "tiles", indexes = {
@Index(name = "tile_x_y", columnList = "x, y", unique = true)})
-public class TileBean implements Serializable, Comparable {
+public class TileBean implements Comparable {
protected String id;
protected Integer x;
@@ -43,7 +42,7 @@ public class TileBean implements Serializable, Comparable {
protected String tileDirection;
protected String signalAccessoryType;
protected String accessoryId;
- protected String sensorId;
+ protected Integer sensorId;
protected List neighbours;
@@ -60,11 +59,11 @@ public TileBean(String id, TileType tileType, Orientation orientation, Direction
this(id, tileType, orientation, direction, x, y, null, null, null);
}
- public TileBean(String id, TileType tileType, Orientation orientation, Direction direction, Point center, SignalType signalType, String accessoryId, String sensorId) {
+ public TileBean(String id, TileType tileType, Orientation orientation, Direction direction, Point center, SignalType signalType, String accessoryId, Integer sensorId) {
this(id, tileType, orientation, direction, center.x, center.y, signalType, null, sensorId);
}
- public TileBean(String id, TileType tileType, Orientation orientation, Direction direction, Integer x, Integer y, SignalType signalType, String accessoryId, String sensorId) {
+ public TileBean(String id, TileType tileType, Orientation orientation, Direction direction, Integer x, Integer y, SignalType signalType, String accessoryId, Integer sensorId) {
this.id = id;
this.setTileType(tileType);
this.tileOrientation = orientation.getOrientation();
@@ -208,11 +207,11 @@ public void setAccessoryId(String accessoryId) {
}
@Column(name = "sensor_id")
- public String getSensorId() {
+ public Integer getSensorId() {
return sensorId;
}
- public void setSensorId(String sensorId) {
+ public void setSensorId(Integer sensorId) {
this.sensorId = sensorId;
}
diff --git a/src/main/java/jcs/persistence/H2PersistenceService.java b/src/main/java/jcs/persistence/H2PersistenceService.java
index 6b4e8534..64fe3b1c 100755
--- a/src/main/java/jcs/persistence/H2PersistenceService.java
+++ b/src/main/java/jcs/persistence/H2PersistenceService.java
@@ -18,14 +18,16 @@
import com.dieselpoint.norm.Database;
import com.dieselpoint.norm.DbException;
import java.awt.Image;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
+import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import javax.imageio.ImageIO;
import jcs.entities.AccessoryBean;
@@ -40,29 +42,48 @@
import jcs.entities.SensorBean;
import jcs.entities.TileBean;
import jcs.persistence.sqlmakers.H2SqlMaker;
-import jcs.ui.layout.tiles.Tile;
import org.tinylog.Logger;
public class H2PersistenceService implements PersistenceService {
- private Database database;
+ protected Database database;
- private final HashMap imageCache;
- private final HashMap functionImageCache;
+ protected final HashMap imageCache;
+ protected final HashMap functionImageCache;
+ protected final PropertyChangeSupport changeSupport;
public H2PersistenceService() {
- connect();
+ initConnect();
imageCache = new HashMap<>();
functionImageCache = new HashMap<>();
+ changeSupport = new PropertyChangeSupport(this);
+ postInit();
+ }
+
+ private void initConnect() {
+ connect();
+ }
+
+ private void postInit() {
setJCSPropertiesAsSystemProperties();
}
- private void connect() {
+ protected void connect() {
Logger.debug("Connecting to: " + System.getProperty("norm.jdbcUrl") + " with db user: " + System.getProperty("norm.user"));
database = new Database();
database.setSqlMaker(new H2SqlMaker());
}
+ @Override
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ changeSupport.addPropertyChangeListener(listener);
+ }
+
+ @Override
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ changeSupport.removePropertyChangeListener(listener);
+ }
+
@Override
public List getProperties() {
List props = database.results(JCSPropertyBean.class);
@@ -77,15 +98,15 @@ public JCSPropertyBean getProperty(String key) {
@Override
public JCSPropertyBean persist(JCSPropertyBean property) {
- JCSPropertyBean prop
- = database.where("p_key=?", property.getKey()).first(JCSPropertyBean.class);
- if (prop != null) {
+ JCSPropertyBean oldProp = database.where("p_key=?", property.getKey()).first(JCSPropertyBean.class);
+ if (oldProp != null) {
int rows = database.update(property).getRowsAffected();
Logger.trace(rows + " rows updated");
} else {
int rows = database.insert(property).getRowsAffected();
Logger.trace(rows + " rows inserted");
}
+ changeSupport.firePropertyChange("data.property", oldProp, property);
return property;
}
@@ -93,14 +114,27 @@ public JCSPropertyBean persist(JCSPropertyBean property) {
public void remove(JCSPropertyBean property) {
int rows = database.delete(property).getRowsAffected();
Logger.trace(rows + " rows deleted");
+ changeSupport.firePropertyChange("data.property.deleted", property, null);
}
@Override
- public List getSensors() {
+ public List getAllSensors() {
List sensors = database.results(SensorBean.class);
return sensors;
}
+ @Override
+ public List getSensors() {
+ String commandStationId = getDefaultCommandStation().getId();
+ return getSensorsByCommandStationId(commandStationId);
+ }
+
+ @Override
+ public List