org.bidib.wizard.mvc.main.controller.MainController Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bidibwizard-client Show documentation
Show all versions of bidibwizard-client Show documentation
jBiDiB BiDiB Wizard Client Application POM
package org.bidib.wizard.mvc.main.controller;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.PostConstruct;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.bidib.api.json.types.ConnectionPhase;
import org.bidib.jbidibc.exchange.vendorcv.VendorCvData;
import org.bidib.jbidibc.exchange.vendorcv.VendorCvError;
import org.bidib.jbidibc.exchange.vendorcv.VendorCvFactory;
import org.bidib.jbidibc.messages.enums.LcOutputType;
import org.bidib.jbidibc.messages.enums.NetBidibSocketType;
import org.bidib.jbidibc.messages.enums.SysErrorEnum;
import org.bidib.jbidibc.messages.exception.InvalidConfigurationException;
import org.bidib.jbidibc.messages.helpers.Context;
import org.bidib.jbidibc.messages.helpers.DefaultContext;
import org.bidib.jbidibc.messages.port.PortConfigValue;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.NodeUtils;
import org.bidib.jbidibc.messages.utils.ThreadFactoryBuilder;
import org.bidib.wizard.api.event.ErrorEvent;
import org.bidib.wizard.api.locale.Resources;
import org.bidib.wizard.api.model.Accessory;
import org.bidib.wizard.api.model.NodeInterface;
import org.bidib.wizard.api.model.connection.BidibConnection;
import org.bidib.wizard.api.model.event.NodeStatusEvent;
import org.bidib.wizard.api.model.event.NodeStatusEvent.StatusIdentifier;
import org.bidib.wizard.api.model.listener.NodeErrorListener;
import org.bidib.wizard.api.model.listener.NodeListListener;
import org.bidib.wizard.api.model.listener.NodeSelectionListener;
import org.bidib.wizard.api.notification.NodeStatusInfo.LoadStatus;
import org.bidib.wizard.api.service.console.ConsoleColor;
import org.bidib.wizard.api.service.console.ConsoleService;
import org.bidib.wizard.api.service.node.BoosterService;
import org.bidib.wizard.api.service.node.CommandStationService;
import org.bidib.wizard.api.service.node.NodeService;
import org.bidib.wizard.api.service.node.SwitchingNodeService;
import org.bidib.wizard.api.utils.PortListUtils;
import org.bidib.wizard.client.common.alert.BidibAlert;
import org.bidib.wizard.client.common.component.LabeledDisplayItems;
import org.bidib.wizard.client.common.controller.CvDefinitionPanelControllerInterface;
import org.bidib.wizard.client.common.event.BidibConnectionEvent;
import org.bidib.wizard.client.common.event.MainControllerBoosterOnEvent;
import org.bidib.wizard.client.common.event.MainControllerEvent;
import org.bidib.wizard.client.common.event.MenuEvent;
import org.bidib.wizard.client.common.view.DockUtils;
import org.bidib.wizard.client.common.view.statusbar.StatusBar;
import org.bidib.wizard.common.context.DefaultApplicationContext;
import org.bidib.wizard.common.exception.ConnectionException;
import org.bidib.wizard.common.exception.InternalStartupException;
import org.bidib.wizard.common.labels.WizardLabelWrapper;
import org.bidib.wizard.common.model.settings.MiscSettingsInterface;
import org.bidib.wizard.common.model.settings.WizardSettingsInterface;
import org.bidib.wizard.common.script.node.types.TargetType;
import org.bidib.wizard.common.service.SettingsService;
import org.bidib.wizard.config.MainViewFactory;
import org.bidib.wizard.config.PairingControllerFactory;
import org.bidib.wizard.core.model.connection.ConnectionRegistry;
import org.bidib.wizard.core.service.ConnectionService;
import org.bidib.wizard.model.ports.BacklightPort;
import org.bidib.wizard.model.ports.LightPort;
import org.bidib.wizard.model.ports.Port;
import org.bidib.wizard.model.ports.ServoPort;
import org.bidib.wizard.model.ports.SwitchPairPort;
import org.bidib.wizard.model.ports.SwitchPort;
import org.bidib.wizard.model.status.BoosterStatus;
import org.bidib.wizard.model.status.CommandStationStatus;
import org.bidib.wizard.mvc.common.exception.NodeChangeVetoException;
import org.bidib.wizard.mvc.console.controller.ConsoleController;
import org.bidib.wizard.mvc.main.controller.exception.CloseAbortedException;
import org.bidib.wizard.mvc.main.controller.listener.AlertListener;
import org.bidib.wizard.mvc.main.controller.listener.AlertListener.AlertAction;
import org.bidib.wizard.mvc.main.model.ConnectionPhaseModel;
import org.bidib.wizard.mvc.main.model.MainModel;
import org.bidib.wizard.mvc.main.view.MainNodeListActionListener;
import org.bidib.wizard.mvc.main.view.MainView;
import org.bidib.wizard.mvc.main.view.exchange.NodeExchangeHelper;
import org.bidib.wizard.mvc.tips.controller.TipOfDayClosedListener;
import org.bidib.wizard.mvc.tips.controller.TipOfDayController;
import org.bidib.wizard.startup.WizardStartupParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.xml.sax.SAXException;
import com.jidesoft.swing.FolderChooser;
import com.vlsolutions.swing.docking.Dockable;
import com.vlsolutions.swing.docking.DockingContext;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
public class MainController implements MainControllerInterface {
private static final Logger LOGGER = LoggerFactory.getLogger(MainController.class);
private final MainModel mainModel;
@Autowired
private MainViewFactory mainViewFactory;
private MainView mainView;
@Autowired
private MainNodeListActionListener mainNodeListActionListener;
private final ScheduledExecutorService selectedNodeChangeWorker =
Executors
.newScheduledThreadPool(1,
new ThreadFactoryBuilder().setNameFormat("selectedNodeChangeWorkers-thread-%d").build());
@Autowired
private CommandStationService commandStationService;
@Autowired
private AlertController alertController;
@Autowired
private TipOfDayController tipOfDayController;
@Autowired
private MiscSettingsInterface miscSettings;
@Autowired
private WizardSettingsInterface wizardSettings;
@Autowired
private SettingsService settingsService;
@Autowired
private ConnectionService connectionService;
@Autowired
private NodeService nodeService;
@Autowired
private BoosterService boosterService;
@Autowired
private SwitchingNodeService switchingNodeService;
@Autowired
private StatusBar statusBar;
@Autowired
private WizardLabelWrapper wizardLabelWrapper;
@Autowired
private ApplicationContext context;
@Autowired
private CvDefinitionPanelControllerInterface cvDefinitionPanelController;
@Autowired
private WizardStartupParams startupParams;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Autowired
private ConsoleService consoleService;
private final ConnectionPhaseModel connectionPhaseModel;
private CompositeDisposable compDisposable = new CompositeDisposable();
private CompositeDisposable compDispConnectionStatusChanges = new CompositeDisposable();
@Autowired
private PairingControllerFactory pairingControllerFactory;
private PairingController pairingController;
private final String connectionId;
@Autowired
private DockingContext dockingContext;
private AtomicBoolean startupPassed = new AtomicBoolean();
private final ScheduledExecutorService openConnectionWorker;
/**
* the startup workers
*/
private final ScheduledExecutorService startupWorkers =
Executors
.newScheduledThreadPool(1, new ThreadFactoryBuilder().setNameFormat("startupWorkers-thread-%d").build());
public MainController(final MainModel model, final ConnectionPhaseModel connectionPhaseModel, String connectionId) {
LOGGER.info("Created MainController, provided connectionId: {}", connectionId);
this.connectionId = connectionId;
this.connectionPhaseModel = connectionPhaseModel;
connectionPhaseModel.setConnectionId(this.connectionId);
this.mainModel = model;
final ThreadFactory namedThreadFactory =
new ThreadFactoryBuilder().setNameFormat("openConnectionWorkers-thread-%d").build();
this.openConnectionWorker = Executors.newScheduledThreadPool(1, namedThreadFactory);
}
@PostConstruct
public void init() {
LOGGER.info("Initialize the main controller.");
final NodeErrorListener nodeErrorListener = new NodeErrorListener() {
@Override
public void nodeErrorChanged(
final NodeInterface node, final SysErrorEnum sysError, final String reasonData) {
// notify the console
SwingUtilities.invokeLater(() -> {
try {
// ensure console is visible
ConsoleController.ensureConsoleVisible();
LOGGER
.info("The error state has changed for node: {}, sysError: {}, reasonData: {}", node,
sysError, reasonData);
String reason = null;
if (sysError != null) {
reason = reasonData;
// add line
String consoleLineText =
String.format("The node %s is set to error state: %s", node, reason);
LOGGER.warn(consoleLineText);
consoleService.addConsoleLine(ConsoleColor.red, consoleLineText);
}
else {
reason = reasonData;
if (StringUtils.isNotBlank(reason)) {
// add line
String consoleLineText =
String.format("The node %s is set to error state: %s", node, reason);
LOGGER.warn(consoleLineText);
consoleService.addConsoleLine(ConsoleColor.red, consoleLineText);
}
else {
// add line
consoleService
.addConsoleLine(ConsoleColor.black,
String.format("The error state on node %s is cleared.", node));
}
}
}
catch (Exception ex) {
LOGGER
.warn("Add new error line to console failed, sysError: {}, reasonData: {}", sysError,
node.getReasonData(), ex);
}
});
}
@Override
public void nodeStallChanged(final NodeInterface node, final Boolean nodeStall) {
// notify the console
SwingUtilities.invokeLater(() -> {
try {
// ensure console is visible
ConsoleController.ensureConsoleVisible();
// add line
consoleService
.addConsoleLine(ConsoleColor.black,
String
.format("The node %s is %s", node,
(nodeStall != null && nodeStall.booleanValue()) ? "stall" : "not stall"));
}
catch (Exception ex) {
LOGGER.warn("Add new error line to console failed", ex);
}
});
}
};
mainModel.setNodeErrorListener(nodeErrorListener);
}
public boolean hasStartupPassed() {
return startupPassed.get();
}
@Override
public void clearNodes() {
LOGGER.info("Clear the nodes from the model.");
mainModel.getStatusModel().setCd(false);
mainModel.getStatusModel().setRx(false);
mainModel.getStatusModel().setTx(false);
mainModel.getStatusModel().setCd(false);
mainModel.clearNodes();
}
private void loadLabels() {
// make sure the labels directory is available and write enabled
String labelPath = miscSettings.getBidibConfigDir();
LOGGER.info("Check if the directory for labels exists: {}", labelPath);
boolean labelPathIsValid = false;
do {
try {
File dir = FileUtils.getFile(labelPath);
// Path dir = Paths.get(labelPath);
if (!dir.exists()) {
// if (!Files.exists(dir)) {
LOGGER.info("Try to create the directory for labels: {}", labelPath);
try {
// dir = Files.createDirectories(dir);
FileUtils.forceMkdir(dir);
LOGGER.info("Created new directory for labels: {}", dir);
}
catch (IOException ioex) {
LOGGER.warn("Create new directory for labels failed.", ioex);
if (SystemUtils.IS_OS_LINUX && labelPath.startsWith("/home/")) {
String userName = System.getProperty("user.name");
// check if the name is correct
int beginIndex = 6;
int endIndex = labelPath.indexOf("/", beginIndex);
String configuredUserName = labelPath.substring(beginIndex, endIndex);
if (!userName.equals(configuredUserName)) {
LOGGER
.warn(
"The current username '{}' does not match the configured username '{}' for the label path.",
userName, configuredUserName);
Object[] options =
{ Resources.getString(NodeExchangeHelper.class, "labeldirerror.correct"),
Resources.getString(NodeExchangeHelper.class, "labeldirerror.ignore") };
int answer =
JOptionPane
.showOptionDialog(JOptionPane.getFrameForComponent(null),
Resources
.getString(NodeExchangeHelper.class,
"labeldirerror.message_username_mismatch",
new Object[] { userName, configuredUserName, labelPath }),
Resources.getString(NodeExchangeHelper.class, "labeldirerror.title"),
JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE, null, options,
options[0]);
if (answer == JOptionPane.YES_OPTION) {
labelPath = "/home/" + userName + labelPath.substring(endIndex);
LOGGER.info("The user selected to correct the label path: {}", labelPath);
miscSettings.setBidibConfigDir(labelPath);
settingsService.storeSettings();
// dir = Paths.get(labelPath);
dir = FileUtils.getFile(labelPath);
// if (!Files.exists(dir)) {
if (!dir.exists()) {
LOGGER.info("Try to create the directory for labels: {}", labelPath);
try {
// dir = Files.createDirectories(dir);
FileUtils.forceMkdir(dir);
LOGGER.info("Created new directory for labels: {}", dir);
}
catch (IOException ioex2) {
LOGGER.warn("Create new directory for labels failed.", ioex);
}
}
}
}
}
}
}
if (!dir.exists()) {
LOGGER.warn("The directory for labels is not available. Check permission on path: {}", labelPath);
throw new IllegalStateException(
"The directory for labels is not available. Check permission on path: " + labelPath);
}
if (!dir.canWrite()) {
LOGGER
.warn("The directory for labels is not write enabled. Check permission on path: {}", labelPath);
throw new IllegalStateException(
"The directory for labels is not write enabled. Check permission on path: " + labelPath);
}
LOGGER.info("The label directory is write enabled: {}", labelPath);
labelPathIsValid = true;
}
catch (Exception ex) {
LOGGER
.warn(
"The directory for labels is not available or is not write enabled. Check permission on path: {}",
labelPath, ex);
int choice =
JOptionPane
.showOptionDialog(JOptionPane.getFrameForComponent(null), Resources
.getString(NodeExchangeHelper.class, "labeldirerror.message", new Object[] { labelPath }),
Resources.getString(NodeExchangeHelper.class, "labeldirerror.title"),
JOptionPane.OK_CANCEL_OPTION, JOptionPane.ERROR_MESSAGE, null, null, null);
// interpret the user's choice
if (choice == JOptionPane.CANCEL_OPTION) {
System.exit(0);
}
else {
// let the user change the label directory
FolderChooser chooser = new FolderChooser();
String userHome = System.getProperty("user.home");
final File bidibDefaultDir = new File(userHome, ".bidib");
chooser.setSelectedFolder(bidibDefaultDir);
int returnVal = chooser.showOpenDialog(JOptionPane.getFrameForComponent(null));
if (returnVal == FolderChooser.APPROVE_OPTION) {
LOGGER.info("You chose to open this folder: {}", chooser.getSelectedFile().getPath());
labelPath = chooser.getSelectedFile().getPath();
}
else {
System.exit(0);
}
}
}
}
while (!labelPathIsValid);
// check if the label path was changed
if (!labelPath.equals(miscSettings.getBidibConfigDir())) {
LOGGER.info("Set the new label path: {}", labelPath);
miscSettings.setBidibConfigDir(labelPath);
settingsService.storeSettings();
}
startupWorkers.submit(() -> {
LOGGER.info("Start load and register label factories.");
final AtomicBoolean startupPassed = new AtomicBoolean(false);
try {
reloadLabels(null);
startupPassed.set(true);
}
catch (Exception ex) {
LOGGER.warn("Load labels failed.", ex);
}
LOGGER.info("Load and register labels passed. Invoke open UI.");
SwingUtilities.invokeLater(() -> {
LOGGER.info("Open the UI from AWT-thread.");
if (startupPassed.get()) {
prepareAndOpenUI();
}
else {
JOptionPane
.showMessageDialog(null,
Resources.getString(MainController.class, "mandatory-directory-missing.text"),
Resources.getString(MainController.class, "mandatory-directory-missing.title"),
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
LOGGER.info("Open the UI has finished.");
});
});
}
/**
* Reload the labels of the node.
*
* @param uniqueId
* the unique id of the node
*/
private void reloadLabels(Long uniqueId) {
LOGGER.info("Reload labels for uniqueId: {}", uniqueId);
if (uniqueId != null) {
wizardLabelWrapper.loadLabels(uniqueId);
}
}
private boolean setDefaultCursor() {
LOGGER.info("Set default cursor");
return mainView.setBusy(false);
}
private boolean setWaitCursor() {
LOGGER.info("Set wait cursor");
return mainView.setBusy(true);
}
private PairingController getPairingController() {
if (this.pairingController == null) {
LOGGER.info("Create the pairing controller.");
this.pairingController = pairingControllerFactory.create(statusBar, connectionService);
}
return this.pairingController;
}
/**
* Start the main controller.
*/
public void start() {
LOGGER.info("Start the main controller");
DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_MAIN_CONTROLLER, this);
loadLabels();
}
private void prepareAndOpenUI() {
LOGGER.info("Open the UI.");
this.mainView = mainViewFactory.create(settingsService);
// subscribe to information of initial load of node tree data
Disposable dispNodeStatusInfoChanges = connectionService.subscribeNodeStatusInfoChanges(nsi -> {
LOGGER.info("The node status info has changed: {}", nsi);
if (this.connectionId.equals(nsi.getConnectionId())) {
if (nsi.getLoadStatus() == LoadStatus.FINISHED) {
LOGGER
.info("The initial load of the node tree data has finished. Total number of nodes: {}",
nsi.getArgs());
mainModel.signalInitialLoadFinished();
String statusText = null;
if (nsi.getArgs() != null && nsi.getArgs().length > 0) {
statusText = Resources.formatString(nsi.getResourceKey(), nsi.getArgs());
}
else {
statusText = "The initial load of the node tree data has finished.";
}
statusBar.setStatusText(statusText);
}
else if (nsi.getLoadStatus() == LoadStatus.UNKNOWN) {
try {
String statusText = Resources.formatString(nsi.getResourceKey(), nsi.getArgs());
statusBar.setStatusText(statusText);
}
catch (Exception ex) {
LOGGER.warn("Format and display the status bar message failed: {}", nsi, ex);
}
}
}
}, error -> {
LOGGER.warn("The initial load of the node tree data has failed.", error);
});
compDisposable.add(dispNodeStatusInfoChanges);
// subscribe to changes of connection status
Disposable dispConnectionStatus = connectionService.subscribeConnectionStatusChanges(ci -> {
String connectionId = ci.getConnectionId();
if (this.connectionId.equals(connectionId)) {
// publish the change of the connection status
publishStatus(connectionId, ci.getConnectionState().getActualPhase(), null);
}
}, error -> {
LOGGER.warn("Subscription to connection status signalled an error: ", error);
publishStatus(error);
});
compDisposable.add(dispConnectionStatus);
// subscribe to connection actions
Disposable dispConnectionAction = connectionService.subscribeConnectionActions(ci -> {
String connectionId = ci.getConnectionId();
if (this.connectionId.equals(connectionId)) {
final PairingController pairingController = getPairingController();
// publish the change of the connection status
pairingController.publishAction(connectionId, ci.getMessageKey(), ci.getContext());
}
}, error -> {
LOGGER.warn("Subscription to connection action signalled an error: ", error);
publishAction(error);
});
compDisposable.add(dispConnectionAction);
mainView.createComponents();
// layout the frame content
mainView.prepareFrame();
// configure the main view
DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_MAIN_FRAME, mainView.getFrame());
LOGGER.info("Fetched startup params: {}", startupParams);
// check the startup mode
if (startupParams != null) {
String startupMode = startupParams.getStartupMode();
if (WizardStartupParams.MODE_ICONIFIED.equalsIgnoreCase(startupMode)) {
mainView.getFrame().setExtendedState(JFrame.ICONIFIED);
}
}
// show the frame
mainView.setVisible(true);
mainView.bringWindowToFront();
// display the connect hint in the status bar
mainView.setStatusText(Resources.getString(getClass(), "connectHint"), StatusBar.DISPLAY_NORMAL);
// add the node list listener
mainView.addNodeListListener(mainNodeListActionListener);
DefaultApplicationContext
.getInstance()
.register(DefaultApplicationContext.KEY_MAIN_NODELIST_ACTION_LISTENER, mainNodeListActionListener);
// add the node selection listener
mainView.addNodeListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(final ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
LOGGER.info("The list selection has changed on the node list: {}", e.getSource());
final LabeledDisplayItems nodeList =
(LabeledDisplayItems) e.getSource();
// get the new selected node
final NodeInterface node = nodeList.getSelectedItem();
LOGGER
.info("Value changed, selected node in nodeList: {}, selectedNode in mainModel: {}", node,
mainModel.getSelectedNode());
// do not load the node if it is already selected
if (node == null || node.equals(mainModel.getSelectedNode())) {
LOGGER.info("The node is not available or already selected!");
return;
}
// TODO if the node is partially loaded already we must trigger the load of the configuration and
// show the details after the load has finished
if (StatusIdentifier.InitialLoadPartiallyFinished == node.getNodeLoadStatusIdentifier()) {
// we must subscribe to node events to detect when the load has finished and all nodes have
// finished loading the configuration and port status
// TODO maybe register the disposable with the node to allow dispose when node is loaded twice
// bu has not finished before
final CompositeDisposable disposable = new CompositeDisposable();
final BidibConnection connection =
MainController.this.connectionService.find(ConnectionRegistry.CONNECTION_ID_MAIN);
LOGGER
.info(
"Found partially loaded node that is selected to display the details. Subscribe to nodeEvents on connection: {}",
connection);
try {
final Disposable dispNodeEventSubscription = connection.subscribeNodeEvents(nodeEvent -> {
LOGGER.info("Received nodeEvent: {}", nodeEvent);
if (nodeEvent instanceof NodeStatusEvent) {
final NodeStatusEvent nse = (NodeStatusEvent) nodeEvent;
handleNodeStatusEvent(connection, nse, disposable, node,
loadedNode -> selectNode(loadedNode));
}
}, err -> {
LOGGER.warn("Received error from nodeEvent subject.", err);
}, () -> {
LOGGER.info("NodeEventSubject has completed.");
});
disposable.add(dispNodeEventSubscription);
LOGGER.info("Finished subscribe to nodeEvents on connection: {}", connection);
MainController.this.nodeService
.loadNodeConfiguration(ConnectionRegistry.CONNECTION_ID_MAIN, node);
}
finally {
LOGGER.info("Load the partially loaded node is initiated, node: {}", node);
}
return;
}
// make sure the initial loading of the tree structure has finished
if (StatusIdentifier.InitialLoadFinished != node.getNodeLoadStatusIdentifier()) {
LOGGER
.warn(
"The initial load of the nodeTab has not finished yet. Cancel selection and load of node details: {}",
node.toNodeAddressAndUniqueString());
statusBar
.setStatusText(String
.format(Resources.getString(MainController.class, "load-config-not-finished"),
node.toNodeAddressAndUniqueString()));
return;
}
selectNode(node);
}
}
});
// Add the CV definition request listener
mainView
.addCvDefinitionRequestListener(new MainCvDefinitionRequestListener(this.mainModel, nodeService,
connectionId, statusBar, mainView.getFrame()));
// add the window listener
mainView.setWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
LOGGER.info("The main window will be closed.");
// ask the user to save all nodes if running simulation
boolean isSimulation = connectionService.isSimulation(MainController.this.connectionId);
LOGGER.info("The current connection is a simulation: {}", isSimulation);
if (isSimulation) {
boolean continueClose =
showConfirmDialog(
Resources.getString(MainController.class, "close-simulation-connection.message"),
Resources.getString(MainController.class, "close-simulation-connection.title"));
if (!continueClose) {
LOGGER.info("The user cancelled the close main window operation.");
return;
}
}
// }
LOGGER.info("Close main window operation was not aborted.");
stop();
}
});
this.startupPassed.set(true);
ConsoleController.ensureConsoleVisible();
// show the booster table
if (wizardSettings.isShowBoosterTable()) {
LOGGER.info("Show the booster table is selected in preferences.");
// open the booster table ...
try {
this.applicationEventPublisher.publishEvent(new MenuEvent(MenuEvent.Action.boosterTable));
}
catch (Exception ex) {
LOGGER.warn("Open booster table failed.", ex);
}
}
// show the RXTX log view
if (wizardSettings.isShowRxTxView()) {
LOGGER.info("Show the RXTX logview is selected in preferences.");
// open the booster table ...
try {
this.applicationEventPublisher.publishEvent(new MenuEvent(MenuEvent.Action.rxtxView));
}
catch (Exception ex) {
LOGGER.warn("Open RXTX logview failed.", ex);
}
}
if (miscSettings.isLoadWorkspaceAtStartup()) {
String workspacePath = this.miscSettings.getWorkspacePath();
File loadFile = null;
try {
loadFile = new File(workspacePath, "workspace.xml");
LOGGER.info("Load workspace from file: {}", loadFile);
if (!loadFile.getParentFile().exists()) {
throw new IllegalArgumentException("The requested file is not available: " + loadFile);
}
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(loadFile))) {
dockingContext.readXML(in);
LOGGER.info("Load workspace passed.");
}
catch (IOException | ParserConfigurationException | SAXException ioe) {
LOGGER.warn("Load workspace failed.", ioe);
}
}
catch (Exception ex) {
LOGGER.warn("Load workspace from file failed.", ex);
}
}
subscribeConnectionStatusChanges();
// evaluate the auto connect startup parameters
boolean isAutoConnect = false;
if (startupParams != null) {
isAutoConnect = startupParams.isAutoConnect();
}
if (isAutoConnect) {
LOGGER.info("Queue the autoConnect task.");
SwingUtilities.invokeLater(() -> {
// trigger the autoConnect
try {
LOGGER.info("Try to autoconnect.");
MainController.this.applicationEventPublisher
.publishEvent(new BidibConnectionEvent(connectionId, BidibConnectionEvent.Action.connect));
}
catch (Exception ex) {
LOGGER.warn("Autoconnect failed: {}", ex);
}
});
}
else {
if (wizardSettings.isUseHotPlugController()) {
try {
LOGGER.info("Create and start AlertController.");
alertController.start();
LOGGER.info("Create and start AlertController has finished.");
}
catch (Exception ex) {
LOGGER.warn("Start AlertController for USB devices failed.", ex);
}
catch (Error err) {
LOGGER.warn("Start AlertController for USB devices failed.", err);
}
}
else {
LOGGER.info("The usage of hotplug controller is disabled in preferences.");
}
}
showTipOfDay();
}
private void selectNode(final NodeInterface node) {
// select the node details tab
try {
String searchKey = "tabPanel";
LOGGER.info("Search for view with key: {}", searchKey);
Dockable tabPanel = mainView.getDesktop().getContext().getDockableByKey(searchKey);
DockUtils.selectWindow(tabPanel);
}
catch (Exception ex) {
LOGGER.warn("Select tabPanel failed.", ex);
}
LOGGER.info("Set the new selected node in the model: {}", node);
try {
mainModel.setSelectedNode(node, false);
}
catch (NodeChangeVetoException ex) {
LOGGER.warn("Change the selected node was vetoed: {}", ex.getMessage());
mainView
.setStatusText(String
.format(Resources.getString(MainController.class, "change-node-vetoed"),
mainModel.getSelectedNode()),
StatusBar.DISPLAY_NORMAL);
// ask the user if he wants to discard the pending changes
int result =
JOptionPane
.showConfirmDialog(mainView.getFrame(),
Resources.getString(MainController.class, "discardPendingChanges.text"),
Resources.getString(MainController.class, "discardPendingChanges.title"),
JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
if (result != JOptionPane.OK_OPTION) {
return;
}
// force the node change
mainModel.setSelectedNode(node, true);
}
// use an executor
selectedNodeChangeWorker.schedule(() -> {
LOGGER.info("Process node changed starting, Node-changed-thread-{}", System.currentTimeMillis());
try {
SwingUtilities.invokeLater(() -> setWaitCursor());
StopWatch sw = new StopWatch();
sw.start();
mainView
.setStatusText(String.format(Resources.getString(MainController.class, "load-config"), node),
StatusBar.DISPLAY_NORMAL);
final Context context = new DefaultContext();
// fetch the cv definition
LOGGER.info("Load the CV definition because no vendorCV is stored in node: {}", node);
VendorCvData vendorCV = cvDefinitionPanelController.loadCvDefinition(context, node);
boolean nodeHasLimitations = true;
if (node != null) {
// ignore sys errors
nodeHasLimitations = node.isBootloaderNode() || node.isNodeHasError(true);
LOGGER.info("The node was checked for limitiations: {}", nodeHasLimitations);
}
sw.stop();
LOGGER.info("Process node changed took: {}", sw);
if (vendorCV != null) {
mainView
.setStatusText(
String.format(Resources.getString(MainController.class, "load-config-finished"), node, sw),
StatusBar.DISPLAY_NORMAL);
}
else {
List vendorCVErrors =
context.get(VendorCvFactory.VENDORCV_ERRORS, List.class, Collections.emptyList());
if (CollectionUtils.isNotEmpty(vendorCVErrors)) {
LOGGER.warn("Load vendor CV data from files failed: {}", vendorCVErrors);
mainView
.setStatusText(String
.format(
Resources
.getString(MainController.class, "load-config-finished-load-vendorcv-failed"),
node, sw),
StatusBar.DISPLAY_ERROR);
}
else {
mainView
.setStatusText(String
.format(Resources.getString(MainController.class, "load-config-finished-no-vendorcv"),
node, sw),
StatusBar.DISPLAY_NORMAL);
}
}
}
catch (Exception ex) {
LOGGER.warn("Process node change caused an error.", ex);
}
finally {
SwingUtilities.invokeLater(() -> setDefaultCursor());
LOGGER.info("Process node changed finished.");
}
}, 0, TimeUnit.MILLISECONDS);
}
private void subscribeConnectionStatusChanges() {
LOGGER.info("Subscribe to connection status changes. Currently configured connectionId: {}", this.connectionId);
// register for connection events on connection service because we want to know if the main connection is
// opened
Disposable dispConnectionStatusChanges = this.connectionService.subscribeConnectionStatusChanges(ci -> {
LOGGER.info("The connection status has changed: {}", ci);
if (this.connectionId.equals(ci.getConnectionId())) {
LOGGER.info("The status of the configured connection has changed.");
if (ci.getConnectionState().getActualPhase() == ConnectionPhase.CONNECTING) {
LOGGER.info("Connection is now connecting, connectionId: {}", this.connectionId);
try {
final BidibConnection conn = MainController.this.connectionService.find(this.connectionId);
if (conn != null) {
LOGGER.info("Set the node provider to the model.");
MainController.this.mainModel.setNodeProvider(conn.getNodeProvider());
}
else {
LOGGER
.warn(
"Cannot set the node provider in the model because no connection found with id: {}",
this.connectionId);
}
}
catch (Exception ex) {
LOGGER
.warn(
"Find the connection and set the node provider in the model failed. Current connectionId: {}",
this.connectionId, ex);
}
compDispConnectionStatusChanges.dispose();
compDispConnectionStatusChanges.clear();
}
}
}, err -> {
LOGGER.warn("Subscription to connection status changes caused an error.", err);
if (compDispConnectionStatusChanges != null) {
compDispConnectionStatusChanges.dispose();
compDispConnectionStatusChanges.clear();
}
}, () -> {
LOGGER.info("The subscription to connection status changes has completed.");
});
compDispConnectionStatusChanges.add(dispConnectionStatusChanges);
}
private void showTipOfDay() {
if (wizardSettings.isShowTipOfDay()) {
LOGGER.info("Show tip of days.");
final List> alertList = new LinkedList<>();
final AlertListener alertListener = new AlertListener() {
@Override
public void alertAdded(final BidibAlert> alert, AlertAction alertAction) {
LOGGER.info("Alert added: {}", alert);
switch (alertAction) {
case DEVICE_ADDED:
alertList.add(alert);
break;
default:
alertList.remove(alert);
break;
}
}
@Override
public void alertRemoved(final BidibAlert> alert) {
LOGGER.info("Alert removed: {}", alert);
}
};
if (alertController != null) {
alertController.addAlertListener(alertListener);
}
final TipOfDayClosedListener listener = new TipOfDayClosedListener() {
@Override
public void closed() {
LOGGER.info("The tips of day dialog was closed. Show an alert if available.");
for (BidibAlert> alert : alertList) {
LOGGER.info("Show alert: {}", alert);
alertController.showAlert(alert, AlertAction.DEVICE_ADDED, mainView.getFrame());
}
// remove the alerts from the list
alertList.clear();
if (alertController != null) {
alertController.removeAlertListener(alertListener);
}
}
};
try {
tipOfDayController.start(listener);
}
catch (Exception ex) {
LOGGER.warn("Start the tip of day controller failed.", ex);
}
}
else {
LOGGER.info("Show tip of days is disabled in settings.");
}
}
@Override
public void stop() {
LOGGER.info("Stop the main controller.");
selectedNodeChangeWorker.shutdownNow();
compDispConnectionStatusChanges.dispose();
mainView.saveWindowPosition();
try {
LOGGER.info("Dispose the compDisposable: {}", compDisposable);
compDisposable.dispose();
}
catch (Exception ex) {
LOGGER.warn("Dispose the compDisposable failed.", ex);
}
if (alertController != null) {
try {
alertController.stopWatcher();
}
catch (Exception ex) {
LOGGER.warn("Stop usbHotPlugController failed.", ex);
}
}
mainView.performShutdown();
mainView.setVisible(false);
mainView.getFrame().dispose();
int exitCode = SpringApplication.exit(this.context, () -> 0);
LOGGER.info("Exit with exitCode: {}", exitCode);
System.exit(exitCode);
}
@Override
public void savePendingChanges() {
LOGGER.info("Save the pending changes of the current selected node.");
mainView.savePendingChanges();
}
@Override
public void addNodeListListener(NodeListListener nodeListListener) {
LOGGER.info("Add new nodeListListener: {}", nodeListListener);
mainModel.addNodeListListener(nodeListListener);
}
@Override
public void removeNodeListListener(NodeListListener nodeListListener) {
LOGGER.info("Remove nodeListListener: {}", nodeListListener);
mainModel.removeNodeListListener(nodeListListener);
}
@Override
public void addNodeSelectionListListener(NodeSelectionListener l) {
mainModel.addNodeSelectionListener(l);
}
@Override
public void removeNodeSelectionListener(NodeSelectionListener l) {
mainModel.removeNodeSelectionListener(l);
}
@EventListener
public void handleConnectionEvent(final BidibConnectionEvent event) {
LOGGER.info("Received connection event: {}", event);
SwingUtilities.invokeLater(() -> {
if (this.connectionId.equals(event.getConnectionId())) {
switch (event.getAction()) {
case connect:
openConnection();
break;
case disconnect:
try {
closeConnection();
}
catch (CloseAbortedException ex) {
LOGGER.warn("Close connection was aborted, reason: {}", ex.getMessage());
}
break;
default:
break;
}
}
});
}
private final Object openThreadLock = new Object();
private Future> openConnectionFuture;
@Override
public void openConnection() {
LOGGER.info("### Open the connection to BiDiB.");
// create the context
final Context context = new DefaultContext();
doOpenConnection(context);
}
private void doOpenConnection(final Context context) {
// Use the connection service to connect and disconnect
if (connectionService.isConnected(this.connectionId)) {
LOGGER.warn("The is opened already. Skip open connection.");
return;
}
synchronized (openThreadLock) {
// use a thread to open the port
LOGGER.info("Start a thread to open the port.");
if (openConnectionFuture != null) {
LOGGER.warn("An openThread is already running!!!!");
return;
}
final String openConnectionId = this.connectionId;
// keep the openThread as an instance variable
final Runnable openThread = () -> {
LOGGER.info("The open thread is starting.");
try {
final Function afterCreateOrFind = conn -> {
LOGGER.info("Open connection has passed, connectionId: {}", this.connectionId);
// if (localHostHandlerFactory != null) {
// LOGGER.info("Create the localHost handler.");
//
// // create the localhost handler
// BidibDistributedMessageListener localHostHandler =
// localHostHandlerFactory.createLocalHostHandler(conn);
// context.register(BidibDistributedMessageListener.class.getSimpleName(), localHostHandler);
// }
return conn;
};
connectionService.connect(openConnectionId, afterCreateOrFind, afterCreateOrFind, context);
}
catch (ConnectionException | InternalStartupException ex) {
LOGGER.warn("Connect failed.", ex);
String message = ex.getMessage();
if (StringUtils.isNotBlank(ex.getUri())) {
StringBuilder sb = new StringBuilder(ex.getUri());
sb.append("\r\n").append(ex.getMessage());
message = sb.toString();
}
showErrorDialog(Resources.getString(MainController.class, "open-connection.failed", message),
Resources.getString(MainController.class, "open-connection.title"));
}
LOGGER.info("+++ The openThread has finished.");
openConnectionFuture = null;
};
LOGGER.info("Start the open thread.");
openConnectionFuture = this.openConnectionWorker.submit(openThread);
}
}
@Override
public void listenNetBidib() {
LOGGER.info("### Listen for incomng netBiDiB connections.");
final Context context = new DefaultContext();
// TODO make the constants for the keys
context.register(NetBidibSocketType.class.getSimpleName(), NetBidibSocketType.serverSocket);
doOpenConnection(context);
}
@Override
public void closeConnection() {
LOGGER.info("### Close the connection to BiDiB.");
// ask the user to save all nodes if running simulation
boolean isSimulation = connectionService.isSimulation(this.connectionId);
LOGGER.info("The current connection is a simulation: {}", isSimulation);
if (isSimulation) {
boolean continueClose =
showConfirmDialog(Resources.getString(MainController.class, "close-simulation-connection.message"),
Resources.getString(MainController.class, "close-simulation-connection.title"));
if (!continueClose) {
LOGGER.info("The user cancelled the disconnect.");
throw new CloseAbortedException("The user cancelled the disconnect.");
}
}
// check if we have pending changes
try {
this.mainModel.setSelectedNode(null, false);
}
catch (NodeChangeVetoException ex) {
LOGGER.warn("The currently selected node has pending changes.", ex);
boolean continueClose =
showConfirmDialog(Resources.getString(MainController.class, "close-connection-pending-changes.message"),
Resources.getString(MainController.class, "close-connection-pending-changes.title"));
if (!continueClose) {
LOGGER.info("The user cancelled the disconnect.");
throw new CloseAbortedException("The user cancelled the disconnect.");
}
// save the pending changes
this.mainView.savePendingChanges();
// clear selected node
this.mainModel.setSelectedNode(null, true);
}
// check if a thread is running that reads the structure
if (openConnectionFuture != null && !openConnectionFuture.isDone()) {
LOGGER.warn("The openThread is running!!!! Wait for termination.");
try {
LOGGER.info("### Interrupt the open thread.");
boolean cancelled = openConnectionFuture.cancel(true);
LOGGER.info("### The open thread was cancelled: {}", cancelled);
LOGGER.info("### The open thread has finished.");
}
catch (Exception ex) {
LOGGER.warn("### Wait for termination of openThread failed.", ex);
}
}
// disconnect from bidib
try {
connectionService.disconnect(this.connectionId);
}
catch (Exception ex) {
LOGGER.warn("Disconnect connection failed.", ex);
}
LOGGER.info("The connection service has closed the connection.");
if (!this.compDispConnectionStatusChanges.isDisposed()) {
LOGGER
.warn(
"The compDispConnectionStatusChanges is not disposed! Dispose now to add new subscription to connection status changes.");
this.compDispConnectionStatusChanges.dispose();
this.compDispConnectionStatusChanges.clear();
}
LOGGER
.info(
"Create new compositeDisposable for connection status changes and subscribe to connection status changes.");
this.compDispConnectionStatusChanges = new CompositeDisposable();
subscribeConnectionStatusChanges();
}
private void showErrorDialog(final String message, final String title) {
if (SwingUtilities.isEventDispatchThread()) {
JOptionPane.showMessageDialog(mainView.getFrame(), message, title, JOptionPane.ERROR_MESSAGE);
}
else {
SwingUtilities
.invokeLater(() -> JOptionPane
.showMessageDialog(mainView.getFrame(), message, title, JOptionPane.ERROR_MESSAGE));
}
}
private boolean showConfirmDialog(final String message, final String title) {
int result = JOptionPane.showConfirmDialog(mainView.getFrame(), message, title, JOptionPane.OK_CANCEL_OPTION);
if (JOptionPane.OK_OPTION == result) {
return true;
}
return false;
}
@Override
public void allBoosterOff() {
for (NodeInterface node : mainModel.getNodeProvider().getNodes()) {
if (NodeUtils.hasBoosterFunctions(node.getUniqueId())) {
LOGGER.info("Switch booster off: {}", node);
try {
boosterService.setBoosterState(this.connectionId, node.getBoosterNode(), BoosterStatus.OFF);
}
catch (Exception ex) {
LOGGER.warn("Switch booster off failed for node: {}", node, ex);
}
}
}
}
@Override
public void allBoosterOn(boolean boosterAndCommandStation, CommandStationStatus requestedCommandStationState) {
LOGGER
.info("Switch all boosters on, boosterAndCommandStation: {}, requestedCommandStationState: {}",
boosterAndCommandStation, requestedCommandStationState);
if (boosterAndCommandStation) {
LOGGER.info("Switch the command stations on before the boosters will be switched on.");
// use the command station service to witch the command station on
for (NodeInterface node : mainModel.getNodeProvider().getNodes()) {
if (node.getCommandStationNode() != null && NodeUtils.hasCommandStationFunctions(node.getUniqueId())) {
try {
LOGGER.info("Send soft-stop to command station to stop all locos: {}", node);
commandStationService
.setCommandStationState(this.connectionId, node.getCommandStationNode(),
CommandStationStatus.SOFTSTOP);
Thread.sleep(300);
// LOGGER.info("Clear the loco buffer before switch on the command station, node: {}", node);
// commandStationService.clearLocoBuffer(node);
//
// Thread.sleep(300);
}
catch (Exception ex) {
LOGGER.warn("Switch command station to SOFTSTOP failed for node: {}", node, ex);
}
try {
LOGGER.info("Switch command station on: {}", node);
commandStationService
.setCommandStationState(this.connectionId, node.getCommandStationNode(),
requestedCommandStationState);
}
catch (Exception ex) {
LOGGER.warn("Switch command station on failed for node: {}", node, ex);
}
}
}
LOGGER.info("Wait a little bit to let the command station send the DCC signal.");
try {
Thread.sleep(300);
}
catch (Exception ex) {
LOGGER.warn("Wait a little bit to let the command station send the DCC signal was interrupted.", ex);
}
}
LOGGER.info("Switch all boosters on.");
for (NodeInterface node : mainModel.getNodeProvider().getNodes()) {
if (NodeUtils.hasBoosterFunctions(node.getUniqueId())) {
LOGGER.info("Switch booster on: {}", node);
try {
boosterService.setBoosterState(this.connectionId, node.getBoosterNode(), BoosterStatus.ON);
}
catch (Exception ex) {
LOGGER.warn("Switch booster on failed for node: {}", node, ex);
}
}
}
}
@Override
public void resetNode(final NodeInterface node) {
LOGGER.info("Reset the current node: {}", node);
nodeService.reset(this.connectionId, node);
}
@Override
public void transferAccessoryToNode(final NodeInterface node, final Accessory accessory) {
LOGGER
.info("Transfer the accessory to the node and save permanently: {}",
(accessory != null ? accessory.getDebugString() : null));
try {
switchingNodeService.saveAccessory(this.connectionId, node.getSwitchingNode(), accessory);
LOGGER.info("Transfer and save accessory passed.");
}
catch (InvalidConfigurationException ex) {
LOGGER.warn("Transfer accessory or save accessory failed.", ex);
mainModel.setNodeHasError(mainModel.getSelectedNode(), true);
throw ex;
}
}
@Override
public void replacePortConfig(TargetType portType, final Map> portConfig) {
LOGGER.info("Replace the port config, portType: {}, portConfig: {}", portType, portConfig);
try {
NodeInterface node = mainModel.getSelectedNode();
switch (portType.getScriptingTargetType()) {
case BACKLIGHTPORT:
BacklightPort backlightPort =
PortListUtils.findPortByPortNumber(node.getBacklightPorts(), portType.getPortNum().intValue());
if (backlightPort != null) {
// update the port config
backlightPort.setPortConfigX(portConfig);
// TODO make this wait for the response from the node
setPortParameters(node, backlightPort, portConfig);
}
else {
LOGGER.warn("No backlightPort available with port number: {}", portType.getPortNum());
}
break;
case LIGHTPORT:
LightPort lightPort =
PortListUtils.findPortByPortNumber(node.getLightPorts(), portType.getPortNum().intValue());
if (lightPort != null) {
// update the port config
lightPort.setPortConfigX(portConfig);
// TODO make this wait for the response from the node
setPortParameters(node, lightPort, portConfig);
}
else {
LOGGER.warn("No lightPort available with port number: {}", portType.getPortNum());
}
break;
case SERVOPORT:
ServoPort servoPort =
PortListUtils.findPortByPortNumber(node.getServoPorts(), portType.getPortNum());
if (servoPort != null) {
// update the port config
servoPort.setPortConfigX(portConfig);
// write to node
// TODO make this wait for the response from the node
setPortParameters(node, servoPort, portConfig);
}
else {
LOGGER.warn("No servoPort available with port number: {}", portType.getPortNum());
}
break;
case SWITCHPORT:
SwitchPort switchPort =
PortListUtils.findPortByPortNumber(node.getEnabledSwitchPorts(), portType.getPortNum());
if (switchPort != null) {
// update the port config
switchPort.setPortConfigX(portConfig);
// update the port config
setPortParameters(node, switchPort, portConfig);
}
else {
LOGGER.warn("No switchPort available with port number: {}", portType.getPortNum());
}
break;
case SWITCHPAIRPORT:
SwitchPairPort switchPairPort =
PortListUtils.findPortByPortNumber(node.getEnabledSwitchPairPorts(), portType.getPortNum());
if (switchPairPort != null) {
// update the port config
switchPairPort.setPortConfigX(portConfig);
// update the port config
setPortParameters(node, switchPairPort, portConfig);
}
else {
LOGGER.warn("No switchPairPort available with port number: {}", portType.getPortNum());
}
break;
default:
LOGGER.warn("Unsupported port type detected: {}", portType);
break;
}
}
catch (Exception ex) {
LOGGER.warn("Replace port config on node failed.", ex);
}
}
@Override
public void mapPortType(TargetType portType) {
LOGGER.info("Map the port to the new type, portType: {}", portType);
NodeInterface node = mainModel.getSelectedNode();
Port> port = null;
LcOutputType lcOutputType = null;
switch (portType.getScriptingTargetType()) {
case BACKLIGHTPORT:
port = PortListUtils.findPortByPortNumber(node.getBacklightPorts(), portType.getPortNum().intValue());
lcOutputType = LcOutputType.BACKLIGHTPORT;
break;
case INPUTPORT:
port = PortListUtils.findPortByPortNumber(node.getInputPorts(), portType.getPortNum().intValue());
lcOutputType = LcOutputType.INPUTPORT;
break;
case LIGHTPORT:
port = PortListUtils.findPortByPortNumber(node.getLightPorts(), portType.getPortNum().intValue());
lcOutputType = LcOutputType.LIGHTPORT;
break;
case MOTORPORT:
port = PortListUtils.findPortByPortNumber(node.getMotorPorts(), portType.getPortNum().intValue());
lcOutputType = LcOutputType.MOTORPORT;
break;
case SERVOPORT:
port = PortListUtils.findPortByPortNumber(node.getServoPorts(), portType.getPortNum().intValue());
lcOutputType = LcOutputType.SERVOPORT;
break;
case SOUNDPORT:
port = PortListUtils.findPortByPortNumber(node.getSoundPorts(), portType.getPortNum().intValue());
lcOutputType = LcOutputType.SOUNDPORT;
break;
case SWITCHPORT:
port = PortListUtils.findPortByPortNumber(node.getSwitchPorts(), portType.getPortNum().intValue());
lcOutputType = LcOutputType.SWITCHPORT;
break;
case SWITCHPAIRPORT:
port = PortListUtils.findPortByPortNumber(node.getSwitchPairPorts(), portType.getPortNum().intValue());
lcOutputType = LcOutputType.SWITCHPAIRPORT;
break;
default:
LOGGER.warn("Unsupported port type detected: {}", portType);
break;
}
if (port == null) {
LOGGER.warn("No port found to map for portType: {}", portType);
throw new RuntimeException("No port found to map for portType:" + portType);
}
Map> portConfig = new LinkedHashMap<>();
switchingNodeService.setPortConfig(this.connectionId, node.getSwitchingNode(), port, lcOutputType, portConfig);
}
private void setPortParameters(
final NodeInterface node, final Port> port, final Map> portConfig) {
switchingNodeService.setPortConfig(this.connectionId, node.getSwitchingNode(), port);
}
/**
* Send notification to users subscribed on channel "/topic/connectionState".
*
* @param error
* the error.
*/
private void publishStatus(Throwable error) {
LOGGER.error("Notify error on connectionState: {}", error);
}
/**
* Send notification to users subscribed on channel "/topic/system/connectionState".
*
* @param connectionPhase
* the connectionPhase.
*/
private void publishStatus(String connectionId, final ConnectionPhase connectionPhase, String error) {
LOGGER
.info("Notify new connectionState, connectionId: {}, connectionPhase: {}, error: {}", connectionId,
connectionPhase, error);
if (connectionId.equals(this.connectionId)) {
final ConnectionPhase prevConnectionPhase = connectionPhaseModel.getConnectionPhase();
if (SwingUtilities.isEventDispatchThread()) {
connectionPhaseModel.setConnectionPhase(connectionPhase);
}
else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
connectionPhaseModel.setConnectionPhase(connectionPhase);
}
});
}
switch (connectionPhase) {
case CONNECTED:
LOGGER
.info("Port was opened is signalled, connectionId: {}, prevConnectionPhase: {}", connectionId,
prevConnectionPhase);
if (SwingUtilities.isEventDispatchThread()) {
mainModel.getStatusModel().setCd(true);
if (ConnectionPhase.STALL == prevConnectionPhase) {
showConsoleStall(connectionId, false);
}
}
else {
SwingUtilities.invokeLater(() -> {
mainModel.getStatusModel().setCd(true);
if (ConnectionPhase.STALL == prevConnectionPhase) {
showConsoleStall(connectionId, false);
}
});
}
break;
case DISCONNECTED:
LOGGER.info("Port was closed is signalled, connectionId: {}", connectionId);
// clear all nodes
if (SwingUtilities.isEventDispatchThread()) {
handleClosed(connectionId);
}
else {
SwingUtilities.invokeLater(() -> handleClosed(connectionId));
}
break;
case STALL:
SwingUtilities.invokeLater(() -> showConsoleStall(connectionId, true));
break;
default:
break;
}
}
else {
LOGGER.info("The connectionId is not matching, stored: {}, provided: {}", this.connectionId, connectionId);
}
}
private void showConsoleStall(String connectionId, boolean stall) {
// notify the console
// SwingUtilities.invokeLater(() -> {
try {
// ensure console is visible
ConsoleController.ensureConsoleVisible();
String consoleLineText = null;
if (stall) {
LOGGER.info("The stall state of the connection has been set to true, connectionId: {}", connectionId);
// add line
consoleLineText =
String
.format("The stall state of the connection has been set to true, connectionId: %s",
connectionId);
}
else {
LOGGER.info("The stall state of the connection has been set to false., connectionId: {}", connectionId);
// add line
consoleLineText =
String
.format("The stall state of the connection has been set to false, connectionId: %s",
connectionId);
}
LOGGER.warn(consoleLineText);
consoleService.addConsoleLine(ConsoleColor.black, consoleLineText);
}
catch (Exception ex) {
LOGGER
.warn("Add change of stall state of connection to console failed, connectionId: {}", connectionId, ex);
}
// });
}
private void handleClosed(final String connectionId) {
LOGGER.info("Port was closed, set the status text and clear the nodes, connectionId: {}", connectionId);
// stop the model clock
mainModel.getStatusModel().setModelClockStartEnabled(false);
clearNodes();
mainModel.getStatusModel().setCd(false);
mainModel.signalResetInitialLoadFinished();
// release the cv definition
// model.setCvDefinition(null);
// release the pairing dialog
if (pairingController != null) {
LOGGER.info("Close the pairing dialog on disconnect.");
pairingController.closeDialogs();
}
if (compDispConnectionStatusChanges.isDisposed()) {
this.compDispConnectionStatusChanges = new CompositeDisposable();
subscribeConnectionStatusChanges();
}
if (openConnectionFuture != null) {
LOGGER.info("+++ The connection was closed. Free the openConnectionFuture.");
openConnectionFuture = null;
}
}
/**
* Publish the error.
*
* @param error
* the error.
*/
private void publishAction(Throwable error) {
LOGGER.error("Notify error on action: {}", error);
}
@EventListener
public void handleMenuEvent(final MainControllerEvent event) {
LOGGER.info("Handle the menu event: {}", event);
SwingUtilities.invokeLater(() -> {
switch (event.getAction()) {
case allBoosterOff:
allBoosterOff();
break;
case allBoosterOn:
final MainControllerBoosterOnEvent boosterOnEvent = (MainControllerBoosterOnEvent) event;
allBoosterOn(boosterOnEvent.isBoosterAndCommandStation(),
boosterOnEvent.getRequestedCommandStationState());
break;
case stop:
stop();
break;
case listenNetBidib:
listenNetBidib();
break;
case savePendingChanges:
savePendingChanges();
break;
default:
break;
}
});
}
private void handleNodeStatusEvent(
final BidibConnection connection, final NodeStatusEvent nse, final CompositeDisposable disposable,
final NodeInterface node, final Consumer nodeSelector) {
LOGGER.info("Received nodeStatusEvent after node was loaded: {}", nse);
if (nse.getConnectionId().equals(connection.getConnectionId())
&& StatusIdentifier.InitialLoadFinished == nse.getStatusIdentifier()
&& node.getUniqueId() == nse.getUniqueId()) {
long uniqueId = nse.getUniqueId();
LOGGER
.info("Received InitialLoadFinished for node with uniqueId: {}", ByteUtils.formatHexUniqueId(uniqueId));
disposable.dispose();
try {
nodeSelector.accept(node);
}
catch (Exception ex) {
LOGGER.warn("Select fully loaded node failed.", ex);
}
}
}
@EventListener
public void handleErrorEvent(final ErrorEvent event) {
LOGGER.info("Handle the error event: {}", event);
if (Objects.equals(this.connectionId, event.getConnectionId())
&& StringUtils.isNotBlank(event.getResourceKey())) {
String message = Resources.getString(ErrorEvent.class, event.getResourceKey() + ".message");
String title = Resources.getString(ErrorEvent.class, event.getResourceKey() + ".title");
showErrorDialog(message, title);
}
}
}