All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bidib.wizard.script.client.controller.ScriptClientController Maven / Gradle / Ivy

There is a newer version: 2.0.27
Show newest version
package org.bidib.wizard.script.client.controller;

import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.swing.SwingUtilities;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.bidib.api.json.types.ConnectionPhase;
import org.bidib.jbidibc.core.node.ConfigurationVariable;
import org.bidib.jbidibc.messages.Feature;
import org.bidib.jbidibc.messages.enums.AccessoryExecutionState;
import org.bidib.jbidibc.messages.enums.IdentifyState;
import org.bidib.jbidibc.messages.enums.LcOutputType;
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.wizard.api.locale.Resources;
import org.bidib.wizard.api.model.Accessory;
import org.bidib.wizard.api.model.BoosterNodeInterface;
import org.bidib.wizard.api.model.CommandStationNodeInterface;
import org.bidib.wizard.api.model.Macro;
import org.bidib.wizard.api.model.NodeInterface;
import org.bidib.wizard.api.model.NodeProvider;
import org.bidib.wizard.api.model.OccupancyNodeInterface;
import org.bidib.wizard.api.model.SwitchingNodeInterface;
import org.bidib.wizard.api.model.debug.DebugConnection;
import org.bidib.wizard.api.model.event.DebugActionEvent;
import org.bidib.wizard.api.model.listener.CommandStationStatusListener;
import org.bidib.wizard.api.notification.NodeStatusInfo.LoadStatus;
import org.bidib.wizard.api.script.ScriptCommand;
import org.bidib.wizard.api.script.ScriptEngineListener;
import org.bidib.wizard.api.script.ScriptStatus;
import org.bidib.wizard.api.script.Scripting;
import org.bidib.wizard.api.service.console.ConsoleService;
import org.bidib.wizard.api.service.node.BoosterService;
import org.bidib.wizard.api.service.node.NodeService;
import org.bidib.wizard.api.service.node.SwitchingNodeService;
import org.bidib.wizard.api.utils.AccessoryListUtils;
import org.bidib.wizard.api.utils.PortListUtils;
import org.bidib.wizard.client.common.view.DockKeys;
import org.bidib.wizard.client.common.view.DockUtils;
import org.bidib.wizard.common.model.settings.WizardSettingsInterface;
import org.bidib.wizard.common.script.DefaultScriptContext;
import org.bidib.wizard.common.script.ScriptExecutionException;
import org.bidib.wizard.common.script.booster.BoosterScripting;
import org.bidib.wizard.common.script.common.CommonScripting;
import org.bidib.wizard.common.script.debug.DebugInterfaceScripting;
import org.bidib.wizard.common.script.node.NodeScripting;
import org.bidib.wizard.common.script.node.types.CvType;
import org.bidib.wizard.common.script.node.types.FeatureType;
import org.bidib.wizard.common.script.node.types.ScriptingTargetType;
import org.bidib.wizard.common.script.node.types.TargetType;
import org.bidib.wizard.common.script.switching.AccessoryScripting;
import org.bidib.wizard.common.script.switching.NodeTreeScripting;
import org.bidib.wizard.common.script.switching.PortScripting;
import org.bidib.wizard.common.script.test.StringChunkResponseHandler;
import org.bidib.wizard.common.script.test.TestScripting;
import org.bidib.wizard.core.model.connection.ConnectionRegistry;
import org.bidib.wizard.core.service.ConnectionService;
import org.bidib.wizard.core.service.DebugConnectionService;
import org.bidib.wizard.core.service.node.DebugService;
import org.bidib.wizard.model.ports.AnalogPort;
import org.bidib.wizard.model.ports.BacklightPort;
import org.bidib.wizard.model.ports.FeedbackPort;
import org.bidib.wizard.model.ports.LightPort;
import org.bidib.wizard.model.ports.MotorPort;
import org.bidib.wizard.model.ports.Port;
import org.bidib.wizard.model.ports.ServoPort;
import org.bidib.wizard.model.ports.SoundPort;
import org.bidib.wizard.model.ports.SwitchPairPort;
import org.bidib.wizard.model.ports.SwitchPort;
import org.bidib.wizard.model.status.BidibStatus;
import org.bidib.wizard.model.status.BoosterStatus;
import org.bidib.wizard.model.status.CommandStationStatus;
import org.bidib.wizard.model.status.LightPortStatus;
import org.bidib.wizard.model.status.SoundPortStatus;
import org.bidib.wizard.model.status.SwitchPortStatus;
import org.bidib.wizard.script.client.controller.listener.ScriptClientControllerListener;
import org.bidib.wizard.script.client.model.ScriptClientLogModel;
import org.bidib.wizard.script.client.model.ScriptClientLogModel.ConsoleColor;
import org.bidib.wizard.script.client.model.ScriptClientModel;
import org.bidib.wizard.script.client.view.ScriptActionDialog;
import org.bidib.wizard.script.client.view.ScriptClientView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jgoodies.binding.value.ValueHolder;
import com.jgoodies.binding.value.ValueModel;
import com.vlsolutions.swing.docking.Dockable;
import com.vlsolutions.swing.docking.DockableState;
import com.vlsolutions.swing.docking.DockingConstants;
import com.vlsolutions.swing.docking.DockingDesktop;
import com.vlsolutions.swing.docking.DockingUtilities;
import com.vlsolutions.swing.docking.RelativeDockablePosition;
import com.vlsolutions.swing.docking.TabbedDockableContainer;
import com.vlsolutions.swing.docking.event.DockableStateChangeEvent;
import com.vlsolutions.swing.docking.event.DockableStateChangeListener;

import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.Subject;

public class ScriptClientController
    implements ScriptClientControllerListener, PortScripting, AccessoryScripting, NodeTreeScripting, NodeScripting,
    BoosterScripting, CommonScripting, DebugInterfaceScripting, TestScripting, ScriptEngineListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(ScriptClientController.class);

    private final DockingDesktop desktop;

    private DockableStateChangeListener dockableStateChangeListener;

    private final WizardSettingsInterface wizardSettings;

    private final Supplier nodeProviderSupplier;

    private ScriptClientView scriptClientView;

    private final NodeService nodeService;

    private final SwitchingNodeService switchingNodeService;

    private final BoosterService boosterService;

    private final DebugService debugService;

    private ScriptClientLogModel logModel;

    private ScriptClientModel scriptClientModel;

    private DefaultScriptContext scriptContext;

    private final ConsoleService consoleService;

    private final ConnectionService connectionService;

    private final DebugConnectionService debugConnectionService;

    private ValueModel currentCommandModel = new ValueHolder();

    public ScriptClientController(final DockingDesktop desktop, final WizardSettingsInterface wizardSettings,
        final NodeService nodeService, final SwitchingNodeService switchingNodeService,
        final BoosterService boosterService, final ConsoleService consoleService,
        final ConnectionService connectionService, final DebugConnectionService debugConnectionService,
        final DebugService debugService, final Supplier nodeProviderSupplier) {
        this.desktop = desktop;
        this.wizardSettings = wizardSettings;
        this.nodeService = nodeService;
        this.switchingNodeService = switchingNodeService;
        this.boosterService = boosterService;
        this.nodeProviderSupplier = nodeProviderSupplier;
        this.consoleService = consoleService;
        this.connectionService = connectionService;
        this.debugConnectionService = debugConnectionService;
        this.debugService = debugService;
    }

    public void start() {
        // check if the nodes client view is already opened
        String searchKey = DockKeys.SCRIPT_CLIENT_VIEW;
        LOGGER.info("Search for view with key: {}", searchKey);
        Dockable view = desktop.getContext().getDockableByKey(searchKey);
        if (view != null) {
            LOGGER.info("Select the existing script client view instead of open a new one.");
            DockUtils.selectWindow(view);
            return;
        }

        LOGGER.info("Create new ScriptClientView.");

        this.scriptClientModel = new ScriptClientModel();
        this.logModel = new ScriptClientLogModel();
        this.scriptContext = new DefaultScriptContext();

        this.scriptClientView =
            new ScriptClientView(this.desktop, this.wizardSettings, this, this.scriptClientModel, this.logModel,
                this.consoleService, this.scriptContext);
        this.scriptClientView.initComponents(this.currentCommandModel);

        // add dockable
        DockableState[] dockables = desktop.getDockables();
        LOGGER.info("Current dockables: {}", new Object[] { dockables });
        if (dockables.length > 1) {

            DockableState tabPanelNodeDetails = null;
            // search the node details tab panel
            for (DockableState dockable : dockables) {

                if (DockKeys.DOCKKEY_TAB_PANEL.equals(dockable.getDockable().getDockKey())) {
                    LOGGER.info("Found the tab panel dockable.");
                    tabPanelNodeDetails = dockable;

                    break;
                }
            }

            Dockable dock = desktop.getDockables()[1].getDockable();
            if (tabPanelNodeDetails != null) {
                LOGGER.info("Add the scriptClientView next to the node details panel.");
                dock = tabPanelNodeDetails.getDockable();

                TabbedDockableContainer container = DockingUtilities.findTabbedDockableContainer(dock);
                int order = 0;
                if (container != null) {
                    order = container.getTabCount();
                }
                LOGGER.info("Add new scriptClientView at order: {}", order);

                desktop.createTab(dock, scriptClientView, order, true);
            }
            else {
                desktop.split(dock, scriptClientView, DockingConstants.SPLIT_RIGHT);
            }
        }
        else {
            desktop.addDockable(scriptClientView, RelativeDockablePosition.RIGHT);
        }

        this.scriptClientView.initKeyBindings();

        // add listener to detect when the view is closed
        this.dockableStateChangeListener = new DockableStateChangeListener() {

            @Override
            public void dockableStateChanged(DockableStateChangeEvent event) {
                if (event.getNewState().getDockable().equals(scriptClientView) && event.getNewState().isClosed()) {
                    LOGGER.info("ScriptClientView was closed, free resources.");

                    try {
                        desktop.removeDockableStateChangeListener(dockableStateChangeListener);
                    }
                    catch (Exception ex) {
                        LOGGER
                            .warn("Remove dockableStateChangeListener from desktop failed: "
                                + dockableStateChangeListener, ex);
                    }
                    finally {
                        dockableStateChangeListener = null;
                    }

                    try {
                        scriptClientView.close();
                    }
                    finally {
                        scriptClientView = null;
                    }
                }
            }
        };
        desktop.addDockableStateChangeListener(this.dockableStateChangeListener);

        this.scriptClientView.prepareModel(this.nodeProviderSupplier.get());

    }

    @Override
    public void viewClosed() {
        LOGGER.info("The view is closed.");
    }

    @Override
    public void currentCommandChanged(ScriptCommand command) {
        if (SwingUtilities.isEventDispatchThread()) {
            currentCommandModel.setValue((command != null ? command.toString() : null));
        }
        else {
            try {
                SwingUtilities.invokeAndWait(() -> {
                    currentCommandModel.setValue((command != null ? command.toString() : null));
                    ScriptClientController.this.logModel.addConsoleLine(ConsoleColor.black, "Executing : " + command);
                });
            }
            catch (InvocationTargetException | InterruptedException e) {
                LOGGER.warn("Update current command failed.", e);
            }
        }
    }

    @Override
    public void scriptStatusChanged(final ScriptStatus scriptStatus) {

        SwingUtilities.invokeLater(() -> {
            switch (scriptStatus) {
                case RUNNING:
                    break;
                case STOPPED:
                case FINISHED:
                    this.logModel.addConsoleLine(ConsoleColor.green, "Execution of script has finished.");
                    break;
                case FINISHED_WITH_ERRORS:
                case ABORTED:
                    this.logModel
                        .addConsoleLine(ConsoleColor.red,
                            "Execution of script was aborted or has finished with errors.");

                    if (this.scriptContext.get(Scripting.KEY_SCRIPT_ERRORS) != null) {
                        List errors = (List) this.scriptContext.get(Scripting.KEY_SCRIPT_ERRORS);
                        for (String error : errors) {
                            LOGGER.warn("Script errors detected: {}", error);

                            this.logModel.addConsoleLine(ConsoleColor.red, "Error: " + error);
                        }
                    }
                    break;
                default:
                    break;
            }

            this.scriptClientView.scriptStatusChanged(scriptStatus);

            currentCommandChanged(null);
        });
    }

    @Override
    public List getNodesByVidAndPid(int vid, int pid) {
        this.logModel.addConsoleLine(ConsoleColor.black, "Get all nodes with VID: " + vid + " and PID: " + pid);

        List nodes = this.nodeService.getAllNodes(ConnectionRegistry.CONNECTION_ID_MAIN);
        return nodes
            .stream().filter(n -> NodeUtils.isMatchingVidAndPid(n.getUniqueId(), vid, pid))
            .collect(Collectors.toList());
    }

    @Override
    public List getAllNodes() {
        this.logModel.addConsoleLine(ConsoleColor.black, "Get all nodes");

        final List nodes = this.nodeService.getAllNodes(ConnectionRegistry.CONNECTION_ID_MAIN);
        return Collections.unmodifiableList(nodes);
    }

    @Override
    public void setBoosterStatus(BoosterNodeInterface boosterNode, BoosterStatus requestedStatus) {
        LOGGER.info("Set the booster status, node: {}, requestedStatus: {}", boosterNode, requestedStatus);

        this.boosterService.setBoosterState(ConnectionRegistry.CONNECTION_ID_MAIN, boosterNode, requestedStatus);
    }

    @Override
    public void setCommandStationStatus(
        CommandStationNodeInterface commandStationNode, CommandStationStatus requestedStatus) {
        LOGGER
            .info("Set the command station status, node: {}, requestedStatus: {}", commandStationNode, requestedStatus);

        this.boosterService
            .setCommandStationState(ConnectionRegistry.CONNECTION_ID_MAIN, commandStationNode, requestedStatus);
    }

    @Override
    public void setCv(Long uniqueId, CvType... cvTypes) {
        LOGGER.info("Set the CV directly on the node, uniqueId: {}, cvTypes: {}", uniqueId, cvTypes);

        final NodeInterface node = this.nodeService.getNode(ConnectionRegistry.CONNECTION_ID_MAIN, uniqueId);
        if (node != null && node.getUniqueId() != uniqueId.longValue()) {
            LOGGER.warn("Set CV can only be performed on an available node!");
            return;
        }

        List cvList = new LinkedList<>();

        // prepare the list of CV values
        for (CvType cvType : cvTypes) {
            cvList.add(new ConfigurationVariable(cvType.getCvNumber(), cvType.getCvValue()));
        }

        this.nodeService.setConfigVariables(ConnectionRegistry.CONNECTION_ID_MAIN, node, cvList);
    }

    @Override
    public List getCv(Long uniqueId, CvType... cvTypes) {
        LOGGER.info("Get the CV directly from the node, uniqueId: {}, cvTypes: {}", uniqueId, cvTypes);

        final NodeInterface node = this.nodeService.getNode(ConnectionRegistry.CONNECTION_ID_MAIN, uniqueId);
        if (node != null && node.getUniqueId() != uniqueId.longValue()) {
            LOGGER.warn("Set CV can only be performed on an available node!");
            return Collections.emptyList();
        }

        List cvList = new LinkedList<>();

        // prepare the list of CV values
        for (CvType cvType : cvTypes) {

            cvList.add(new ConfigurationVariable(cvType.getCvNumber(), cvType.getCvValue()));
        }

        final List cvReadList =
            this.nodeService.queryConfigVariables(ConnectionRegistry.CONNECTION_ID_MAIN, node, cvList);

        return cvReadList;
    }

    @Override
    public void setFeature(Long uniqueId, FeatureType... featureTypes) {
        LOGGER.info("Set the features directly on the node, uniqueId: {}, featureTypes: {}", uniqueId, featureTypes);

        final NodeInterface node = this.nodeService.getNode(ConnectionRegistry.CONNECTION_ID_MAIN, uniqueId);
        if (node != null && node.getUniqueId() != uniqueId.longValue()) {
            LOGGER.warn("Set features can only be performed on an available node!");
            return;
        }

        final List features = new LinkedList<>();
        for (FeatureType featureType : featureTypes) {
            features.add(new Feature(featureType.getFeatureNumber(), featureType.getFeatureValue()));
        }

        this.nodeService.setFeatures(ConnectionRegistry.CONNECTION_ID_MAIN, node, features);
    }

    @Override
    public List featuresGetAll(Long uniqueId, boolean discardCache) {
        LOGGER.info("Get all features directly from the node, uniqueId: {}, discardCache: {}", uniqueId, discardCache);

        final NodeInterface node = this.nodeService.getNode(ConnectionRegistry.CONNECTION_ID_MAIN, uniqueId);
        if (node != null && node.getUniqueId() != uniqueId.longValue()) {
            LOGGER.warn("Get all features can only be performed on an available node!");
            return Collections.emptyList();
        }

        return this.nodeService.queryAllFeatures(ConnectionRegistry.CONNECTION_ID_MAIN, node, discardCache);
    }

    @Override
    public void setLabel(Long uuid, TargetType portType) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setMacro(Long uuid, Macro macro) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setAccessory(Long uuid, Accessory accessory) {
        // TODO Auto-generated method stub

    }

    @Override
    public boolean isNodeHasRestartPending(Long uuid) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void resetNode(Long uuid) {
        // TODO Auto-generated method stub

    }

    @Override
    public void reselectNode(Long uuid) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setPortConfig(Long uniqueId, TargetType targetType, Map> portConfig) {
        LOGGER
            .info("Set the port scripting, uuid: {}, targetType: {}, portConfig: {}",
                ByteUtils.formatHexUniqueId(uniqueId), targetType, portConfig);

        final NodeInterface node = this.nodeService.getNode(ConnectionRegistry.CONNECTION_ID_MAIN, uniqueId);

        int portId = targetType.getPortNum();
        ScriptingTargetType scriptingTargetType = targetType.getScriptingTargetType();

        Port port = null;
        switch (scriptingTargetType) {
            case ANALOGPORT:
                port = new AnalogPort();
                port.setId(portId);
                break;
            case BACKLIGHTPORT:
                port = new BacklightPort();
                port.setId(portId);
                break;
            case LIGHTPORT:
                port = new LightPort();
                port.setId(portId);
                break;
            case SERVOPORT:
                port = new ServoPort();
                port.setId(portId);
                break;
            case SWITCHPORT:
                port = new SwitchPort();
                port.setId(portId);
                break;
            case SWITCHPAIRPORT:
                port = new SwitchPairPort();
                port.setId(portId);
                break;
            case SOUNDPORT:
                port = new SoundPort();
                port.setId(portId);
                break;
            default:
                LOGGER.error("Unsupported port type detected: {}", scriptingTargetType);
                break;
        }

        switchingNodeService
            .setPortConfig(ConnectionRegistry.CONNECTION_ID_MAIN, node.getSwitchingNode(), port, (LcOutputType) null,
                portConfig);

    }

    @Override
    public void assertPortType(Long uuid, TargetType portType) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setActiveAspect(SwitchingNodeInterface node, int accessoryNumber, int aspectNumber) {
        LOGGER
            .info("Activate the aspect, node: {}, accessoryNumber: {}, aspectNumber: {}", node, accessoryNumber,
                aspectNumber);

        final Accessory accessory =
            AccessoryListUtils.findAccessoryByAccessoryNumber(node.getNode().getAccessories(), accessoryNumber);

        switchingNodeService.setAccessoryAspect(ConnectionRegistry.CONNECTION_ID_MAIN, node, accessory, aspectNumber);
    }

    @Override
    public AccessoryExecutionState getAccessoryExecutionState(SwitchingNodeInterface node, int accessoryNumber) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void sendPortStatusAction(
        SwitchingNodeInterface node, LcOutputType lcOutputType, int portNumber, BidibStatus portStatus) {

        final SwitchingNodeInterface selectedNode = node;

        Port port = null;
        switch (lcOutputType) {
            case SWITCHPORT:

                final SwitchPortStatus switchPortStatus = (SwitchPortStatus) portStatus;

                final SwitchPort switchPort = new SwitchPort();
                switchPort.setId(portNumber);
                switchPort.setStatus(switchPortStatus);
                port = switchPort;
                break;
            case SWITCHPAIRPORT:

                final SwitchPortStatus switchPairPortStatus = (SwitchPortStatus) portStatus;

                final SwitchPairPort switchPairPort = new SwitchPairPort();
                switchPairPort.setId(portNumber);
                switchPairPort.setStatus(switchPairPortStatus);
                port = switchPairPort;
                break;
            case SOUNDPORT:

                final SoundPortStatus soundPortStatus = (SoundPortStatus) portStatus;

                final SoundPort soundPort = new SoundPort();
                soundPort.setId(portNumber);
                soundPort.setStatus(soundPortStatus);
                port = soundPort;
                break;
            case LIGHTPORT:
                final LightPortStatus lightPortStatus = (LightPortStatus) portStatus;

                final LightPort lightPort = new LightPort();
                lightPort.setId(portNumber);
                lightPort.setStatus(lightPortStatus);
                port = lightPort;
                break;
            default:
                LOGGER.warn("Unsupported port type: {}", lcOutputType);
                break;
        }

        if (port != null) {
            switchingNodeService.setPortStatus(ConnectionRegistry.CONNECTION_ID_MAIN, selectedNode, port);
        }
    }

    @Override
    public void sendPortValueAction(SwitchingNodeInterface node, LcOutputType lcOutputType, int port, int portValue) {
        final SwitchingNodeInterface selectedNode = node;

        switch (lcOutputType) {
            case SERVOPORT:
                final ServoPort servoPort = new ServoPort();
                servoPort.setId(port);
                servoPort.setValue(portValue);
                switchingNodeService.setPortStatus(ConnectionRegistry.CONNECTION_ID_MAIN, selectedNode, servoPort);
                break;
            case MOTORPORT:
                final MotorPort motorPort = new MotorPort();
                motorPort.setId(port);
                motorPort.setValue(portValue);
                switchingNodeService.setPortStatus(ConnectionRegistry.CONNECTION_ID_MAIN, selectedNode, motorPort);
                break;
            default:
                break;
        }
    }

    @Override
    public void assertPortStatusAction(
        final SwitchingNodeInterface node, LcOutputType lcOutputType, int portNumber, BidibStatus expectedPortStatus) {
        final SwitchingNodeInterface selectedNode = node;

        final Port port =
            switchingNodeService.getPort(ConnectionRegistry.CONNECTION_ID_MAIN, selectedNode, lcOutputType, portNumber);
        if (expectedPortStatus != port.getStatus()) {
            LOGGER
                .warn("The expected port status ({}) does not match the current status: {}", expectedPortStatus,
                    port.getStatus());
            throw new IllegalStateException("Port status is not expected: " + port.getStatus());
        }
        LOGGER.info("The expected port status matches the current status: {}", port.getStatus());
    }

    @Override
    public void assertPortStatusAction(
        Long uniqueId, LcOutputType lcOutputType, int portNumber, BidibStatus expectedPortStatus) {

        final NodeInterface node = this.nodeService.getNode(ConnectionRegistry.CONNECTION_ID_MAIN, uniqueId);
        if (node != null && node.getUniqueId() != uniqueId.longValue()) {
            LOGGER.warn("Query occupancy state can only be performed on an available node!");
            throw new IllegalStateException("Query occupancy state can only be performed on an available node!");
        }

        OccupancyNodeInterface occupancyNode = node.getOccupancyNode();
        if (occupancyNode == null) {
            LOGGER.warn("Query occupancy state can only be performed on an available node!");
            throw new IllegalStateException("Query occupancy state can only be performed on an available node!");
        }

        final Port port = PortListUtils.getPort(occupancyNode.getNode(), lcOutputType, portNumber);
        if (expectedPortStatus != port.getStatus()) {
            LOGGER
                .warn("The expected port status ({}) does not match the current status: {}", expectedPortStatus,
                    port.getStatus());
            throw new IllegalStateException("Port status is not expected: " + port.getStatus());
        }
        LOGGER.info("The expected port status matches the current status: {}", port.getStatus());
    }

    @Override
    public void assertPortStatusActionWithDialog(
        Long uniqueId, int waitSeconds, String dialogMessage, LcOutputType lcOutputType, int portNumber,
        BidibStatus expectedPortStatus) {

        final NodeInterface node = this.nodeService.getNode(ConnectionRegistry.CONNECTION_ID_MAIN, uniqueId);
        if (node != null && node.getUniqueId() != uniqueId.longValue()) {
            LOGGER.warn("Query occupancy state can only be performed on an available node!");
            throw new IllegalStateException("Query occupancy state can only be performed on an available node!");
        }

        OccupancyNodeInterface occupancyNode = node.getOccupancyNode();
        if (occupancyNode == null) {
            LOGGER.warn("Query occupancy state can only be performed on an available node!");
            throw new IllegalStateException("Query occupancy state can only be performed on an available node!");
        }

        final String nodeLabel =
            StringUtils.isNotBlank(node.getLabel()) ? node.getLabel() : ByteUtils.getUniqueIdAsString(uniqueId);

        final FeedbackPort port =
            (FeedbackPort) PortListUtils.getPort(occupancyNode.getNode(), lcOutputType, portNumber);
        if (expectedPortStatus == port.getStatus()) {

            LOGGER
                .info("The expected port status ({}) matches the current status: {}", expectedPortStatus,
                    port.getStatus());
            this.logModel
                .addConsoleLine(ConsoleColor.black, "Checked status of occupancy port " + portNumber + " on node: "
                    + nodeLabel + ". Current status: " + port.getStatus());
            return;
        }

        LOGGER
            .info("Show the wait dialog for {}s because the expected port status does not match the current status: {}",
                waitSeconds, port.getStatus());

        this.logModel.addConsoleLine(ConsoleColor.black, "Check occupancy port status on node: " + nodeLabel);

        final ScriptActionDialog actionDialogHolder[] = new ScriptActionDialog[1];
        final CompletableFuture future = new CompletableFuture<>();

        // show the action dialog
        SwingUtilities.invokeLater(() -> {

            final PropertyChangeListener listener = evt -> {

                BidibStatus occupancyState = port.getStatus();
                LOGGER.info("The occupancy status has been changed: {}", occupancyState);
                future.complete(occupancyState);
            };

            try {
                port.addPropertyChangeListener(FeedbackPort.PROPERTY_STATUS, listener);

                final Context context = new DefaultContext();
                context.register(CommonScripting.KEY_ACTION_TIMEOUT, Integer.valueOf(waitSeconds));

                // TODO change the dialog message
                // String dialogMessage =
                // "Wait for change of occupancy state of port " + portNumber + " on node: " + nodeLabel;
                final ScriptActionDialog actionDialog =
                    new ScriptActionDialog(desktop, context, true, dialogMessage, (result) -> {
                        LOGGER.info("Dialog was cancelled.");
                    });

                actionDialogHolder[0] = actionDialog;

                actionDialog.setAlwaysOnTop(true);
                actionDialog.setVisible(true);
            }
            finally {
                node.removePropertyChangeListener(NodeInterface.PROPERTY_IDENTIFY_STATE, listener);

                if (!future.isDone()) {
                    future.completeExceptionally(new RuntimeException("No occupancy status change received."));
                }
            }
        });

        final Runnable scriptActionDialog = () -> {
            final ScriptActionDialog actionDialog = actionDialogHolder[0];
            if (actionDialog != null) {
                LOGGER.info("Close the action dialog.");
                try {
                    actionDialog.setVisible(false);
                }
                catch (Exception ex) {
                    LOGGER.warn("Close the action dialog failed.", ex);
                }
            }
            else {
                LOGGER.warn("Skip close the action dialog because no instance available.");
            }
        };

        BidibStatus occupancyState = null;
        try {
            occupancyState = future.get(waitSeconds + 5, TimeUnit.SECONDS);
            LOGGER.info("Current occupancyState: {}", occupancyState);

            SwingUtilities.invokeAndWait(scriptActionDialog);
        }
        catch (ExecutionException | TimeoutException | InterruptedException ex) {
            LOGGER.warn("Wait for occupancy state change was not successful.", ex);
            throw new IllegalArgumentException("No answer from occupancy node!");
        }
        catch (InvocationTargetException ex) {
            LOGGER.warn("Close action dialog failed.", ex);
            throw new IllegalArgumentException("Internal error!");
        }

        // Check if the port status matches the expected status
        if (expectedPortStatus != occupancyState) {
            LOGGER
                .warn("The expected port status ({}) does not match the current status: {}", expectedPortStatus,
                    port.getStatus());
            throw new IllegalStateException("Port status is not expected: " + port.getStatus());
        }
        LOGGER.info("The expected port status matches the current status: {}", port.getStatus());

        this.logModel
            .addConsoleLine(ConsoleColor.black, "Checked status of occupancy port " + portNumber + " on node: "
                + nodeLabel + ". Current status: " + port.getStatus());

    }

    @Override
    public void setIdentifyState(Long uniqueId, IdentifyState identifyState) {
        LOGGER.info("Set the identify state on the node, uniqueId: {}, identifyState: {}", uniqueId, identifyState);

        final NodeInterface node = this.nodeService.getNode(ConnectionRegistry.CONNECTION_ID_MAIN, uniqueId);
        if (node != null && node.getUniqueId() != uniqueId.longValue()) {
            LOGGER.warn("Set identify state can only be performed on an available node!");
            return;
        }

        nodeService.identify(ConnectionRegistry.CONNECTION_ID_MAIN, node, identifyState);
    }

    @Override
    public IdentifyState queryIdentifyState(Long uniqueId) {

        final NodeInterface node = this.nodeService.getNode(ConnectionRegistry.CONNECTION_ID_MAIN, uniqueId);
        if (node != null && node.getUniqueId() != uniqueId.longValue()) {
            LOGGER.warn("Query identify state can only be performed on an available node!");
            throw new IllegalStateException("Query identify state can only be performed on an available node!");
        }

        LOGGER.info("Show the Action dialog.");

        final String nodeLabel =
            StringUtils.isNotBlank(node.getLabel()) ? node.getLabel() : ByteUtils.getUniqueIdAsString(uniqueId);

        this.logModel.addConsoleLine(ConsoleColor.black, "Press ID button on node: " + nodeLabel);

        final ScriptActionDialog actionDialogHolder[] = new ScriptActionDialog[1];
        final CompletableFuture future = new CompletableFuture<>();

        // show the action dialog
        SwingUtilities.invokeLater(() -> {

            final PropertyChangeListener listener = evt -> {
                IdentifyState identifyState = node.getIdentifyState();
                LOGGER.info("The identify state has been changed: {}", identifyState);
                future.complete(identifyState);
            };

            try {
                node.addPropertyChangeListener(NodeInterface.PROPERTY_IDENTIFY_STATE, listener);

                final Context context = new DefaultContext();
                context.register(CommonScripting.KEY_ACTION_TIMEOUT, Integer.valueOf(30));
                String dialogMessage = Resources.getString(ScriptActionDialog.class, "pressIdentifyMessage", nodeLabel);
                final ScriptActionDialog actionDialog =
                    new ScriptActionDialog(desktop, context, true, dialogMessage, (result) -> {
                        LOGGER.info("Dialog was cancelled.");
                    });

                actionDialogHolder[0] = actionDialog;

                actionDialog.setAlwaysOnTop(true);
                actionDialog.setVisible(true);
            }
            finally {
                node.removePropertyChangeListener(NodeInterface.PROPERTY_IDENTIFY_STATE, listener);

                if (!future.isDone()) {
                    future.completeExceptionally(new RuntimeException("No identify status change received."));
                }
            }
        });

        final Runnable scriptActionDialog = () -> {
            final ScriptActionDialog actionDialog = actionDialogHolder[0];
            if (actionDialog != null) {
                LOGGER.info("Close the action dialog.");
                try {
                    actionDialog.setVisible(false);
                }
                catch (Exception ex) {
                    LOGGER.warn("Close the action dialog failed.", ex);
                }
            }
            else {
                LOGGER.warn("Skip close the action dialog because no instance available.");
            }
        };

        IdentifyState identifyState = null;
        try {
            identifyState = future.get(35, TimeUnit.SECONDS);
            LOGGER.info("Current identifyState: {}", identifyState);

            SwingUtilities.invokeAndWait(scriptActionDialog);
        }
        catch (ExecutionException | TimeoutException | InterruptedException ex) {
            LOGGER.warn("Wait for identify state change was not successful.", ex);
            throw new IllegalArgumentException("No answer from booster node!");
        }
        catch (InvocationTargetException ex) {
            LOGGER.warn("Close action dialog failed.", ex);
            throw new IllegalArgumentException("Internal error!");
        }

        return identifyState;
    }

    @Override
    public void echo(String echoMessage) {

        this.logModel.addConsoleLine(ConsoleColor.blue, echoMessage);

    }

    @Override
    public boolean echoAndShowConfirmDialog(Long uniqueId, int timeout, String dialogMessage) {

        final NodeInterface node = this.nodeService.getNode(ConnectionRegistry.CONNECTION_ID_MAIN, uniqueId);
        if (node != null && node.getUniqueId() != uniqueId.longValue()) {
            LOGGER.warn("Show confirm dialog can only be performed on an available node!");
            throw new IllegalStateException("Show confirm dialog can only be performed on an available node!");
        }

        LOGGER.info("Show the Action dialog.");

        final String nodeLabel =
            StringUtils.isNotBlank(node.getLabel()) ? node.getLabel() : ByteUtils.getUniqueIdAsString(uniqueId);

        String echoMessage = "Show wait dialog for node: " + nodeLabel;
        this.logModel.addConsoleLine(ConsoleColor.blue, echoMessage);

        final ScriptActionDialog actionDialogHolder[] = new ScriptActionDialog[1];
        final CompletableFuture future = new CompletableFuture<>();

        // show the action dialog
        SwingUtilities.invokeLater(() -> {

            try {
                final Context context = new DefaultContext();
                context.register(CommonScripting.KEY_ACTION_TIMEOUT, Integer.valueOf(timeout));
                // String dialogMessage = Resources.getString(ScriptActionDialog.class, "pressIdentifyMessage",
                // nodeLabel);
                final ScriptActionDialog actionDialog =
                    new ScriptActionDialog(desktop, context, true, dialogMessage, okResult -> {
                        LOGGER.info("Dialog was closed with OK option.");

                        future.complete(Boolean.TRUE);
                    }, result -> {
                        LOGGER.info("Dialog was cancelled.");

                        future.complete(Boolean.FALSE);
                    });

                actionDialogHolder[0] = actionDialog;

                actionDialog.setAlwaysOnTop(true);
                actionDialog.setVisible(true);
            }
            finally {
                if (!future.isDone()) {
                    future.completeExceptionally(new RuntimeException("Wait for action expired."));
                }
            }
        });

        final Runnable scriptActionDialog = () -> {
            final ScriptActionDialog actionDialog = actionDialogHolder[0];
            if (actionDialog != null) {
                LOGGER.info("Close the action dialog.");
                try {
                    actionDialog.setVisible(false);
                }
                catch (Exception ex) {
                    LOGGER.warn("Close the action dialog failed.", ex);
                }
            }
            else {
                LOGGER.warn("Skip close the action dialog because no instance available.");
            }
        };

        Boolean continueTask = Boolean.FALSE;
        try {
            if (timeout > 0) {
                continueTask = future.get(timeout + 5, TimeUnit.SECONDS);
            }
            else {
                LOGGER.info("Wait for user to close the dialog without timeout.");
                continueTask = future.get();
            }
            LOGGER.info("Current continueTask flag: {}", continueTask);

            if (continueTask) {
                this.logModel.addConsoleLine(ConsoleColor.green, "User pressed OK button.");
            }
            else {
                this.logModel.addConsoleLine(ConsoleColor.red, "User pressed Cancel button or wait time has elapsed.");
            }

            SwingUtilities.invokeAndWait(scriptActionDialog);
        }
        catch (ExecutionException | TimeoutException | InterruptedException ex) {
            LOGGER.warn("Wait for continue task was not successful.", ex);
        }
        catch (InvocationTargetException ex) {
            LOGGER.warn("Close action dialog failed.", ex);
        }

        return continueTask;
    }

    @Override
    public void execute(String executable, List parameters) {
        LOGGER.info("Executable: {}, parameters: {}", executable, parameters);

        try {
            List args = new ArrayList<>();
            args.add(executable);
            args.addAll(parameters);

            StringBuilder sb = new StringBuilder(executable);
            if (CollectionUtils.isNotEmpty(parameters)) {
                sb.append(", parameter: ").append(parameters);
            }
            this.logModel.addConsoleLine(ConsoleColor.black, "Execute command: " + sb.toString());

            Process process = new ProcessBuilder(args.toArray(new String[0]))/* .inheritIO() */.start();

            int exitCode = process.waitFor();
            LOGGER.info("Current exit code: {}", exitCode);

            String output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8);
            this.logModel.addConsoleLine(ConsoleColor.black, "Output: " + output);

            if (exitCode > 0) {
                this.logModel.addConsoleLine(ConsoleColor.red, "Execute command failed. Exit code: " + exitCode);

                String error = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8);
                this.logModel.addConsoleLine(ConsoleColor.red, "Error: " + error);

                throw new RuntimeException("Execute command failed. Exit code: " + exitCode);
            }
            else {
                this.logModel.addConsoleLine(ConsoleColor.blue, "Execute command passed.");
            }

        }
        catch (IOException ex) {
            LOGGER.info("Create process failed.");
            throw new RuntimeException("Create process failed.");
        }
        catch (InterruptedException ex) {
            LOGGER.info("Wait for process completion failed.");
            throw new RuntimeException("Wait for process completion failed.");
        }
    }

    @Override
    public String setString(Long uniqueId, int namespace, int index, String value) {
        LOGGER
            .info("Set the string on the node, uniqueId: {}, namespace: {}, index: {}, value: {}", uniqueId, namespace,
                index, value);

        final NodeInterface node = this.nodeService.getNode(ConnectionRegistry.CONNECTION_ID_MAIN, uniqueId);
        if (node != null && node.getUniqueId() != uniqueId.longValue()) {
            LOGGER.warn("Set string can only be performed on an available node!");
            return null;
        }

        String returnValue =
            nodeService.setString(ConnectionRegistry.CONNECTION_ID_MAIN, node, namespace, index, value);
        LOGGER.info("Current returnValue: {}", returnValue);
        return returnValue;
    }

    @Override
    public String getString(Long uniqueId, int namespace, int index) {
        LOGGER.info("Set the string on the node, uniqueId: {}, namespace: {}, index: {}", uniqueId, namespace, index);

        final NodeInterface node = this.nodeService.getNode(ConnectionRegistry.CONNECTION_ID_MAIN, uniqueId);
        if (node != null && node.getUniqueId() != uniqueId.longValue()) {
            LOGGER.warn("Set string can only be performed on an available node!");
            return null;
        }

        String returnValue = nodeService.getString(ConnectionRegistry.CONNECTION_ID_MAIN, node, namespace, index);
        LOGGER.info("Current returnValue: {}", returnValue);
        return returnValue;
    }

    @Override
    public NodeInterface getNodeByUniqueIdWithoutClassBits(byte[] uniqueId) {
        this.logModel
            .addConsoleLine(ConsoleColor.black, "Get node with uniqueId: 0x" + ByteUtils.bytesToHex(uniqueId, false));

        return this.nodeService.getNodeByUniqueIdWithoutClassBits(ConnectionRegistry.CONNECTION_ID_MAIN, uniqueId);
    }

    @Override
    public void connect(String connectionId) {
        this.logModel.addConsoleLine(ConsoleColor.black, "Connect: " + connectionId);

        // create the context
        final Context context = new DefaultContext();
        this.connectionService.connect(connectionId, conn -> conn, conn -> conn, context);

        this.logModel.addConsoleLine(ConsoleColor.black, "Connect passed.");
    }

    @Override
    public void disconnect(String connectionId) {
        this.logModel.addConsoleLine(ConsoleColor.black, "Disconnect: " + connectionId);

        this.connectionService.disconnect(connectionId);

        this.logModel.addConsoleLine(ConsoleColor.black, "Disconnect passed.");
    }

    private String connectedDebugInterface;

    private CompositeDisposable compDispDebugInterface;

    public void setConnectedDebugInterface(String connectedDebugInterface) {
        this.connectedDebugInterface = connectedDebugInterface;
    }

    public String getConnectedDebugInterface() {
        return connectedDebugInterface;
    }

    @Override
    public void connectDebug(String connectionId, String portIdentifier, Integer baudRate) {
        this.logModel
            .addConsoleLine(ConsoleColor.black,
                "Connect debug: " + connectionId + (portIdentifier != null ? ", port: " + portIdentifier : "")
                    + (baudRate != null ? ", baudRate: " + baudRate : ""));

        if (connectedDebugInterface != null) {
            LOGGER.warn("The debug interface is connected already.");
            throw new IllegalStateException("The debug interface is connected already: " + connectionId);
        }

        setConnectedDebugInterface(null);

        // create the context
        final Context context = new DefaultContext();
        final DebugConnection debugConnection =
            this.debugConnectionService.connect(connectionId, portIdentifier, baudRate, context);

        setConnectedDebugInterface(connectionId);

        this.compDispDebugInterface = new CompositeDisposable();

        Disposable dispDebugDataEvents = debugConnection.subscribeDataEvents(dde -> {
            LOGGER.info("Received data from debug: {}", ByteUtils.bytesToHex(dde.getData()));
            try {
                this.logModel
                    .addConsoleLine(ConsoleColor.blue,
                        "Received: " + new String(dde.getData(), StandardCharsets.UTF_8));
            }
            catch (Exception ex) {
                LOGGER.warn("Add received data to console failed.", ex);
            }
        }, th -> {
            LOGGER.warn("The debugData subject signalled an error.", th);
        }, () -> {
            LOGGER.info("The debugData subject has completed.");
        });
        this.compDispDebugInterface.add(dispDebugDataEvents);

        this.logModel.addConsoleLine(ConsoleColor.black, "Connect debug passed.");
    }

    @Override
    public void disconnectDebug(String connectionId) {
        this.logModel.addConsoleLine(ConsoleColor.black, "Disconnect debug: " + connectionId);

        this.debugConnectionService.disconnect(connectionId);

        setConnectedDebugInterface(null);

        if (this.compDispDebugInterface != null) {
            LOGGER.info("Dispose the subscription of the debug data events.");
            this.compDispDebugInterface.dispose();
            this.compDispDebugInterface = null;
        }

        this.logModel.addConsoleLine(ConsoleColor.black, "Disconnect debug passed.");
    }

    @Override
    public void sendDebugText(String message) {
        this.logModel.addConsoleLine(ConsoleColor.black, "Send message to debug: " + message);

        String connectionId = getConnectedDebugInterface();

        this.debugConnectionService.sendText(connectionId, message);
    }

    @Override
    public String setStringAndHandleReponse(
        Long uniqueId, int namespace, int index, String value, int responseTimeout,
        final StringChunkResponseHandler chunkResponseHandler) {

        final String connectionId = ConnectionRegistry.CONNECTION_ID_MAIN;

        final NodeInterface node = this.nodeService.getNode(connectionId, uniqueId);
        if (node != null && node.getUniqueId() != uniqueId.longValue()) {
            LOGGER.warn("Set string can only be performed on an available node!");
            return null;
        }

        final Subject subjectDebugEvents = this.debugService.getSubjectDebugEvents();
        final CountDownLatch continueLatch = new CountDownLatch(1);
        final CompositeDisposable compDispDebugEvents = new CompositeDisposable();
        try {

            Disposable disp = subjectDebugEvents.observeOn(Schedulers.single()).subscribe(nodeDebugEvent -> {
                boolean continueRetrieve =
                    chunkResponseHandler
                        .handle(nodeDebugEvent.getConnectionId(), nodeDebugEvent.getNode(),
                            nodeDebugEvent.getNamespace(), nodeDebugEvent.getStringId(), nodeDebugEvent.getValue(),
                            logMessage -> logModel.addConsoleLine(ConsoleColor.blue, logMessage));

                if (!continueRetrieve) {
                    LOGGER.info("The retrieve process has finished.");
                    continueLatch.countDown();
                }
                else {
                    LOGGER.info("Retrieved data but retrieve process must continue.");
                }
            });

            compDispDebugEvents.add(disp);

            String returnValue = nodeService.setString(connectionId, node, namespace, index, value);
            LOGGER.info("Current returnValue: {}", returnValue);

            // if ("MJLI".equals(returnValue)) {
            // this is our start marker that the transmission will start
            boolean continueRetrieve =
                chunkResponseHandler
                    .handle(connectionId, node, namespace, index, returnValue,
                        logMessage -> logModel.addConsoleLine(ConsoleColor.blue, logMessage));

            if (continueRetrieve) {
                LOGGER.info("Wait for termination of retrieve process, responseTimeout: {}", responseTimeout);
                try {
                    continueLatch.await(responseTimeout, TimeUnit.SECONDS);
                }
                catch (InterruptedException ex) {
                    LOGGER.warn("Wait for termination retrieve process was interrupted.", ex);
                }
            }
            // }

        }
        finally {
            compDispDebugEvents.dispose();
        }

        return null;
    }

    @Override
    public CommandStationStatus queryCommandStationStatus(final CommandStationNodeInterface commandStationNode) {

        LOGGER.info("Show the Action dialog.");

        final NodeInterface node = commandStationNode.getNode();

        final String nodeLabel =
            StringUtils.isNotBlank(node.getLabel()) ? node.getLabel()
                : ByteUtils.getUniqueIdAsString(node.getUniqueId());

        this.logModel.addConsoleLine(ConsoleColor.black, "Press ID button on node: " + nodeLabel);

        final ScriptActionDialog actionDialogHolder[] = new ScriptActionDialog[1];
        final CompletableFuture future = new CompletableFuture<>();

        // show the action dialog
        SwingUtilities.invokeLater(() -> {

            final CommandStationStatusListener listener = (n, status) -> {
                final CommandStationStatus commandStationState = commandStationNode.getCommandStationStatus();
                LOGGER.info("The command station state has been changed: {}", commandStationState);
                future.complete(commandStationState);
            };

            try {
                commandStationNode.addCommandStationStatusListener(listener);

                final Context context = new DefaultContext();
                context.register(CommonScripting.KEY_ACTION_TIMEOUT, Integer.valueOf(30));
                String dialogMessage =
                    Resources.getString(ScriptActionDialog.class, "pressCommandStationStatusButtonMessage", nodeLabel);
                final ScriptActionDialog actionDialog =
                    new ScriptActionDialog(desktop, context, true, dialogMessage, (result) -> {
                        LOGGER.info("Dialog was cancelled.");
                    });

                actionDialogHolder[0] = actionDialog;

                actionDialog.setAlwaysOnTop(true);
                actionDialog.setVisible(true);
            }
            finally {
                commandStationNode.removeCommandStationStatusListener(listener);

                if (!future.isDone()) {
                    future.completeExceptionally(new RuntimeException("No command station status change received."));
                }
            }
        });

        final Runnable scriptActionDialog = () -> {
            final ScriptActionDialog actionDialog = actionDialogHolder[0];
            if (actionDialog != null) {
                LOGGER.info("Close the action dialog.");
                try {
                    actionDialog.setVisible(false);
                }
                catch (Exception ex) {
                    LOGGER.warn("Close the action dialog failed.", ex);
                }
            }
            else {
                LOGGER.warn("Skip close the action dialog because no instance available.");
            }
        };

        final CommandStationStatus commandStationState;
        try {
            commandStationState = future.get(35, TimeUnit.SECONDS);
            LOGGER.info("Current commandStationState: {}", commandStationState);

            SwingUtilities.invokeAndWait(scriptActionDialog);
        }
        catch (ExecutionException | TimeoutException | InterruptedException ex) {
            LOGGER.warn("Wait for command station state change was not successful.", ex);
            throw new IllegalArgumentException("No answer from command station node!");
        }
        catch (InvocationTargetException ex) {
            LOGGER.warn("Close action dialog failed.", ex);
            throw new IllegalArgumentException("Internal error!");
        }

        return commandStationState;
    }

    @Override
    public String findReachableDevice(List addresses, int retryCount) {

        int currentRetry = 0;
        do {
        for (String currentIp : addresses) {
            this.logModel.addConsoleLine(ConsoleColor.black, "Try to find netBiDiB device at: " + currentIp);

            try {
                InetAddress address = InetAddress.getByName(currentIp);
                boolean reachable = address.isReachable(5000);

                LOGGER.info("Host {} is reachable: {}", currentIp, reachable);

                if (reachable) {
                    LOGGER.info("Found reachable host: {}", currentIp);
                    this.logModel.addConsoleLine(ConsoleColor.blue, "Found reachable host: " + currentIp);
                    return currentIp;
                }
            }
            catch (Exception ex) {
                LOGGER.warn("Check if IP is reachable failed.", ex);
            }

            if (Thread.currentThread().isInterrupted()) {
                LOGGER.info("The thread is interrupted.");
                throw new ScriptExecutionException("The script execution was interrupted.");
            }
        }

            currentRetry++;
        }
        while (currentRetry < (retryCount + 1));

        this.logModel.addConsoleLine(ConsoleColor.red, "No reachable hosts found.");
        return null;
    }

    @Override
    public void connect(final String connectionId, String address) {
        this.logModel
            .addConsoleLine(ConsoleColor.black,
                "Change connection parameters for connection: " + connectionId + ", use address: " + address);

        final CompositeDisposable compDisposable = new CompositeDisposable();

        final CountDownLatch continueLatch = new CountDownLatch(1);

        // 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 (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());

                    continueLatch.countDown();
                }
            }
        }, 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 (connectionId.equals(ci.getConnectionId())) {

                if (ConnectionPhase.DISCONNECTED == ci.getConnectionState().getActualPhase()) {
                    LOGGER.info("The connection was disconnected!");
                    compDisposable.dispose();

                    continueLatch.countDown();
                }
            }
        }, error -> {
            LOGGER.warn("Subscription to connection status signalled an error: ", error);
            // publishStatus(error);
        });
        compDisposable.add(dispConnectionStatus);

        // create the context
        final Context context = new DefaultContext();
        this.connectionService.connect(connectionId, conn -> {
            LOGGER.info("Set the address to use: {}", address);
            conn.setUri(address);
            return conn;
        }, conn -> {
            LOGGER.info("Set the address to use: {}", address);
            conn.setUri(address);
            return conn;
        }, context);

        this.logModel.addConsoleLine(ConsoleColor.black, "Connect passed. Wait for loading nodes.");

        LOGGER.info(">>>> Wait for finish loading nodes.");

        synchronized (continueLatch) {
            try {
                continueLatch.await(35, TimeUnit.SECONDS);

                this.logModel.addConsoleLine(ConsoleColor.black, "Loading nodes has finished.");
            }
            catch (InterruptedException ex) {
                LOGGER.warn("Wait for end of loading nodes failed.", ex);
                throw new ScriptExecutionException("The script execution was interrupted.");
            }
        }

        LOGGER.info(">>>> Connect has finished.");
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy