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

org.bidib.wizard.mvc.main.controller.MainController Maven / Gradle / Ivy

There is a newer version: 2.0.0-M1
Show newest version
package org.bidib.wizard.mvc.main.controller;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.swing.ImageIcon;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.bidib.jbidibc.core.BidibLibrary;
import org.bidib.jbidibc.core.ConnectionListener;
import org.bidib.jbidibc.core.Feature;
import org.bidib.jbidibc.core.NodeListener;
import org.bidib.jbidibc.core.ProtocolVersion;
import org.bidib.jbidibc.core.SoftwareVersion;
import org.bidib.jbidibc.core.StringData;
import org.bidib.jbidibc.core.enumeration.IoBehaviourEnum;
import org.bidib.jbidibc.core.enumeration.LcMacroState;
import org.bidib.jbidibc.core.enumeration.LcOutputType;
import org.bidib.jbidibc.core.enumeration.PortConfigKeys;
import org.bidib.jbidibc.core.enumeration.PortConfigStatus;
import org.bidib.jbidibc.core.enumeration.SysErrorEnum;
import org.bidib.jbidibc.core.exception.InvalidConfigurationException;
import org.bidib.jbidibc.core.exception.NoAnswerException;
import org.bidib.jbidibc.core.exception.PortNotFoundException;
import org.bidib.jbidibc.core.exception.PortNotOpenedException;
import org.bidib.jbidibc.core.exception.ReasonAware;
import org.bidib.jbidibc.core.helpers.Context;
import org.bidib.jbidibc.core.helpers.DefaultContext;
import org.bidib.jbidibc.core.node.BidibNode;
import org.bidib.jbidibc.core.node.ConfigurationVariable;
import org.bidib.jbidibc.core.node.RootNode;
import org.bidib.jbidibc.core.port.BytePortConfigValue;
import org.bidib.jbidibc.core.port.PortConfigValue;
import org.bidib.jbidibc.core.utils.ByteUtils;
import org.bidib.jbidibc.core.utils.NodeUtils;
import org.bidib.jbidibc.core.utils.ProductUtils;
import org.bidib.jbidibc.debug.DebugMessageListener;
import org.bidib.jbidibc.debug.DebugMessageReceiver;
import org.bidib.jbidibc.debug.DebugReader;
import org.bidib.jbidibc.exchange.vendorcv.VendorCvData;
import org.bidib.jbidibc.exchange.vendorcv.VendorCvFactory;
import org.bidib.jbidibc.ui.OS;
import org.bidib.wizard.comm.AnalogPortStatus;
import org.bidib.wizard.comm.BacklightPortStatus;
import org.bidib.wizard.comm.BidibStatus;
import org.bidib.wizard.comm.Communication;
import org.bidib.wizard.comm.CommunicationFactory;
import org.bidib.wizard.comm.InputPortStatus;
import org.bidib.wizard.comm.LightPortStatus;
import org.bidib.wizard.comm.MotorPortStatus;
import org.bidib.wizard.comm.ServoPortStatus;
import org.bidib.wizard.comm.SoundPortStatus;
import org.bidib.wizard.comm.SwitchPortStatus;
import org.bidib.wizard.comm.listener.CommunicationListener;
import org.bidib.wizard.dialog.FileDialog;
import org.bidib.wizard.labels.LabelType;
import org.bidib.wizard.labels.Labels;
import org.bidib.wizard.locale.Resources;
import org.bidib.wizard.main.DefaultApplicationContext;
import org.bidib.wizard.mvc.common.model.PreferencesPortType;
import org.bidib.wizard.mvc.main.model.Accessory;
import org.bidib.wizard.mvc.main.model.AccessoryFactory;
import org.bidib.wizard.mvc.main.model.AnalogPort;
import org.bidib.wizard.mvc.main.model.AnalogPortLabels;
import org.bidib.wizard.mvc.main.model.BacklightPort;
import org.bidib.wizard.mvc.main.model.BacklightPortLabels;
import org.bidib.wizard.mvc.main.model.DefaultTransferListener;
import org.bidib.wizard.mvc.main.model.FeedbackPort;
import org.bidib.wizard.mvc.main.model.FeedbackPortLabels;
import org.bidib.wizard.mvc.main.model.Flag;
import org.bidib.wizard.mvc.main.model.GenericPort;
import org.bidib.wizard.mvc.main.model.InputPort;
import org.bidib.wizard.mvc.main.model.InputPortLabels;
import org.bidib.wizard.mvc.main.model.LightPort;
import org.bidib.wizard.mvc.main.model.LightPortLabels;
import org.bidib.wizard.mvc.main.model.Macro;
import org.bidib.wizard.mvc.main.model.MacroFactory;
import org.bidib.wizard.mvc.main.model.MacroFactory.ExportFormat;
import org.bidib.wizard.mvc.main.model.MacroLabels;
import org.bidib.wizard.mvc.main.model.MacroRef;
import org.bidib.wizard.mvc.main.model.MacroSaveState;
import org.bidib.wizard.mvc.main.model.MainMessageListener;
import org.bidib.wizard.mvc.main.model.MainModel;
import org.bidib.wizard.mvc.main.model.MotorPort;
import org.bidib.wizard.mvc.main.model.MotorPortLabels;
import org.bidib.wizard.mvc.main.model.Node;
import org.bidib.wizard.mvc.main.model.NodeLabels;
import org.bidib.wizard.mvc.main.model.Port;
import org.bidib.wizard.mvc.main.model.ServoPort;
import org.bidib.wizard.mvc.main.model.ServoPortLabels;
import org.bidib.wizard.mvc.main.model.SoundPort;
import org.bidib.wizard.mvc.main.model.SoundPortLabels;
import org.bidib.wizard.mvc.main.model.SwitchPort;
import org.bidib.wizard.mvc.main.model.SwitchPortLabels;
import org.bidib.wizard.mvc.main.model.function.Function;
import org.bidib.wizard.mvc.main.model.listener.BacklightPortListener;
import org.bidib.wizard.mvc.main.model.listener.CvDefinitionRequestListener;
import org.bidib.wizard.mvc.main.model.listener.InputPortListener;
import org.bidib.wizard.mvc.main.model.listener.LightPortListener;
import org.bidib.wizard.mvc.main.model.listener.NodeListListener;
import org.bidib.wizard.mvc.main.model.listener.OutputListener;
import org.bidib.wizard.mvc.main.model.listener.ServoPortListener;
import org.bidib.wizard.mvc.main.model.listener.SwitchPortListener;
import org.bidib.wizard.mvc.main.view.MainNodeListListener;
import org.bidib.wizard.mvc.main.view.MainView;
import org.bidib.wizard.mvc.main.view.component.AccessoryFileDialog;
import org.bidib.wizard.mvc.main.view.component.LabeledDisplayItems;
import org.bidib.wizard.mvc.main.view.component.MacroFileDialog;
import org.bidib.wizard.mvc.main.view.menu.listener.MainMenuListener;
import org.bidib.wizard.mvc.main.view.panel.listener.AccessoryListListener;
import org.bidib.wizard.mvc.main.view.panel.listener.AccessoryTableListener;
import org.bidib.wizard.mvc.main.view.panel.listener.MacroListListener;
import org.bidib.wizard.mvc.main.view.panel.listener.MacroTableListener;
import org.bidib.wizard.mvc.main.view.panel.listener.StatusListener;
import org.bidib.wizard.mvc.main.view.statusbar.StatusBar;
import org.bidib.wizard.mvc.preferences.model.Preferences;
import org.bidib.wizard.mvc.script.view.NodeScripting;
import org.bidib.wizard.script.node.types.TargetType;
import org.bidib.wizard.utils.AccessoryLabelFactory;
import org.bidib.wizard.utils.AccessoryLabelUtils;
import org.bidib.wizard.utils.DockUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vlsolutions.swing.docking.Dockable;

public class MainController implements MainControllerInterface {
    private static final Logger LOGGER = LoggerFactory.getLogger(MainController.class);

    private final MainModel model = new MainModel();

    private MainView view;

    private MainNodeListListener mainNodeListListener;

    private Labels accessoryLabels;

    private final AnalogPortLabels analogPortLabels = new AnalogPortLabels();

    private final FeedbackPortLabels feedbackPortLabels = new FeedbackPortLabels();

    private final InputPortLabels inputPortLabels = new InputPortLabels();

    private final LightPortLabels lightPortLabels = new LightPortLabels();

    private final BacklightPortLabels backlightPortLabels = new BacklightPortLabels();

    private final MacroLabels macroLabels = new MacroLabels();

    private final MotorPortLabels motorPortLabels = new MotorPortLabels();

    private final NodeLabels nodeLabels = new NodeLabels();

    private final ServoPortLabels servoPortLabels = new ServoPortLabels();

    private final SoundPortLabels soundPortLabels = new SoundPortLabels();

    private final SwitchPortLabels switchPortLabels = new SwitchPortLabels();

    private Function[] functions;

    private boolean ignoreWaitTimeout;

    private final Map newNodeCreatedThreadRegistry =
        new LinkedHashMap();

    private final ScheduledExecutorService selectedNodeChangeWorker = Executors.newScheduledThreadPool(1);

    private Timer boosterCurrentTimer;

    private static final long CURRENT_UPDATE_TIMEOUT = 3000;

    public MainController() {
        ignoreWaitTimeout = Preferences.getInstance().isIgnoreWaitTimeout();
        LOGGER.info("Created MainController, ignoreWaitTimeout: {}", ignoreWaitTimeout);
    }

    /**
     * Create a new wizard node and return the initialized wizard node.
     */
    protected Node createNode(Communication communication, org.bidib.jbidibc.core.Node node) {
        Node wizardNode = null;
        LOGGER.info("Create new 'wizard' node from jbidibc.Node: {}", node);

        if (communication != null && node != null) {
            wizardNode = new Node(node);

            communication.clearCachedData(node);
            if (!communication.isNodeRegistered(node)) {
                communication.registerNode(node);
            }

            boolean limitedNode = true;
            int magic = BidibNode.BIDIB_MAGIC_UNKNOWN;
            try {
                magic = communication.getMagic(node);
            }
            catch (NoAnswerException ex) {
                // the magic stays as BIDIB_MAGIC_UNKNOWN and this signals an error
                LOGGER.warn("The node did not answer to the first getMagic request: {}", node, ex);

                try {
                    Thread.sleep(500);
                    magic = communication.getMagic(node);
                }
                catch (NoAnswerException ex1) {
                    // the magic stays as BIDIB_MAGIC_UNKNOWN and this signals an error
                    LOGGER.warn("The node did not answer to the second getMagic request: {}", node, ex1);
                }
                catch (InterruptedException ex1) {
                    LOGGER.warn(
                        "The current thread was interrupted before the second getMagic request to node was sent: {}",
                        node, ex1);
                }
            }

            switch (magic) {
                case BidibNode.BIDIB_MAGIC_UNKNOWN:
                    // the node has an error
                    wizardNode.setNodeHasError(true);
                    break;
                case BidibLibrary.BIDIB_BOOT_MAGIC:
                    LOGGER
                        .info(
                            "Node returned boot magic: {}. This is a limited node that does not answer to feature requests!",
                            magic);
                    wizardNode.setBootloaderNode(true);

                    // the node supports FW update by specification
                    wizardNode.setUpdatable(true);
                    break;
                default:
                    LOGGER.info("Node returned magic: {}", magic);

                    // the node has no limitations
                    limitedNode = false;

                    // get the firmware version
                    SoftwareVersion softwareVersion = communication.getSoftwareVersion(node);
                    LOGGER.info("SW version of new node: {}", softwareVersion);
                    wizardNode.getNode().setSoftwareVersion(softwareVersion);

                    // get the protocol version
                    ProtocolVersion protocolVersion = communication.getProtocolVersion(node);
                    LOGGER.info("Protocol version of new node: {}", protocolVersion);
                    wizardNode.getNode().setProtocolVersion(protocolVersion);

                    // after we fetch the magic we must try to get the FEATURE_RELEVANT_PID_BITS
                    Feature relevantPidBits =
                        communication.readFeature(node, BidibLibrary.FEATURE_RELEVANT_PID_BITS, true);
                    if (relevantPidBits != null) {
                        wizardNode.getNode().setRelevantPidBits(relevantPidBits.getValue());
                    }

                    // get the FEATURE_STRING_SIZE
                    Feature stringSize = communication.readFeature(node, BidibLibrary.FEATURE_STRING_SIZE, true);
                    if (stringSize != null && stringSize.getValue() > 0) {
                        LOGGER.info("Node supports FEATURE_STRING_SIZE: {}", stringSize.getValue());
                        wizardNode.getNode().setStringSize(stringSize.getValue());

                        int namespace = StringData.NAMESPACE_NODE;
                        int index = 0;
                        String storedString = null;
                        try {
                            storedString = communication.getString(node, namespace, index);
                        }
                        catch (RuntimeException ex) {
                            LOGGER.warn("Fetch stored string failed, namespace: {}, index: {}", namespace, index, ex);
                        }
                        LOGGER.info("Fetched storedString[{}:{}]: {}", namespace, index, storedString);
                        wizardNode.getNode().setStoredString(index, storedString);
                        index++;
                        try {
                            storedString = communication.getString(node, namespace, index);
                        }
                        catch (RuntimeException ex) {
                            LOGGER.warn("Fetch stored string failed, namespace: {}, index: {}", namespace, index, ex);
                        }
                        LOGGER.info("Fetched storedString[{}:{}]: {}", namespace, index, storedString);
                        wizardNode.getNode().setStoredString(index, storedString);
                    }
                    else {
                        LOGGER.info("Node has not FEATURE_STRING_SIZE available or string size is 0.");
                    }

                    // check if the node supports FW update
                    wizardNode.setUpdatable(communication.isUpdatable(node));

                    if (ProductUtils.isOneBootloader(node.getUniqueId())) {
                        LOGGER.info("Found a OneBootloader node: {}", node);
                    }
                    break;
            }

            if (!wizardNode.isNodeHasError()) {
                // collect some configuration of the node
                wizardNode.setCommandStation(communication.isCommandStation(node));

                if (wizardNode.isCommandStation()) {
                    LOGGER.info("Ask the commandstation node if the RailCom+ feature is available.");
                    wizardNode.setRailComPlusAvailable(communication.isRailComPlusAvailable(node));

                    LOGGER.info("Set the POM Update available for the command station node.");
                    wizardNode.setPomUpdateAvailable(true);
                }
                wizardNode.setBooster(communication.isBooster(node));

                // check if the node has feedback ports to know its a feedback device
                if (!limitedNode && NodeUtils.hasFeedbackFunctions(node.getUniqueId())) {
                    int numFeedbackPorts = communication.getFeedbackPorts(node).size();
                    LOGGER.info("Number of feedback ports: {}", numFeedbackPorts);
                    if (numFeedbackPorts > 0) {
                        LOGGER.debug("The new node has feedback ports.");
                        wizardNode.setAddressMessagesEnabled(communication.isAddressMessagesEnabled(node));
                        wizardNode.setFeedbackMessagesEnabled(communication.isFeedbackMessagesEnabled(node));
                    }
                }
                else {
                    LOGGER.debug("The new node has no feedback functions.");
                }

                // check if the node has switch functions
                if (!limitedNode && NodeUtils.hasSwitchFunctions(node.getUniqueId())) {
                    Feature portFlatModel =
                        communication.readFeature(node, BidibLibrary.FEATURE_CTRL_PORT_FLAT_MODEL, true);
                    if (portFlatModel != null) {
                        wizardNode.getNode().setPortFlatModel(portFlatModel.getValue());
                    }
                    else {
                        LOGGER.info("The feature FEATURE_CTRL_PORT_FLAT_MODEL is not available.");
                    }

                    Feature switchPortConfigAvailable =
                        communication.readFeature(node, BidibLibrary.FEATURE_SWITCH_CONFIG_AVAILABLE, true);
                    if (switchPortConfigAvailable != null) {
                        wizardNode.getNode().setSwitchPortConfigAvailable(
                            switchPortConfigAvailable.getValue() == 1 ? true : false);
                    }
                    else {
                        LOGGER.info("The feature FEATURE_SPORT_CONFIG_AVAILABLE is not available.");
                    }

                    int numInputPorts = communication.getInputPorts(node).size();
                    LOGGER.debug("Number of input ports: {}", numInputPorts);
                    if (numInputPorts > 0) {
                        LOGGER.debug("The new node has input ports.");
                        wizardNode.setKeyMessagesEnabled(communication.isKeyMessagesEnabled(node));
                    }
                    int macroLength = communication.getMacroLength(node);
                    LOGGER.debug("Supported macro lenght: {}", macroLength);
                    if (macroLength > 0) {
                        LOGGER.debug("The new node has macros.");

                        // check from the features
                        wizardNode.setDccStartEnabled(communication.isDccStartEnabled(node));
                        wizardNode.setExternalStartEnabled(communication.isExternalStartEnabled(node));
                    }
                    wizardNode.setStorableMacroCount(communication.getStorableMacroCount(node));
                }
                else {
                    LOGGER.debug("The new node has no accessory functions.");
                }
            }
            else {
                LOGGER.error("The new node has errors: {}", wizardNode);
            }

            wizardNode.setLabel(nodeLabels.getLabel(node.getUniqueId()));
            LOGGER.info("Prepared new 'wizard' node: {}", wizardNode);
        }
        return wizardNode;
    }

    @Override
    public void clearNodes() {
        LOGGER.info("Clear the nodes from the model.");

        model.getStatusModel().setCd(false);
        model.getStatusModel().setRx(false);
        model.getStatusModel().setTx(false);
        LOGGER.warn("Communication factory is not available!");
        model.getStatusModel().setCd(false);

        model.clearNodes();
    }

    @Override
    public Collection getNodes() {
        return model.getNodes();
    }

    private Node findNode(org.bidib.jbidibc.core.Node commNode) {
        Node result = null;

        if (commNode != null) {
            for (Node node : model.getNodes()) {
                if (node.getNode().getUniqueId() == commNode.getUniqueId()) {
                    result = node;
                    break;
                }
            }
        }
        return result;
    }

    private Function[] getFunctions() {
        Function[] result = null;

        if (functions != null) {
            try {
                result = new Function[functions.length];
                for (int index = 0; index < functions.length; index++) {
                    if (functions[index] != null) {
                        result[index] = ((Function) functions[index].clone());
                    }
                }
            }
            catch (CloneNotSupportedException e) {
                throw new RuntimeException(e);
            }
        }
        return result;
    }

    private void loadLabels() {

        // make sure the labels directory is available and write enabled
        String labelPath = Preferences.getInstance().getLabelPath();
        LOGGER.info("Check if the directory for labels exists: {}", labelPath);

        try {
            Path dir = Paths.get(labelPath);

            if (!Files.exists(dir)) {
                LOGGER.info("Try to create the directory for labels: {}", labelPath);

                try {
                    dir = Files.createDirectories(dir);

                    LOGGER.info("Created new directory for labels: {}", dir);
                }
                catch (IOException ioex) {
                    LOGGER.warn("Create new directory for labels failed.", ioex);

                    if (OS.isLinux() && 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(Labels.class, "labeldirerror.correct"),
                                    Resources.getString(Labels.class, "labeldirerror.ignore") };

                            int answer =
                                JOptionPane.showOptionDialog(JOptionPane.getFrameForComponent(null), Resources
                                    .getString(Labels.class, "labeldirerror.message_username_mismatch", new Object[] {
                                        userName, configuredUserName, labelPath }), Resources.getString(Labels.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);
                                Preferences.getInstance().setLabelPath(labelPath);
                                Preferences.getInstance().save(null);

                                dir = Paths.get(labelPath);
                                if (!Files.exists(dir)) {
                                    LOGGER.info("Try to create the directory for labels: {}", labelPath);
                                    try {
                                        dir = Files.createDirectories(dir);
                                        LOGGER.info("Created new directory for labels: {}", dir);
                                    }
                                    catch (IOException ioex2) {
                                        LOGGER.warn("Create new directory for labels failed.", ioex);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (!Files.exists(dir)) {
                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 (!Files.isWritable(dir)) {
                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);
            }
            else {
                LOGGER.info("The label directory is write enabled: {}", labelPath);
            }
        }
        catch (Exception ex) {
            LOGGER.warn(
                "The directory for labels is not available or is not write enabled. Check permission on path: {}",
                labelPath, ex);

            JOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(null),
                Resources.getString(Labels.class, "labeldirerror.message", new Object[] { labelPath }),
                Resources.getString(Labels.class, "labeldirerror.title"), JOptionPane.ERROR_MESSAGE);
        }

        analogPortLabels.load();
        backlightPortLabels.load();
        feedbackPortLabels.load();
        inputPortLabels.load();
        lightPortLabels.load();
        motorPortLabels.load();
        servoPortLabels.load();
        soundPortLabels.load();
        switchPortLabels.load();

        nodeLabels.load();
        // accessoryLabels.load();
        AccessoryLabelFactory factory = new AccessoryLabelFactory();
        accessoryLabels = factory.getAccessoryLabels(factory.getDefaultFileName());

        macroLabels.load();

        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_ANALOGPORT_LABELS,
            analogPortLabels);
        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_BACKLIGHTPORT_LABELS,
            backlightPortLabels);
        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_FEEDBACKPORT_LABELS,
            feedbackPortLabels);
        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_INPUTPORT_LABELS,
            inputPortLabels);
        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_LIGHTPORT_LABELS,
            lightPortLabels);
        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_MOTORPORT_LABELS,
            motorPortLabels);
        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_SERVOPORT_LABELS,
            servoPortLabels);
        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_SOUNDPORT_LABELS,
            soundPortLabels);
        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_SWITCHPORT_LABELS,
            switchPortLabels);

        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_NODE_LABELS, nodeLabels);
        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_MACRO_LABELS, macroLabels);
        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_ACCESSORY_LABELS,
            accessoryLabels);
    }

    private void setDefaultCursor() {
        view.setBusy(false);
    }

    private void setFunctions(Function[] functions) {
        if (functions != null) {
            try {
                this.functions = new Function[functions.length];
                for (int index = 0; index < functions.length; index++) {
                    if (functions[index] != null) {
                        this.functions[index] = ((Function) functions[index].clone());
                    }
                }
            }
            catch (CloneNotSupportedException e) {
                throw new RuntimeException(e);
            }
        }
        else {
            this.functions = null;
        }
    }

    private void setWaitCursor() {
        view.setBusy(true);
    }

    /**
     * Start the main controller.
     */
    public void start() {

        LOGGER.info("Start the main controller");

        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_PORTS_PROVIDER, model);

        loadLabels();

        // this initializes Bidib ...
        CommunicationFactory.addTransferListener(new DefaultTransferListener(model));

        CommunicationFactory.addMessageListener(new MainMessageListener(model));

        CommunicationFactory.addNodeListener(new NodeListener() {
            @Override
            public void nodeLost(org.bidib.jbidibc.core.Node commNode) {
                Node node = findNode(commNode);

                LOGGER.info("Node lost, commNode: {}, node: {}", commNode, node);
                if (node != null) {
                    LOGGER.info("Remove lost node from communication: {}", node);
                    Communication communication = CommunicationFactory.getInstance();
                    communication.removeNode(node.getNode());
                    model.removeNode(node);

                    // remove the node from the newNodeCreated-thread registry
                    synchronized (newNodeCreatedThreadRegistry) {
                        Thread newNodeCreatedThread = newNodeCreatedThreadRegistry.remove(commNode);
                        if (newNodeCreatedThread != null) {
                            try {
                                LOGGER.warn("Interrupt the newNodeCreatedThread: {}", newNodeCreatedThread);
                                if (newNodeCreatedThread.isAlive()) {
                                    newNodeCreatedThread.interrupt();
                                }
                            }
                            catch (Exception ex) {
                                LOGGER.warn("Interrupt newNodeCreatedThread failed.", ex);
                            }
                        }
                        else {
                            LOGGER.info("The lost node is not in the registry of new created nodes threads.");
                        }
                    }

                    view.setStatusText(String.format(Resources.getString(MainController.class, "node-lost"), commNode),
                        StatusBar.DISPLAY_ERROR);
                }
                else {
                    LOGGER.debug("Node not found.");
                }
            }

            @Override
            public void nodeNew(final org.bidib.jbidibc.core.Node commNode) {
                LOGGER.info("New node in system: {}", commNode);

                // check if the root node is initialized ...
                if (ByteUtils.arrayEquals(commNode.getAddr(), RootNode.ROOTNODE_ADDR)) {
                    LOGGER.info("Process the root node: {}", commNode);
                }
                else if (!CollectionUtils.isNotEmpty(getNodes())) {
                    LOGGER.warn("A new node is signaled but the root node was not processed yet. Skip the new node.");
                    return;
                }

                // TODO if an existing node is added with a new version this will not detect the problem!
                // TODO change the logic here
                LOGGER.info("Current newNodeCreatedThreadRegistry: {}", newNodeCreatedThreadRegistry);
                Thread newNodeCreatedThread = null;

                synchronized (newNodeCreatedThreadRegistry) {

                    org.bidib.jbidibc.core.Node foundKey =
                        org.apache.commons.collections4.CollectionUtils.find(newNodeCreatedThreadRegistry.keySet(),
                            new Predicate() {

                                @Override
                                public boolean evaluate(org.bidib.jbidibc.core.Node node) {
                                    if (ByteUtils.arrayEquals(node.getAddr(), commNode.getAddr())
                                        && node.getUniqueId() == commNode.getUniqueId()) {
                                        LOGGER.debug("Found equal node: {}", node);
                                        return true;
                                    }
                                    return false;
                                }

                            });
                    if (foundKey != null) {
                        LOGGER.warn("The new node is already in the registration process: {}", commNode);
                        return;
                    }

                    // TODO we should better use a queue and let the network be read from only 1 thread
                    newNodeCreatedThread =
                        new Thread(new NewNodeReader(MainController.this, view, model, newNodeCreatedThreadRegistry,
                            commNode), "newNode-created-thread-" + String.valueOf(System.currentTimeMillis()));

                    // add the node to the newNodeCreated-thread registry
                    LOGGER
                        .info("Add new node to newNodeCreatedThreadRegistry and start thread, commNode: {}", commNode);
                    // synchronized (newNodeCreatedThreadRegistry) {
                    newNodeCreatedThreadRegistry.put(commNode, newNodeCreatedThread);
                }
                newNodeCreatedThread.start();
            }
        });

        // create the main view
        view = new MainView(model);
        view.createComponents();
        view.setIconImage(new ImageIcon(getClass().getResource("/icons/frameIcon_48x48.png")).getImage());

        // layout the frame content
        view.prepareFrame();
        view.setVisible(true);

        CommunicationFactory.addCommunicationListener(new CommunicationListener() {

            @Override
            public void opened(String port) {
                view.setStatusText(Resources.getString(CommunicationFactory.class, "open-port-passed") + " " + port,
                    StatusBar.DISPLAY_NORMAL);

                model.getStatusModel().setCd(true);
            }

            @Override
            public void closed(final String port) {
                // clear all nodes
                if (SwingUtilities.isEventDispatchThread()) {
                    handleClosed(port);
                }
                else {
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            handleClosed(port);
                        }
                    });
                }
            }

            private void handleClosed(final String port) {
                LOGGER.info("Port was closed, set the status text and clear the nodes.");
                if (StringUtils.isNotBlank(port)) {
                    view.setStatusText(Resources.getString(CommunicationFactory.class, "close-port-passed") + " "
                        + port, StatusBar.DISPLAY_NORMAL);
                }
                else {
                    view.setStatusText(null, StatusBar.DISPLAY_NORMAL);
                }
                clearNodes();

                model.getStatusModel().setCd(false);

                model.signalResetInitialLoadFinished();
                // release the cv definition
                model.setCvDefinition(null);
            }

            @Override
            public void initialized() {
            }

            @Override
            public void status(String statusText, int displayDuration) {
                view.setStatusText(statusText, displayDuration);
            }
        });

        // display the connect hint in the status bar
        view.setStatusText(Resources.getString(getClass(), "connectHint"), StatusBar.DISPLAY_NORMAL);

        // add the node list listener
        mainNodeListListener = new MainNodeListListener(view, model);
        mainNodeListListener.setNodeLabels(nodeLabels);
        mainNodeListListener.setAccessoryLabels(accessoryLabels);
        mainNodeListListener.setMacroLabels(macroLabels);
        mainNodeListListener.setPortLabels(analogPortLabels, feedbackPortLabels, inputPortLabels, lightPortLabels,
            backlightPortLabels, motorPortLabels, servoPortLabels, soundPortLabels, switchPortLabels);
        view.addNodeListListener(mainNodeListListener);

        DefaultApplicationContext.getInstance().register(DefaultApplicationContext.KEY_MAINNODELISTLISTENER,
            mainNodeListListener);

        // add the node selection listener
        view.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());

                    // use an executor
                    selectedNodeChangeWorker.schedule(new Runnable() {

                        @Override
                        public void run() {

                            LOGGER.info("Process node changed starting, Node-changed-thread-{}",
                                System.currentTimeMillis());

                            // TODO make sure the initial loading of the tree structure has finished

                            if (!model.isInitialLoadFinished()) {
                                LOGGER
                                    .warn("The initial load of the nodeTab has not finished yet. Cancel selection and load of node details.");
                                return;
                            }

                            // select the node details tab
                            try {
                                String searchKey = "tabPanel";
                                LOGGER.info("Search for view with key: {}", searchKey);
                                Dockable tabPanel = view.getDesktop().getContext().getDockableByKey(searchKey);
                                DockUtils.selectWindow(tabPanel);
                            }
                            catch (Exception ex) {
                                LOGGER.warn("Select tabPanel failed.", ex);
                            }

                            try {
                                setWaitCursor();

                                StopWatch sw = new StopWatch();
                                sw.start();

                                final LabeledDisplayItems nodeList = (LabeledDisplayItems) e.getSource();
                                final Node node = nodeList.getSelectedItem();

                                // do not load the node if it is already selected
                                if (node != null && node.equals(model.getSelectedNode())) {
                                    LOGGER.info("The node is already selected!");
                                    return;
                                }

                                LOGGER.debug("Set the new selected node in the model: {}", node);
                                model.setSelectedNode(node);

                                view.setStatusText(
                                    String.format(Resources.getString(MainController.class, "load-config"), node),
                                    StatusBar.DISPLAY_NORMAL);

                                // fetch the cv definition
                                VendorCvData vendorCV = null;
                                if (node != null) {
                                    try {

                                        File file = new File("");
                                        file = new File(file.getAbsoluteFile(), "data/BiDiBNodeVendorData");

                                        String userHome = System.getProperty("user.home");
                                        File searchPathUserHome =
                                            new File(userHome, ".BiDiBWizard/data/BiDiBNodeVendorData");

                                        String labelPath = Preferences.getInstance().getLabelPath();
                                        File searchPathLabelPath = new File(labelPath, "data/BiDiBNodeVendorData");

                                        vendorCV =
                                            VendorCvFactory.getCvDefinition(node.getNode(),
                                                searchPathUserHome.getAbsolutePath(),
                                                searchPathLabelPath.getAbsolutePath(), file.getAbsolutePath(),
                                                "classpath:/bidib");
                                    }
                                    catch (Exception ex) {
                                        LOGGER.warn("Get CV definition for node failed.", ex);
                                    }

                                    if (vendorCV != null) {
                                        node.setVendorCV(vendorCV);
                                        node.prepareVendorCVTree();
                                    }
                                    else if (ProductUtils.isOneBootloader(node.getUniqueId())) {
                                        LOGGER.info("The current node is a OneBootloader.");
                                    }
                                }
                                model.setCvDefinition(vendorCV);

                                boolean nodeHasLimitations = true;
                                if (node != null) {
                                    // ignore sys errors
                                    nodeHasLimitations = node.isBootloaderNode() || node.isNodeHasError(true);

                                    LOGGER.info("The node was checked for limitiations: {}", nodeHasLimitations);
                                }

                                if (!nodeHasLimitations && CommunicationFactory.getInstance() != null) {
                                    getNodeConfiguration(node);

                                    queryNodeStatus(node);
                                }
                                else {
                                    // update all port lists with the values that are now available
                                    model.updatePortLists();
                                }

                                sw.stop();
                                LOGGER.info("Process node changed took: {}", sw);
                                view.setStatusText(String.format(
                                    Resources.getString(MainController.class, "load-config-finished"), node, sw),
                                    StatusBar.DISPLAY_NORMAL);

                            }
                            catch (Exception ex) {
                                LOGGER.warn("Process node change caused an error: {}", ex);
                            }
                            finally {
                                setDefaultCursor();
                                LOGGER.info("Process node changed finished.");
                            }
                        }
                    }, 0, TimeUnit.MILLISECONDS);
                }
            }
        });

        view.addAccessoryListListener(new AccessoryListListener() {
            @Override
            public void exportAccessory(final Accessory accessory) {
                // save the accessory
                AccessoryFileDialog dialog = new AccessoryFileDialog(
                    view,
                    // default is legacy filter
                    FileDialog.SAVE, accessory) {
                    @Override
                    public void approve(String fileName) {
                        try {
                            ExportFormat exportFormat =
                                (getCheckUseLegacyFormat().isSelected() ? ExportFormat.serialization : ExportFormat.jaxb);
                            AccessoryFactory.saveAccessory(fileName, accessory, exportFormat);

                            // update the status bar
                            view.setStatusText(
                                String.format(Resources.getString(MainController.class, "exportedAccessory"), fileName),
                                StatusBar.DISPLAY_NORMAL);
                        }
                        catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
                };
                dialog.showDialog();
            }

            @Override
            public void importAccessory(final Accessory accessory) {
                AccessoryFileDialog dialog = new AccessoryFileDialog(
                    view, FileDialog.OPEN, accessory) {

                    @Override
                    public void approve(String fileName) {
                        ExportFormat exportFormat =
                            (getCheckUseLegacyFormat().isSelected() ? ExportFormat.serialization : ExportFormat.jaxb);
                        Accessory accessory = AccessoryFactory.loadAccessory(fileName, exportFormat, model);

                        if (accessory != null) {
                            accessory.setId(model.getSelectedAccessory().getId());
                            // set the total number possible macros for this accessory
                            accessory.setMacroSize(CommunicationFactory.getInstance().getAccessoryLength(
                                model.getSelectedNode().getNode()));
                            model.replaceAccessory(accessory);
                        }
                    }
                };
                dialog.showDialog();
            }

            @Override
            public void labelChanged(final Accessory accessory, String label) {
                // final Accessory accessory = (Accessory) object;

                // accessory.setLabel(label);
                mainNodeListListener.replaceAccessoryLabel(model.getSelectedNode().getNode().getUniqueId(), accessory,
                    label, true);
            }

            @Override
            public void reloadAccessory(Accessory accessory) {
                if (accessory != null && accessory.hasPendingChanges()) {

                    // show dialog
                    int result =
                        JOptionPane.showConfirmDialog(view,
                            Resources.getString(MainController.class, "accessory_has_pending_changes"),
                            Resources.getString(MainController.class, "pending_changes"), JOptionPane.OK_CANCEL_OPTION);
                    if (result != JOptionPane.OK_OPTION) {
                        LOGGER.info("User canceled discard pending changes.");
                        return;
                    }

                    // reset pending changes
                    accessory.setPendingChanges(false);
                }

                CommunicationFactory.getInstance().reloadAccessory(model.getSelectedNode().getNode(), accessory);
                accessory.setPendingChanges(false);
            }

            @Override
            public void saveAccessory(Accessory accessory) {
                CommunicationFactory.getInstance().saveAccessory(model.getSelectedNode().getNode(), accessory);
            }
        });

        // add a list selection listener for the accessory list
        view.addAccessoryListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(final ListSelectionEvent e) {
                if (!e.getValueIsAdjusting()) {
                    try {

                        // the selected accessory has changed
                        setWaitCursor();

                        final JList accessoryList = (JList) e.getSource();
                        Accessory accessory = accessoryList.getSelectedValue();

                        if (accessory != null) {
                            model.setSelectedAccessory(accessory);
                        }
                    }
                    finally {
                        setDefaultCursor();
                    }
                }
            }
        });
        view.addAccessoryTableListener(new AccessoryTableListener() {
            @Override
            public void delete(int[] rows) {
                for (int index = rows.length - 1; index >= 0; index--) {
                    model.getSelectedAccessory().removeMacro(rows[index]);
                }
            }

            private MacroRef findNextFreeMacro() {

                SortedSet macroIds = new TreeSet<>();
                for (Accessory accessory : model.getAccessories()) {

                    for (MacroRef aspect : accessory.getAspects()) {

                        macroIds.add(aspect.getId());
                    }
                }

                // macro id starts from 0
                int macroId = 0;
                for (Integer currentId : macroIds) {
                    if (currentId.intValue() == macroId) {
                        macroId++;
                    }
                    else {
                        LOGGER.info("Found free macroId: {}", macroId);
                    }
                }
                MacroRef macroRef = null;
                if (macroId < model.getMacros().size()) {
                    LOGGER.info("Create new MacroRef with macroId: {}", macroId);
                    macroRef = new MacroRef(macroId);
                }
                else {
                    macroRef = new MacroRef();
                }
                return macroRef;
            }

            @Override
            public void insertEmptyAfter(int row) {
                MacroRef macroRef = findNextFreeMacro();
                model.getSelectedAccessory().addAspectAfter(row, macroRef);
            }

            @Override
            public void insertEmptyBefore(int row) {
                MacroRef macroRef = findNextFreeMacro();
                model.getSelectedAccessory().addAspectBefore(row >= 0 ? row : 0, macroRef);
            }

            @Override
            public void testButtonPressed(int aspect) {
                // TODO check if the accessory has pending changes
                Accessory accessory = model.getSelectedAccessory();
                if (accessory.hasPendingChanges()) {
                    // show a hint to transfer the macro to the node
                    int result =
                        JOptionPane.showConfirmDialog(view, Resources.getString(AccessoryTableListener.class,
                            "accessory_transfer_pending_changes_before_test"), Resources.getString(
                            AccessoryTableListener.class, "test"), JOptionPane.YES_NO_OPTION,
                            JOptionPane.WARNING_MESSAGE);

                    if (result == JOptionPane.YES_OPTION) {
                        CommunicationFactory.getInstance().saveAccessory(model.getSelectedNode().getNode(), accessory);
                    }
                }

                CommunicationFactory.getInstance().startAccessory(model.getSelectedNode().getNode(),
                    model.getSelectedAccessory(), aspect);
            }
        });
        view.addAnalogPortListener(new OutputListener() {
            @Override
            public void labelChanged(Port port, String label) {
                port.setLabel(label);
                if (label != null && label.length() > 0) {
                    analogPortLabels.setLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId(), label);
                }
                else {
                    analogPortLabels.removeLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId());
                }
                try {
                    analogPortLabels.save();
                }
                catch (InvalidConfigurationException ex) {
                    LOGGER.warn("Save analog port labels failed.", ex);

                    String labelPath = ex.getReason();
                    JOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(null),
                        Resources.getString(Labels.class, "labelfileerror.message", new Object[] { labelPath }),
                        Resources.getString(Labels.class, "labelfileerror.title"), JOptionPane.ERROR_MESSAGE);
                }
            }

            @Override
            public void statusChanged(Port port, AnalogPortStatus status) {
            }

            @Override
            public void testButtonPressed(Port port) {
                CommunicationFactory.getInstance().activateAnalogPort(model.getSelectedNode().getNode(), port.getId(),
                    (AnalogPortStatus) port.getStatus());
            }

            @Override
            public void configChanged(Port port) {
            }
        });
        view.addBoosterStatusListener(new StatusListener() {
            @Override
            public void switchedOff() {
                CommunicationFactory.getInstance().boosterOff(model.getSelectedNode().getNode());
            }

            @Override
            public void switchedOn() {
                CommunicationFactory.getInstance().boosterOn(model.getSelectedNode().getNode());
            }
        });
        view.addInputPortListener(new InputPortListener() {
            @Override
            public void labelChanged(Port port, String label) {
                port.setLabel(label);
                if (label != null && label.length() > 0) {
                    inputPortLabels.setLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId(), label);
                }
                else {
                    inputPortLabels.removeLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId());
                }
                try {
                    inputPortLabels.save();
                }
                catch (InvalidConfigurationException ex) {
                    LOGGER.warn("Save input port labels failed.", ex);

                    String labelPath = ex.getReason();
                    JOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(null),
                        Resources.getString(Labels.class, "labelfileerror.message", new Object[] { labelPath }),
                        Resources.getString(Labels.class, "labelfileerror.title"), JOptionPane.ERROR_MESSAGE);
                }
            }

            @Override
            public void statusChanged(Port port, InputPortStatus status) {
            }

            @Override
            public void configChanged(Port port) {
            }

            @Override
            public void testButtonPressed(Port port) {
            }

            @Override
            public void changePortType(LcOutputType portType, InputPort port) {

                // TODO verify that this is not called twice! See listener in InputPortListPanel
                LOGGER.info("The port type will change to: {}, port: {}", portType, port);

                CommunicationFactory.getInstance().setPortParameters(model.getSelectedNode().getNode(), port.getId(),
                    portType, null);
            }

            @Override
            public void valuesChanged(InputPort port, PortConfigKeys... portConfigKeys) {
            }
        });

        view.addLightPortListener(new LightPortListener() {
            @Override
            public void valuesChanged(LightPort port) {
                try {
                    CommunicationFactory.getInstance().setLightPortParameters(model.getSelectedNode().getNode(),
                        port.getId(), port.getPwmMin(), port.getPwmMax(), port.getDimMin(), port.getDimMax(),
                        port.getRgbValue());
                }
                catch (Exception ex) {
                    LOGGER.warn("Set the lightport parameters failed.", ex);
                    // model.getSelectedNode().setNodeHasError(true);
                    model.setNodeHasError(model.getSelectedNode(), true);
                }
            }

            @Override
            public void labelChanged(Port port, String label) {
                port.setLabel(label);
                if (label != null && label.length() > 0) {
                    lightPortLabels.setLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId(), label);
                }
                else {
                    lightPortLabels.removeLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId());
                }
                try {
                    lightPortLabels.save();
                }
                catch (InvalidConfigurationException ex) {
                    LOGGER.warn("Save light port labels failed.", ex);

                    String labelPath = ex.getReason();
                    JOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(null),
                        Resources.getString(Labels.class, "labelfileerror.message", new Object[] { labelPath }),
                        Resources.getString(Labels.class, "labelfileerror.title"), JOptionPane.ERROR_MESSAGE);
                }
            }

            @Override
            public void statusChanged(Port port, LightPortStatus status) {
            }

            @Override
            public void testButtonPressed(Port port) {
                CommunicationFactory.getInstance().activateLightPort(model.getSelectedNode().getNode(), port.getId(),
                    (LightPortStatus) port.getStatus());
            }

            @Override
            public void configChanged(Port port) {
            }
        });

        // handle backlight
        view.addBacklightPortListener(new BacklightPortListener() {
            @Override
            public void labelChanged(Port port, String label) {
                port.setLabel(label);
                if (label != null && label.length() > 0) {
                    backlightPortLabels.setLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId(), label);
                }
                else {
                    backlightPortLabels.removeLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId());
                }
                try {
                    backlightPortLabels.save();
                }
                catch (InvalidConfigurationException ex) {
                    LOGGER.warn("Save backlight port labels failed.", ex);

                    String labelPath = ex.getReason();
                    JOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(null),
                        Resources.getString(Labels.class, "labelfileerror.message", new Object[] { labelPath }),
                        Resources.getString(Labels.class, "labelfileerror.title"), JOptionPane.ERROR_MESSAGE);
                }
            }

            @Override
            public void statusChanged(Port port, BacklightPortStatus status) {
                LOGGER.debug("Status of backlight port has changed, port: {}, status: {}", port, status);
            }

            @Override
            public void testButtonPressed(Port port) {

                BacklightPort backlightPort = (BacklightPort) port;
                LOGGER.info("Test pressed for port: {}, port.value: {}", port, backlightPort.getValue());

                CommunicationFactory.getInstance().activateBacklightPort(model.getSelectedNode().getNode(),
                    backlightPort, backlightPort.getValue());
            }

            @Override
            public void valuesChanged(BacklightPort port) {
                LOGGER
                    .debug(
                        "Values have changed for backlight port: {}. Set the new backlight port parameters, dimSlopeDown: {}, dimSlopeUp: {}, dmxMapping: {}.",
                        port, port.getDimSlopeUp(), port.getDimSlopeDown(), port.getDmxMapping());
                CommunicationFactory.getInstance().setBacklightPortParameters(model.getSelectedNode().getNode(),
                    port.getId(), port.getDimSlopeDown(), port.getDimSlopeUp(), port.getDmxMapping());
            }

            @Override
            public void configChanged(Port port) {
            }
        });

        view.addCvDefinitionRequestListener(new CvDefinitionRequestListener() {

            @Override
            public void loadCvValues(List configVariables) {
                LOGGER.info("Load CV definition values!");
                StopWatch sw = new StopWatch();
                sw.start();
                try {
                    setWaitCursor();

                    CommunicationFactory.getInstance().getConfigurationVariables(model.getSelectedNode().getNode(),
                        configVariables);

                    LOGGER.debug("Update model with configuration variables: {}", configVariables);
                    model.updateConfigurationVariableValues(configVariables);
                }
                finally {
                    setDefaultCursor();
                }
                sw.stop();

                LOGGER.info("Load CV values has finished! Total loading duration: {}", sw);
                view.setStatusText(String.format(Resources.getString(MainController.class, "loadCvFinished"), sw),
                    StatusBar.DISPLAY_NORMAL);
            }

            @Override
            public void writeCvValues(List cvList) {
                LOGGER.info("Write CV definition values!");
                StopWatch sw = new StopWatch();
                sw.start();
                try {
                    setWaitCursor();

                    List configVars =
                        CommunicationFactory.getInstance().writeConfigurationVariables(
                            model.getSelectedNode().getNode(), cvList);
                    // iterate over the collection of stored variables in the model and update the values.
                    // After that notify the tree and delete the new values that are now stored in the node
                    model.updateConfigurationVariableValues(configVars);
                }
                finally {
                    setDefaultCursor();
                }
                sw.stop();

                LOGGER.info("Write CV values has finished! Total writing duration: {}", sw);
                view.setStatusText(String.format(Resources.getString(MainController.class, "writeCvFinished"), sw),
                    StatusBar.DISPLAY_NORMAL);
            }

            @Override
            public void activateAspect(int aspectNumber) {
                LOGGER.info("Start accessory with aspect: {}", aspectNumber);

                try {
                    setWaitCursor();

                    // TODO fix this to use the correct accessory
                    Accessory accessory = new Accessory();
                    accessory.setId(0);

                    CommunicationFactory.getInstance().startAccessory(model.getSelectedNode().getNode(), accessory,
                        aspectNumber);
                }
                finally {
                    setDefaultCursor();
                }
            }
        });

        view.addMacroListListener(new MacroListListener() {
            @Override
            public void exportMacro(final Macro macro) {

                if (CollectionUtils.isEmpty(macro.getFunctions())) {
                    // no functions in macro, ask the user to export empty macro
                    // show dialog
                    int result =
                        JOptionPane.showConfirmDialog(view,
                            Resources.getString(MainController.class, "ask_export_empty_macro"),
                            Resources.getString(MainController.class, "export_macro.title"),
                            JOptionPane.OK_CANCEL_OPTION);
                    if (result != JOptionPane.OK_OPTION) {
                        LOGGER.info("User canceled export empty macro.");
                        return;
                    }
                }

                FileDialog dialog = new MacroFileDialog(
                    view,
                    // default is legacy filter
                    FileDialog.SAVE, macro) {

                    @Override
                    public void approve(String fileName) {
                        try {
                            boolean flatPortModel = false;
                            if (model.getSelectedNode() != null
                                && model.getSelectedNode().getNode().isPortFlatModelAvailable()) {
                                flatPortModel = true;
                            }
                            LOGGER.info("Set the flat port model flag: {}", flatPortModel);
                            macro.setFlatPortModel(flatPortModel);

                            ExportFormat exportFormat = ExportFormat.jaxb;
                            // (getCheckUseLegacyFormat().isSelected() ? ExportFormat.serialization
                            // : ExportFormat.jaxb);
                            MacroFactory.saveMacro(fileName, macro, exportFormat);

                            // update the status bar
                            view.setStatusText(
                                String.format(Resources.getString(MainController.class, "exportedMacro"), fileName),
                                StatusBar.DISPLAY_NORMAL);
                        }
                        catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }

                };
                dialog.showDialog();
            }

            @Override
            public void importMacro() {

                if (model.getSelectedMacro() != null
                    && model.getSelectedMacro().getMacroSaveState().equals(MacroSaveState.PENDING_CHANGES)) {

                    // show dialog
                    int result =
                        JOptionPane.showConfirmDialog(view,
                            Resources.getString(MainController.class, "macro_has_pending_changes"),
                            Resources.getString(MainController.class, "pending_changes"), JOptionPane.OK_CANCEL_OPTION);
                    if (result != JOptionPane.OK_OPTION) {
                        LOGGER.info("User canceled discard pending changes.");
                        return;
                    }

                    // reset pending changes
                    model.getSelectedMacro().setMacroSaveState(MacroSaveState.SAVED_ON_NODE);
                }

                FileDialog dialog = new MacroFileDialog(
                    view, FileDialog.OPEN, null) {

                    @Override
                    public void approve(String fileName) {
                        ExportFormat exportFormat =
                            (getCheckUseLegacyFormat().isSelected() ? ExportFormat.serialization : ExportFormat.jaxb);

                        // a new macro instance is created
                        Macro macro = MacroFactory.loadMacro(fileName, exportFormat, model);

                        LOGGER.info("Loaded macro, flatPortModel: {}", macro.isFlatPortModel());
                        // set the new macro id
                        macro.setId(model.getSelectedMacro().getId());
                        // set pending changes flag
                        macro.setMacroSaveState(MacroSaveState.PENDING_CHANGES);

                        // replace the imported macro
                        replaceMacro(macro, false);
                    }
                };
                dialog.showDialog();
            }

            @Override
            public void labelChanged(final Macro macro, String label) {
                LOGGER.info("Label of macro has changed, macro: {}, label: {}", macro, label);
                mainNodeListListener.replaceMacroLabel(model.getSelectedNode().getNode().getUniqueId(), macro, label,
                    true);
            }

            @Override
            public void reloadMacro(Macro macro) {

                if (macro != null && macro.getMacroSaveState().equals(MacroSaveState.PENDING_CHANGES)) {

                    // show dialog
                    int result =
                        JOptionPane.showConfirmDialog(view,
                            Resources.getString(MainController.class, "macro_has_pending_changes"),
                            Resources.getString(MainController.class, "pending_changes"), JOptionPane.OK_CANCEL_OPTION);
                    if (result != JOptionPane.OK_OPTION) {
                        LOGGER.info("User canceled discard pending changes.");
                        return;
                    }

                    // reset pending changes
                    macro.setMacroSaveState(MacroSaveState.SAVED_ON_NODE);
                }

                try {
                    LcMacroState lcMacroState =
                        CommunicationFactory.getInstance().reloadMacro(model.getSelectedNode(), macro);
                    LOGGER.info("Reload macro returned: {}", lcMacroState);

                    model.getSelectedMacro().setMacroSaveState(MacroSaveState.PERMANENTLY_STORED_ON_NODE);
                    macro.setContainsError(false);
                }
                catch (InvalidConfigurationException ex) {
                    LOGGER.warn("Restore macro content failed.", ex);

                    model.setNodeHasError(model.getSelectedNode(), true);
                }
            }

            @Override
            public void saveMacro(Macro macro) {
                LOGGER.info("Save macro: {}", macro);
                try {
                    // the macro save state is reset by the saveMacro call

                    LcMacroState lcMacroState =
                        CommunicationFactory.getInstance().saveMacro(model.getSelectedNode(), macro);
                    LOGGER.info("Save macro returned: {}", lcMacroState);

                    macro.setContainsError(false);
                }
                catch (InvalidConfigurationException ex) {
                    LOGGER.warn("Save macro content failed.", ex);

                    model.setNodeHasError(model.getSelectedNode(), true);
                }
            }

            @Override
            public void startMacro(Macro macro, boolean transferBeforeStart) {
                LOGGER.info("Start macro: {}, transferBeforeStart: {}", macro, transferBeforeStart);
                LcMacroState lcMacroState =
                    CommunicationFactory.getInstance().startMacro(model.getSelectedNode(), macro, transferBeforeStart);
                LOGGER.info("Start macro returned: {}", lcMacroState);
            }

            @Override
            public void stopMacro(Macro macro) {
                LOGGER.info("Stop macro: {}", macro);
                LcMacroState lcMacroState =
                    CommunicationFactory.getInstance().stopMacro(model.getSelectedNode().getNode(), macro);
                LOGGER.info("Stop macro returned: {}", lcMacroState);
            }

            @Override
            public void transferMacro(Macro macro) {
                LOGGER.info("Transfer macro: {}", macro);
                try {
                    CommunicationFactory.getInstance().transferMacro(model.getSelectedNode(), macro);
                    LOGGER.info("Transfer macro passed.");
                }
                catch (InvalidConfigurationException ex) {
                    LOGGER.warn("Transfer macro content failed.", ex);

                    model.setNodeHasError(model.getSelectedNode(), true);
                }
            }

            @Override
            public void initializeMacro(Macro macro) {
                // and force the panels to reload
                try {
                    if (model.getSelectedMacro() != null && model.getSelectedMacro().equals(macro)
                        && model.getSelectedMacro().getMacroSaveState().equals(MacroSaveState.PENDING_CHANGES)) {
                        // show dialog
                        int result =
                            JOptionPane.showConfirmDialog(view,
                                Resources.getString(MainController.class, "macro_has_pending_changes"),
                                Resources.getString(MainController.class, "pending_changes"),
                                JOptionPane.OK_CANCEL_OPTION);
                        if (result != JOptionPane.OK_OPTION) {
                            LOGGER.info("User canceled discard pending changes.");
                            return;
                        }

                        // reset pending changes
                        model.getSelectedMacro().setMacroSaveState(MacroSaveState.SAVED_ON_NODE);
                    }
                    // initialize the macro
                    macro.initialize();
                    macro.setMacroSaveState(MacroSaveState.PENDING_CHANGES);

                    model.setSelectedMacro(macro);
                }
                catch (Exception ex) {
                    LOGGER.warn("Set the new selcted macro failed.", ex);
                }
            }

        });
        view.addMacroListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(final ListSelectionEvent e) {
                if (!e.getValueIsAdjusting()) {
                    final JList macroList = (JList) e.getSource();
                    try {
                        setWaitCursor();
                        if (!(macroList.getSelectedValue() instanceof Macro)) {
                            // do not react on change of label
                            return;
                        }

                        Macro macro = macroList.getSelectedValue();

                        if (macro != null) {
                            // This loads the macro content effectively ...
                            LOGGER.info("Load the macro content from the node.");

                            try {
                                macro =
                                    CommunicationFactory.getInstance().getMacroContent(model.getSelectedNode(), macro);
                                model.setSelectedMacro(macro);
                            }
                            catch (InvalidConfigurationException ex) {
                                LOGGER.warn("Restore macro content failed.", ex);

                                // set an empty macro
                                macro.setContainsError(true);
                                model.setSelectedMacro(macro);

                                model.setNodeHasError(model.getSelectedNode(), true);
                            }
                            catch (Exception ex) {
                                LOGGER.warn("Get the content of the selected macro failed.", ex);
                            }
                        }
                    }
                    finally {
                        setDefaultCursor();
                    }
                }
            }
        });
        view.addMacroTableListener(new MacroTableListener() {
            @Override
            public void copy(Function[] functions) {
                setFunctions(functions);
            }

            @Override
            public void cut(int[] rows, Function[] functions) {
                setFunctions(functions);
                for (int index = rows.length - 1; index >= 0; index--) {
                    model.getSelectedMacro().removeFunction(rows[index]);
                }
            }

            @Override
            public void delete(int[] rows) {
                for (int index = rows.length - 1; index >= 0; index--) {
                    model.getSelectedMacro().removeFunction(rows[index]);
                }
            }

            @Override
            public void insertEmptyAfter(int row) {
                model.getSelectedMacro().addFunctionsAfter(row, null);
            }

            @Override
            public void insertEmptyBefore(int row) {
                model.getSelectedMacro().addFunctionsBefore(row >= 0 ? row : 0, null);
            }

            @Override
            public void pasteAfter(int row) {
                model.getSelectedMacro().addFunctionsAfter(row, getFunctions());
            }

            @Override
            public void pasteBefore(int row) {
                model.getSelectedMacro().addFunctionsBefore(row >= 0 ? row : 0, getFunctions());
            }

            @Override
            public void pasteInvertedAfter(int row) {
                model.getSelectedMacro().addFunctionsInvertedAfter(row, getFunctions());
            }
        });

        MainMenuListener mainMenuListener = new DefaultMainMenuListener(view, this);

        view.addMainMenuListener(mainMenuListener);
        view.addToolBarListener(mainMenuListener);

        view.addMotorPortListener(new OutputListener() {
            @Override
            public void labelChanged(Port port, String value) {
                port.setLabel(value);
                if (value != null && value.length() > 0) {
                    motorPortLabels.setLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId(), value);
                }
                else {
                    motorPortLabels.removeLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId());
                }
                try {
                    motorPortLabels.save();
                }
                catch (InvalidConfigurationException ex) {
                    LOGGER.warn("Save motor port labels failed.", ex);

                    String labelPath = ex.getReason();
                    JOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(null),
                        Resources.getString(Labels.class, "labelfileerror.message", new Object[] { labelPath }),
                        Resources.getString(Labels.class, "labelfileerror.title"), JOptionPane.ERROR_MESSAGE);
                }
            }

            @Override
            public void statusChanged(Port port, MotorPortStatus status) {
            }

            @Override
            public void testButtonPressed(Port port) {
                CommunicationFactory.getInstance().activateMotorPort(model.getSelectedNode().getNode(), port.getId(),
                    (MotorPortStatus) port.getStatus());
            }

            @Override
            public void configChanged(Port port) {
            }
        });
        view.addServoPortListener(new ServoPortListener() {
            @Override
            public void labelChanged(Port port, String label) {
                port.setLabel(label);
                if (label != null && label.length() > 0) {
                    servoPortLabels.setLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId(), label);
                }
                else {
                    servoPortLabels.removeLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId());
                }
                try {
                    servoPortLabels.save();
                }
                catch (InvalidConfigurationException ex) {
                    LOGGER.warn("Save servo port labels failed.", ex);

                    String labelPath = ex.getReason();
                    JOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(null),
                        Resources.getString(Labels.class, "labelfileerror.message", new Object[] { labelPath }),
                        Resources.getString(Labels.class, "labelfileerror.title"), JOptionPane.ERROR_MESSAGE);
                }
            }

            @Override
            public void statusChanged(Port port, ServoPortStatus status) {
                LOGGER.debug("Status of servo port has changed, port: {}, status: {}", port, status);
            }

            @Override
            public void testButtonPressed(Port port) {
                LOGGER.info("Test pressed for port: {}", port);
                CommunicationFactory.getInstance().activateServoPort(model.getSelectedNode().getNode(), port.getId(),
                    ((ServoPort) port).getValue());
            }

            @Override
            public void valuesChanged(ServoPort port) {
                LOGGER.info("Values have changed for servo port: {}. Set the new servo parameters.", port);
                CommunicationFactory.getInstance().setServoPortParameters(model.getSelectedNode().getNode(),
                    port.getId(), port.getTrimDown(), port.getTrimUp(), port.getSpeed());
            }

            @Override
            public void configChanged(Port port) {
            }
        });
        view.addSoundPortListener(new OutputListener() {
            @Override
            public void labelChanged(Port port, String label) {
                port.setLabel(label);
                if (label != null && label.length() > 0) {
                    soundPortLabels.setLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId(), label);
                }
                else {
                    soundPortLabels.removeLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId());
                }
                try {
                    soundPortLabels.save();
                }
                catch (InvalidConfigurationException ex) {
                    LOGGER.warn("Save sound port labels failed.", ex);

                    String labelPath = ex.getReason();
                    JOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(null),
                        Resources.getString(Labels.class, "labelfileerror.message", new Object[] { labelPath }),
                        Resources.getString(Labels.class, "labelfileerror.title"), JOptionPane.ERROR_MESSAGE);
                }
            }

            @Override
            public void statusChanged(Port port, SoundPortStatus status) {
            }

            @Override
            public void testButtonPressed(Port port) {
                CommunicationFactory.getInstance().activateSoundPort(model.getSelectedNode().getNode(), port.getId(),
                    (SoundPortStatus) port.getStatus());
            }

            @Override
            public void configChanged(Port port) {
            }
        });
        view.addStatusListener(new StatusListener() {
            @Override
            public void switchedOff() {
                model.getStatusModel().setModelClockStartEnabled(false);
                model.getStatusModel().setRunning(false);
            }

            @Override
            public void switchedOn() {
                model.getStatusModel().setModelClockStartEnabled(true);
                model.getStatusModel().setRunning(true);
            }
        });
        view.addSwitchPortListener(new SwitchPortListener() {
            @Override
            public void labelChanged(Port port, String label) {
                port.setLabel(label);
                if (label != null && label.length() > 0) {
                    switchPortLabels.setLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId(), label);
                }
                else {
                    switchPortLabels.removeLabel(model.getSelectedNode().getNode().getUniqueId(), port.getId());
                }
                try {
                    switchPortLabels.save();
                }
                catch (InvalidConfigurationException ex) {
                    LOGGER.warn("Save switch port labels failed.", ex);

                    String labelPath = ex.getReason();
                    JOptionPane.showMessageDialog(JOptionPane.getFrameForComponent(null),
                        Resources.getString(Labels.class, "labelfileerror.message", new Object[] { labelPath }),
                        Resources.getString(Labels.class, "labelfileerror.title"), JOptionPane.ERROR_MESSAGE);
                }
            }

            @Override
            public void statusChanged(Port port, SwitchPortStatus status) {
                LOGGER.debug("Status of switchlight port has changed, port: {}, status: {}", port, status);
            }

            @Override
            public void valuesChanged(SwitchPort port, PortConfigKeys... portConfigKeys) {

                LOGGER.info("The port value are changed for port: {}", port);

                Map> values = new LinkedHashMap<>();

                for (PortConfigKeys key : portConfigKeys) {
                    switch (key) {
                        case BIDIB_PCFG_IO_CTRL:
                            IoBehaviourEnum ioBehaviour = port.getIoBehaviour();
                            values.put(BidibLibrary.BIDIB_PCFG_IO_CTRL, new BytePortConfigValue(ioBehaviour.getType()));
                            break;
                        case BIDIB_PCFG_TICKS:
                            int switchOffTime = port.getSwitchOffTime();
                            values.put(BidibLibrary.BIDIB_PCFG_TICKS,
                                new BytePortConfigValue(ByteUtils.getLowByte(switchOffTime)));
                            break;
                        default:
                            LOGGER.warn("Unsupported port config key detected: {}", key);
                            break;
                    }
                }

                // don't set the port type param to not send BIDIB_PCFG_RECONFIG
                CommunicationFactory.getInstance().setPortParameters(model.getSelectedNode().getNode(), port.getId(),
                    null, values);
            }

            @Override
            public void testButtonPressed(Port port) {
                CommunicationFactory.getInstance().activateSwitchPort(model.getSelectedNode().getNode(), port.getId(),
                    (SwitchPortStatus) port.getStatus());
            }

            @Override
            public void configChanged(Port port) {
            }

            @Override
            public void changePortType(LcOutputType portType, SwitchPort port) {
                LOGGER.info("The port type will change to: {}, port: {}", portType, port);

                Map> values = new LinkedHashMap<>();

                CommunicationFactory.getInstance().setPortParameters(model.getSelectedNode().getNode(), port.getId(),
                    portType, values);
            }
        });
        view.setWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                stop();
            }
        });

        if (Preferences.getInstance().isShowBoosterTable()) {
            LOGGER.info("Show the booster table is selected in preferences.");

            // open the booster table ...
            try {
                mainMenuListener.boosterTable();
            }
            catch (Exception ex) {
                LOGGER.warn("Open booster table failed.", ex);
            }
        }

        try {
            // start the booster current timer
            boosterCurrentTimer = new Timer(1000, new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    LOGGER.trace("The booster current timer has elapsed.");

                    long now = System.currentTimeMillis();

                    if (model.getSelectedNode() != null && model.getSelectedNode().isBooster()) {
                        if (model.getBoosterCurrent() >= 0
                            && model.getLastCurrentUpdate() < (now - CURRENT_UPDATE_TIMEOUT)) {
                            // the current value is outdated -> clear the value
                            LOGGER.info("the current value is outdated -> clear the value, node: {}",
                                model.getSelectedNode());

                            model.setBoosterCurrent(-1);
                        }
                    }
                }
            });
            boosterCurrentTimer.setCoalesce(true);
            boosterCurrentTimer.start();
        }
        catch (Exception ex) {
            LOGGER.warn("Start the booster current timer failed.", ex);
        }
    }

    private void getNodeConfiguration(Node node) {
        LOGGER.info("Get the node configuration for node: {}, protocol version: {}", node, node
            .getNode().getProtocolVersion());
        Communication communication = CommunicationFactory.getInstance();

        List feedbackPorts = new LinkedList();
        List analogPorts = new LinkedList();
        List inputPorts = new LinkedList();
        List lightPorts = new LinkedList();
        List backlightPorts = new LinkedList();
        List motorPorts = new LinkedList();
        List servoPorts = new LinkedList();
        List soundPorts = new LinkedList();
        final List switchPorts = new LinkedList();

        if (node != null) {

            // check if we have flat or type-oriented port model
            if (node.getNode().isPortFlatModelAvailable()) {
                LOGGER.info("Get the port information from flat port model.");

                if (NodeUtils.hasFeedbackFunctions(node.getUniqueId())) {
                    LOGGER.debug("Get the feedback ports from the node.");
                    for (FeedbackPort feedbackPort : communication.getFeedbackPorts(node.getNode())) {
                        feedbackPort.setLabel(feedbackPortLabels.getLabel(node.getNode().getUniqueId(),
                            feedbackPort.getId()));
                        feedbackPorts.add(feedbackPort);
                    }

                    model.setFeedbackPorts(feedbackPorts);
                }
                else {
                    LOGGER.debug("Node has no feedback ports configured.");
                    model.setFeedbackPorts(feedbackPorts);
                }

                int totalPortCount = node.getNode().getPortFlatModel();
                LOGGER.info("Create total number of ports: {}", totalPortCount);
                List ports = new LinkedList<>();
                for (int portId = 0; portId < totalPortCount; portId++) {
                    GenericPort port = new GenericPort(portId);
                    ports.add(port);
                }

                node.setGenericPorts(ports);

                // clear the ports in the model to force usage of generic ports
                model.setAnalogPorts(analogPorts);
                model.setBacklightPorts(backlightPorts);
                model.setLightPorts(lightPorts);
                model.setInputPorts(inputPorts);
                model.setMotorPorts(motorPorts);
                model.setServoPorts(servoPorts);
                model.setSoundPorts(soundPorts);
                model.setSwitchPorts(switchPorts);

                // trigger query of port config
                LOGGER.info("Trigger query of port config for node: {}", node);

                // check for the protocol version
                if (node.getNode().getProtocolVersion().isHigherThan(ProtocolVersion.VERSION_0_5)) {
                    LOGGER.info("The current node uses queryAllPortConfig to get the port config from the node.");

                    // query the port config
                    communication.queryAllPortConfig(node.getNode(), null, null, null);

                    try {
                        boolean configPendingFound = false;
                        for (GenericPort port : node.getGenericPorts()) {
                            if (PortConfigStatus.CONFIG_PENDING.equals(port.getConfigStatus())) {
                                // found a port with config pending status
                                LOGGER.info("Found a port with config pending status: {}", port);

                                configPendingFound = true;
                                break;
                            }
                        }

                        if (configPendingFound) {
                            LOGGER
                                .info("Found config pending status found in generic ports, clear the port cache on the node and update the port lists after queryAllPortConfig.");
                            node.setNodeHasError(true);
                            node.setErrorState(SysErrorEnum.BIDIB_ERR_TXT,
                                ByteUtils.bstr(Resources.getString(MainController.class, "missing-port-config")));
                            node.clearPortCache();

                            model.updatePortLists();
                        }
                        else {
                            LOGGER.info("No pending config found, don't update of ports list.");
                        }
                    }
                    catch (Exception ex) {
                        LOGGER.warn("Check if all generic ports have been configured failed.", ex);
                    }

                }
                else {
                    communication.queryPortConfig(node.getNode(), node.getGenericPorts());
                }
            }
            else {
                // process port-type oriented model
                LOGGER.info("Get the port information from port-type oriented model.");

                if (NodeUtils.hasFeedbackFunctions(node.getUniqueId())) {
                    LOGGER.debug("Get the feedback ports from the node.");
                    for (FeedbackPort feedbackPort : communication.getFeedbackPorts(node.getNode())) {
                        feedbackPort.setLabel(feedbackPortLabels.getLabel(node.getNode().getUniqueId(),
                            feedbackPort.getId()));
                        feedbackPorts.add(feedbackPort);
                    }

                    model.setFeedbackPorts(feedbackPorts);
                }
                else {
                    LOGGER.debug("Node has no feedback ports configured.");
                    model.setFeedbackPorts(feedbackPorts);
                }

                // TODO if we use asynchronous initialization of ports we must create the ports
                // first, add to model and trigger initialize afterwards

                if (NodeUtils.hasSwitchFunctions(node.getUniqueId())) {
                    LOGGER.debug("Get the switch functions from the node.");

                    // check for the protocol version
                    boolean queryAllPortConfigSupported = false;
                    if (node.getNode().getProtocolVersion().isHigherThan(ProtocolVersion.VERSION_0_5)) {

                        // the OneDMX 2.0.0 is a special case
                        if (ProductUtils.isOneDMX(node.getUniqueId())
                            && node.getNode().getSoftwareVersion().equals(new SoftwareVersion(2, 0, 0))) {

                            LOGGER.info("The current OneDMX-2.00.00 has no support for queryAllPortConfig.");

                            queryAllPortConfigSupported = false;
                        }
                        // the STu-0.01.04 is a special case
                        else if (ProductUtils.isSTu(node.getUniqueId())
                            && node.getNode().getSoftwareVersion().equals(new SoftwareVersion(0, 1, 4))) {

                            LOGGER.info("The current STu-0.01.04 has no support for queryAllPortConfig.");

                            queryAllPortConfigSupported = false;
                        }
                        else {
                            LOGGER.info("The current node has support for queryAllPortConfig.");

                            queryAllPortConfigSupported = true;
                        }
                    }

                    LOGGER.debug("Get the analog ports from the node.");
                    for (AnalogPort analogPort : communication.getAnalogPorts(node.getNode())) {
                        analogPort
                            .setLabel(analogPortLabels.getLabel(node.getNode().getUniqueId(), analogPort.getId()));
                        analogPorts.add(analogPort);
                    }
                    model.setAnalogPorts(analogPorts);

                    if (CollectionUtils.isNotEmpty(analogPorts)) {
                        if (queryAllPortConfigSupported) {
                            communication.queryAllPortConfig(node.getNode(), LcOutputType.ANALOGPORT, 0,
                                analogPorts.size());
                        }
                    }

                    LOGGER.debug("Get the input ports from the node.");
                    for (InputPort inputPort : communication.getInputPorts(node.getNode())) {
                        inputPort.setLabel(inputPortLabels.getLabel(node.getNode().getUniqueId(), inputPort.getId()));
                        inputPorts.add(inputPort);
                    }
                    model.setInputPorts(inputPorts);

                    // TODO check if this would work
                    // LOGGER.info("Number of input ports from the node: {}", inputPorts.size());
                    // if (CollectionUtils.isNotEmpty(inputPorts)) {
                    // if (!queryAllPortConfigSupported) {
                    // // trigger query of input port config
                    // communication.queryInputPortConfig(node.getNode());
                    // }
                    // else {
                    // communication.queryAllPortConfig(node.getNode(), LcOutputType.INPUTPORT, 0,
                    // switchPorts.size());
                    // }
                    // }

                    LOGGER.debug("Get the switch ports from the node.");
                    for (SwitchPort switchPort : communication.getSwitchPorts(node.getNode())) {
                        // set the label
                        switchPort
                            .setLabel(switchPortLabels.getLabel(node.getNode().getUniqueId(), switchPort.getId()));
                        switchPorts.add(switchPort);
                    }
                    model.setSwitchPorts(switchPorts);

                    LOGGER.info("Number of switch ports from the node: {}", switchPorts.size());
                    if (CollectionUtils.isNotEmpty(switchPorts)) {
                        if (!queryAllPortConfigSupported) {
                            // trigger query of switch port config
                            communication.querySwitchPortConfig(node.getNode());
                        }
                        else {
                            communication.queryAllPortConfig(node.getNode(), LcOutputType.SWITCHPORT, 0,
                                switchPorts.size());
                        }
                    }

                    LOGGER.debug("Get the light ports from the node.");
                    for (LightPort lightPort : communication.getLightPorts(node.getNode())) {
                        lightPort.setLabel(lightPortLabels.getLabel(node.getNode().getUniqueId(), lightPort.getId()));
                        lightPorts.add(lightPort);
                    }
                    model.setLightPorts(lightPorts);

                    if (CollectionUtils.isNotEmpty(lightPorts)) {
                        if (!queryAllPortConfigSupported) {
                            // trigger query of light port config
                            communication.queryLightPortConfig(node.getNode());
                        }
                        else {
                            communication.queryAllPortConfig(node.getNode(), LcOutputType.LIGHTPORT, 0,
                                lightPorts.size());
                        }
                    }

                    LOGGER.debug("Get the backlight ports from the node.");
                    for (BacklightPort backlightPort : communication.getBacklightPorts(node.getNode())) {
                        backlightPort.setLabel(backlightPortLabels.getLabel(node.getNode().getUniqueId(),
                            backlightPort.getId()));
                        backlightPorts.add(backlightPort);
                    }
                    model.setBacklightPorts(backlightPorts);

                    if (CollectionUtils.isNotEmpty(backlightPorts)) {
                        if (!queryAllPortConfigSupported) {
                            // trigger query of backlight port config
                            communication.queryBacklightPortConfig(node.getNode());
                        }
                        else {
                            communication.queryAllPortConfig(node.getNode(), LcOutputType.BACKLIGHTPORT, 0,
                                backlightPorts.size());
                        }
                    }

                    Collection nodeMotorPorts = communication.getMotorPorts(node.getNode());
                    if (CollectionUtils.isNotEmpty(nodeMotorPorts)) {
                        LOGGER.debug("Get the motor ports from the node.");
                        for (MotorPort motorPort : nodeMotorPorts) {
                            motorPort
                                .setLabel(motorPortLabels.getLabel(node.getNode().getUniqueId(), motorPort.getId()));
                            motorPorts.add(motorPort);
                        }
                    }
                    else {
                        LOGGER.debug("The current node has no motor ports.");
                    }
                    model.setMotorPorts(motorPorts);

                    if (CollectionUtils.isNotEmpty(motorPorts)) {
                        if (queryAllPortConfigSupported) {
                            // query the configuration of the motor ports
                            communication.queryAllPortConfig(node.getNode(), LcOutputType.MOTORPORT, 0,
                                motorPorts.size());
                        }
                    }

                    LOGGER.debug("Get the servo ports from the node.");
                    for (ServoPort servoPort : communication.getServoPorts(node.getNode())) {
                        servoPort.setLabel(servoPortLabels.getLabel(node.getNode().getUniqueId(), servoPort.getId()));
                        servoPorts.add(servoPort);
                    }
                    model.setServoPorts(servoPorts);

                    if (CollectionUtils.isNotEmpty(servoPorts)) {
                        if (!queryAllPortConfigSupported) {
                            // trigger query of servo port config
                            communication.queryServoPortConfig(node.getNode());
                        }
                        else {
                            communication.queryAllPortConfig(node.getNode(), LcOutputType.SERVOPORT, 0,
                                servoPorts.size());
                        }
                    }

                    LOGGER.debug("Get the sound ports from the node.");
                    for (SoundPort soundPort : communication.getSoundPorts(node.getNode())) {
                        soundPort.setLabel(soundPortLabels.getLabel(node.getNode().getUniqueId(), soundPort.getId()));
                        soundPorts.add(soundPort);
                    }
                    model.setSoundPorts(soundPorts);

                    if (CollectionUtils.isNotEmpty(soundPorts)) {
                        if (queryAllPortConfigSupported) {
                            // query the configuration of the sound ports
                            communication.queryAllPortConfig(node.getNode(), LcOutputType.SOUNDPORT, 0,
                                soundPorts.size());
                        }
                    }

                    if (!queryAllPortConfigSupported) {
                        LOGGER.info("Get the port mapping.");
                        PortRemappingSupport portRemappingSupport = new PortRemappingSupport();
                        portRemappingSupport.processPortRemapping(node, switchPorts, inputPorts, servoPorts);
                    }
                }
                else {
                    LOGGER.info("Node has no switch functions configured.");
                    model.setAnalogPorts(analogPorts);
                    model.setInputPorts(inputPorts);
                    model.setSwitchPorts(switchPorts);
                    model.setLightPorts(lightPorts);
                    model.setBacklightPorts(backlightPorts);
                    model.setMotorPorts(motorPorts);
                    model.setServoPorts(servoPorts);
                    model.setSoundPorts(soundPorts);
                }

                // update all port lists with the values that are now available
                model.updatePortLists();
            }

            List flags = new LinkedList();
            List macros = new LinkedList();
            List accessories = new LinkedList();

            if (NodeUtils.hasAccessoryFunctions(node.getUniqueId())) {
                LOGGER.debug("Get the flags.");
                for (Flag flag : communication.getFlags()) {
                    flags.add(flag);
                }
                model.setFlags(flags);

                // check if the node has macros
                Feature macroLevel =
                    communication.readFeature(node.getNode(), BidibLibrary.FEATURE_CTRL_MAC_LEVEL, true);

                int macroLevelValue = 0;
                if (macroLevel != null) {
                    macroLevelValue = macroLevel.getValue();
                }

                if (macroLevelValue > 0) {
                    LOGGER.info("Get the macros from the node.");
                    try {
                        for (Macro macro : communication.getMacros(node.getNode())) {
                            // set the user-defined label
                            macro.setLabel(macroLabels.getLabel(node.getNode().getUniqueId(), macro.getId()));

                            // reset the changed flag on the macros
                            macro.setMacroSaveState(MacroSaveState.PERMANENTLY_STORED_ON_NODE);

                            // add the current macro to the list
                            macros.add(macro);
                        }
                    }
                    catch (RuntimeException e) {
                        LOGGER.warn("Get the macros from the node failed.", e);
                        model.setNodeHasError(node, true);
                    }
                }
                else {
                    LOGGER.info("The current node has no macros.");
                }
                model.setMacros(macros);

                LOGGER.info("Get the accessories from the node.");
                try {
                    Collection nodeAccessories = communication.getAccessories(node.getNode());
                    if (nodeAccessories != null) {
                        for (Accessory accessory : nodeAccessories) {
                            // accessory
                            // .setLabel(accessoryLabels.getLabel(node.getNode().getUniqueId(), accessory.getId()));

                            LabelType label =
                                AccessoryLabelUtils.getAccessoryLabel(accessoryLabels, node.getNode().getUniqueId(),
                                    accessory.getId());

                            accessory.setLabel(label.getLabelString());

                            // reset the changed flag on the accessory
                            accessory.setPendingChanges(false);

                            accessories.add(accessory);
                        }
                    }
                    else {
                        LOGGER.debug("Node has no accessories configured.");
                    }
                }
                catch (NoAnswerException e) {
                    LOGGER.warn("Get accessories from node failed.", e);
                    model.setNodeHasError(node, true);
                }
                model.setAccessories(accessories);

                if (CollectionUtils.isNotEmpty(accessories)) {

                    // Feature accessoryMacroMapped =
                    // communication.readFeature(node.getNode(), BidibLibrary.FEATURE_ACCESSORY_MACROMAPPED, false);
                    // if (accessoryMacroMapped != null && accessoryMacroMapped.getValue() < 1) {
                    LOGGER.info("Query the accessory state from the node.");
                    try {
                        communication.queryAccessoryState(node.getNode(), accessories.toArray(new Accessory[0]));
                    }
                    catch (Exception ex) {
                        LOGGER.warn("Query accessory state from node failed.", ex);
                    }
                    // }
                }
                else {
                    LOGGER.info("No accessories on node available.");
                }
            }
            else {
                model.setFlags(flags);
                model.setMacros(macros);
                model.setAccessories(accessories);
            }
        }
    }

    private void queryNodeStatus(Node node) {
        Communication communication = CommunicationFactory.getInstance();
        // initialize the current values
        if (NodeUtils.hasBoosterFunctions(node.getUniqueId())) {
            LOGGER.info("The node is a booster, set the booster maximum value.");
            // set the max booster current
            int boosterMaxCurrent = communication.getBoosterMaximumCurrent(node.getNode());
            LOGGER.info("Set the booster maximum current value: {}", boosterMaxCurrent);

            model.setBoosterMaximumCurrent(boosterMaxCurrent);

            // trigger fetch the current booster values
            communication.boosterQuery(node.getNode());
        }
        else {
            LOGGER.info("Current node has no booster functions: {}", node);
        }

        if (model.getFeedbackPorts().size() > 0) {
            LOGGER.info("Query the feedback ports.");
            communication.getFeedbackPortStatus(node.getNode(), 0, model.getFeedbackPorts().size());
        }
        else {
            LOGGER.info("No feedback ports to query available.");
        }

        // check if the query port status feature is enabled
        if (NodeUtils.hasSwitchFunctions(node.getUniqueId())) {
            LOGGER.info("The node has switching functions.");

            if (CollectionUtils.isNotEmpty(model.getInputPorts())) {
                LOGGER.info("Query the status of the input ports.");

                List portIds = new LinkedList();
                for (InputPort inputPort : model.getInputPorts()) {

                    if (inputPort.isEnabled()) {
                        portIds.add(inputPort.getId());
                    }
                    else {
                        LOGGER.info("Skip query port status of disabled input port with portId: {}", inputPort.getId());
                    }
                }
                communication.getInputPortStatus(node.getNode(), portIds);
            }

            if (communication.isQueryPortStatusEnabled(node.getNode())) {
                try {
                    List servoPorts = new LinkedList(model.getServoPorts());
                    LOGGER.info("Query the port status for the servo ports: {}", servoPorts);

                    List portIds = new LinkedList();
                    for (ServoPort servoPort : servoPorts) {
                        if (servoPort.isEnabled()) {
                            // query the port status
                            portIds.add(servoPort.getId());
                        }
                        else {
                            LOGGER.info("Skip query port status of disabled servo port with portId: {}",
                                servoPort.getId());
                        }
                    }
                    communication.queryPortStatus(LcOutputType.SERVOPORT, node.getNode(), portIds);
                }
                catch (Exception ex) {
                    LOGGER.warn("Query the port status for the servo ports failed.", ex);
                }

                try {
                    List switchPorts = new LinkedList(model.getSwitchPorts());
                    LOGGER.info("Query the port status for the switch ports: {}", switchPorts);

                    List portIds = new LinkedList();
                    for (SwitchPort switchPort : switchPorts) {

                        if (switchPort.isEnabled()) {
                            // query the port status
                            portIds.add(switchPort.getId());
                        }
                        else {
                            LOGGER.info("Skip query port status of disabled switch port with portId: {}",
                                switchPort.getId());
                        }
                    }
                    communication.queryPortStatus(LcOutputType.SWITCHPORT, node.getNode(), portIds);
                }
                catch (Exception ex) {
                    LOGGER.warn("Query the port status for the switch ports failed.", ex);
                }

                try {
                    LOGGER.info("Query the port status for the backlight ports: {}", model.getBacklightPorts());
                    for (BacklightPort backlightPort : model.getBacklightPorts()) {
                        communication
                            .queryPortStatus(LcOutputType.BACKLIGHTPORT, node.getNode(), backlightPort.getId());
                    }
                }
                catch (Exception ex) {
                    LOGGER.warn("Query the port status for the backlight ports failed.", ex);
                }

                try {
                    LOGGER.info("Query the port status for the light ports: {}", model.getLightPorts());
                    for (LightPort lightPort : model.getLightPorts()) {
                        communication.queryPortStatus(LcOutputType.LIGHTPORT, node.getNode(), lightPort.getId());
                    }
                }
                catch (Exception ex) {
                    LOGGER.warn("Query the port status for the light ports failed.", ex);
                }
            }
            else {
                LOGGER.info("Query port status is not enabled for node: {}", node);
            }
        }
        else {
            LOGGER.info("Node has no switch port functions.");
        }
    }

    @Override
    public void stop() {
        LOGGER.info("Stop the main controller.");
        selectedNodeChangeWorker.shutdownNow();

        view.saveWindowPosition();

        view.setVisible(false);
        System.exit(0);
    }

    @Override
    public void addNodeListListener(NodeListListener nodeListListener) {
        LOGGER.info("Add new nodeListListener: {}", nodeListListener);
        model.addNodeListListener(nodeListListener);
    }

    @Override
    public void removeNodeListListener(NodeListListener nodeListListener) {
        LOGGER.info("Remove nodeListListener: {}", nodeListListener);
        model.removeNodeListListener(nodeListListener);
    }

    private NodeScripting nodeScripting;

    @Override
    public synchronized NodeScripting getNodeScripting() {
        if (nodeScripting == null) {
            LOGGER.info("Create new NodeScripting.");
            nodeScripting = new DefaultNodeScripting(model, this);
        }

        return nodeScripting;
    }

    @Override
    public void openConnection() {

        // use a thread to open the port
        LOGGER.info("Start a thread to open the port.");
        Thread openThread = new Thread(new Runnable() {

            @Override
            public void run() {
                LOGGER.info("The open thread is starting.");

                final Context context = new DefaultContext();

                // connect to system
                view.setStatusText(Resources.getString(DefaultMainMenuListener.class, "open-port"),
                    StatusBar.DISPLAY_NORMAL);
                try {

                    // get the value from the preferences
                    context.register("ignoreWrongReceiveMessageNumber",
                        Boolean.valueOf(Preferences.getInstance().isIgnoreWrongReceiveMessageNumber()));
                    context.register("ignoreFlowControl",
                        Boolean.valueOf(Preferences.getInstance().isIgnoreFlowControl()));

                    CommunicationFactory.getInstance().open(null, context);
                    LOGGER.info("Opened communication.");
                }
                catch (InvalidConfigurationException ex) {
                    LOGGER.warn("Open port and create nodes failed.", ex);
                    if (ex.getCause() instanceof ReasonAware) {
                        ReasonAware reasonAware = (ReasonAware) ex.getCause();
                        String causeTemplate = Resources.getString(ReasonAware.class, "causeTemplate");
                        String reason = Resources.getString(reasonAware.getClass(), reasonAware.getReason());
                        view.setStatusText(String.format(causeTemplate, ex.getCause().getMessage(), reason),
                            StatusBar.DISPLAY_NORMAL);

                        showErrorDialog(String.format(causeTemplate, ex.getCause().getMessage(), reason),
                            Resources.getString(MainController.class, "error.title"));
                    }
                    else {
                        String causeTemplate =
                            Resources.getString(InvalidConfigurationException.class, "causeTemplate");
                        String message = null;
                        if (StringUtils.isNotBlank(ex.getReason())) {
                            message = Resources.getString(ex.getClass(), ex.getReason());
                        }
                        else {
                            message = (ex.getCause() != null ? ex.getCause().getMessage() : ex.getMessage());
                        }
                        view.setStatusText(String.format(causeTemplate, message), StatusBar.DISPLAY_NORMAL);

                        showErrorDialog(String.format(causeTemplate, message),
                            Resources.getString(MainController.class, "error.title"));
                    }
                }
                catch (NoAnswerException ex) {
                    LOGGER.warn("Open port and establish communication failed.", ex);
                    view.setStatusText(Resources.getString(ex.getClass(), "bidib-communication-failure"),
                        StatusBar.DISPLAY_NORMAL);

                    context.register("testIsDebugIfActive", Boolean.TRUE);
                    context.register("noAnswerEx", ex);
                }
                catch (Exception ex) {
                    LOGGER.warn("Open port and create nodes failed.", ex);

                    if (ex.getCause() != null) {
                        view.setStatusText(Resources.getString(null, ex.getCause().getClass().getName()) + " "
                            + ex.getCause().getMessage(), StatusBar.DISPLAY_NORMAL);

                        showErrorDialog(Resources.getString(null, ex.getCause().getClass().getName()) + " "
                            + ex.getCause().getMessage(), Resources.getString(MainController.class, "error.title"));
                    }
                    else {
                        view.setStatusText(Resources.getString(null, ex.getClass().getName()) + " " + ex.getMessage(),
                            StatusBar.DISPLAY_NORMAL);

                        showErrorDialog(Resources.getString(null, ex.getClass().getName()) + " " + ex.getMessage(),
                            Resources.getString(MainController.class, "error.title"));
                    }
                }
                LOGGER.info("The open thread has finished.");

                if (context.get("testIsDebugIfActive", Boolean.class, Boolean.FALSE) == Boolean.TRUE) {
                    LOGGER.info("Test if the debug interface is active.");

                    // use the debug controller to check the interface
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            PreferencesPortType portType = Preferences.getInstance().getSelectedPortType();
                            String commPort = portType.getConnectionName();
                            String result = sendInfoRequest(commPort);

                            NoAnswerException ex = context.get("noAnswerEx", NoAnswerException.class, null);
                            if (StringUtils.isNotBlank(result)) {

                                showErrorDialog(Resources.getString(ex.getClass(),
                                    "bidib-communication-failure-debug-interface-active", new Object[] { result }),
                                    Resources.getString(MainController.class, "error.title"));
                            }
                            else {
                                // display the original error message
                                showErrorDialog(Resources.getString(ex.getClass(), "bidib-communication-failure"),
                                    Resources.getString(MainController.class, "error.title"));
                            }
                        }
                    });
                }

            }
        }, "openThread");
        LOGGER.info("Start the open thread.");
        openThread.start();
    }

    private String sendInfoRequest(String portName) {
        LOGGER.warn("Send the info request to the root node, portName: {}", portName);

        final StringBuilder receivedMessages = new StringBuilder();

        try {
            final Object resultLock = new Object();

            int baudRate = 19200;
            DebugMessageListener messageListener = null;
            DebugMessageReceiver messageReceiver = null;
            DebugReader debugReader = null;
            if (debugReader == null) {
                LOGGER.info("Create new instance of debug reader.");

                try {
                    messageListener = new DebugMessageListener() {

                        @Override
                        public void debugMessage(final String message) {
                            LOGGER.info("debug message received: {}", message);

                            receivedMessages.append(message);

                            if (message.indexOf('\n') > -1) {
                                LOGGER.info("Received CR, notify the waiting thread.");
                                synchronized (resultLock) {
                                    resultLock.notify();
                                }
                            }
                        }
                    };
                    messageReceiver = new DebugMessageReceiver();
                    messageReceiver.addMessageListener(messageListener);
                    debugReader = new DebugReader(messageReceiver);
                }
                catch (Exception ex) {
                    LOGGER.warn("Open debug port failed.", ex);
                }
            }

            try {
                debugReader.open(portName, baudRate, new ConnectionListener() {

                    @Override
                    public void opened(String port) {
                        LOGGER.info("Port opened: {}", port);

                    }

                    @Override
                    public void closed(String port) {
                        LOGGER.info("Port closed: {}", port);
                    }
                }, null);
            }
            catch (PortNotFoundException | PortNotOpenedException ex) {
                LOGGER.warn("Open debug port failed.", ex);
            }
            catch (Exception ex) {
                LOGGER.warn("Open debug port failed.", ex);
            }

            String infoRequest = "?";

            // send twice because the first message might not be evaluated correct because of data in buffer
            // on the receiving side because the debug interface expects the line feed.
            debugReader.send(infoRequest);
            debugReader.send(infoRequest);

            synchronized (resultLock) {
                resultLock.wait(1000);
            }

            debugReader.close();

            messageReceiver.removeMessageListener(messageListener);
            messageListener = null;
            messageReceiver = null;

            debugReader = null;
        }
        catch (Exception ex) {
            LOGGER.error("Send info request failed.", ex);
        }
        finally {
        }

        return receivedMessages.toString();
    }

    private void showErrorDialog(final String message, final String title) {
        if (SwingUtilities.isEventDispatchThread()) {
            JOptionPane.showMessageDialog(view, message, title, JOptionPane.ERROR_MESSAGE);
        }
        else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    JOptionPane.showMessageDialog(view, message, title, JOptionPane.ERROR_MESSAGE);
                }
            });
        }
    }

    @Override
    public void allBoosterOff() {

        for (Node node : model.getNodes()) {
            if (NodeUtils.hasBoosterFunctions(node.getUniqueId())) {
                LOGGER.info("Switch booster off: {}", node);
                try {
                    CommunicationFactory.getInstance().boosterOff(node.getNode());
                }
                catch (Exception ex) {
                    LOGGER.warn("Switch booster off failed for node: {}", node, ex);
                }
            }
        }
    }

    @Override
    public void replaceMacro(Macro macro, boolean saveOnNode) {
        LOGGER.info("Replace the macro: {}", macro);
        if (macro != null) {
            // set the total number possible functions for this macro
            macro.setFunctionSize(CommunicationFactory.getInstance().getMacroLength(model.getSelectedNode().getNode()));

            if (model.getSelectedMacro() != null
                && model.getSelectedMacro().getMacroSaveState().equals(MacroSaveState.PENDING_CHANGES)) {
                // show dialog
                int result =
                    JOptionPane.showConfirmDialog(view,
                        Resources.getString(MainController.class, "macro_has_pending_changes"),
                        Resources.getString(MainController.class, "pending_changes"), JOptionPane.OK_CANCEL_OPTION);
                if (result != JOptionPane.OK_OPTION) {
                    LOGGER.info("User canceled discard pending changes.");
                    return;
                }
            }

            // replace the macro in the main model
            model.replaceMacro(macro);
        }

        if (saveOnNode) {
            LOGGER.info("Transfer the macro to node and save permanently: {}", macro);
            try {
                CommunicationFactory.getInstance().saveMacro(model.getSelectedNode(), macro);
                LOGGER.info("Transfer and save macro passed.");
                // reset pending changes
                // macro.setMacroSaveState(false);
            }
            catch (InvalidConfigurationException ex) {
                LOGGER.warn("Transfer macro content or save macro failed.", ex);

                model.setNodeHasError(model.getSelectedNode(), true);
            }
        }
    }

    @Override
    public void resetNode(Node node) {
        LOGGER.info("Reset the current node: {}", node);
        CommunicationFactory.getInstance().reset(node.getNode());
    }

    @Override
    public void replaceAccessory(Accessory accessory, boolean saveOnNode) {
        LOGGER.info("Replace the accessory: {}", (accessory != null ? accessory.getDebugString() : null));

        if (accessory != null) {
            model.replaceAccessory(accessory);
        }

        if (saveOnNode) {
            LOGGER.info("Transfer the accessory to node and save permanently.");
            try {
                CommunicationFactory.getInstance().saveAccessory(model.getSelectedNode().getNode(), accessory);
                LOGGER.info("Transfer and save accessory passed.");
            }
            catch (InvalidConfigurationException ex) {
                LOGGER.warn("Transfer accessory or save accessory failed.", ex);

                model.setNodeHasError(model.getSelectedNode(), true);
            }
        }
    }

    @Override
    public void replacePortConfig(TargetType portType, final Map> portConfig) {
        LOGGER.info("Replace the port config, portType: {}, portConfig: {}", portType, portConfig);
        try {
            Node node = model.getSelectedNode();

            switch (portType.getScriptingTargetType()) {
                case BACKLIGHTPORT: {
                    Number lightDimmDown = (Number) portConfig.get(BidibLibrary.BIDIB_PCFG_DIMM_DOWN_8_8).getValue();
                    int dimmDown = lightDimmDown.intValue();
                    Number lightDimmUp = (Number) portConfig.get(BidibLibrary.BIDIB_PCFG_DIMM_UP_8_8).getValue();
                    int dimmUp = lightDimmUp.intValue();

                    int dmxMapping = 1;
                    CommunicationFactory.getInstance().setBacklightPortParameters(node.getNode(),
                        portType.getPortNum().intValue(), dimmDown, dimmUp, dmxMapping);
                }
                    break;
                case LIGHTPORT: {
                    Number lightLevelOff = (Number) portConfig.get(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_OFF).getValue();
                    int levelOn = lightLevelOff.intValue();
                    Number lightLevelOn = (Number) portConfig.get(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_ON).getValue();
                    int levelOff = lightLevelOn.intValue();
                    Number lightDimmDown = (Number) portConfig.get(BidibLibrary.BIDIB_PCFG_DIMM_DOWN_8_8).getValue();
                    int dimmDown = lightDimmDown.intValue();
                    Number lightDimmUp = (Number) portConfig.get(BidibLibrary.BIDIB_PCFG_DIMM_UP_8_8).getValue();
                    int dimmUp = lightDimmUp.intValue();
                    Integer lightRgb = null;
                    PortConfigValue rgbValue = portConfig.get(BidibLibrary.BIDIB_PCFG_RGB);
                    if (rgbValue != null) {
                        lightRgb = (Integer) rgbValue.getValue();
                    }
                    CommunicationFactory.getInstance().setLightPortParameters(node.getNode(),
                        portType.getPortNum().intValue(), levelOff, levelOn, dimmDown, dimmUp, lightRgb);
                }
                    break;
                case SERVOPORT: {
                    Number servoTrimDown = (Number) portConfig.get(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_L).getValue();
                    int trimDown = servoTrimDown.intValue();
                    Number servoTrimUp = (Number) portConfig.get(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_H).getValue();
                    int trimUp = servoTrimUp.intValue();
                    Number servoSpeed = (Number) portConfig.get(BidibLibrary.BIDIB_PCFG_SERVO_SPEED).getValue();
                    int speed = servoSpeed.intValue();

                    CommunicationFactory.getInstance().setServoPortParameters(node.getNode(),
                        portType.getPortNum().intValue(), trimDown, trimUp, speed);
                }
                    break;
                case SWITCHPORT: {
                    Number switchIoType = (Number) portConfig.get(BidibLibrary.BIDIB_PCFG_IO_CTRL).getValue();
                    int ioType = switchIoType.intValue();
                    IoBehaviourEnum ioBehaviour = IoBehaviourEnum.valueOf(ByteUtils.getLowByte(ioType));
                    Number switchTime = (Number) portConfig.get(BidibLibrary.BIDIB_PCFG_TICKS).getValue();
                    int time = switchTime.intValue();

                    CommunicationFactory.getInstance().setSwitchPortParameters(node.getNode(),
                        portType.getPortNum().intValue(), ioBehaviour, time);
                }
                    break;
                default:
                    LOGGER.warn("Unsupported port type detected: {}", portType);
                    break;
            }

        }
        catch (Exception ex) {
            LOGGER.warn("Replace port config on node failed.", ex);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy