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

org.bidib.wizard.common.node.Node Maven / Gradle / Ivy

package org.bidib.wizard.common.node;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.bidib.jbidibc.core.node.ConfigurationVariable;
import org.bidib.jbidibc.exchange.vendorcv.VendorCvData;
import org.bidib.jbidibc.messages.FeedbackAddressData;
import org.bidib.jbidibc.messages.FeedbackDynStateData;
import org.bidib.jbidibc.messages.FeedbackPosition;
import org.bidib.jbidibc.messages.SoftwareVersion;
import org.bidib.jbidibc.messages.StringData;
import org.bidib.jbidibc.messages.enums.DetachedState;
import org.bidib.jbidibc.messages.enums.IdentifyState;
import org.bidib.jbidibc.messages.enums.LcOutputType;
import org.bidib.jbidibc.messages.enums.PositionLocationEnum;
import org.bidib.jbidibc.messages.enums.SysErrorEnum;
import org.bidib.jbidibc.messages.port.PortConfigValue;
import org.bidib.jbidibc.messages.port.PortMapUtils;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.NodeUtils;
import org.bidib.jbidibc.messages.utils.ProductUtils;
import org.bidib.wizard.api.model.Accessory;
import org.bidib.wizard.api.model.BoosterNodeInterface;
import org.bidib.wizard.api.model.CommandStationNodeInterface;
import org.bidib.wizard.api.model.Flag;
import org.bidib.wizard.api.model.InterfaceNodeInterface;
import org.bidib.wizard.api.model.LabelAware;
import org.bidib.wizard.api.model.Macro;
import org.bidib.wizard.api.model.MacroSaveState;
import org.bidib.wizard.api.model.NodeChangePublisher;
import org.bidib.wizard.api.model.NodeInterface;
import org.bidib.wizard.api.model.OccupancyNodeInterface;
import org.bidib.wizard.api.model.PortsProvider;
import org.bidib.wizard.api.model.SwitchingNodeInterface;
import org.bidib.wizard.api.model.event.NodeStatusEvent;
import org.bidib.wizard.api.model.event.NodeStatusEvent.StatusIdentifier;
import org.bidib.wizard.api.model.listener.AccessoryListListener;
import org.bidib.wizard.api.model.listener.CvDefinitionListener;
import org.bidib.wizard.api.model.listener.FlagListListener;
import org.bidib.wizard.api.model.listener.MacroListListener;
import org.bidib.wizard.api.model.listener.NodeListener;
import org.bidib.wizard.api.model.listener.PortListListener;
import org.bidib.wizard.api.model.listener.PortListener;
import org.bidib.wizard.api.model.listener.PortValueListener;
import org.bidib.wizard.common.labels.WizardLabelWrapper;
import org.bidib.wizard.model.ports.AnalogPort;
import org.bidib.wizard.model.ports.BacklightPort;
import org.bidib.wizard.model.ports.FeedbackPort;
import org.bidib.wizard.model.ports.GenericPort;
import org.bidib.wizard.model.ports.InputPort;
import org.bidib.wizard.model.ports.LightPort;
import org.bidib.wizard.model.ports.MotorPort;
import org.bidib.wizard.model.ports.Port;
import org.bidib.wizard.model.ports.ServoPort;
import org.bidib.wizard.model.ports.SoundPort;
import org.bidib.wizard.model.ports.SwitchPairPort;
import org.bidib.wizard.model.ports.SwitchPort;
import org.bidib.wizard.model.status.BidibStatus;
import org.bidib.wizard.model.status.FeedbackConfidenceStatus;
import org.bidib.wizard.model.status.FeedbackPortStatus;
import org.bidib.wizard.model.stringdata.StoredStrings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jgoodies.binding.beans.Model;

/**
 * The {@code Node} implementation represents a BiDiB node in the system.
 */
public class Node extends Model implements NodeInterface, LabelAware, NodeChangePublisher, PortsProvider {

    private static final long serialVersionUID = 1L;

    protected final Logger LOGGER = LoggerFactory.getLogger(getClass());

    private final List listeners = new LinkedList();

    private List macroListListeners = new LinkedList<>();

    private List accessoryListListeners = new LinkedList<>();

    private List cvDefinitionListeners = new LinkedList<>();

    private List flagListListeners = new LinkedList<>();

    private Boolean addressMessagesEnabled;

    private Boolean dccStartEnabled;

    private Boolean externalStartEnabled;

    private Boolean feedbackMessagesEnabled;

    private Boolean feedbackMirrorDisabled;

    private IdentifyState identifyState;

    private DetachedState detachedState = DetachedState.ATTACHED;

    private SysErrorEnum sysError;

    private String reasonData;

    private Boolean keyMessagesEnabled;

    private String label;

    private final org.bidib.jbidibc.messages.Node node;

    private final PropertyChangeListener pclNode;

    private final PropertyChangeListener pclBoosterNode;

    private int storableMacroCount;

    private int maxMacroSteps;

    private int maxAspectsCount;

    private boolean globalDetectorAvailable;

    // the base number is the base number of the RF base in multi cell environments
    private int baseNumber;

    private boolean updatable;

    private SoftwareVersion updateFirmwareVersion;

    private boolean bootloaderNode;

    private boolean nodeHasError;

    private VendorCvData vendorCV;

    private Object configVarsLock = new Object();

    private Object genericPortsLock = new Object();

    private final Map configVariables;

    private List flags = new LinkedList<>();

    private PortConfigHandler genericPortConfigHandler;

    private AnalogPortHandler analogPortHandler;

    private BacklightPortHandler backlightPortHandler;

    private FeedbackPortHandler feedbackPortHandler;

    private InputPortHandler inputPortHandler;

    private LightPortHandler lightPortHandler;

    private MotorPortHandler motorPortHandler;

    private ServoPortHandler servoPortHandler;

    private SoundPortHandler soundPortHandler;

    private SwitchPortHandler switchPortHandler;

    private SwitchPairPortHandler switchPairPortHandler;

    private List macros = new LinkedList<>();

    private List accessories = new LinkedList<>();

    private Map, List> portListListeners = new LinkedHashMap<>();

    private Map, List>>> portListeners =
        new LinkedHashMap<>();

    private Map, List>>> portValueListeners =
        new LinkedHashMap<>();

    private PropertyChangeListener macroPendingChangesListener;

    private PropertyChangeListener accessoryPendingChangesListener;

    private CommandStationNodeInterface commandStationNodeProxy;

    private BoosterNodeInterface boosterNodeProxy;

    private InterfaceNodeInterface interfaceNodeProxy;

    private SwitchingNodeInterface switchingNodeProxy;

    private OccupancyNodeInterface occupancyNodeProxy;

    private StatusIdentifier nodeLoadStatus = StatusIdentifier.InitialLoadPending;

    private final Object initialLoadFinishedLock = new Object();

    // private AtomicBoolean initialLoadPartiallyFinished = new AtomicBoolean(false);

    private ConnectionNodeAwarePublisher publisher;

    private WizardLabelWrapper wizardLabelWrapper;

    // keep the strings stored in namespace 2
    private StoredStrings storedStringsNs2;

    /**
     * Create a new node with the associated bidib node.
     * 
     * @param node
     *            the bidib node is mandatory
     * @throws IllegalArgumentException
     *             if the bidib node is null
     */
    public Node(org.bidib.jbidibc.messages.Node node) {
        if (node == null) {
            throw new IllegalArgumentException("The node must not be null!");
        }
        this.node = node;

        this.configVariables = new LinkedHashMap<>();

        // Listen on changes of the node name from the core node. If the name is read from the real node we must get the
        // change.
        pclNode = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                // LOGGER.debug(">>> Property changed, name: {}", evt.getPropertyName());
                switch (evt.getPropertyName()) {
                    case org.bidib.jbidibc.messages.Node.PROPERTY_USERNAME:
                        LOGGER
                            .info("The username of the node has changed: {}, oldvalue: '{}', newvalue: '{}'",
                                node.getStoredString(StringData.INDEX_USERNAME), evt.getOldValue(), evt.getNewValue());

                        break;
                    default:
                        break;
                }
                firePropertyChange(new PropertyChangeEvent(Node.this, PROPERTY_NODE_PREFIX + evt.getPropertyName(),
                    evt.getOldValue(), evt.getNewValue()));
            }
        };
        this.node.addPropertyChangeListener(pclNode);

        pclBoosterNode = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {

                firePropertyChange(new PropertyChangeEvent(Node.this, "boosterNode." + evt.getPropertyName(),
                    evt.getOldValue(), evt.getNewValue()));

            }
        };
    }

    /**
     * Set the event publisher.
     * 
     * @param publisher
     *            the event publisher
     */
    public void setEventPublisher(final ConnectionNodeAwarePublisher publisher) {
        this.publisher = publisher;
    }

    /**
     * Set the wizard label wrapper.
     * 
     * @param wizardLabelWrapper
     *            the wizard label wrapper
     */
    public void setWizardLabelWrapper(final WizardLabelWrapper wizardLabelWrapper) {
        this.wizardLabelWrapper = wizardLabelWrapper;
    }

    /**
     * Initialize the node.
     */
    public void initialize() {

        if (NodeUtils.hasCommandStationFunctions(node.getUniqueId())) {
            LOGGER.info("The node has command station functions.");
            commandStationNodeProxy = new CommandStationNode(this, publisher);
            LOGGER.debug("Created new instance of CommandStationNode: {}", commandStationNodeProxy);
        }
        if (NodeUtils.hasBoosterFunctions(node.getUniqueId())) {
            LOGGER.info("The node has booster functions.");
            boosterNodeProxy = new BoosterNode(this, publisher);

            // TODO verify if this is good ...
            ((BoosterNode) boosterNodeProxy).addPropertyChangeListener(pclBoosterNode);
        }

        if (NodeUtils.hasSubNodesFunctions(node.getUniqueId())) {
            LOGGER.info("The node has subnodes functions.");
            interfaceNodeProxy = new InterfaceNode(this);
        }

        if (NodeUtils.hasSwitchFunctions(node.getUniqueId()) || NodeUtils.hasAccessoryFunctions(node.getUniqueId())) {
            LOGGER.info("The node has switching functions.");
            switchingNodeProxy = new SwitchingNode(this);
        }
        if (NodeUtils.hasFeedbackFunctions(node.getUniqueId())) {
            LOGGER.info("The node has occupancy functions.");
            occupancyNodeProxy = new OccupancyNode(this);
        }

        // handler for generic ports
        genericPortConfigHandler = new PortConfigHandler(this, publisher);

        analogPortHandler = new AnalogPortHandler(this, publisher, wizardLabelWrapper);
        backlightPortHandler = new BacklightPortHandler(this, publisher, wizardLabelWrapper);
        feedbackPortHandler = new FeedbackPortHandler(this, publisher, wizardLabelWrapper);
        inputPortHandler = new InputPortHandler(this, publisher, wizardLabelWrapper);
        lightPortHandler = new LightPortHandler(this, publisher, wizardLabelWrapper);
        motorPortHandler = new MotorPortHandler(this, publisher, wizardLabelWrapper);
        servoPortHandler = new ServoPortHandler(this, publisher, wizardLabelWrapper);
        soundPortHandler = new SoundPortHandler(this, publisher, wizardLabelWrapper);
        switchPortHandler = new SwitchPortHandler(this, publisher, wizardLabelWrapper);
        switchPairPortHandler = new SwitchPairPortHandler(this, publisher, wizardLabelWrapper);

        macroPendingChangesListener = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                LOGGER.info("The pending changes flag of a macro has been changed.");

                // force refresh of macro list
                fireMacroPendingChangesChanged();
            }
        };

        accessoryPendingChangesListener = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                LOGGER.trace("The property of an accessory has been changed.");
                switch (evt.getPropertyName()) {
                    case Accessory.PROPERTY_PENDING_CHANGES:
                        // force refresh of accessory list
                        fireAccessoryPendingChangesChanged();
                        break;
                    case Accessory.PROPERTY_TOTAL_ASPECTS_OF_ACCESSORY:
                        Integer accessoryId = (evt.getNewValue() != null ? (Integer) evt.getNewValue() : null);
                        LOGGER
                            .info("The total aspects of an accessory have been changed, accessory id: {}", accessoryId);

                        // perform with accessory id
                        fireAccessoryChanged(accessoryId);
                        break;
                    case Accessory.PROPERTY_ACCESSORY_EXECUTION_STATE:
                        fireAccessoryExecutionStateChanged();
                        break;
                    default:
                        break;
                }

            }
        };
    }

    @Override
    public void postLoadNodeDataProcessing() {

        // add the GPIO port identifiers for OneControl and OneDriveTurn nodes

        if (node.isPortFlatModelAvailable()) {
            LOGGER.info("Set the GPIO port identifiers. Current node: {}", this);

            // special handling for OneControl and OneDriveTurn
            String switchPortIdentifier = null;
            if (ProductUtils.isOneControl(getUniqueId())) {
                switchPortIdentifier = "POWER";
            }
            else if (ProductUtils.isOneDriveTurn(getUniqueId())) {
                switchPortIdentifier = "MOTOR";
            }

            int gpioId = 0;
            for (InputPort inputPort : getInputPorts()) {
                inputPort.setPortIdentifier("GPIO " + gpioId++);
            }

            int switchPortId = 0;
            gpioId = 0;
            for (SwitchPort switchPort : getSwitchPorts()) {
                LOGGER.debug("Current switch port: {}", switchPort);
                if (!switchPort.isRemappingEnabled()
                    || !PortMapUtils.supportsPortType(LcOutputType.INPUTPORT, switchPort.getPortConfigX())) {
                    if (switchPortIdentifier != null) {
                        switchPort
                            .setPortIdentifier(switchPortIdentifier + " " + switchPortId + "/" + (switchPortId + 1));
                    }
                    if (!switchPort.isRemappingEnabled()) {
                        switchPortId += 2;
                    }
                }
                else if (switchPort.isRemappingEnabled()) {
                    switchPort.setPortIdentifier("GPIO " + gpioId++);
                }
            }

            switchPortId = 0;
            gpioId = 0;
            for (SwitchPairPort switchPairPort : getSwitchPairPorts()) {
                LOGGER.debug("Current switchPair port: {}", switchPairPort);
                if (!switchPairPort.isRemappingEnabled()
                    || !PortMapUtils.supportsPortType(LcOutputType.INPUTPORT, switchPairPort.getPortConfigX())) {
                    if (switchPortIdentifier != null) {
                        switchPairPort
                            .setPortIdentifier(switchPortIdentifier + " " + switchPortId + "/" + (switchPortId + 1));
                    }
                    switchPortId += 2;
                }
                else if (switchPairPort.isRemappingEnabled()) {
                    switchPairPort.setPortIdentifier("GPIO " + gpioId);
                    gpioId += 2;
                }
            }
        }
    }

    @Override
    public void refreshNode() {
        LOGGER.info("Refresh the node.");

        firePortListChanged(AnalogPort.class);
        firePortListChanged(BacklightPort.class);
        firePortListChanged(InputPort.class);
        firePortListChanged(LightPort.class);
        firePortListChanged(MotorPort.class);
        firePortListChanged(ServoPort.class);
        firePortListChanged(SoundPort.class);
        firePortListChanged(SwitchPairPort.class);
        firePortListChanged(SwitchPort.class);

        fireMacroListChanged();
        fireAccessoryListChanged();

    }

    /**
     * @return returns address of the node
     */
    @Override
    public byte[] getAddr() {
        return node.getAddr();
    }

    /**
     * @return returns the uniqueId of the node
     */
    @Override
    public long getUniqueId() {
        return node.getUniqueId();
    }

    @Override
    public CommandStationNodeInterface getCommandStationNode() {
        return commandStationNodeProxy;
    }

    @Override
    public BoosterNodeInterface getBoosterNode() {
        return boosterNodeProxy;
    }

    @Override
    public InterfaceNodeInterface getInterfaceNode() {
        return interfaceNodeProxy;
    }

    @Override
    public SwitchingNodeInterface getSwitchingNode() {
        return switchingNodeProxy;
    }

    @Override
    public OccupancyNodeInterface getOccupancyNode() {
        return occupancyNodeProxy;
    }

    @Override
    public void addNodeListener(NodeListener l) {
        listeners.add(l);
        addPropertyChangeListener(l);
    }

    @Override
    public void removeNodeListener(NodeListener l) {
        listeners.remove(l);
        removePropertyChangeListener(l);
    }

    @Override
    public void addPortListListener(Class portClazz, PortListListener listener) {
        List listeners = portListListeners.get(portClazz);
        if (listeners == null) {
            listeners = new LinkedList<>();
            portListListeners.put(portClazz, listeners);
        }
        listeners.add(listener);
    }

    @Override
    public void removePortListListener(Class portClazz, PortListListener listener) {
        List listeners = portListListeners.get(portClazz);
        if (listeners != null) {
            listeners.remove(listener);
        }
    }

    @Override
    public void addCvDefinitionListener(CvDefinitionListener listener) {
        if (!cvDefinitionListeners.contains(listener)) {
            cvDefinitionListeners.add(listener);
        }
        else {
            LOGGER.warn("The cvDefinitionListener is registered already: {}", listener);
        }
    }

    @Override
    public void removeCvDefinitionListener(CvDefinitionListener listener) {
        cvDefinitionListeners.remove(listener);
    }

    @Override
    public void firePortListChanged(Class portClazz) {
        LOGGER.info("The port list has changed for class: {}", portClazz);

        List listeners = portListListeners.get(portClazz);
        if (listeners != null) {
            notifyPortListListeners(listeners);
        }
    }

    private void notifyPortListListeners(final Collection listeners) {
        for (PortListListener l : listeners) {
            l.listChanged();
        }
    }

    @Override
    public void addPortListener(Class portClazz, PortListener> listener) {
        List>> listeners = portListeners.get(portClazz);
        if (listeners == null) {
            listeners = new LinkedList<>();
            portListeners.put(portClazz, listeners);
        }

        if (listeners.contains(listener)) {
            LOGGER.warn("Listener is already registered: {}", listener);
        }
        else {
            listeners.add(listener);
        }
    }

    @Override
    public void removePortListener(Class portClazz, PortListener> listener) {
        List>> listeners = portListeners.get(portClazz);
        if (listeners != null) {
            listeners.remove(listener);
        }
    }

    @Override
    public  void firePortConfigChanged(final Port port) {
        Class portClazz = port.getClass();
        LOGGER.debug("The port config has changed for port: {}", port);

        List> listeners = portListeners.get(portClazz);
        if (listeners != null) {
            notifyPortConfigListeners(listeners, port);
        }
        else {
            LOGGER.debug("No port config listeners available for portClazz: {}", portClazz);
        }
    }

    private  void notifyPortConfigListeners(
        final List> listeners, final Port port) {

        PortsChangeWrapper.notifyPortConfigChanged(listeners, this, port);
    }

    @Override
    public  void firePortStatusChanged(Class> portClazz, Port port) {

        List> listeners = portListeners.get(portClazz);
        if (listeners != null) {
            notifyPortStatusListeners(listeners, port);
        }
        else {
            LOGGER.debug("No port status listeners available for portClazz: {}", portClazz);
        }

        // must check for old nodes that have not port query all enabled
        if (StatusIdentifier.InitialLoadFinished != getNodeLoadStatusIdentifier()) {
            final SwitchingNodeInterface switchingNode = getSwitchingNode();
            if (switchingNode != null && !switchingNode.isPortQueryAllEnabled()) {

                // check for pending node status ... if none available trigger the initial node finished flag

                AbstractPortHandler[] handlers =
                    new AbstractPortHandler[] { lightPortHandler, inputPortHandler, switchPortHandler,
                        switchPairPortHandler, servoPortHandler, backlightPortHandler, analogPortHandler,
                        motorPortHandler, soundPortHandler };

                boolean pendingPortStatus = false;
                for (AbstractPortHandler handler : handlers) {
                    pendingPortStatus = handler.hasPendingPortStatus();
                    if (pendingPortStatus) {
                        LOGGER.debug("Found pending port status in handler: {}", handler);
                        break;
                    }
                }

                if (!pendingPortStatus) {
                    LOGGER.info("No pending port status found on node: {}", this);
                    signalInitialLoadFinished();
                }
            }
        }
    }

    private  void notifyPortStatusListeners(
        final List> listeners, final Port port) {

        PortsChangeWrapper.notifyPortStatusChanged(listeners, this, port);
    }

    /**
     * Add a port value listener.
     * 
     * @param portClazz
     *            the port class
     * @param listener
     *            the listener to add
     */
    @Override
    public void addPortValueListener(Class portClazz, PortValueListener listener) {
        List> listeners = portValueListeners.get(portClazz);
        if (listeners == null) {
            listeners = new LinkedList<>();
            portValueListeners.put(portClazz, listeners);
        }
        listeners.add(listener);
    }

    /**
     * Remove a port value listener.
     * 
     * @param portClazz
     *            the port class
     * @param listener
     *            the listener to remove
     */
    @Override
    public void removePortValueListener(Class portClazz, PortValueListener listener) {
        List> listeners = portValueListeners.get(portClazz);
        if (listeners != null) {
            listeners.remove(listener);
        }
    }

    @Override
    public void firePortValueChanged(Class portClazz, Port port) {
        LOGGER.info("The port value has changed for class: {}", portClazz);

        List> listeners = portValueListeners.get(portClazz);
        if (listeners != null) {
            notifyPortValueListeners(listeners, port);
        }
    }

    private void notifyPortValueListeners(final Collection> listeners, final Port port) {

        PortsChangeWrapper.notifyPortValueChanged(listeners, this, port);
    }

    @Override
    public void addMacroListListener(MacroListListener listener) {
        macroListListeners.add(listener);
    }

    @Override
    public void removeMacroListListener(MacroListListener listener) {
        macroListListeners.remove(listener);
    }

    @Override
    public void addAccessoryListListener(AccessoryListListener l) {
        accessoryListListeners.add(l);
    }

    @Override
    public void removeAccessoryListListener(AccessoryListListener listener) {
        accessoryListListeners.remove(listener);
    }

    @Override
    public void addFlagListListener(FlagListListener l) {
        flagListListeners.add(l);
    }

    @Override
    public void removeFlagListListener(FlagListListener l) {
        flagListListeners.remove(l);
    }

    @Override
    public Boolean isAddressMessagesEnabled() {
        return addressMessagesEnabled;
    }

    @Override
    public void setAddressMessagesEnabled(Boolean addressMessagesEnabled) {
        Boolean oldValue = this.addressMessagesEnabled;

        this.addressMessagesEnabled = addressMessagesEnabled;
        fireAddressMessagesEnabledChanged(addressMessagesEnabled);

        firePropertyChange(PROPERTY_ADDRESSMESSAGESENABLED, oldValue, addressMessagesEnabled);
    }

    @Override
    public Boolean isDccStartEnabled() {
        return dccStartEnabled;
    }

    @Override
    public void setDccStartEnabled(Boolean dccStartEnabled) {
        this.dccStartEnabled = dccStartEnabled;
        fireDccStartEnabledChanged(dccStartEnabled);
    }

    @Override
    public Boolean isExternalStartEnabled() {
        return externalStartEnabled;
    }

    @Override
    public void setExternalStartEnabled(Boolean externalStartEnabled) {
        this.externalStartEnabled = externalStartEnabled;
        fireExternalStartEnabledChanged(externalStartEnabled);
    }

    @Override
    public Boolean isFeedbackMessagesEnabled() {
        return feedbackMessagesEnabled;
    }

    @Override
    public void setFeedbackMessagesEnabled(Boolean feedbackMessagesEnabled) {
        this.feedbackMessagesEnabled = feedbackMessagesEnabled;
        fireFeedbackMessagesEnabledChanged(feedbackMessagesEnabled);
    }

    @Override
    public Boolean isFeedbackMirrorDisabled() {
        return feedbackMirrorDisabled;
    }

    @Override
    public void setFeedbackMirrorDisabled(Boolean feedbackMirrorDisabled) {
        LOGGER.info("Set the feedback mirror messages disabled: {}", feedbackMirrorDisabled);
        this.feedbackMirrorDisabled = feedbackMirrorDisabled;
        fireFeedbackMirrorDisabledChanged(feedbackMirrorDisabled);
    }

    @Override
    public IdentifyState getIdentifyState() {
        return identifyState;
    }

    @Override
    public void setIdentifyState(final IdentifyState identifyState) {
        LOGGER.info("Set the identifyState: {}", identifyState);

        final IdentifyState oldValue = this.identifyState;

        this.identifyState = identifyState;

        firePropertyChange(PROPERTY_IDENTIFY_STATE, oldValue, this.identifyState);
        fireIdentifyStateChanged(identifyState);
    }

    @Override
    public void setDetachedState(final DetachedState detachedState) {
        final DetachedState oldValue = this.detachedState;
        this.detachedState = detachedState;

        this.node.setDetached(detachedState == DetachedState.DETACHED);

        firePropertyChange(PROPERTY_DETACHED, oldValue, this.detachedState);
        fireDetachedStateChanged(detachedState);
    }

    @Override
    public DetachedState getDetachedState() {
        return detachedState;
    }

    public final static class ErrorStatePropertyChange {
        private final SysErrorEnum sysError;

        private final String reasonData;

        public ErrorStatePropertyChange(SysErrorEnum sysError, String reasonData) {
            super();
            this.sysError = sysError;
            this.reasonData = reasonData;
        }

        /**
         * @return the sysError
         */
        public SysErrorEnum getSysError() {
            return sysError;
        }

        /**
         * @return the reasonData
         */
        public String getReasonData() {
            return reasonData;
        }

    }

    @Override
    public void setErrorState(final SysErrorEnum sysError, final String reasonData) {

        LOGGER.info("Set the error state: {}, reasonData: {}", sysError, reasonData);

        final ErrorStatePropertyChange oldValue = new ErrorStatePropertyChange(this.sysError, this.reasonData);

        this.sysError = sysError;
        this.reasonData = reasonData;

        final ErrorStatePropertyChange newValue = new ErrorStatePropertyChange(this.sysError, this.reasonData);

        firePropertyChange(PROPERTY_ERROR_STATE, oldValue, newValue);
    }

    @Override
    public SysErrorEnum getErrorState() {
        return sysError;
    }

    @Override
    public String getReasonData() {
        return reasonData;
    }

    @Override
    public void setReasonData(final String reasonData) {

        final ErrorStatePropertyChange oldValue = new ErrorStatePropertyChange(this.sysError, this.reasonData);

        this.reasonData = reasonData;

        final ErrorStatePropertyChange newValue = new ErrorStatePropertyChange(this.sysError, this.reasonData);

        LOGGER.info("New reason data was set.");

        firePropertyChange(PROPERTY_REASON_DATA, oldValue, newValue);
    }

    @Override
    public boolean isNodeHasRestartPendingError() {
        return SysErrorEnum.BIDIB_ERR_RESET_REQUIRED.equals(sysError);
    }

    @Override
    public Boolean isKeyMessagesEnabled() {
        return keyMessagesEnabled;
    }

    @Override
    public void setInputMessagesEnabled(Boolean keyMessagesEnabled) {
        this.keyMessagesEnabled = keyMessagesEnabled;
        fireKeyMessagesEnabledChanged(keyMessagesEnabled);
    }

    @Override
    public String getLabel() {

        if (this.node != null) {
            // we prefer the name from the core node
            String userName = this.node.getStoredString(StringData.INDEX_USERNAME);
            LOGGER.trace("Found userName: {}", userName);
            if (StringUtils.isNotBlank(userName)) {
                return userName;
            }
        }

        return label;
    }

    @Override
    public void setLabel(String label) {
        LOGGER.info("Set the label: {}", label);
        String oldValue = this.label;

        this.label = label;

        firePropertyChange(PROPERTY_LABEL, oldValue, label);

        fireLabelsChanged();
    }

    @Override
    public org.bidib.jbidibc.messages.Node getNode() {
        return node;
    }

    @Override
    public int getStorableMacroCount() {
        return storableMacroCount;
    }

    @Override
    public void setStorableMacroCount(int storableMacroCount) {
        this.storableMacroCount = storableMacroCount;
    }

    /**
     * @return the maximum supported steps per macro
     */
    @Override
    public int getMaxMacroSteps() {
        return maxMacroSteps;
    }

    /**
     * @param maxMacroSteps
     *            the maximum supported steps per macro
     */
    public void setMaxMacroSteps(int maxMacroSteps) {
        this.maxMacroSteps = maxMacroSteps;
    }

    /**
     * @return the genericPorts
     */
    @Override
    public List getGenericPorts() {
        return genericPortConfigHandler.getGenericPorts();
    }

    @Override
    public List> getPorts() {
        List> ports = new LinkedList<>();
        ports.addAll(analogPortHandler.getPorts(this));
        ports.addAll(backlightPortHandler.getPorts(this));
        ports.addAll(inputPortHandler.getPorts(this));
        ports.addAll(lightPortHandler.getPorts(this));
        ports.addAll(motorPortHandler.getPorts(this));
        ports.addAll(servoPortHandler.getPorts(this));
        ports.addAll(soundPortHandler.getPorts(this));
        ports.addAll(switchPairPortHandler.getPorts(this));
        ports.addAll(switchPortHandler.getPorts(this));
        return ports;
    }

    /**
     * @param genericPorts
     *            the genericPorts to set
     */
    @Override
    public void setGenericPorts(List genericPorts) {

        synchronized (this.genericPortsLock) {
            LOGGER.info("Set the generic ports on the node: {}", this);

            genericPortConfigHandler.setGenericPorts(genericPorts);

            clearPortCache();
        }
    }

    /**
     * @return the flag returns {@code true} after the generic ports are set, {@code false} otherwise
     */
    @Override
    public boolean isGenericPortsSet() {
        return genericPortConfigHandler.isGenericPortsSet();
    }

    @Override
    public void setPortConfig(
        LcOutputType portType, int portNumber, Map> portConfig,
        final PropertyChangeListener pcl) {

        if (node.isPortFlatModelAvailable()) {

            // use the port config handler to set the port config
            genericPortConfigHandler.setPortConfig(portNumber, portConfig, pcl, this);
        }
        else {
            LOGGER
                .info("Set port config for type-oriented port model, porttype: {}, portNumber: {}", portType,
                    portNumber);

            switch (portType) {
                case ANALOGPORT:
                    setAnalogPortConfig(portNumber, portConfig);
                    break;
                case BACKLIGHTPORT:
                    setBacklightPortConfig(portNumber, portConfig);
                    break;
                case INPUTPORT:
                    setInputPortConfig(portNumber, portConfig);
                    break;
                case LIGHTPORT:
                    setLightPortConfig(portNumber, portConfig);
                    break;
                case MOTORPORT:
                    setMotorPortConfig(portNumber, portConfig);
                    break;
                case SERVOPORT:
                    setServoPortConfig(portNumber, portConfig);
                    break;
                case SOUNDPORT:
                    setSoundPortConfig(portNumber, portConfig);
                    break;
                case SWITCHPORT:
                    setSwitchPortConfig(portNumber, portConfig);
                    break;
                // add support for more port types
                default:
                    break;
            }

        }
    }

    @Override
    public void clearPortCache() {
        LOGGER.info("Clear the port cache on node: {}", this);

        synchronized (genericPortsLock) {
            // clear all cached ports
            analogPortHandler.clearPorts(this);
            backlightPortHandler.clearPorts(this);
            feedbackPortHandler.clearPorts(this);
            inputPortHandler.clearPorts(this);
            lightPortHandler.clearPorts(this);
            motorPortHandler.clearPorts(this);
            servoPortHandler.clearPorts(this);
            soundPortHandler.clearPorts(this);
            switchPortHandler.clearPorts(this);
            switchPairPortHandler.clearPorts(this);
        }
    }

    @Override
    public void setAnalogPorts(List analogPorts) {
        LOGGER.info("Set the analog ports on the node: {}", analogPorts);

        analogPortHandler.setPorts(analogPorts, this);
    }

    @Override
    public List getAnalogPorts() {

        return analogPortHandler.getPorts(this);
    }

    @Override
    public boolean hasAnalogPorts() {
        LOGGER.debug("Check if analog ports are available.");
        return analogPortHandler.hasPorts();
    }

    /**
     * Set the configuration of the analog port.
     * 
     * @param portNumber
     *            the port number
     * @param portConfig
     *            the new port configuration
     */
    @Override
    public void setAnalogPortConfig(int portNumber, Map> portConfig) {

        analogPortHandler.setPortConfig(portNumber, portConfig, this);
    }

    @Override
    public void setSwitchPorts(List switchPorts) {
        LOGGER.info("Set the switch ports on the node: {}", switchPorts);

        switchPortHandler.setPorts(switchPorts, this);

    }

    @Override
    public List getSwitchPorts() {

        return switchPortHandler.getPorts(this);
    }

    @Override
    public List getEnabledSwitchPorts() {
        return switchPortHandler.getEnabledPorts();
    }

    /**
     * Set the configuration of the switch port.
     * 
     * @param portNumber
     *            the port number
     * @param portConfig
     *            the new port configuration
     */
    @Override
    public void setSwitchPortConfig(int portNumber, Map> portConfig) {

        switchPortHandler.setPortConfig(portNumber, portConfig, this);
    }

    /**
     * @return node has switch ports
     */
    @Override
    public boolean hasSwitchPorts() {
        LOGGER.debug("Check if switch ports are available.");
        return switchPortHandler.hasPorts();
    }

    /**
     * Set the new switch port status.
     * 
     * @param portNumber
     *            the port number
     * @param portState
     *            the port state
     */
    @Override
    public void setSwitchPortStatus(final int portNumber, int portState) {
        switchPortHandler.setPortStatus(portNumber, portState, this);
    }

    @Override
    public void setSwitchPairPorts(List switchPairPorts) {
        LOGGER.info("Set the switchPair ports on the node: {}", switchPairPorts);

        switchPairPortHandler.setPorts(switchPairPorts, this);

    }

    @Override
    public List getSwitchPairPorts() {

        return switchPairPortHandler.getPorts(this);
    }

    @Override
    public List getEnabledSwitchPairPorts() {
        return switchPairPortHandler.getEnabledPorts();
    }

    /**
     * Set the configuration of the switchPair port.
     * 
     * @param portNumber
     *            the port number
     * @param portConfig
     *            the new port configuration
     */
    @Override
    public void setSwitchPairPortConfig(int portNumber, Map> portConfig) {

        switchPairPortHandler.setPortConfig(portNumber, portConfig, this);
    }

    /**
     * @return node has switchPair ports
     */
    @Override
    public boolean hasSwitchPairPorts() {
        LOGGER.debug("Check if switchPair ports are available.");
        return switchPairPortHandler.hasPorts();
    }

    /**
     * Set the new switchPair port status.
     * 
     * @param portNumber
     *            the port number
     * @param portState
     *            the port state
     */
    @Override
    public void setSwitchPairPortStatus(final int portNumber, int portState) {
        switchPairPortHandler.setPortStatus(portNumber, portState, this);
    }

    @Override
    public void setLightPorts(List lightPorts) {
        LOGGER.info("Set the light ports on the node: {}", lightPorts);

        lightPortHandler.setPorts(lightPorts, this);
    }

    @Override
    public List getLightPorts() {

        return lightPortHandler.getPorts(this);
    }

    @Override
    public List getEnabledLightPorts() {
        return lightPortHandler.getEnabledPorts();
    }

    /**
     * Set the configuration of the light port.
     * 
     * @param portNumber
     *            the port number
     * @param portConfig
     *            the new port configuration
     */
    @Override
    public void setLightPortConfig(int portNumber, Map> portConfig) {
        LOGGER.info("Set the lightport config for port: {}", portNumber);
        lightPortHandler.setPortConfig(portNumber, portConfig, this);
    }

    /**
     * @return node has light ports
     */
    @Override
    public boolean hasLightPorts() {
        LOGGER.debug("Check if light ports are available.");
        return lightPortHandler.hasPorts();
    }

    /**
     * Set the new switch port status.
     * 
     * @param portNumber
     *            the port number
     * @param portState
     *            the port state
     */
    @Override
    public void setLightPortStatus(final int portNumber, int portState) {
        lightPortHandler.setPortStatus(portNumber, portState, this);
    }

    @Override
    public void setBacklightPorts(List backlightPorts) {
        LOGGER.info("Set the backlight ports on the node: {}", backlightPorts);

        backlightPortHandler.setPorts(backlightPorts, this);
    }

    /**
     * @return the backlight ports
     */
    @Override
    public List getBacklightPorts() {

        return backlightPortHandler.getPorts(this);
    }

    /**
     * Set the configuration of the backlight port.
     * 
     * @param portNumber
     *            the port number
     * @param portConfig
     *            the new port configuration
     */
    @Override
    public void setBacklightPortConfig(int portNumber, Map> portConfig) {

        backlightPortHandler.setPortConfig(portNumber, portConfig, this);
    }

    /**
     * @return the port has backlight ports
     */
    @Override
    public boolean hasBacklightPorts() {
        LOGGER.debug("Check if backlight ports are available.");
        return backlightPortHandler.hasPorts();
    }

    @Override
    public void setBacklightPortValue(int portNumber, int portValue) {

        backlightPortHandler.setPortValue(portNumber, portValue, this);
    }

    @Override
    public void setInputPorts(List inputPorts) {
        LOGGER.info("Set the input ports on the node: {}", inputPorts);

        inputPortHandler.setPorts(inputPorts, this);
    }

    @Override
    public List getInputPorts() {

        return inputPortHandler.getPorts(this);
    }

    @Override
    public List getEnabledInputPorts() {
        return inputPortHandler.getEnabledPorts();
    }

    /**
     * Set the configuration of the input port.
     * 
     * @param portNumber
     *            the port number
     * @param portConfig
     *            the new port configuration
     */
    public void setInputPortConfig(int portNumber, Map> portConfig) {

        inputPortHandler.setPortConfig(portNumber, portConfig, this);
    }

    /**
     * @return the port has input ports
     */
    @Override
    public boolean hasInputPorts() {
        LOGGER.debug("Check if input ports are available.");
        return inputPortHandler.hasPorts();
    }

    @Override
    public void setInputPortStatus(final int portNumber, int portState) {
        inputPortHandler.setPortStatus(portNumber, portState, this);
    }

    @Override
    public void setServoPorts(List servoPorts) {
        LOGGER.info("Set the servo ports on the node: {}", servoPorts);

        servoPortHandler.setPorts(servoPorts, this);
    }

    @Override
    public List getServoPorts() {

        return servoPortHandler.getPorts(this);
    }

    /**
     * Set the configuration of the servo port.
     * 
     * @param portNumber
     *            the port number
     * @param portConfig
     *            the new port configuration
     */
    @Override
    public void setServoPortConfig(int portNumber, Map> portConfig) {

        servoPortHandler.setPortConfig(portNumber, portConfig, this);
    }

    @Override
    public boolean hasServoPorts() {
        LOGGER.debug("Check if servo ports are available.");

        return servoPortHandler.hasPorts();
    }

    @Override
    public void setServoPortValue(int portNumber, int portValue) {

        servoPortHandler.setPortValue(portNumber, portValue, this);
    }

    @Override
    public void setMotorPorts(List motorPorts) {
        LOGGER.info("Set the motor ports on the node: {}", motorPorts);

        motorPortHandler.setPorts(motorPorts, this);
    }

    @Override
    public List getMotorPorts() {

        return motorPortHandler.getPorts(this);
    }

    /**
     * Set the configuration of the motor port.
     * 
     * @param portNumber
     *            the port number
     * @param portConfig
     *            the new port configuration
     */
    public void setMotorPortConfig(int portNumber, Map> portConfig) {

        motorPortHandler.setPortConfig(portNumber, portConfig, this);
    }

    @Override
    public boolean hasMotorPorts() {
        LOGGER.debug("Check if motor ports are available.");

        return motorPortHandler.hasPorts();
    }

    @Override
    public void setMotorPortValue(final int portNumber, int portValue) {
        motorPortHandler.setPortValue(portNumber, portValue, this);
    }

    @Override
    public void setSoundPorts(List soundPorts) {
        LOGGER.info("Set the sound ports on the node: {}", soundPorts);

        soundPortHandler.setPorts(soundPorts, this);
    }

    /**
     * @return the sound ports
     */
    @Override
    public List getSoundPorts() {

        return soundPortHandler.getPorts(this);
    }

    /**
     * Set the configuration of the sound port.
     * 
     * @param portNumber
     *            the port number
     * @param portConfig
     *            the new port configuration
     */
    @Override
    public void setSoundPortConfig(int portNumber, Map> portConfig) {

        soundPortHandler.setPortConfig(portNumber, portConfig, this);
    }

    /**
     * @return the port has sound ports
     */
    @Override
    public boolean hasSoundPorts() {
        LOGGER.debug("Check if sound ports are available.");

        return soundPortHandler.hasPorts();
    }

    @Override
    public List getEnabledSoundPorts() {
        return soundPortHandler.getEnabledPorts();
    }

    /**
     * Set the new sound port status.
     * 
     * @param portNumber
     *            the port number
     * @param portState
     *            the port state
     */
    @Override
    public void setSoundPortStatus(final int portNumber, int portState) {
        soundPortHandler.setPortStatus(portNumber, portState, this);
    }

    @Override
    public boolean isGlobalDetectorAvailable() {
        return globalDetectorAvailable;
    }

    @Override
    public void setGlobalDetectorAvailable(boolean globalDetectorAvailable) {
        this.globalDetectorAvailable = globalDetectorAvailable;
    }

    @Override
    public int getBaseNumber() {
        return baseNumber;
    }

    @Override
    public void setBaseNumber(int baseNumber) {
        int oldValue = this.baseNumber;

        this.baseNumber = baseNumber;

        firePropertyChange(PROPERTY_BASENUMBER, oldValue, this.baseNumber);
    }

    @Override
    public boolean isUpdatable() {
        return updatable;
    }

    @Override
    public void setUpdatable(boolean updatable) {
        this.updatable = updatable;
    }

    @Override
    public SoftwareVersion getUpdateFirmwareVersion() {
        return this.updateFirmwareVersion;
    }

    @Override
    public void setUpdateFirmwareVersion(SoftwareVersion updateFirmwareVersion) {
        SoftwareVersion oldValue = this.updateFirmwareVersion;
        this.updateFirmwareVersion = updateFirmwareVersion;

        firePropertyChange(PROPERTY_UDPATE_FIRMWARE_VERSION, oldValue, this.updateFirmwareVersion);
    }

    /**
     * @return the bootloaderNode
     */
    @Override
    public boolean isBootloaderNode() {
        return bootloaderNode;
    }

    /**
     * @param bootloaderNode
     *            the bootloaderNode to set
     */
    @Override
    public void setBootloaderNode(boolean bootloaderNode) {
        this.bootloaderNode = bootloaderNode;
    }

    /**
     * @return the nodeHasError
     */
    @Override
    public boolean isNodeHasError() {
        return nodeHasError || (sysError != null && !SysErrorEnum.BIDIB_ERR_NONE.equals(sysError));
    }

    /**
     * @param ignoreSysError
     *            flag to allow ignore sys errors
     * @return node has error flag set
     */
    @Override
    public boolean isNodeHasError(boolean ignoreSysError) {
        if (ignoreSysError) {
            return nodeHasError;
        }
        return isNodeHasError();
    }

    /**
     * @return the nodeHasSysError
     */
    public boolean isNodeHasSysError() {
        return (sysError != null && !SysErrorEnum.BIDIB_ERR_NONE.equals(sysError));
    }

    /**
     * @param nodeHasError
     *            the nodeHasError to set
     */
    @Override
    public void setNodeHasError(boolean nodeHasError) {
        LOGGER.info("The node has an error: {}", nodeHasError);
        boolean oldValue = this.nodeHasError;

        this.nodeHasError = nodeHasError;
        if (!nodeHasError) {
            setErrorState(null, null);
        }

        firePropertyChange(PROPERTY_NODE_HAS_ERROR, oldValue, this.nodeHasError);
    }

    @Override
    public boolean isFlatPortModel() {
        return getNode().isPortFlatModelAvailable();
    }

    /**
     * @return the vendorCV
     */
    @Override
    public VendorCvData getVendorCV() {
        return vendorCV;
    }

    /**
     * @param vendorCV
     *            the vendorCV to set
     */
    @Override
    public void setVendorCV(VendorCvData vendorCV) {
        LOGGER.debug("Set the vendorCV: {}", vendorCV);
        this.vendorCV = vendorCV;
    }

    @Override
    public Map getConfigVariables() {
        if (configVariables == null) {
            return Collections.emptyMap();
        }

        // TODO return Collections.unmodifiableMap() ???
        return configVariables;
    }

    @Override
    public void setConfigVariables(List configurationVariables) {

        final List changedNames = new LinkedList<>();

        synchronized (configVarsLock) {

            LOGGER.info("Set the configuration variables. Clear the old configuration variables.");
            this.configVariables.clear();

            if (configurationVariables != null) {

                // create list with distinct items
                changedNames
                    .addAll(configurationVariables
                        .stream().map(cv -> cv.getName()).distinct().collect(Collectors.toList()));

                // create a map with the cvName as key and the CV as value
                this.configVariables
                    .putAll(configurationVariables
                        .stream().distinct().collect(Collectors.toMap(cv -> cv.getName(), cv -> cv)));
            }
        }

        fireConfigurationVariablesChanged(false, changedNames);
    }

    /**
     * @param configurationVars
     *            the configurationVariable values to update
     */
    @Override
    public void updateConfigVariableValues(final List configurationVars, final boolean read) {
        // 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

        LOGGER.info("Write config variables to node, number of CVs: {}, read: {}", configurationVars.size(), read);

        final List changedNames = new LinkedList<>();

        synchronized (configVarsLock) {
            for (ConfigurationVariable updatedCV : configurationVars) {

                changedNames.add(updatedCV.getName());

                ConfigurationVariable storedCV = this.configVariables.get(updatedCV.getName());

                if (storedCV != null) {
                    storedCV.setValue(updatedCV.getValue());
                    storedCV.setTimeout(updatedCV.isTimeout());
                }
                else {
                    LOGGER
                        .info("Add new CV because the updated CV was not found in the stored CV values: {}", updatedCV);

                    configVariables.put(updatedCV.getName(), updatedCV);
                }
            }

            LOGGER.info("Number of stored CV: {}", configVariables.size());
        }

        fireConfigurationVariablesChanged(read, changedNames);
    }

    private void fireConfigurationVariablesChanged(final boolean read, final List changedNames) {
        notifyConfigurationVariableValueListeners(cvDefinitionListeners, read, changedNames);
    }

    private void notifyConfigurationVariableValueListeners(
        final Collection listeners, final boolean read, final List changedNames) {
        for (CvDefinitionListener l : listeners) {
            l.cvDefinitionValuesChanged(read, changedNames);
        }
    }

    private void fireAddressMessagesEnabledChanged(Boolean isAddressMessagesEnabled) {
        for (NodeListener l : listeners) {
            l.addressMessagesEnabledChanged(this, isAddressMessagesEnabled);
        }
    }

    private void fireDccStartEnabledChanged(Boolean isDccStartEnabled) {
        for (NodeListener l : listeners) {
            l.dccStartEnabledChanged(this, isDccStartEnabled);
        }
    }

    private void fireExternalStartEnabledChanged(Boolean isExternalStartEnabled) {
        for (NodeListener l : listeners) {
            l.externalStartEnabledChanged(this, isExternalStartEnabled);
        }
    }

    private void fireFeedbackMessagesEnabledChanged(Boolean isFeedbackMessagesEnabled) {
        for (NodeListener l : listeners) {
            l.feedbackMessagesEnabledChanged(this, isFeedbackMessagesEnabled);
        }
    }

    private void fireFeedbackMirrorDisabledChanged(Boolean feedbackMirrorDisabled) {
        for (NodeListener l : listeners) {
            l.feedbackMirrorDisabledChanged(this, feedbackMirrorDisabled);
        }
    }

    private void fireIdentifyStateChanged(IdentifyState identifyState) {
        for (NodeListener l : listeners) {
            l.identifyStateChanged(this, identifyState);
        }
    }

    private void fireDetachedStateChanged(DetachedState detachedState) {
        for (NodeListener l : listeners) {
            l.detachedStateChanged(this, detachedState);
        }
    }

    private void fireKeyMessagesEnabledChanged(Boolean isKeyMessagesEnabled) {
        for (NodeListener l : listeners) {
            l.keyMessagesEnabledChanged(this, isKeyMessagesEnabled);
        }
    }

    private void fireLabelsChanged() {
        for (NodeListener l : listeners) {
            l.labelsChanged(this);
        }
    }

    @Override
    public boolean equals(Object other) {
        LOGGER.debug("equals, other: {}", other);
        if (other != null) {
            return ((Node) other).getNode().equals(node);
        }
        return false;
    }

    @Override
    public int hashCode() {
        if (node != null) {
            return node.hashCode();
        }
        else {
            LOGGER.warn("No core node available!");
        }
        return super.hashCode();
    }

    @Override
    public String toString() {
        String result = getLabel();

        if (StringUtils.isNotBlank(result)) {
            return "[" + NodeUtils.formatAddress(getAddr()) + "] " + result;
        }

        result = ByteUtils.getUniqueIdAsString(node.getUniqueId());
        return "[" + NodeUtils.formatAddress(getAddr()) + "] " + result;
    }

    @Override
    public String toNodeAddressAndUniqueString() {
        final StringBuilder nodeData = new StringBuilder();

        nodeData.append("[").append(NodeUtils.formatAddress(getAddr())).append("], ");
        nodeData.append(ByteUtils.getUniqueIdAsString(node.getUniqueId()));

        String result = getLabel();
        if (StringUtils.isNotBlank(result)) {
            nodeData.append(", ").append(result);
        }

        return nodeData.toString();
    }

    @Override
    public FeedbackPort getGlobalDetectorFeedbackPort() {
        LOGGER.debug("Get the feedback ports.");

        return feedbackPortHandler.getGlobalDetectorFeedbackPort();
    }

    @Override
    public List getFeedbackPorts() {
        LOGGER.debug("Get the feedback ports.");

        return feedbackPortHandler.getPorts(this);
    }

    @Override
    public void setFeedbackPorts(List feedbackPorts) {
        LOGGER.info("Set the feedback ports: {}", feedbackPorts);

        feedbackPortHandler.setPorts(feedbackPorts, this);
    }

    @Override
    public boolean hasFeedbackPorts() {
        LOGGER.debug("Check if feedback ports are available.");
        return feedbackPortHandler.hasPorts();
    }

    @Override
    public int getFeedbackPortCount() {
        if (getOccupancyNode() != null) {
            return getOccupancyNode().getFeedbackPortCount();
        }
        return 0;
    }

    /**
     * Set the feedback port status.
     * 
     * @param detectorNumber
     *            the detector number
     * @param status
     *            the new port status
     * @param timestamp
     *            the timestamp
     */
    @Override
    public void setFeedbackPortStatus(int detectorNumber, final FeedbackPortStatus status, final Integer timestamp) {
        LOGGER
            .debug("feedback port status, detector number: {}, status: {}, timestamp: {}", detectorNumber, status,
                timestamp);

        feedbackPortHandler.setPortStatus(detectorNumber, status, timestamp, this);
    }

    /**
     * Set the detected addresses for the feedback port.
     * 
     * @param detectorNumber
     *            the detector number
     * @param addresses
     *            the detected addresses
     */
    @Override
    public void setFeedbackPortAddresses(int detectorNumber, final List addresses) {
        LOGGER.debug("feedback port addresses, detector number: {}, addresses: {}", detectorNumber, addresses);

        feedbackPortHandler.setPortAddresses(detectorNumber, addresses, this);
    }

    /**
     * Confidence is signaled from the node.
     * 
     * @param feedbackConfidenceStatus
     *            the confidence status
     */
    @Override
    public void setFeedbackPortGroupConfidence(final FeedbackConfidenceStatus feedbackConfidenceStatus) {
        LOGGER.debug("feedback port confidence, feedbackConfidenceStatus: {}", feedbackConfidenceStatus);

        feedbackPortHandler
            .setFeedbackPortGroupConfidence(feedbackConfidenceStatus, this, (propertyName, oldValue, newValue) -> {
                LOGGER.info("Fire the property change for propertyName: {}, newValue: {}", propertyName, newValue);
                firePropertyChange(propertyName, oldValue, newValue);
            });
    }

    @Override
    public FeedbackConfidenceStatus getFeedbackPortGroupConfidence() {
        return feedbackPortHandler.getFeedbackPortGroupConfidence();
    }

    /**
     * Confidence is signaled from the node.
     * 
     * @param portNumber
     *            the port number (not the detector number)
     * @param freeze
     *            the freeze flag of the detector
     * @param noSignal
     *            the noSignal flag of the detector
     * @param invalid
     *            {@code false} the occupancy detection is detected from the track signal of the detector, {@code true}
     *            detection of occupancy is not possible
     */
    @Override
    public void setFeedbackPortConfidence(int portNumber, boolean invalid, boolean freeze, boolean noSignal) {
        LOGGER
            .debug("feedback port confidence, port number: {}, invalid: {}, freeze: {}, noSignal: {}", portNumber,
                invalid, freeze, noSignal);

        feedbackPortHandler.setFeedbackPortConfidence(portNumber, invalid, freeze, noSignal, this);
    }

    @Override
    public void setFeedbackPortSpeed(int detectorNumber, int address, int speed) {
        LOGGER
            .debug("feedback port speed, detector number: {}, address: {}, speed: {}", detectorNumber, address, speed);

        feedbackPortHandler.setFeedbackPortSpeed(detectorNumber, address, speed, this);
    }

    @Override
    public void setFeedbackPortDynStates(int detectorNumber, final List dynStates) {

        feedbackPortHandler.setFeedbackPortDynStates(detectorNumber, dynStates, this);
    }

    @Override
    public List getFlags() {
        return Collections.unmodifiableList(flags);
    }

    @Override
    public void setFlags(Collection flags) {
        // synchronize because the flags can be accessed from different threads
        synchronized (this.flags) {
            this.flags.clear();
            if (flags != null) {
                this.flags.addAll(flags);
            }
        }

        fireFlagListChanged();
    }

    /**
     * Set the macros of the current node.
     * 
     * @param macros
     *            the macros
     */
    @Override
    public void setMacros(List macros) {
        synchronized (this.macros) {
            // remove existing property change listener
            for (Macro macro : this.macros) {
                macro.removePropertyChangeListener(Macro.PROPERTY_PENDING_CHANGES, macroPendingChangesListener);
            }

            // clear references to existing macros
            this.macros.clear();
            if (macros != null) {
                // ... and add new macros
                this.macros.addAll(macros);

                // sort by macro id
                Collections.sort(this.macros, new Comparator() {

                    @Override
                    public int compare(Macro o1, Macro o2) {
                        return (o1.getId() - o2.getId());
                    }
                });

            }

            // add property change listener to new macros
            for (Macro macro : this.macros) {
                macro.addPropertyChangeListener(Macro.PROPERTY_PENDING_CHANGES, macroPendingChangesListener);
            }
        }
        fireMacroListChanged();
    }

    @Override
    public List getMacros() {
        synchronized (macros) {
            return Collections.unmodifiableList(macros);
        }
    }

    /**
     * @return node has macros
     */
    @Override
    public boolean hasMacros() {
        LOGGER.debug("Check if macros are available.");
        synchronized (macros) {
            return CollectionUtils.isNotEmpty(macros);
        }
    }

    /**
     * Check if all macros were loaded from the node.
     * 
     * @return unloaded macro detected
     */
    @Override
    public boolean hasUnloadedMacros() {
        synchronized (macros) {
            for (Macro macro : macros) {

                if (MacroSaveState.NOT_LOADED_FROM_NODE.equals(macro.getMacroSaveState())) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Replace the macro with the provided macro.
     * 
     * @param macro
     *            the new macro
     */
    @Override
    public Macro replaceMacro(final Macro macro, boolean silent) {
        LOGGER.info("Replace the macro: {}", macro);

        Macro existingMacro = null;

        synchronized (macros) {

            existingMacro = macros.get(macro.getId());
            if (existingMacro != null) {

                if (existingMacro == macro) {
                    LOGGER.info("Skip update the same instance: {}", macro);
                }
                else {

                    // replace the content of the existing macro but not the macro instance itself
                    existingMacro.clearFunctions();
                    existingMacro.clearStartConditions();

                    existingMacro.setFunctions(macro.getFunctions());
                    existingMacro.setStartConditions(macro.getStartConditions());

                    existingMacro.setCycles(macro.getCycles());
                    existingMacro.setSpeed(macro.getSpeed());

                    // keep the name of the macro
                    if (StringUtils.isNotBlank(macro.getLabel())) {
                        existingMacro.setLabel(macro.getLabel());
                    }
                }
            }
            else {
                LOGGER.warn("No existing macro found with id: {}", macro.getId());
            }
        }

        if (!silent) {
            fireMacroListChanged();
        }
        return existingMacro;
    }

    private void fireMacroListChanged() {
        LOGGER.info("The macro list has changed.");
        for (MacroListListener l : macroListListeners) {
            l.listChanged();
        }
    }

    private void fireMacroPendingChangesChanged() {
        for (MacroListListener l : macroListListeners) {
            l.pendingChangesChanged();
        }
    }

    @Override
    public void setAccessories(List accessories) {

        List oldValue = null;
        synchronized (this.accessories) {
            LOGGER.info("Set accessories: {}", accessories);

            // remove existing property change listener
            for (Accessory accessory : this.accessories) {
                accessory.removePropertyChangeListener(accessoryPendingChangesListener);
            }

            oldValue = new LinkedList<>(this.accessories);

            this.accessories.clear();
            if (accessories != null) {
                this.accessories.addAll(accessories);

                // sort by accessory id
                Collections.sort(this.accessories, new Comparator() {

                    @Override
                    public int compare(Accessory o1, Accessory o2) {
                        return (o1.getId() - o2.getId());
                    }
                });

            }

            // add property change listener to new accessory
            for (Accessory accessory : this.accessories) {
                accessory.addPropertyChangeListener(accessoryPendingChangesListener);
            }
        }

        fireAccessoryListChanged();

        firePropertyChange(PROPERTY_ACCESSORIES, oldValue, accessories);
    }

    @Override
    public List getAccessories() {
        synchronized (accessories) {
            return Collections.unmodifiableList(accessories);
        }
    }

    /**
     * @return node has accessories
     */
    @Override
    public boolean hasAccessories() {
        LOGGER.debug("Check if accessories are available.");
        synchronized (accessories) {
            return CollectionUtils.isNotEmpty(accessories);
        }
    }

    @Override
    public int getMaxAspects() {
        return maxAspectsCount;
    }

    @Override
    public void setMaxAspects(int maxAspectsCount) {
        this.maxAspectsCount = maxAspectsCount;
    }

    @Override
    public Accessory replaceAccessory(final Accessory accessory, boolean silent) {

        Accessory existingAccessory = null;
        synchronized (accessories) {

            existingAccessory = accessories.get(accessory.getId());

            LOGGER.info("Found accessory to update: {}", existingAccessory);
            if (existingAccessory != null) {

                // if (accessory.getStartupState() != null) {
                existingAccessory.setStartupState(accessory.getStartupState());
                // }
                // else {
                // LOGGER.info("Set the startup state to unchanged.");
                // existingAccessory.setStartupState(BidibLibrary.ASPECT_PARAM_UNCHANGED);
                // }

                // keep the switch time
                existingAccessory.setSwitchTime(accessory.getSwitchTime());

                // keep the time base unit
                existingAccessory.setTimeBaseUnit(accessory.getTimeBaseUnit());

                // add the aspects
                existingAccessory.setAspects(accessory.getAspects());

                // keep the name of the accessory
                if (StringUtils.isNotBlank(accessory.getLabel())) {
                    existingAccessory.setLabel(accessory.getLabel());
                }

                // sort by accessory id
                Collections.sort(this.accessories, new Comparator() {

                    @Override
                    public int compare(Accessory o1, Accessory o2) {
                        return (o1.getId() - o2.getId());
                    }
                });
            }
            else {
                LOGGER.warn("No existing accessory found with id: {}", accessory.getId());
            }
        }

        if (!silent) {
            fireAccessoryListChanged();
        }
        return existingAccessory;
    }

    private void fireAccessoryPendingChangesChanged() {
        for (AccessoryListListener l : accessoryListListeners) {
            l.pendingChangesChanged();
        }
    }

    private void fireAccessoryExecutionStateChanged() {
        for (AccessoryListListener l : accessoryListListeners) {
            l.pendingChangesChanged();
        }
    }

    private void fireAccessoryChanged(Integer accessoryId) {
        for (AccessoryListListener l : accessoryListListeners) {
            l.accessoryChanged(accessoryId);
        }
    }

    private void fireAccessoryListChanged() {
        for (AccessoryListListener l : accessoryListListeners) {
            l.listChanged();
        }
    }

    private void fireFlagListChanged() {
        notifyFlagListListeners(flagListListeners);
    }

    private void notifyFlagListListeners(final Collection listeners) {
        for (FlagListListener l : listeners) {
            l.listChanged();
        }
    }

    @Override
    public void setFeedbackPosition(
        int decoderAddress, PositionLocationEnum locationType, int locationAddress, byte[] extendedData) {

        // TODO keep the feedback positions?
        FeedbackPosition feedbackPosition =
            new FeedbackPosition(decoderAddress, locationType, locationAddress, extendedData,
                System.currentTimeMillis());
        firePropertyChange(PROPERTY_FEEDBACKPOSITIONS, null, feedbackPosition);
    }

    @Override
    public boolean isCvDefinitionAvailable() {

        return vendorCV != null;
    }

    @Override
    public  void firePortIndexedPropertyChange(
        final Port port, String propertyName, int index, Object oldValue, Object newValue) {

        super.fireIndexedPropertyChange(propertyName, index, oldValue, newValue);
    }

    @Override
    public void signalInitialLoadRegistered() {
        StatusIdentifier oldValue;
        synchronized (this.initialLoadFinishedLock) {
            oldValue = nodeLoadStatus;

            this.nodeLoadStatus = StatusIdentifier.InitialLoadRegistered;
        }

        LOGGER
            .info("The node load status is changed to registered, node: {}, address: {}, oldValue: {}", this,
                NodeUtils.formatAddress(getAddr()), oldValue);

        // TODO we should decouple the property change from the thread that changes the property value
        firePropertyChange(PROPERTY_NODE_LOAD_STATUS, oldValue, StatusIdentifier.InitialLoadRegistered);

        final NodeStatusEvent nse =
            new NodeStatusEvent(publisher.getConnectionId(), publisher.getUniqueId(),
                NodeUtils.formatAddress(getAddr()), StatusIdentifier.InitialLoadRegistered);
        LOGGER.info("Publish the nodeStatusEvent: {}", nse);
        publisher.getSubjectNodeEvents().onNext(nse);
    }

    @Override
    public void signalInitialLoadFinished() {

        StatusIdentifier oldValue;
        synchronized (this.initialLoadFinishedLock) {
            oldValue = nodeLoadStatus;

            this.nodeLoadStatus = StatusIdentifier.InitialLoadFinished;

            this.initialLoadFinishedLock.notifyAll();
        }

        LOGGER
            .info("The initial load has finished, node: {}, address: {}, oldValue: {}", this,
                NodeUtils.formatAddress(getAddr()), oldValue);

        // TODO we should decouple the property change from the thread that changes the property value
        firePropertyChange(PROPERTY_NODE_LOAD_STATUS, oldValue, StatusIdentifier.InitialLoadFinished);

        final NodeStatusEvent nse =
            new NodeStatusEvent(publisher.getConnectionId(), publisher.getUniqueId(),
                NodeUtils.formatAddress(getAddr()), StatusIdentifier.InitialLoadFinished);
        LOGGER.info("Publish the nodeStatusEvent: {}", nse);
        publisher.getSubjectNodeEvents().onNext(nse);
    }

    @Override
    public void signalInitialLoadPartiallyFinished() {

        StatusIdentifier oldValue;
        synchronized (this.initialLoadFinishedLock) {
            oldValue = nodeLoadStatus;

            this.nodeLoadStatus = StatusIdentifier.InitialLoadPartiallyFinished;

            this.initialLoadFinishedLock.notifyAll();
        }

        LOGGER
            .info("The initial load has partially finished, node: {}, address: {}, oldValue: {}", this,
                NodeUtils.formatAddress(getAddr()), oldValue);

        // TODO we should decouple the property change from the thread that changes the property value
        firePropertyChange(PROPERTY_NODE_LOAD_STATUS, oldValue, StatusIdentifier.InitialLoadPartiallyFinished);

        final NodeStatusEvent nse =
            new NodeStatusEvent(publisher.getConnectionId(), publisher.getUniqueId(),
                NodeUtils.formatAddress(getAddr()), StatusIdentifier.InitialLoadPartiallyFinished);
        LOGGER.info("Publish the nodeStatusEvent: {}", nse);
        publisher.getSubjectNodeEvents().onNext(nse);
    }

    @Override
    public void signalResetInitialLoadFinished() {
        // LOGGER.info("The initial load is reset, node: {}", this);

        StatusIdentifier oldValue;
        synchronized (this.initialLoadFinishedLock) {
            oldValue = nodeLoadStatus;

            this.nodeLoadStatus = StatusIdentifier.InitialLoadPending;

            this.initialLoadFinishedLock.notifyAll();
        }

        LOGGER.info("The initial load is reset, node: {}, oldValue: {}", this, oldValue);

        // TODO we should decouple the property change from the thread that changes the property value
        firePropertyChange(PROPERTY_NODE_LOAD_STATUS, oldValue, StatusIdentifier.InitialLoadPending);

        final NodeStatusEvent nse =
            new NodeStatusEvent(publisher.getConnectionId(), publisher.getUniqueId(),
                NodeUtils.formatAddress(getAddr()), StatusIdentifier.InitialLoadPending);
        LOGGER.info("Publish the nodeStatusEvent: {}", nse);
        publisher.getSubjectNodeEvents().onNext(nse);
    }

    @Override
    public StatusIdentifier getNodeLoadStatusIdentifier() {
        StatusIdentifier initialLoadFinished;
        synchronized (this.initialLoadFinishedLock) {
            initialLoadFinished = this.nodeLoadStatus;
        }
        LOGGER.debug("The current initialLoadFinished: {}, node: {}", initialLoadFinished, node);
        return initialLoadFinished;
    }

    @Override
    public Object getInitialLoadFinishedLock() {
        return this.initialLoadFinishedLock;
    }

    @Override
    public StoredStrings getStoredStringsNs2() {
        return storedStringsNs2;
    }

    @Override
    public void setStoredStringsNs2(StoredStrings storedStringsNs2) {
        StoredStrings oldValue = getStoredStringsNs2();

        this.storedStringsNs2 = storedStringsNs2;

        firePropertyChange(PROPERTY_STOREDSTRINGS_NS2, oldValue, this.storedStringsNs2);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy