org.bidib.wizard.dcca.client.controller.DccAdvController Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bidibwizard-dcca-client Show documentation
Show all versions of bidibwizard-dcca-client Show documentation
jBiDiB BiDiB Wizard DCC-A client POM
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.");
}
}
}