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

org.bidib.wizard.dcca.client.controller.DccAdvController Maven / Gradle / Ivy

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

import java.beans.PropertyChangeEvent;
import java.io.File;
import java.util.BitSet;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.bidib.jbidibc.core.schema.UserDevicesListFactory;
import org.bidib.jbidibc.core.schema.decoder.userdevices.UserDevicesList;
import org.bidib.jbidibc.messages.DccAInfoIndexedString;
import org.bidib.jbidibc.messages.DccAInfoShortGui;
import org.bidib.jbidibc.messages.DccAInfoShortInfo;
import org.bidib.jbidibc.messages.DccATidData;
import org.bidib.jbidibc.messages.DecoderIdAddressData;
import org.bidib.jbidibc.messages.DecoderUniqueIdData;
import org.bidib.jbidibc.messages.enums.DccAOpCodeBm;
import org.bidib.jbidibc.messages.enums.DccAdvAcknowledge;
import org.bidib.jbidibc.messages.enums.DccAdvLogonType;
import org.bidib.jbidibc.messages.enums.DccAdvOpCodes;
import org.bidib.jbidibc.messages.enums.DccAdvOpCodesAck;
import org.bidib.jbidibc.messages.enums.DccAdvSelectInfoOpCode;
import org.bidib.jbidibc.messages.helpers.Context;
import org.bidib.jbidibc.messages.helpers.DefaultContext;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.ThreadFactoryBuilder;
import org.bidib.wizard.api.locale.Resources;
import org.bidib.wizard.api.model.CommandStationNodeInterface;
import org.bidib.wizard.api.model.NodeInterface;
import org.bidib.wizard.api.model.connection.AbstractMessageEvent;
import org.bidib.wizard.api.model.connection.BidibConnection;
import org.bidib.wizard.api.model.connection.event.CommandStationDccAdvAcknowledgeMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationDccAdvInfoFirmwareIdMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationDccAdvInfoFullNameMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationDccAdvInfoProductNameMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationDccAdvInfoShortGuiMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationDccAdvInfoShortInfoMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationDccAdvInfoShortNameMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationDccAdvLogonAssignAckMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationDccAdvLogonCollisionMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationDccAdvLogonNewDidMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationDccAdvTidMessageEvent;
import org.bidib.wizard.api.service.node.CommandStationService;
import org.bidib.wizard.client.common.rxjava2.PropertyChangeEventSource;
import org.bidib.wizard.client.common.view.DockKeys;
import org.bidib.wizard.client.common.view.DockUtils;
import org.bidib.wizard.common.model.settings.ExperimentalSettingsInterface;
import org.bidib.wizard.common.model.settings.WizardSettingsInterface;
import org.bidib.wizard.common.service.SettingsService;
import org.bidib.wizard.core.model.connection.ConnectionRegistry;
import org.bidib.wizard.core.model.connection.MessageAdapter;
import org.bidib.wizard.core.model.connection.MessageEventConsumer;
import org.bidib.wizard.core.model.settings.ExperimentalSettings;
import org.bidib.wizard.core.service.ConnectionService;
import org.bidib.wizard.dcca.client.controller.listener.DccAdvControllerListener;
import org.bidib.wizard.dcca.client.controller.listener.DccAdvDecoderActionListener;
import org.bidib.wizard.dcca.client.model.DccAdvDecoderModel;
import org.bidib.wizard.dcca.client.model.DccAdvDecoderModel.DetectionStatus;
import org.bidib.wizard.dcca.client.model.DccAdvModel;
import org.bidib.wizard.dcca.client.view.DccAdvView;
import org.bidib.wizard.dcca.client.view.listener.DccAdvViewListener;
import org.bidib.wizard.model.status.DirectionStatus;
import org.bidib.wizard.model.status.SpeedSteps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.vlsolutions.swing.docking.Dockable;
import com.vlsolutions.swing.docking.DockableState;
import com.vlsolutions.swing.docking.DockingConstants;
import com.vlsolutions.swing.docking.DockingDesktop;
import com.vlsolutions.swing.docking.DockingUtilities;
import com.vlsolutions.swing.docking.RelativeDockablePosition;
import com.vlsolutions.swing.docking.TabbedDockableContainer;

import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;

public class DccAdvController implements DccAdvControllerListener {

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

    private static final int TIMEUNIT_100MS = 100;

    private final DockingDesktop desktop;

    private final NodeInterface node;

    private DccAdvModel dccAdvModel;

    private DccAdvView dccAdvView;

    private DccAdvViewListener dccAdvViewListener;

    private DccAdvDecoderActionListener dccAdvDecoderActionListener;

    private BlockingQueue pendingDecoderQueue = new LinkedBlockingQueue<>();

    private int dccaDecoderTimeout = 5;

    private int dccaMaxAnswerRetryCount = DccAdvDecoderModel.MAX_ANSWER_RETRY_COUNT;

    private int dccaLogonAssignAcknTimeout = 5;

    private int dccaMaxLogonAssignRetryCount = 5;

    @Autowired
    private ExperimentalSettingsInterface experimentalSettings;

    @Autowired
    private WizardSettingsInterface wizardSettings;

    @Autowired
    private ConnectionService connectionService;

    @Autowired
    private CommandStationService commandStationService;

    @Autowired
    private SettingsService settingsService;

    private MessageAdapter messageAdapter;

    /**
     * the pending decoder workers are used to process the queue of the decoders that needs to be processed
     */
    private final ScheduledExecutorService pendingDecodersQueueWorkers =
        Executors
            .newScheduledThreadPool(1,
                new ThreadFactoryBuilder().setNameFormat("pendingDecodersQueueWorkers-thread-%d").build());

    /**
     * the sendQueue workers are used to process the sendQueue of the decoders and send the GET_INFO messages
     */
    private final ScheduledExecutorService sendQueueWorkers =
        Executors
            .newScheduledThreadPool(1, new ThreadFactoryBuilder().setNameFormat("sendQueueWorkers-thread-%d").build());

    private final ScheduledExecutorService logonEnableWorker =
        Executors
            .newScheduledThreadPool(1,
                new ThreadFactoryBuilder().setNameFormat("logonEnableWorkers-thread-%d").build());

    private ScheduledFuture scheduledFutureLogonEnable;

    public DccAdvController(final NodeInterface node, final DockingDesktop desktop) {
        this.desktop = desktop;
        this.node = node;
    }

    public void start() {

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

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

        dccAdvDecoderActionListener = new DccAdvDecoderActionListener() {

            @Override
            public void setSpeedAndLight(final DccAdvDecoderModel decoder, Integer speed, boolean lightsOn) {
                LOGGER
                    .info("Set the speed and lights for decoder: {}, speed: {}, lightsOn: {}", decoder, speed,
                        lightsOn);

                dccAdvViewListener.setSpeedAndLight(decoder, speed, lightsOn);
            }

            @Override
            public void assignAddress(DccAdvDecoderModel decoder) {
                LOGGER.info("Assign the address: {}", decoder);

                dccAdvViewListener.assignAddress(decoder);
            }
        };

        dccAdvModel = new DccAdvModel();
        dccAdvModel.initialize(dccAdvDecoderActionListener);

        // TODO
        File userDevicesFile = settingsService.prepareDefaultUserDevicesListFile();

        LOGGER.info("Look for userDevicesFile: {}", userDevicesFile);
        if (userDevicesFile != null) {
            final UserDevicesList userDevicesList = UserDevicesListFactory.getUserDevicesList(userDevicesFile);
            dccAdvModel.setUserDevicesList(userDevicesList);
        }

        dccAdvView = new DccAdvView(desktop, dccAdvModel, this);

        boolean powerUserMode = wizardSettings.isPowerUser();
        dccaDecoderTimeout = experimentalSettings.getDccaDecoderTimeout();
        LOGGER.info("Configured dccaDecoderTimeout: {}", dccaDecoderTimeout);

        dccaMaxAnswerRetryCount = experimentalSettings.getDccaMaxAnswerRetryCount();
        LOGGER.info("Configured dccaMaxAnswerRetryCount: {}", dccaMaxAnswerRetryCount);

        experimentalSettings.addPropertyChangeListener(ExperimentalSettings.PROPERTY_DCCA_DECODER_TIMEOUT, evt -> {
            dccaDecoderTimeout = experimentalSettings.getDccaDecoderTimeout();
            LOGGER.info("Configured dccaDecoderTimeout: {}", dccaDecoderTimeout);
        });

        experimentalSettings
            .addPropertyChangeListener(ExperimentalSettings.PROPERTY_DCCA_MAX_ANSWER_RETRY_COUNT, evt -> {
                dccaMaxAnswerRetryCount = experimentalSettings.getDccaMaxAnswerRetryCount();
                LOGGER.info("Configured dccaMaxAnswerRetryCount: {}", dccaMaxAnswerRetryCount);
            });

        dccaLogonAssignAcknTimeout = experimentalSettings.getDccaLogonAssignAcknTimeout();
        LOGGER.info("Configured dccaLogonAssignAcknTimeout: {}", dccaLogonAssignAcknTimeout);

        dccaMaxLogonAssignRetryCount = experimentalSettings.getDccaMaxLogonAssignRetryCount();
        LOGGER.info("Configured dccaMaxLogonAssignRetryCount: {}", dccaMaxLogonAssignRetryCount);

        experimentalSettings
            .addPropertyChangeListener(ExperimentalSettings.PROPERTY_DCCA_LOGONASSIGN_ACKN_TIMEOUT, evt -> {
                dccaLogonAssignAcknTimeout = experimentalSettings.getDccaLogonAssignAcknTimeout();
                LOGGER.info("Configured dccaLogonAssignAcknTimeout: {}", dccaLogonAssignAcknTimeout);
            });

        experimentalSettings
            .addPropertyChangeListener(ExperimentalSettings.PROPERTY_DCCA_MAX_LOGONASSIGN_RETRY_COUNT, evt -> {
                dccaMaxLogonAssignRetryCount = experimentalSettings.getDccaMaxLogonAssignRetryCount();
                LOGGER.info("Configured dccaMaxLogonAssignRetryCount: {}", dccaMaxLogonAssignRetryCount);
            });

        int dccaLogonEnableInterval = experimentalSettings.getDccaLogonEnableInterval();
        LOGGER.info("Configured dccaLogonEnableInterval: {} [100ms]", dccaLogonEnableInterval);
        dccAdvModel.setLogonEnableInterval(dccaLogonEnableInterval);

        experimentalSettings.addPropertyChangeListener(ExperimentalSettings.PROPERTY_DCCA_LOGONENABLE_INTERVAL, evt -> {
            int logonEnableInterval = experimentalSettings.getDccaLogonEnableInterval();
            LOGGER.info("Configured dccaLogonEnableInterval: {} [100ms]", logonEnableInterval);

            dccAdvModel.setLogonEnableInterval(logonEnableInterval);
        });

        // TODO remove force power user
        powerUserMode = true;
        LOGGER.warn("Forced power user mode.");

        dccAdvModel.setPowerUserMode(powerUserMode);

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

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

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

                    break;
                }
            }

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

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

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

        // prepare the message adapter to handle the DCCA messages
        prepareMessageAdapter();

        if (this.connectionService.isConnected(ConnectionRegistry.CONNECTION_ID_MAIN)) {
            try {
                final BidibConnection connection = this.connectionService.find(ConnectionRegistry.CONNECTION_ID_MAIN);
                if (connection != null && ConnectionRegistry.CONNECTION_ID_MAIN.equals(connection.getConnectionId())) {
                    LOGGER.info("Set the node provider to the model.");
                    this.dccAdvModel.setNodeProvider(connection.getNodeProvider());
                }
            }
            catch (Exception ex) {
                LOGGER.warn("Find the main connection failed.", ex);
            }
        }

        // prepare the view listener to handle the requests from the view
        prepareViewListener();

        dccAdvView.initialize();

        startSendQueueWorker(dccAdvViewListener);

        startPendingDecodersQueueWorker(dccAdvViewListener);
    }

    @Override
    public void viewClosed() {
        LOGGER.info("The DccAdv view was closed. Cleanup the listeners.");

        stopPendingDecodersQueueWorker();

        stopSendQueueWorker();

        // dispose the message adapter
        if (messageAdapter != null) {
            LOGGER.info("Dispose the message adapter.");
            messageAdapter.dispose();

            messageAdapter = null;
        }
    }

    private void prepareMessageAdapter() {

        this.messageAdapter = new MessageAdapter(connectionService) {
            @Override
            protected void onConnected(final BidibConnection connection) {
                super.onConnected(connection);

                if (connection != null && ConnectionRegistry.CONNECTION_ID_MAIN.equals(connection.getConnectionId())) {
                    LOGGER.info("Set the node provider to the model.");
                    DccAdvController.this.dccAdvModel.setNodeProvider(connection.getNodeProvider());
                }
                else {
                    LOGGER
                        .warn(
                            "Cannot set the node provider in the model because no connection available or the connection is not the main connection.");
                }
            }

            @Override
            protected void onDisconnect() {

                LOGGER.info("The communication was closed. Stop the decoder detection.");
                try {
                    if (dccAdvViewListener != null) {
                        dccAdvViewListener.stopDetection();
                    }
                }
                catch (Exception ex) {
                    LOGGER.warn("Stop decoder detection failed.", ex);
                }

                LOGGER.info("Remove the node provider to the model.");
                DccAdvController.this.dccAdvModel.setNodeProvider(null);

                super.onDisconnect();
            }

            @Override
            protected void prepareMessageMap(
                Map, MessageEventConsumer> messageActionMap) {
                LOGGER.info("Prepare the message map.");

                // tid
                messageActionMap.put(CommandStationDccAdvTidMessageEvent.class, (evt, node) -> {
                    CommandStationDccAdvTidMessageEvent event = (CommandStationDccAdvTidMessageEvent) evt;

                    byte[] address = event.getAddress();
                    // DccAdvOpCodesAck opCode = event.getOpCode();
                    DccATidData tidData = event.getTid();

                    LOGGER.info("CS DccAdv Tid was signaled, address: {}, tidData: {}", address, tidData);

                    SwingUtilities.invokeLater(() -> {
                        dccAdvModel.setCommandStationId(tidData.getCid());
                        dccAdvModel.setSessionId(tidData.getSid());
                        dccAdvView
                            .addLog(String
                                .format("The TID was received, CID: %08X, SID: %02X", tidData.getCid(),
                                    tidData.getSid()));
                    });
                });

                // acknowledge
                messageActionMap.put(CommandStationDccAdvAcknowledgeMessageEvent.class, (evt, node) -> {
                    CommandStationDccAdvAcknowledgeMessageEvent event =
                        (CommandStationDccAdvAcknowledgeMessageEvent) evt;

                    byte[] address = event.getAddress();
                    DccAdvOpCodesAck opCode = event.getDccAOpCode();
                    DccAdvAcknowledge ackn = event.getAcknowledge();

                    LOGGER
                        .info("CS DccAdv ackn was signaled, address: {}, opCode: {}, ackn: {}", address, opCode, ackn);
                    SwingUtilities.invokeLater(() -> {
                        dccAdvView
                            .addLog(String
                                .format("Ackn was received. OpCode: %s, Ack: %02x", opCode.name(),
                                    ByteUtils.getInt(ackn.getType())));
                    });
                });

                // logon collision
                messageActionMap.put(CommandStationDccAdvLogonCollisionMessageEvent.class, (evt, node) -> {
                    CommandStationDccAdvLogonCollisionMessageEvent event =
                        (CommandStationDccAdvLogonCollisionMessageEvent) evt;

                    byte[] address = event.getAddress();
                    int detectorNum = event.getDetectorNum();

                    LOGGER
                        .info("Feedback DccAdv logon collision was signaled, address: {}, detectorNum: {}", address,
                            detectorNum);

                    SwingUtilities.invokeLater(() -> {
                        dccAdvView
                            .addLog(String
                                .format("Received opCode: %s, Detector: %02x",
                                    DccAOpCodeBm.BIDIB_DCCA_LOGON_COLLISION.name(), detectorNum));
                    });
                });

                // logon new did
                messageActionMap.put(CommandStationDccAdvLogonNewDidMessageEvent.class, (evt, node) -> {
                    CommandStationDccAdvLogonNewDidMessageEvent event =
                        (CommandStationDccAdvLogonNewDidMessageEvent) evt;

                    byte[] address = event.getAddress();
                    int detectorNum = event.getDetectorNum();

                    final DecoderIdAddressData decoderIdAddressData = event.getDecoderIdAddressData();

                    LOGGER
                        .info(
                            "Feedback DccAdv ackn was signaled, address: {}, detectorNum: {}, decoderIdAddressData: {}",
                            address, detectorNum, decoderIdAddressData);

                    SwingUtilities.invokeLater(() -> {
                        dccAdvView
                            .addLog(String
                                .format("Received opCode: %s, Detector: %02x, did: %s",
                                    DccAOpCodeBm.BIDIB_DCCA_LOGON_NEW_DID.name(), detectorNum,
                                    decoderIdAddressData.toString()));

                    });

                    // update the decoder list, the updated decoder is returned
                    final DccAdvDecoderModel decoder = dccAdvModel.updateDecoderList(decoderIdAddressData, null);

                    // add the decoder to the list of decoders we have to process
                    queueDecoderForProcessing(decoder);
                });

                // logon assign ack
                messageActionMap.put(CommandStationDccAdvLogonAssignAckMessageEvent.class, (evt, node) -> {
                    CommandStationDccAdvLogonAssignAckMessageEvent event =
                        (CommandStationDccAdvLogonAssignAckMessageEvent) evt;

                    byte[] address = event.getAddress();
                    int detectorNum = event.getDetectorNum();

                    final DecoderIdAddressData decoderIdAddressData = event.getDecoderIdAddressData();
                    final Long assignHash = event.getAssignHash();

                    LOGGER
                        .info(
                            "Feedback DccAdv logon assign ack was signaled, address: {}, detectorNum: {}, decoderIdAddressData: {}, assignHash: {}",
                            address, detectorNum, decoderIdAddressData, assignHash);

                    SwingUtilities.invokeLater(() -> {
                        dccAdvView
                            .addLog(String
                                .format("Received opCode: %s, Detector: %02x, did: %s, Hash: 0x%08x",
                                    DccAOpCodeBm.BIDIB_DCCA_LOGON_ASSIGN_ACK.name(), detectorNum,
                                    decoderIdAddressData.toString(), assignHash));
                    });
                    dccAdvModel.updateLogonAssign(decoderIdAddressData, DetectionStatus.ASSIGNED);
                });

                // info short info
                messageActionMap.put(CommandStationDccAdvInfoShortInfoMessageEvent.class, (evt, node) -> {
                    CommandStationDccAdvInfoShortInfoMessageEvent event =
                        (CommandStationDccAdvInfoShortInfoMessageEvent) evt;

                    byte[] address = event.getAddress();
                    int detectorNum = event.getDetectorNum();

                    final DecoderIdAddressData decoderIdAddressData = event.getDecoderIdAddressData();
                    final DccAInfoShortInfo shortInfo = event.getShortInfo();

                    LOGGER
                        .info(
                            "Feedback DccAdv short info signaled, address: {}, detectorNum: {}, decoderIdAddressData: {}, shortInfo: {}",
                            address, detectorNum, decoderIdAddressData, shortInfo);

                    SwingUtilities.invokeLater(() -> {
                        dccAdvView
                            .addLog(String
                                .format("Received opCode: %s, Detector: %02x, did: %s, idx: %d, params: %s",
                                    DccAOpCodeBm.BIDIB_DCCA_INFO_SHORTINFO.name(), detectorNum,
                                    decoderIdAddressData.toString(), shortInfo.getIndex(),
                                    ByteUtils.bytesToHex(shortInfo.getData())));
                    });
                    // update the short info in the decoder table
                    boolean foundDecoder = dccAdvModel.updateDecoderShortInfo(decoderIdAddressData, shortInfo);
                    if (!foundDecoder) {
                        SwingUtilities.invokeLater(() -> {
                            dccAdvView
                                .addLog(String
                                    .format(
                                        "No matching decoder found to update the shortInfo, did: %s, idx: %d, shortName: %s",
                                        DccAOpCodeBm.BIDIB_DCCA_INFO_SHORTINFO.name(), detectorNum,
                                        decoderIdAddressData.toString(), shortInfo.getIndex(),
                                        ByteUtils.bytesToHex(shortInfo.getData())));
                        });
                    }
                });

                // info short gui
                messageActionMap.put(CommandStationDccAdvInfoShortGuiMessageEvent.class, (evt, node) -> {
                    CommandStationDccAdvInfoShortGuiMessageEvent event =
                        (CommandStationDccAdvInfoShortGuiMessageEvent) evt;

                    byte[] address = event.getAddress();
                    int detectorNum = event.getDetectorNum();

                    final DecoderIdAddressData decoderIdAddressData = event.getDecoderIdAddressData();
                    final DccAInfoShortGui shortGui = event.getShortGui();

                    LOGGER
                        .info(
                            "Feedback DccAdv short gui signaled, address: {}, detectorNum: {}, decoderIdAddressData: {}, shortGui: {}",
                            address, detectorNum, decoderIdAddressData, shortGui);

                    SwingUtilities.invokeLater(() -> {
                        dccAdvView
                            .addLog(String
                                .format("Received opCode: %s, Detector: %02x, did: %s, idx: %d, shortGui: %s",
                                    DccAOpCodeBm.BIDIB_DCCA_INFO_SHORTGUI.name(), detectorNum,
                                    decoderIdAddressData.toString(), shortGui.getIndex(),
                                    ByteUtils.bytesToHex(shortGui.getData())));
                    });
                    // update the short info in the decoder table
                    boolean foundDecoder = dccAdvModel.updateDecoderShortGui(decoderIdAddressData, shortGui);
                    if (!foundDecoder) {
                        SwingUtilities.invokeLater(() -> {
                            dccAdvView
                                .addLog(String
                                    .format(
                                        "No matching decoder found to update the shortGui, did: %s, idx: %d, shortGui: %s",
                                        DccAOpCodeBm.BIDIB_DCCA_INFO_SHORTGUI.name(), detectorNum,
                                        decoderIdAddressData.toString(), shortGui.getIndex(),
                                        ByteUtils.bytesToHex(shortGui.getData())));
                        });
                    }
                });

                // info firmware id
                messageActionMap.put(CommandStationDccAdvInfoFirmwareIdMessageEvent.class, (evt, node) -> {
                    CommandStationDccAdvInfoFirmwareIdMessageEvent event =
                        (CommandStationDccAdvInfoFirmwareIdMessageEvent) evt;

                    byte[] address = event.getAddress();
                    int detectorNum = event.getDetectorNum();

                    final DecoderIdAddressData decoderIdAddressData = event.getDecoderIdAddressData();
                    final DccAInfoIndexedString firmwareId = event.getFirmwareId();

                    LOGGER
                        .info(
                            "Feedback DccAdv info signaled, address: {}, detectorNum: {}, decoderIdAddressData: {}, firmwareId: {}",
                            address, detectorNum, decoderIdAddressData, firmwareId);

                    SwingUtilities.invokeLater(() -> {
                        dccAdvView
                            .addLog(String
                                .format("Received opCode: %s, Detector: %02x, did: %s, idx: %d, firmwareId: %s",
                                    DccAOpCodeBm.BIDIB_DCCA_INFO_FIRMWAREID.name(), detectorNum,
                                    decoderIdAddressData.toString(), firmwareId.getIndex(), firmwareId.getData()));
                    });
                    // update the firmwareId in the decoder table
                    boolean foundDecoder = dccAdvModel.updateDecoderFirmwareId(decoderIdAddressData, firmwareId);
                    if (!foundDecoder) {
                        SwingUtilities.invokeLater(() -> {
                            dccAdvView
                                .addLog(String
                                    .format(
                                        "No matching decoder found to update the firmwareId, did: %s, idx: %d, firmwareId: %s",
                                        DccAOpCodeBm.BIDIB_DCCA_INFO_FIRMWAREID.name(), detectorNum,
                                        decoderIdAddressData.toString(), firmwareId.getIndex(), firmwareId.getData()));
                        });
                    }
                });

                // info product name
                messageActionMap.put(CommandStationDccAdvInfoProductNameMessageEvent.class, (evt, node) -> {
                    CommandStationDccAdvInfoProductNameMessageEvent event =
                        (CommandStationDccAdvInfoProductNameMessageEvent) evt;

                    byte[] address = event.getAddress();
                    int detectorNum = event.getDetectorNum();

                    final DecoderIdAddressData decoderIdAddressData = event.getDecoderIdAddressData();
                    final DccAInfoIndexedString productName = event.getProductName();

                    LOGGER
                        .info(
                            "Feedback DccAdv info signaled, address: {}, detectorNum: {}, decoderIdAddressData: {}, productName: {}",
                            address, detectorNum, decoderIdAddressData, productName);

                    SwingUtilities.invokeLater(() -> {
                        dccAdvView
                            .addLog(String
                                .format("Received opCode: %s, Detector: %02x, did: %s, idx: %d, productName: %s",
                                    DccAOpCodeBm.BIDIB_DCCA_INFO_PRODUCTNAME.name(), detectorNum,
                                    decoderIdAddressData.toString(), productName.getIndex(), productName.getData()));
                    });
                    // update the product name in the decoder table
                    boolean foundDecoder = dccAdvModel.updateDecoderProductName(decoderIdAddressData, productName);
                    if (!foundDecoder) {
                        SwingUtilities.invokeLater(() -> {
                            dccAdvView
                                .addLog(String
                                    .format(
                                        "No matching decoder found to update the productName, did: %s, idx: %d, fullName: %s",
                                        DccAOpCodeBm.BIDIB_DCCA_INFO_PRODUCTNAME.name(), detectorNum,
                                        decoderIdAddressData.toString(), productName.getIndex(),
                                        productName.getData()));
                        });
                    }
                });

                // info short name
                messageActionMap.put(CommandStationDccAdvInfoShortNameMessageEvent.class, (evt, node) -> {
                    CommandStationDccAdvInfoShortNameMessageEvent event =
                        (CommandStationDccAdvInfoShortNameMessageEvent) evt;

                    byte[] address = event.getAddress();
                    int detectorNum = event.getDetectorNum();

                    final DecoderIdAddressData decoderIdAddressData = event.getDecoderIdAddressData();
                    final DccAInfoIndexedString shortName = event.getShortName();

                    LOGGER
                        .info(
                            "Feedback DccAdv info signaled, address: {}, detectorNum: {}, decoderIdAddressData: {}, shortName: {}",
                            address, detectorNum, decoderIdAddressData, shortName);

                    SwingUtilities.invokeLater(() -> {
                        dccAdvView
                            .addLog(String
                                .format("Received opCode: %s, Detector: %02x, did: %s, idx: %d, shortName: %s",
                                    DccAOpCodeBm.BIDIB_DCCA_INFO_SHORTNAME.name(), detectorNum,
                                    decoderIdAddressData.toString(), shortName.getIndex(), shortName.getData()));
                    });
                    // update the short name in the decoder table
                    boolean foundDecoder = dccAdvModel.updateDecoderShortName(decoderIdAddressData, shortName);
                    if (!foundDecoder) {
                        SwingUtilities.invokeLater(() -> {
                            dccAdvView
                                .addLog(String
                                    .format(
                                        "No matching decoder found to update the shortName, did: %s, idx: %d, shortName: %s",
                                        DccAOpCodeBm.BIDIB_DCCA_INFO_SHORTNAME.name(), detectorNum,
                                        decoderIdAddressData.toString(), shortName.getIndex(), shortName.getData()));
                        });
                    }
                });

                // info full name
                messageActionMap.put(CommandStationDccAdvInfoFullNameMessageEvent.class, (evt, node) -> {
                    CommandStationDccAdvInfoFullNameMessageEvent event =
                        (CommandStationDccAdvInfoFullNameMessageEvent) evt;

                    byte[] address = event.getAddress();
                    int detectorNum = event.getDetectorNum();

                    final DecoderIdAddressData decoderIdAddressData = event.getDecoderIdAddressData();
                    final DccAInfoIndexedString fullName = event.getFullName();

                    LOGGER
                        .info(
                            "Feedback DccAdv info signaled, address: {}, detectorNum: {}, decoderIdAddressData: {}, fullName: {}",
                            address, detectorNum, decoderIdAddressData, fullName);

                    SwingUtilities.invokeLater(() -> {
                        dccAdvView
                            .addLog(String
                                .format("Received opCode: %s, Detector: %02x, did: %s, idx: %d, fullName: %s",
                                    DccAOpCodeBm.BIDIB_DCCA_INFO_FULLNAME.name(), detectorNum,
                                    decoderIdAddressData.toString(), fullName.getIndex(), fullName.getData()));
                    });
                    // update the short name in the decoder table
                    boolean foundDecoder = dccAdvModel.updateDecoderFullName(decoderIdAddressData, fullName);
                    if (!foundDecoder) {
                        SwingUtilities.invokeLater(() -> {
                            dccAdvView
                                .addLog(String
                                    .format(
                                        "No matching decoder found to update the fullName, did: %s, idx: %d, fullName: %s",
                                        DccAOpCodeBm.BIDIB_DCCA_INFO_FULLNAME.name(), detectorNum,
                                        decoderIdAddressData.toString(), fullName.getIndex(), fullName.getData()));
                        });
                    }
                });

            }

        };
        this.messageAdapter.setNode(node);

        // start the message adapter to subscribe to messages subject
        this.messageAdapter.start();
    }

    /**
     * Prepare the view listener.
     */
    private void prepareViewListener() {

        dccAdvViewListener = new DccAdvViewListener() {

            private CommandStationNodeInterface getCommandStationNode() {

                if (dccAdvModel.getNodeProvider() == null) {
                    LOGGER.warn("No nodeProvider available.");
                    return null;
                }

                // search the command station node
                Collection nodes = dccAdvModel.getNodeProvider().getNodes();
                CommandStationNodeInterface commandStationNode = null;
                for (NodeInterface node : nodes) {
                    if (node.getCommandStationNode() != null) {
                        commandStationNode = node.getCommandStationNode();
                        LOGGER.info("Found a command station node: {}", commandStationNode);

                        break;
                    }
                }
                return commandStationNode;
            }

            @Override
            public boolean isFeatureDccAdvAvailable() {
                CommandStationNodeInterface commandStationNode = getCommandStationNode();
                if (commandStationNode == null) {
                    LOGGER.warn("No command station node available! Operation aborted!");

                    // show dialog
                    JOptionPane
                        .showMessageDialog(null,
                            Resources.getString(DccAdvController.class, "no_commandstation_available"));
                    return false;
                }

                Boolean featureDccAdvAvailable = commandStationNode.isDccAdvAvailable();
                if (featureDccAdvAvailable != null && featureDccAdvAvailable.booleanValue()) {
                    LOGGER.info("The feature FEATURE_GEN_DCCADV_AVAILABLE is available.");
                    return true;
                }

                LOGGER.warn("The feature FEATURE_GEN_DCCADV_AVAILABLE is not available.");
                return false;
            }

            @Override
            public void readCommandStationId() {
                LOGGER.info("Query the TID.");

                CommandStationNodeInterface commandStationNode = getCommandStationNode();
                if (commandStationNode == null) {
                    LOGGER.warn("No command station node available! Operation aborted!");

                    // show dialog
                    JOptionPane
                        .showMessageDialog(null,
                            Resources.getString(DccAdvController.class, "no_commandstation_available"));
                    return;
                }

                dccAdvView.addLog("Query the commandStation id and session id.");

                commandStationService.queryDccAdvTid(ConnectionRegistry.CONNECTION_ID_MAIN, commandStationNode);
            }

            @Override
            public void updateSessionId() {
                LOGGER.info("Update the session ID.");

                CommandStationNodeInterface commandStationNode = getCommandStationNode();
                if (commandStationNode == null) {
                    LOGGER.warn("No command station node available! Operation aborted!");

                    // show dialog
                    JOptionPane
                        .showMessageDialog(null,
                            Resources.getString(DccAdvController.class, "no_commandstation_available"));
                    return;
                }

                if (dccAdvModel.getCommandStationId() == null) {
                    LOGGER.warn("No command station uniqueId available! Operation aborted!");

                    // show dialog
                    JOptionPane
                        .showMessageDialog(null,
                            Resources.getString(DccAdvController.class, "no_commandstation_uniqueid_available"));
                    return;
                }

                // clear the current session ID
                Integer sessionId = dccAdvModel.getSessionId();
                if (sessionId != null) {
                    sessionId++;
                }
                else {
                    sessionId = 1;
                }

                dccAdvView.addLog("Update the session id. New session id: " + sessionId);

                DccATidData tid = new DccATidData(dccAdvModel.getCommandStationId(), sessionId);

                commandStationService.setDccAdvTid(ConnectionRegistry.CONNECTION_ID_MAIN, commandStationNode, tid);
            }

            @Override
            public void startDetection() {
                LOGGER.info("Start detection of DCCA decoders.");

                CommandStationNodeInterface commandStationNode = getCommandStationNode();
                if (commandStationNode == null) {
                    LOGGER.warn("No command station node available! Operation aborted!");

                    // show dialog
                    JOptionPane
                        .showMessageDialog(null,
                            Resources.getString(DccAdvController.class, "no_commandstation_available"));
                    return;
                }

                boolean powerUserMode = true; // Preferences.getInstance().isPowerUser();
                dccAdvModel.setPowerUserMode(powerUserMode);

                if (!dccAdvModel.isPowerUserMode()) {
                    dccAdvView.addLog("Start detection of DCCA decoders with endless repetitions every 500ms.");
                    DccAdvLogonType type = DccAdvLogonType.DCCA_ALL;
                    int count = 0; // endless
                    int interval = 5; // every 500ms

                    commandStationService
                        .sendDccAdvLogonEnable(ConnectionRegistry.CONNECTION_ID_MAIN, commandStationNode, type, count,
                            interval);
                }
                else {
                    DccAdvLogonType type = dccAdvModel.getDccAdvLogonType();
                    // int count = dccAdvModel.getRepetitions();
                    // int interval = dccAdvModel.getInterval();

                    int count = 1;
                    int interval = 0;

                    dccAdvView
                        .addLog("Start detection of DCCA decoders with logon type: " + type + ", count: " + count
                            + ", interval: " + interval);

                    commandStationService
                        .sendDccAdvLogonEnable(ConnectionRegistry.CONNECTION_ID_MAIN, commandStationNode, type, count,
                            interval);
                }

                dccAdvModel.setSendLogonEnableActive(true);

                triggerLogonEnable(100);
            }

            @Override
            public void stopDetection() {
                LOGGER.info("Stop the detection.");

                cancelScheduledFutureLogonEnable();
            }

            @Override
            public void sendLogonEnable() {
                LOGGER.info("Start detection of DCCA decoders.");

                CommandStationNodeInterface commandStationNode = getCommandStationNode();
                if (commandStationNode == null) {
                    LOGGER.warn("No command station node available! Operation aborted!");
                    throw new RuntimeException(
                        "Send LogonEnable is aborted because no command station node available!");
                }

                DccAdvLogonType type = dccAdvModel.getDccAdvLogonType();
                // int count = dccAdvModel.getRepetitions();
                // int interval = dccAdvModel.getInterval();

                int count = 1;
                int interval = 0;

                dccAdvView
                    .addLog("Start detection of DCCA decoders with logon type: " + type + ", count: " + count
                        + ", interval: " + interval);

                commandStationService
                    .sendDccAdvLogonEnable(ConnectionRegistry.CONNECTION_ID_MAIN, commandStationNode, type, count,
                        interval);
            }

            @Override
            public void assignAddress(final DccAdvDecoderModel dccAdvDecoder) {
                CommandStationNodeInterface commandStationNode = getCommandStationNode();
                if (commandStationNode == null) {
                    LOGGER.warn("No command station node available! Operation aborted!");

                    // show dialog
                    JOptionPane
                        .showMessageDialog(null,
                            Resources.getString(DccAdvController.class, "no_commandstation_available"));
                    return;
                }

                LOGGER
                    .info(
                        "Assign the new address, dccAdvDecoder: {}, dccaMaxAssignRetryCount: {}, dccaLogonAssignAcknTimeout: {} ms",
                        dccAdvDecoder, dccaMaxLogonAssignRetryCount, dccaLogonAssignAcknTimeout * TIMEUNIT_100MS);

                DecoderIdAddressData decoderUniqueId = dccAdvDecoder.getDecoderUniqueId();
                Integer manufacturerId = dccAdvDecoder.getManufacturerId();
                DecoderUniqueIdData did = new DecoderUniqueIdData(decoderUniqueId.getDid(), manufacturerId);
                int address = dccAdvDecoder.getDecoderAddress();

                dccAdvView
                    .addLog(String
                        .format("Send opCode: %s, did: %s, address: %d", DccAdvOpCodes.BIDIB_DCCA_LOGON_ASSIGN.name(),
                            DecoderUniqueIdData.getFormattedDecoderUniqueId(did), address));

                // we must control if the acknowledge is received

                final CompositeDisposable compositeDisposable = new CompositeDisposable();

                Observable source = Observable.just(dccAdvDecoder).flatMap(dec -> {

                    Integer manId = dec.getManufacturerId();
                    DecoderUniqueIdData didData = new DecoderUniqueIdData(decoderUniqueId.getDid(), manId);
                    int decoderAddress = dec.getDecoderAddress();

                    LOGGER.info("Send the logon assign, did: {}, address: {}", didData, decoderAddress);

                    commandStationService
                        .sendDccAdvLogonAssign(ConnectionRegistry.CONNECTION_ID_MAIN, commandStationNode, didData,
                            decoderAddress);

                    LOGGER.info("Prepare to wait for property change event.");
                    return PropertyChangeEventSource
                        .fromPropertyChangeEventsOf(dccAdvDecoder, DccAdvDecoderModel.PROPERTY_DETECTIONSTATUS)
                        .timeout(dccaLogonAssignAcknTimeout * TIMEUNIT_100MS, TimeUnit.MILLISECONDS);

                }).doOnError((err) -> {

                    if (err instanceof TimeoutException) {
                        LOGGER
                            .info(
                                "Timeout exception detected while wait for property change event, increment the assignRetryCounter: {}",
                                err.getMessage());
                        dccAdvDecoder.incAssignRetryCounter();
                    }
                    else {
                        LOGGER
                            .warn(
                                "Error detected while wait for property change event, increment the assignRetryCounter.",
                                err);
                        dccAdvDecoder.incAssignRetryCounter();
                    }
                });

                Disposable combinedSubscription = source.retryUntil(() -> {
                    return (dccAdvDecoder.getDetectionStatus() == DetectionStatus.ASSIGNED)
                        || (dccAdvDecoder.getAssignRetryCounter() > dccaMaxLogonAssignRetryCount);
                }).subscribe(decoder -> {
                    LOGGER.info("The decoder was assigned, decoder: {}", decoder);

                    compositeDisposable.dispose();
                }, err -> {
                    if (err instanceof TimeoutException) {
                        LOGGER.info("A timeout exception occured during logon assign: {}", err.getMessage());
                    }
                    else {
                        LOGGER.warn("An error occured during logon assign.", err);
                    }
                }, () -> {
                    LOGGER
                        .info("Completed, dccAdvDecoder: {}, assignRetryCounter: {}", dccAdvDecoder,
                            dccAdvDecoder.getAssignRetryCounter());
                    compositeDisposable.dispose();
                });

                compositeDisposable.add(combinedSubscription);
            }

            @Override
            public void sendSelectInfoRequest(
                final DccAdvDecoderModel dccAdvDecoder, final DccAdvOpCodes opCode,
                final DccAdvSelectInfoOpCode selectInfoOpCode) {
                CommandStationNodeInterface commandStationNode = getCommandStationNode();
                if (commandStationNode == null) {
                    LOGGER.warn("No command station node available! Operation aborted!");
                    return;
                }

                LOGGER
                    .info("Send the select info, dccAdvDecoder: {}, opCode: {}, getInfoOpCode: {}", dccAdvDecoder,
                        opCode, selectInfoOpCode);

                DecoderIdAddressData decoderUniqueId = dccAdvDecoder.getDecoderUniqueId();
                Integer manufacturerId = dccAdvDecoder.getManufacturerId();
                DecoderUniqueIdData didData = new DecoderUniqueIdData(decoderUniqueId.getDid(), manufacturerId);

                int getDataRepetitions = 1;
                switch (selectInfoOpCode) {
                    case BIDIB_DCCA_SPACE_PRODUCTNAME:
                    case BIDIB_DCCA_SPACE_SHORTNAME:
                    case BIDIB_DCCA_SPACE_FIRMWARE_ID:
                        getDataRepetitions = 2;
                        break;
                    case BIDIB_DCCA_SPACE_FULLNAME:
                        getDataRepetitions = 7;
                        break;
                    case BIDIB_DCCA_SPACE_RAILCOMPAGE:
                        getDataRepetitions = 51;
                        break;
                    default:
                        break;
                }

                dccAdvView
                    .addLog(String
                        .format("Send the select info, did: %s, opCode: %s, info: %s, getDataRepetitions: %d",
                            DecoderUniqueIdData.getFormattedDecoderUniqueId(didData), opCode.name(),
                            selectInfoOpCode.name(), getDataRepetitions));

                commandStationService
                    .sendDccAdvSelectInfo(ConnectionRegistry.CONNECTION_ID_MAIN, commandStationNode, opCode, didData,
                        selectInfoOpCode, getDataRepetitions);
            }

            @Override
            public void sendGetDataRequest(DccAdvDecoderModel dccAdvDecoder, DccAdvOpCodes opCode, int repetitions) {

                CommandStationNodeInterface commandStationNode = getCommandStationNode();
                if (commandStationNode == null) {
                    LOGGER.warn("No command station node available! Operation aborted!");
                    return;
                }

                LOGGER
                    .info("Send the get data message, dccAdvDecoder: {}, opCode: {}, repetitions: {}", dccAdvDecoder,
                        opCode, repetitions);

                dccAdvView
                    .addLog(
                        String.format("Send the get data, opCode: %s, repetitions: %d", opCode.name(), repetitions));

                commandStationService
                    .sendDccAdvGetData(ConnectionRegistry.CONNECTION_ID_MAIN, commandStationNode, opCode, repetitions);
            }

            @Override
            public void setSpeedAndLight(DccAdvDecoderModel decoder, Integer speed, boolean lightsOn) {
                CommandStationNodeInterface commandStationNode = getCommandStationNode();
                if (commandStationNode == null) {
                    LOGGER.warn("No command station node available! Operation aborted!");
                    return;
                }

                if (decoder.getDecoderAddress() == null) {
                    LOGGER.warn("No address available.");
                    return;
                }

                LOGGER
                    .info("Set speed and switch lights, speed: {}, lightsOn: {}, decoder: {}", speed, lightsOn,
                        decoder);

                dccAdvView.addLog(String.format("Switch lights on, decoder: %s", decoder.toString()));

                int functionGroupIndex = 0;
                BitSet activeFunctions = new BitSet(functionGroupIndex + 1);
                activeFunctions.set(functionGroupIndex, true);

                BitSet functions = new BitSet(29);
                functions.set(0, lightsOn ? 1 : 0);

                // create the context
                final Context context = new DefaultContext();

                // TODO make the speed steps configurable

                commandStationService
                    .setSpeed(ConnectionRegistry.CONNECTION_ID_MAIN, commandStationNode, decoder.getDecoderAddress(),
                        SpeedSteps.DCC128, speed, DirectionStatus.FORWARD, activeFunctions, functions, context);
            }

        };
        dccAdvView.addDccAdvViewListener(dccAdvViewListener);

    }

    private BlockingQueue sendQueue = new LinkedBlockingQueue<>();

    private AtomicBoolean sendGetInfoRequests = new AtomicBoolean();

    private abstract class GetInfoRequestContainer {
        private final DccAdvDecoderModel dccAdvDecoder;

        private final DccAdvOpCodes opCode;

        public GetInfoRequestContainer(final DccAdvDecoderModel dccAdvDecoder, final DccAdvOpCodes opCode) {
            this.dccAdvDecoder = dccAdvDecoder;
            this.opCode = opCode;
        }

        /**
         * @return the dccAdvDecoder
         */
        public DccAdvDecoderModel getDccAdvDecoder() {
            return dccAdvDecoder;
        }

        /**
         * @return the opCode
         */
        public DccAdvOpCodes getOpCode() {
            return opCode;
        }
    }

    private class GetInfoInfoRequestContainer extends GetInfoRequestContainer {

        private final DccAdvSelectInfoOpCode getInfoOpCode;

        public GetInfoInfoRequestContainer(final DccAdvDecoderModel dccAdvDecoder, final DccAdvOpCodes opCode,
            final DccAdvSelectInfoOpCode getInfoOpCode) {
            super(dccAdvDecoder, opCode);
            this.getInfoOpCode = getInfoOpCode;
        }

        public DccAdvSelectInfoOpCode getInfoOpCode() {
            return getInfoOpCode;
        }

        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
        }
    }

    /**
     * Add a new request to the send queue.
     * 
     * @param dccAdvDecoder
     *            the decoder model
     * @param opCode
     *            the opCode to use
     * @param getInfoOpCode
     *            the info opCode to use
     */
    private void queueGetInfoRequests(
        final DccAdvDecoderModel dccAdvDecoder, final DccAdvOpCodes opCode,
        final DccAdvSelectInfoOpCode getInfoOpCode) {
        GetInfoRequestContainer container = new GetInfoInfoRequestContainer(dccAdvDecoder, opCode, getInfoOpCode);
        LOGGER.info("Queue the getInfoInfoRequest container: {}", container);

        sendQueue.offer(container);

        LOGGER.info("Queued the getInfoRequest container: {}", container);
    }

    private void startSendQueueWorker(final DccAdvViewListener dccAdvViewListener) {
        LOGGER.info("Start the send queue worker for GET_INFO requests.");

        sendGetInfoRequests.set(true);

        // add a task to the worker to let the node process the send queue
        sendQueueWorkers.submit(new Runnable() {

            @Override
            public void run() {
                try {
                    LOGGER.info("Start processing the send queue for GET_INFO requests.");

                    GetInfoRequestContainer request = null;
                    while (sendGetInfoRequests.get()) {
                        request = sendQueue.take();
                        LOGGER.info("Fetched the getInfoRequest container: {}", request);

                        if (request instanceof GetInfoInfoRequestContainer) {
                            dccAdvViewListener
                                .sendSelectInfoRequest(request.getDccAdvDecoder(), request.getOpCode(),
                                    ((GetInfoInfoRequestContainer) request).getInfoOpCode());
                        }
                    }

                }
                catch (InterruptedException ex) {
                    LOGGER.info("Process the send queue for GET_INFO requests has terminated.");
                }
                catch (Exception ex) {
                    LOGGER.warn("Process the send queue for GET_INFO requests failed.", ex);
                }
            }
        });
    }

    private void stopSendQueueWorker() {
        LOGGER.info("Stop the sendQueueWorkers.");

        sendGetInfoRequests.set(false);

        if (!sendQueueWorkers.isShutdown()) {
            sendQueueWorkers.shutdownNow();

            try {
                sendQueueWorkers.awaitTermination(1000, TimeUnit.MILLISECONDS);
            }
            catch (InterruptedException ex) {
                LOGGER.warn("Wait for termination of sendQueueWorkers was interrupted.", ex);
            }
        }
        else {
            LOGGER.info("SendQueueWorkers is already shutdown.");
        }
    }

    private void queueDecoderForProcessing(final DccAdvDecoderModel decoder) {
        // LOGGER.info("Queue the decoder for processing: {}", decoder);

        synchronized (pendingDecoderQueue) {
            if (DetectionStatus.NEW_DID != decoder.getDetectionStatus() || pendingDecoderQueue.contains(decoder)) {
                LOGGER.info("The decoder is already in the queue to be processed or is processed: {}", decoder);
            }
            else {
                cancelScheduledFutureLogonEnable();

                decoder.setMaxAnswerRetryCount(dccaMaxAnswerRetryCount);

                LOGGER
                    .info("Add the decoder for processing to the pending queue: {}, dccaMaxAnswerRetryCount: {}",
                        decoder, dccaMaxAnswerRetryCount);
                pendingDecoderQueue.add(decoder);

            }
        }

    }

    private void cancelScheduledFutureLogonEnable() {

        synchronized (scheduleLogonEnableLock) {
            if (scheduledFutureLogonEnable != null) {
                LOGGER.info("Try to cancel the logon enable task.");
                try {
                    scheduledFutureLogonEnable.cancel(true);

                    scheduledFutureLogonEnable = null;
                }
                catch (Exception ex) {
                    LOGGER.warn("Cancel the logon enable task failed.", ex);
                }
            }
        }
    }

    private AtomicBoolean processPendingDecoders = new AtomicBoolean();

    private void startPendingDecodersQueueWorker(final DccAdvViewListener dccAdvViewListener) {
        LOGGER.info("Start the pendingDecoders queue worker for GET_INFO requests.");

        processPendingDecoders.set(true);

        // add a task to the worker to let the node process the send queue
        pendingDecodersQueueWorkers.submit(() -> {
            try {
                LOGGER.info("Start processing the pending decoders queue.");

                DccAdvDecoderModel decoder = null;
                while (processPendingDecoders.get()) {

                    // if the queue is empty we must trigger the next LOGON
                    if (pendingDecoderQueue.isEmpty()) {
                        LOGGER.info("The queue is empty, trigger the LOGON_ENABLE.");
                        triggerLogonEnable(null);
                    }

                    decoder = pendingDecoderQueue.take();

                    LOGGER.info("Fetched the decoder from queue: {}", decoder);

                    processCurrentDecoder(decoder);
                }
            }
            catch (InterruptedException ex) {
                LOGGER.info("Process the pending decoders queue has terminated.");
            }
            catch (Exception ex) {
                LOGGER.warn("Process the pending decoders queue failed.", ex);
            }
        });
    }

    private void stopPendingDecodersQueueWorker() {
        LOGGER.info("Stop the pendingDecodersQueueWorkers.");

        processPendingDecoders.set(false);

        if (!pendingDecodersQueueWorkers.isShutdown()) {
            pendingDecodersQueueWorkers.shutdownNow();

            try {
                pendingDecodersQueueWorkers.awaitTermination(1000, TimeUnit.MILLISECONDS);
            }
            catch (InterruptedException ex) {
                LOGGER.warn("Wait for termination of pendingDecodersQueueWorkers was interrupted.", ex);
            }
        }
        else {
            LOGGER.info("pendingDecodersQueueWorkers is already shutdown.");
        }
    }

    /**
     * Process fetching the decoder data for the current decoder. If the data is not delivered completely we have to
     * refetch the missing data.
     * 
     * @param decoder
     *            the decoder
     */
    private void processCurrentDecoder(final DccAdvDecoderModel decoder) {
        LOGGER.info("Process the current decoder: {}", decoder);

        synchronized (decoder) {
            decoder.setDetectionStatus(DetectionStatus.GET_INFO);
        }

        do {
            // wait for answers
            try {

                LOGGER.info("Process decoder: {}", decoder);
                synchronized (decoder) {

                    // check which opCode is not complete
                    DccAOpCodeBm[] incompleteOpCodes = decoder.checkIncompleteOpCodes();
                    LOGGER.info("Checked for incomplete opCodes: {}", new Object[] { incompleteOpCodes });
                    for (DccAOpCodeBm incompleteOpCode : incompleteOpCodes) {
                        // trigger the fetch of the data again
                        DccAdvSelectInfoOpCode dccAdvSelectInfoOpCode = null;
                        switch (incompleteOpCode) {
                            case BIDIB_DCCA_INFO_FIRMWAREID:
                                dccAdvSelectInfoOpCode = DccAdvSelectInfoOpCode.BIDIB_DCCA_SPACE_FIRMWARE_ID;
                                break;
                            case BIDIB_DCCA_INFO_PRODUCTNAME:
                                dccAdvSelectInfoOpCode = DccAdvSelectInfoOpCode.BIDIB_DCCA_SPACE_PRODUCTNAME;
                                break;
                            case BIDIB_DCCA_INFO_SHORTNAME:
                                dccAdvSelectInfoOpCode = DccAdvSelectInfoOpCode.BIDIB_DCCA_SPACE_SHORTNAME;
                                break;
                            case BIDIB_DCCA_INFO_FULLNAME:
                                dccAdvSelectInfoOpCode = DccAdvSelectInfoOpCode.BIDIB_DCCA_SPACE_FULLNAME;
                                break;
                            case BIDIB_DCCA_INFO_SHORTINFO:
                                dccAdvSelectInfoOpCode = DccAdvSelectInfoOpCode.BIDIB_DCCA_SPACE_SHORTINFO;
                                break;
                            case BIDIB_DCCA_INFO_SHORTGUI:
                                dccAdvSelectInfoOpCode = DccAdvSelectInfoOpCode.BIDIB_DCCA_SPACE_SHORTGUI;
                                break;
                            default:
                                break;
                        }
                        if (dccAdvSelectInfoOpCode != null) {
                            LOGGER.info("Must retrigger query dccAdvSelectInfoOpCode: {}", dccAdvSelectInfoOpCode);
                            queueGetInfoRequests(decoder, DccAdvOpCodes.BIDIB_DCCA_SELECT_INFO, dccAdvSelectInfoOpCode);
                        }

                        decoder.setLastRequestedInfoOpCode(dccAdvSelectInfoOpCode);
                    }

                    if (incompleteOpCodes.length == 0) {
                        LOGGER
                            .info("All data received for decoder: {}, answerRetryCount: {}", decoder,
                                decoder.getCurrentAnswerRetryCount());
                        break;
                    }

                }
                synchronized (decoder.getCheckDataCompleteLock()) {
                    LOGGER
                        .info("Wait for CheckDataCompleteLock: {} ms, decoder: {}", dccaDecoderTimeout * TIMEUNIT_100MS,
                            decoder);
                    decoder.getCheckDataCompleteLock().wait(dccaDecoderTimeout * TIMEUNIT_100MS);
                }

                decoder.incCurrentAnswerRetryCount();
            }
            catch (InterruptedException ex) {
                LOGGER.warn("Wait for decoder responses was interrupted.", ex);

                if (Thread.interrupted()) {
                    break;
                }
            }

        }
        while (!decoder.isMaxAnswerRetryExceeded());

        LOGGER
            .info("Process the current decoder has finished: {}, maxRetryExceeded: {}, incompleteOpCodes: {}", decoder,
                decoder.isMaxAnswerRetryExceeded(), decoder.checkIncompleteOpCodes());
    }

    private final Object scheduleLogonEnableLock = new Object();

    private void triggerLogonEnable(Integer initialDelayValue) {
        LOGGER.info("Trigger the LogonEnable, initialDelayValue: {}", initialDelayValue);

        if (dccAdvModel.isSendLogonEnableActive() && dccAdvModel.getLogonEnableInterval() > 0) {

            synchronized (scheduleLogonEnableLock) {

                cancelScheduledFutureLogonEnable();

                long initialDelay = 0L;
                if (initialDelayValue != null) {
                    initialDelay = initialDelayValue;
                }
                long period = dccAdvModel.getLogonEnableInterval() * TIMEUNIT_100MS;

                if (period > 0) {
                    LOGGER
                        .info("Schedule the logon enable task with period: {} ms, initialDelay: {} ms", period,
                            initialDelay);
                    scheduledFutureLogonEnable =
                        logonEnableWorker
                            .scheduleAtFixedRate(() -> dccAdvViewListener.sendLogonEnable(), initialDelay, period,
                                TimeUnit.MILLISECONDS);
                }
                else {
                    LOGGER.info("Send a single logon enable.");
                    dccAdvViewListener.sendLogonEnable();
                }

            }
        }
        else {
            LOGGER.info("Send the LOGON_ENABLE is not active or interval == 0.");
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy