Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.bidib.wizard.localhost.LocalHostBidibDistributedMessageHandler Maven / Gradle / Ivy
package org.bidib.wizard.localhost;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.bidib.jbidibc.core.BidibDistributedMessageListener;
import org.bidib.jbidibc.core.node.BidibNode;
import org.bidib.jbidibc.messages.AccessoryState;
import org.bidib.jbidibc.messages.AddressData;
import org.bidib.jbidibc.messages.BidibLibrary;
import org.bidib.jbidibc.messages.DriveState;
import org.bidib.jbidibc.messages.Feature;
import org.bidib.jbidibc.messages.Node;
import org.bidib.jbidibc.messages.enums.BoosterState;
import org.bidib.jbidibc.messages.enums.ClassIdEnum;
import org.bidib.jbidibc.messages.enums.CommandStationState;
import org.bidib.jbidibc.messages.enums.DriveAcknowledge;
import org.bidib.jbidibc.messages.enums.IdentifyState;
import org.bidib.jbidibc.messages.enums.MessageClassEnum;
import org.bidib.jbidibc.messages.enums.TargetModeEnum;
import org.bidib.jbidibc.messages.exception.ProtocolException;
import org.bidib.jbidibc.messages.message.BidibGuestMessage;
import org.bidib.jbidibc.messages.message.BidibMessageInterface;
import org.bidib.jbidibc.messages.message.BidibRequestFactory;
import org.bidib.jbidibc.messages.message.GuestRequestSendMessage;
import org.bidib.jbidibc.messages.message.GuestRequestSubscribeMessage;
import org.bidib.jbidibc.messages.message.GuestRequestUnsubscribeMessage;
import org.bidib.jbidibc.messages.message.GuestResponseNotifyMessage;
import org.bidib.jbidibc.messages.message.GuestResponseSentMessage;
import org.bidib.jbidibc.messages.message.GuestResponseSubscriptionCountMessage;
import org.bidib.jbidibc.messages.message.GuestResponseSubscriptionMessage;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.NodeUtils;
import org.bidib.jbidibc.messages.utils.ThreadFactoryBuilder;
import org.bidib.wizard.api.model.NodeInterface;
import org.bidib.wizard.api.model.NodeProvider;
import org.bidib.wizard.api.model.connection.AbstractMessageEvent;
import org.bidib.wizard.api.model.connection.BidibConnection;
import org.bidib.wizard.api.model.connection.GatewayConnection;
import org.bidib.wizard.api.model.connection.event.AccessoryStateMessageEvent;
import org.bidib.wizard.api.model.connection.event.BoosterDiagMessageEvent;
import org.bidib.wizard.api.model.connection.event.BoosterStateMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationDriveAcknowledgeMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationDriveManualMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationDriveStateMessageEvent;
import org.bidib.wizard.api.model.connection.event.CommandStationStateMessageEvent;
import org.bidib.wizard.api.model.connection.event.FeatureMessageEvent;
import org.bidib.wizard.api.model.connection.event.NodeLostMessageEvent;
import org.bidib.wizard.api.model.connection.event.SysIdentifyStateMessageEvent;
import org.bidib.wizard.api.service.node.CommandStationService;
import org.bidib.wizard.api.service.node.NodeService;
import org.bidib.wizard.api.service.node.SwitchingNodeService;
import org.bidib.wizard.localhost.event.SubscriberEvent;
import org.bidib.wizard.localhost.event.SubscriberEvent.SubscribeAction;
import org.bidib.wizard.localhost.exception.InvalidSubscriptionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.reactivex.rxjava3.core.Scheduler;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.PublishSubject;
/**
* The {@code LocalHostBidibDistributedMessageHandler} provides the handling of guest messages.
*/
public class LocalHostBidibDistributedMessageHandler
implements BidibDistributedMessageListener, BidibDistributedMessagePublisher {
private static final Logger LOGGER = LoggerFactory.getLogger(LocalHostBidibDistributedMessageHandler.class);
private final BidibConnection connection;
private final NodeProvider nodeProvider;
private final BidibRequestFactory requestFactory;
private final CommandStationService commandStationService;
private final SwitchingNodeService switchingNodeService;
private CompositeDisposable compDispMessages = new CompositeDisposable();
protected final Map> subscriptions = new HashMap<>();
private final BlockingQueue receiveQueue = new LinkedBlockingQueue<>();
private ScheduledExecutorService receiveQueueWorker;
private AtomicBoolean processReceiveQueue = new AtomicBoolean();
private final Scheduler computationScheduler;
private PublishSubject subjectSubscriberEvents;
private final BidibResponseSupplier bidibResponseSupplier;
private static final class ReceiveMessageContainer {
private final BidibNode bidibNode;
private final BidibMessageInterface message;
/**
* Create a new container to transport the node and the message that is related to the node.
*
* @param bidibNode
* the node that sent the message (origin of message)
* @param message
* the message
*/
public ReceiveMessageContainer(final BidibNode bidibNode, final BidibMessageInterface message) {
this.bidibNode = bidibNode;
this.message = message;
}
/**
* @return the bidib node that sent the message
*/
public BidibNode getBidibNode() {
return bidibNode;
}
public BidibMessageInterface getMessage() {
return message;
}
}
/**
* Create a new instance of the LocalHostBidibDistributedMessageHandler.
*
* @param connection
* the connection
* @param nodeProvider
* the node provider
*/
public LocalHostBidibDistributedMessageHandler(final BidibConnection connection, final NodeProvider nodeProvider,
final BidibRequestFactory requestFactory, final NodeService nodeService,
final CommandStationService commandStationService, final SwitchingNodeService switchingNodeService) {
LOGGER.info("Create new instance of LocalHostBidibDistributedMessageHandler.");
this.connection = connection;
this.nodeProvider = nodeProvider;
this.requestFactory = requestFactory;
this.commandStationService = commandStationService;
this.switchingNodeService = switchingNodeService;
this.subjectSubscriberEvents = PublishSubject.create();
this.bidibResponseSupplier =
new BidibResponseSupplier(this.connection, this.nodeProvider, this.requestFactory, nodeService,
this.commandStationService, this.switchingNodeService);
// subscribe to messages of the connection
this.computationScheduler =
RxJavaPlugins
.createComputationScheduler(
new ThreadFactoryBuilder().setNameFormat("localHostSubjectMessagesWorkers-thread-%d").build());
Disposable dispMessages =
this.connection.getSubjectMessages().observeOn(this.computationScheduler).subscribe(me -> {
LOGGER.debug("Received message event: {}", me);
processMessageEvent(me);
}, err -> {
LOGGER.warn("The message subject has signalled an error.", err);
});
this.compDispMessages.add(dispMessages);
final ThreadFactory namedThreadFactory =
new ThreadFactoryBuilder().setNameFormat("localHostRcvQueueWorkers-thread-%d").build();
receiveQueueWorker = Executors.newScheduledThreadPool(2, namedThreadFactory);
final int receiveTimeout = 1000;
processReceiveQueue.set(true);
receiveQueueWorker.submit(() -> {
LOGGER.info("Start the receiver queue.");
while (processReceiveQueue.get()) {
try {
final ReceiveMessageContainer messageContainer =
this.receiveQueue.poll(receiveTimeout, TimeUnit.MILLISECONDS);
if (messageContainer != null) {
handleDistributedMessage(messageContainer);
}
}
catch (InterruptedException ex) {
LOGGER.info("Process receive queue was interrupted and the receiver queue will terminate.");
break;
}
catch (Exception ex) {
LOGGER.warn("Process receive queue failed. Ignore and continue.");
}
}
LOGGER.info("The receiver queue has finished.");
});
this.connection.setLocalHostEnabled(true);
}
@Override
public void shutdown() {
LOGGER.info("Shutdown the LocalHostBidibDistributedMessageHandler.");
this.compDispMessages.dispose();
LOGGER.info("Stop the receiver queue.");
processReceiveQueue.set(false);
receiveQueueWorker.shutdownNow();
this.computationScheduler.shutdown();
}
@Override
public void handleDistributedMessage(final BidibNode bidibNode, final BidibMessageInterface message) {
LOGGER.info("handleDistributedMessage, node: {}, message: {}", bidibNode, message);
// add the message to the receive queue
final ReceiveMessageContainer messgeContainer = new ReceiveMessageContainer(bidibNode, message);
this.receiveQueue.add(messgeContainer);
}
/**
* Process the distributed message.
*
* @param messgeContainer
* the container with the node and the message
*/
private void handleDistributedMessage(final ReceiveMessageContainer messgeContainer) {
final BidibNode bidibNode = messgeContainer.getBidibNode();
final BidibMessageInterface message = messgeContainer.getMessage();
int type = ByteUtils.getInt(message.getType());
switch (type) {
case BidibLibrary.MSG_GUEST_RESP_SUBSCRIPTION_COUNT:
case BidibLibrary.MSG_GUEST_RESP_SUBSCRIPTION:
case BidibLibrary.MSG_GUEST_RESP_SENT:
case BidibLibrary.MSG_GUEST_RESP_NOTIFY:
break;
case BidibLibrary.MSG_GUEST_REQ_SUBSCRIBE:
handleGuestReqSubscribe((GuestRequestSubscribeMessage) message, this.connection, bidibNode);
break;
case BidibLibrary.MSG_GUEST_REQ_UNSUBSCRIBE:
handleGuestReqUnsubscribe((GuestRequestUnsubscribeMessage) message, this.connection, bidibNode);
break;
case BidibLibrary.MSG_GUEST_REQ_SEND:
handleGuestReqSend((GuestRequestSendMessage) message, this.connection, bidibNode);
break;
default:
break;
}
}
/**
*
* @param guestRequestSubscribeMessage
* the message
* @param connection
* the connection
* @param bidibNode
* the bidib node that sent the message
*/
protected void handleGuestReqSubscribe(
final GuestRequestSubscribeMessage guestRequestSubscribeMessage, final BidibConnection connection,
final BidibNode bidibNode) {
LOGGER.info("Handle the GuestRequestSubscribeMessage: {}", guestRequestSubscribeMessage);
// the address of the node that sent the message
final byte[] originNodeAddress = guestRequestSubscribeMessage.getAddr();
final TargetModeEnum targetMode = guestRequestSubscribeMessage.getTargetMode();
int subscription = guestRequestSubscribeMessage.getSubscription();
final NodeSubscriptionData nodeSubscriptionData =
new NodeSubscriptionData(originNodeAddress, targetMode, bidibNode.getCachedUniqueId(), subscription);
final int subscriptionUpstream = guestRequestSubscribeMessage.getSubscriptionUpstream();
final int subscriptionDownstream = guestRequestSubscribeMessage.getSubscriptionDownstream();
LOGGER
.info(
"Received subscribe request from node with address: {} ({}) for targetMode: {}, subscriptionUpstream: {}, subscriptionUpstream: {}, subscription: {}",
NodeUtils.formatAddress(nodeSubscriptionData.getSubscriberAddress()),
ByteUtils.getUniqueIdAsString(bidibNode.getCachedUniqueId()), targetMode, subscriptionUpstream,
subscriptionDownstream, StringUtils.leftPad(Integer.toString(subscription, 2), 16, "0"));
// int acknowledge = 0;
// keep track of the nodes and messageClass that are registered
prepareSubscription(subscription, nodeSubscriptionData);
try {
byte[] uniqueId = guestRequestSubscribeMessage.getTargetUniqueId();
int ackSequence = guestRequestSubscribeMessage.getNum();
publishResponseSubscription(targetMode, uniqueId, ackSequence, subscription, nodeSubscriptionData);
}
catch (ProtocolException ex) {
LOGGER.warn("Publish guestResponseSubscriptionMessage failed.", ex);
}
}
/**
*
* @param guestRequestSubscribeMessage
* the message
* @param connection
* the connection
* @param bidibNode
* the bidib node that sent the message
*/
protected void handleGuestReqUnsubscribe(
final GuestRequestUnsubscribeMessage guestRequestUnsubscribeMessage, final BidibConnection connection,
final BidibNode bidibNode) {
LOGGER.info("Handle the GuestRequestUnsubscribeMessage: {}", guestRequestUnsubscribeMessage);
// the address of the node that sent the message
final byte[] originNodeAddress = guestRequestUnsubscribeMessage.getAddr();
final TargetModeEnum targetMode = guestRequestUnsubscribeMessage.getTargetMode();
int subscription = guestRequestUnsubscribeMessage.getSubscription();
final NodeSubscriptionData nodeSubscriptionData =
new NodeSubscriptionData(originNodeAddress, targetMode, bidibNode.getCachedUniqueId(), subscription);
final int subscriptionUpstream = guestRequestUnsubscribeMessage.getSubscriptionUpstream();
final int subscriptionDownstream = guestRequestUnsubscribeMessage.getSubscriptionDownstream();
LOGGER
.info(
"Received unsubscribe request from node with address: {} ({}) for targetMode: {}, subscriptionUpstream: {}, subscriptionUpstream: {}, subscription: {}",
NodeUtils.formatAddress(nodeSubscriptionData.getSubscriberAddress()),
ByteUtils.getUniqueIdAsString(bidibNode.getCachedUniqueId()), targetMode, subscriptionUpstream,
subscriptionDownstream, StringUtils.leftPad(Integer.toString(subscription, 2), 16, "0"));
// keep track of the nodes and messageClass that are registered
// TODO handle unsubscribe
prepareUnsubscribe(subscription, nodeSubscriptionData);
try {
byte[] uniqueId = guestRequestUnsubscribeMessage.getTargetUniqueId();
int ackSequence = guestRequestUnsubscribeMessage.getNum();
publishResponseSubscription(targetMode, uniqueId, ackSequence, subscription, nodeSubscriptionData);
}
catch (ProtocolException ex) {
LOGGER.warn("Publish gateResponseSubscriptionMessage failed.", ex);
}
}
protected void publishResponseSubscription(
final TargetModeEnum targetMode, byte[] targetModeUniqueId, int ackSequence, int subscription,
final NodeSubscriptionData nodeSubscriptionData) {
int result = 0;
try {
// we must provide the response for all targetModes
final List nodes = new ArrayList<>();
switch (targetMode) {
case BIDIB_TARGET_MODE_UID:
LOGGER
.info("Publish gateway response, uniqueId: {}",
ByteUtils.convertUniqueIdToString(targetModeUniqueId));
NodeInterface node = nodeProvider.findNodeByUniqueIdWithoutClass(targetModeUniqueId);
if (node == null) {
LOGGER
.warn("No node found with uniqueId: {}",
ByteUtils.convertUniqueIdToString(targetModeUniqueId));
result = 0xFF;
}
else {
nodes.add(node);
}
break;
case BIDIB_TARGET_MODE_DCCGEN:
// check if DCC generator nodes are available
List dccGenNodes =
nodeProvider.findNodesByClass(ClassIdEnum.BIT_DCC_GEN_DRIVE_AND_SWITCH);
if (CollectionUtils.isEmpty(dccGenNodes)) {
LOGGER.warn("No DCCGEN node found.");
result = 0xFF;
}
else {
nodes.addAll(dccGenNodes);
}
break;
case BIDIB_TARGET_MODE_BOOSTER:
// check if booster nodes are available
List boosterNodes = nodeProvider.findNodesByClass(ClassIdEnum.BIT_BOOSTER);
if (CollectionUtils.isEmpty(boosterNodes)) {
LOGGER.warn("No BOOSTER node found.");
result = 0xFF;
}
else {
nodes.addAll(boosterNodes);
}
break;
case BIDIB_TARGET_MODE_ACCESSORY:
// check if accessory nodes are available
List accessoryNodes = nodeProvider.findNodesByClass(ClassIdEnum.BIT_ACCESSORY);
if (CollectionUtils.isEmpty(accessoryNodes)) {
LOGGER.warn("No ACCESSORY node found.");
result = 0xFF;
}
else {
nodes.addAll(accessoryNodes);
}
break;
case BIDIB_TARGET_MODE_SWITCH:
// check if switching nodes are available
List switchNodes = nodeProvider.findNodesByClass(ClassIdEnum.BIT_SWITCHING);
if (CollectionUtils.isEmpty(switchNodes)) {
LOGGER.warn("No SWITCHING node found.");
result = 0xFF;
}
else {
nodes.addAll(switchNodes);
}
break;
case BIDIB_TARGET_MODE_TOP:
node = nodeProvider.findNodeByAddress(Node.ROOTNODE_ADDR);
if (node == null) {
LOGGER.warn("No root node found!");
result = 0xFF;
}
else {
nodes.add(node);
}
break;
default:
LOGGER.warn("Unsupported targetMode detected: {}", targetMode);
throw new InvalidSubscriptionException(0xFF, "Unsupported targetMode detected: " + targetMode);
}
if (!nodes.isEmpty()) {
int nodeCount = nodes.size();
try {
// byte[] uniqueId = guestRequestUnsubscribeMessage.getUniqueId();
// send the RESP_SUBSCRIPTION_COUNT
final GuestResponseSubscriptionCountMessage guestResponseSubscriptionCountMessage =
requestFactory
.createGuestResponseSubscriptionCountMessage(nodeSubscriptionData.getSubscriberAddress(), 0,
targetMode, targetModeUniqueId, ackSequence, result, nodeCount);
publishMessage(nodeSubscriptionData, guestResponseSubscriptionCountMessage);
}
catch (ProtocolException ex1) {
LOGGER.warn("Publish guestResponseSubscriptionCountMessage failed.", ex1);
}
// send RESP_SUBSCRIPTION
for (NodeInterface node : nodes) {
try {
byte[] nodeUniqueId = NodeUtils.getTargetModeUniqueId(node.getUniqueId());
// send the MSG_GUEST_RESP_SUBSCRIPTION to the node
final GuestResponseSubscriptionMessage guestResponseSubscriptionMessage =
requestFactory
.createGuestResponseSubscriptionMessage(nodeSubscriptionData.getSubscriberAddress(), 0,
targetMode, nodeUniqueId, ackSequence, result, subscription);
publishMessage(nodeSubscriptionData, guestResponseSubscriptionMessage);
}
catch (ProtocolException ex1) {
LOGGER.warn("Publish guestResponseSubscriptionMessage failed.", ex1);
}
}
}
}
catch (InvalidSubscriptionException ex) {
LOGGER.warn("The subscription is not supported.", ex);
int nodeCount = 1;
result = ex.getResult();
try {
// send the RESP_SUBSCRIPTION_COUNT
final GuestResponseSubscriptionCountMessage guestResponseSubscriptionCountMessage =
requestFactory
.createGuestResponseSubscriptionCountMessage(nodeSubscriptionData.getSubscriberAddress(), 0,
targetMode, targetModeUniqueId, ackSequence, result, nodeCount);
publishMessage(nodeSubscriptionData, guestResponseSubscriptionCountMessage);
}
catch (ProtocolException ex1) {
LOGGER.warn("Publish guestResponseSubscriptionCountMessage failed.", ex1);
}
// send the MSG_GUEST_RESP_SUBSCRIPTION to the node
try {
final GuestResponseSubscriptionMessage guestResponseSubscriptionMessage =
requestFactory
.createGuestResponseSubscriptionMessage(nodeSubscriptionData.getSubscriberAddress(), 0,
targetMode, targetModeUniqueId, ackSequence, result, subscription);
publishMessage(nodeSubscriptionData, guestResponseSubscriptionMessage);
}
catch (ProtocolException ex1) {
LOGGER.warn("Publish guestResponseSubscriptionMessage failed.", ex1);
}
}
}
protected void prepareSubscription(int subscriptionClasses, final NodeSubscriptionData nodeSubscriptionData) {
for (MessageClassEnum messageClass : MessageClassEnum.values()) {
if (messageClass == MessageClassEnum.BIDIB_MESSAGE_CLASS_NOTHING) {
continue;
}
boolean isActive = MessageClassEnum.isAnyActive(messageClass, subscriptionClasses);
LOGGER.info("Current subscription message class: {}, isActive: {}", messageClass, isActive);
try {
List subscriptionDataList = this.subscriptions.get(messageClass);
if (subscriptionDataList == null) {
LOGGER
.info("No existing subscriptions for message class found: {}, current nodeSubscriptionData: {}",
messageClass, nodeSubscriptionData);
if (!isActive) {
LOGGER.info("The subscription is not active.");
continue;
}
// no existing subscriptions for message class found
subscriptionDataList = new ArrayList<>();
this.subscriptions.put(messageClass, subscriptionDataList);
subscriptionDataList.add(nodeSubscriptionData);
subjectSubscriberEvents
.onNext(new SubscriberEvent(SubscribeAction.add, nodeSubscriptionData.getSubscriberAddress(),
subscriptionClasses).withUniqueId(nodeSubscriptionData.getSubscriberUniqueId()));
}
else {
LOGGER.info("Found existing subscriptions for message class: {}", messageClass);
// check if the subscription for the node is available already
NodeSubscriptionData existingSubscriptionData =
IterableUtils
.find(subscriptionDataList, data -> Objects
.equals(data.getSubscriberUniqueId(), nodeSubscriptionData.getSubscriberUniqueId()));
if (existingSubscriptionData != null) {
LOGGER
.info("The original uniqueId is registered already: {}",
ByteUtils.formatHexUniqueId(nodeSubscriptionData.getSubscriberUniqueId()));
if (!isActive) {
LOGGER.info("Remove subscription for messageClass: {}", messageClass);
subscriptionDataList.remove(nodeSubscriptionData);
subjectSubscriberEvents
.onNext(new SubscriberEvent(SubscribeAction.add,
nodeSubscriptionData.getSubscriberAddress(), subscriptionClasses)
.withUniqueId(nodeSubscriptionData.getSubscriberUniqueId()));
}
else {
LOGGER.info("The subscription is still active: {}", messageClass);
}
}
else {
LOGGER
.info("Register uniqueId: {}, messageClass: {}",
ByteUtils.formatHexUniqueId(nodeSubscriptionData.getSubscriberUniqueId()),
messageClass);
subscriptionDataList.add(nodeSubscriptionData);
subjectSubscriberEvents
.onNext(
new SubscriberEvent(SubscribeAction.add, nodeSubscriptionData.getSubscriberAddress(),
subscriptionClasses).withUniqueId(nodeSubscriptionData.getSubscriberUniqueId()));
}
}
}
catch (Exception ex) {
LOGGER.warn("Prepare subscriptions failed.", ex);
// TODO: handle exception
}
}
}
protected void prepareUnsubscribe(int subscriptionClasses, final NodeSubscriptionData nodeSubscriptionData) {
// handle the unsubscribe request
for (MessageClassEnum messageClass : MessageClassEnum.values()) {
if (messageClass == MessageClassEnum.BIDIB_MESSAGE_CLASS_NOTHING) {
continue;
}
boolean isActive = MessageClassEnum.isAnyActive(messageClass, subscriptionClasses);
LOGGER.info("Current subscription message class: {}, isActive: {}", messageClass, isActive);
try {
List subscriptionDataList = this.subscriptions.get(messageClass);
if (subscriptionDataList == null) {
LOGGER
.info("No existing subscriptions for message class found: {}, current nodeSubscriptionData: {}",
messageClass, nodeSubscriptionData);
// if (!isActive) {
// LOGGER.info("The subscription is not active.");
// continue;
// }
//
// // no existing subscriptions for message class found
// subscriptionDataList = new ArrayList<>();
// this.subscriptions.put(messageClass, subscriptionDataList);
// subscriptionDataList.add(nodeSubscriptionData);
//
// subjectSubscriberEvents
// .onNext(new SubscriberEvent(SubscribeAction.add, nodeSubscriptionData.getSubscriberAddress(),
// subscriptionClasses).withUniqueId(nodeSubscriptionData.getSubscriberUniqueId()));
}
else {
LOGGER.info("Found existing subscriptions for message class: {}", messageClass);
// check if the subscription for the node is available already
NodeSubscriptionData existingSubscriptionData =
IterableUtils
.find(subscriptionDataList, data -> Objects
.equals(data.getSubscriberUniqueId(), nodeSubscriptionData.getSubscriberUniqueId()));
if (existingSubscriptionData != null) {
LOGGER
.info("The original uniqueId is registered already: {}",
ByteUtils.formatHexUniqueId(nodeSubscriptionData.getSubscriberUniqueId()));
if (isActive) {
LOGGER.info("Remove subscription for messageClass: {}", messageClass);
subscriptionDataList.remove(nodeSubscriptionData);
subjectSubscriberEvents
.onNext(new SubscriberEvent(SubscribeAction.remove,
nodeSubscriptionData.getSubscriberAddress(), subscriptionClasses)
.withUniqueId(nodeSubscriptionData.getSubscriberUniqueId()));
}
else {
LOGGER.info("The subscription is still active: {}", messageClass);
}
}
else {
LOGGER
.info("Register uniqueId: {}, messageClass: {}",
ByteUtils.formatHexUniqueId(nodeSubscriptionData.getSubscriberUniqueId()),
messageClass);
subscriptionDataList.add(nodeSubscriptionData);
subjectSubscriberEvents
.onNext(
new SubscriberEvent(SubscribeAction.remove, nodeSubscriptionData.getSubscriberAddress(),
subscriptionClasses).withUniqueId(nodeSubscriptionData.getSubscriberUniqueId()));
}
}
}
catch (Exception ex) {
LOGGER.warn("Prepare subscriptions failed.", ex);
// TODO: handle exception
}
}
}
private final Map nodeContainerMap =
Collections.synchronizedMap(new LinkedHashMap());
private BidibNodeContainer getBidibNodeContainer(Long uniqueId) {
BidibNodeContainer bidibNodeContainer = null;
synchronized (nodeContainerMap) {
bidibNodeContainer = nodeContainerMap.get(uniqueId);
if (bidibNodeContainer == null) {
bidibNodeContainer = new BidibNodeContainer(uniqueId);
nodeContainerMap.put(uniqueId, bidibNodeContainer);
}
}
return bidibNodeContainer;
}
protected void handleGuestReqSend(
final GuestRequestSendMessage guestRequestSendMessage, final BidibConnection connection,
final BidibNode bidibNode) {
LOGGER.info("Handle the GuestRequestSendMessage: {}", guestRequestSendMessage);
final byte[] originNodeAddress = guestRequestSendMessage.getAddr();
final TargetModeEnum targetMode = guestRequestSendMessage.getTargetMode();
final NodeInterface targetNode;
try {
targetNode = findNodeByTargetMode(guestRequestSendMessage, targetMode);
LOGGER.info("Resolved the target node: {}, targetMode: {}", targetNode, targetMode);
}
catch (ProtocolException ex) {
LOGGER.warn("Find node by targetMode failed. Discard handle message: {}", guestRequestSendMessage, ex);
// send error
int result = 0xFF; // no existing node
int ackSequence = guestRequestSendMessage.getNum();
try {
// send the MSG_GUEST_RESP_SENT to the node
final GuestResponseSentMessage guestResponseSentMessage =
this.requestFactory
.createGuestResponseSentMessage(originNodeAddress, 0, targetMode, new byte[] { 0, 0, 0, 0, 0 },
ackSequence, result);
final NodeSubscriptionData nodeSubscriptionData =
new NodeSubscriptionData(originNodeAddress, targetMode, 0L, null);
publishMessage(nodeSubscriptionData, guestResponseSentMessage);
}
catch (ProtocolException ex1) {
LOGGER.info("Process command from wrapped message failed.", ex1);
}
return;
}
// get the origin node by it's address
final NodeInterface originNode = nodeProvider.findNodeByAddress(originNodeAddress);
LOGGER.info("Resolved the origin node: {}", originNode);
// get the node container for the target node
final BidibNodeContainer bidibNodeContainer = getBidibNodeContainer(targetNode.getUniqueId());
// force subscription on origin node
final NodeSubscriptionData nodeSubscriptionData =
new NodeSubscriptionData(originNodeAddress, targetMode, originNode.getUniqueId(), null);
LOGGER.info("Force subscription, nodeSubscriptionData: {}", nodeSubscriptionData);
int subscription = 0xFFFF;
prepareSubscription(subscription, nodeSubscriptionData);
LOGGER.info("Current originNodeAddress: {}, targetMode: {}", originNodeAddress, targetMode);
this.bidibResponseSupplier
.processMessages(this.requestFactory, guestRequestSendMessage, targetNode, targetMode, nodeSubscriptionData,
bidibNodeContainer, this);
}
private NodeInterface findNodeByTargetMode(final BidibGuestMessage guestMessage, final TargetModeEnum targetMode)
throws ProtocolException {
NodeInterface node = null;
LOGGER.info("Find node by targetMode: {}", targetMode);
switch (targetMode) {
case BIDIB_TARGET_MODE_UID:
node = nodeProvider.findNodeByUniqueIdWithoutClass(guestMessage.getTargetUniqueId());
break;
case BIDIB_TARGET_MODE_DCCGEN:
// get the first DCC generator
List dccGenNodes =
nodeProvider.findNodesByClass(ClassIdEnum.BIT_DCC_GEN_DRIVE_AND_SWITCH);
if (CollectionUtils.isNotEmpty(dccGenNodes)) {
node = dccGenNodes.get(0);
}
break;
case BIDIB_TARGET_MODE_BOOSTER:
// get the first booster
List boosterNodes = nodeProvider.findNodesByClass(ClassIdEnum.BIT_BOOSTER);
if (CollectionUtils.isNotEmpty(boosterNodes)) {
node = boosterNodes.get(0);
}
break;
case BIDIB_TARGET_MODE_ACCESSORY:
// get the first accessory node
List accessoryNodes = nodeProvider.findNodesByClass(ClassIdEnum.BIT_ACCESSORY);
if (CollectionUtils.isNotEmpty(accessoryNodes)) {
node = accessoryNodes.get(0);
}
break;
case BIDIB_TARGET_MODE_TOP:
node = nodeProvider.findNodeByAddress(Node.ROOTNODE_ADDR);
break;
default:
LOGGER.warn("Unsupported target mode detected: {}", targetMode);
break;
}
LOGGER.info("The found node: {}", node);
if (node == null) {
throw new ProtocolException("Requested node not found.");
}
return node;
}
/**
* Process the message event that is received from the interface.
*
* @param me
* the message event
*/
private void processMessageEvent(final AbstractMessageEvent me) {
switch (me.getMessageType()) {
case BidibLibrary.MSG_CS_STATE:
notifyCommandStationSubscribers((CommandStationStateMessageEvent) me);
break;
case BidibLibrary.MSG_CS_DRIVE_ACK:
notifyCommandStationSubscribers((CommandStationDriveAcknowledgeMessageEvent) me);
break;
case BidibLibrary.MSG_CS_DRIVE_MANUAL:
notifyCommandStationSubscribers((CommandStationDriveManualMessageEvent) me);
break;
case BidibLibrary.MSG_CS_DRIVE_STATE:
notifyCommandStationSubscribers((CommandStationDriveStateMessageEvent) me);
break;
case BidibLibrary.MSG_ACCESSORY_STATE:
notifyAccessorySubscribers((AccessoryStateMessageEvent) me);
break;
case BidibLibrary.MSG_BOOST_STAT:
notifyBoosterSubscribers((BoosterStateMessageEvent) me);
break;
case BidibLibrary.MSG_BOOST_DIAGNOSTIC:
notifyBoosterSubscribers((BoosterDiagMessageEvent) me);
break;
case BidibLibrary.MSG_NODE_LOST:
notifyNodeLost((NodeLostMessageEvent) me);
break;
case BidibLibrary.MSG_SYS_IDENTIFY_STATE:
notifySystemSubscribers((SysIdentifyStateMessageEvent) me);
break;
case BidibLibrary.MSG_FEATURE:
notifyFeatureAndUserSubscribers((FeatureMessageEvent) me);
break;
default:
break;
}
}
private void notifyNodeLost(NodeLostMessageEvent me) {
LOGGER.info("Node lost: {}", me.getNode());
// the node address of the lost node
byte[] nodeAddress = me.getNode().getAddr();
for (Entry> entry : this.subscriptions.entrySet()) {
// create a copy of the addresses
final List subscriptionDataList = new ArrayList<>(entry.getValue());
for (NodeSubscriptionData subscriptionData : subscriptionDataList) {
// check if the node or a subnode of the node is registered
if (ByteUtils.arrayEquals(subscriptionData.getSubscriberAddress(), nodeAddress)
|| NodeUtils.isSubNode(nodeAddress, subscriptionData.getSubscriberAddress())) {
LOGGER.info("Remove address: {}", NodeUtils.formatAddress(subscriptionData.getSubscriberAddress()));
entry.getValue().remove(subscriptionData);
}
}
}
}
private List getSubscriptionDataList(final MessageClassEnum messageClass) {
final List subscriptionDataList = subscriptions.get(messageClass);
return subscriptionDataList != null ? subscriptionDataList : Collections.emptyList();
}
private void notifyFeatureAndUserSubscribers(final FeatureMessageEvent me) {
LOGGER.info("Publish the feature message event to the guests: {}", me);
try {
// fetch all address node data that are registered for the message class
final List subscriptionDataList =
getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_FEATURE_AND_USER);
LOGGER.info("Fetched subscriptions for class FEATURE_AND_USER: {}", subscriptionDataList);
if (!subscriptionDataList.isEmpty()) {
final Feature feature = me.getFeature();
byte[] nodeAddress = me.getAddress(); // the address of the node
int messageNum = me.getMessageNum();
final NodeInterface originNode = nodeProvider.findNodeByAddress(me.getAddress());
final BidibMessageInterface featureResponse =
requestFactory.createFeatureResponse(nodeAddress, messageNum, feature);
publishAsGuestResponseNotifyMessage(originNode, featureResponse, subscriptionDataList);
}
}
catch (ProtocolException ex) {
LOGGER.warn("Publish guestResponseNotifyMessage failed.", ex);
}
}
private void notifySystemSubscribers(final SysIdentifyStateMessageEvent me) {
try {
// fetch all address node data that are registered for the message class
final List subscriptionDataList =
getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_SYSTEM);
LOGGER.info("Fetched subscriptions for class SYSTEM: {}", subscriptionDataList);
if (!subscriptionDataList.isEmpty()) {
final IdentifyState identifyState = me.getIdentifyState();
byte[] nodeAddress = me.getAddress(); // the address of the node
int messageNum = me.getMessageNum();
final NodeInterface originNode = nodeProvider.findNodeByAddress(me.getAddress());
final BidibMessageInterface csStateResponse =
requestFactory.createSysIdentifyStateResponse(nodeAddress, messageNum, identifyState);
publishAsGuestResponseNotifyMessage(originNode, csStateResponse, subscriptionDataList);
}
}
catch (ProtocolException ex) {
LOGGER.warn("Publish guestResponseNotifyMessage failed.", ex);
}
}
private void notifyCommandStationSubscribers(final CommandStationStateMessageEvent me) {
try {
// fetch all address node data that are registered for the message class
final List subscriptionDataList =
getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_COMMANDSTATION);
LOGGER.info("Fetched subscriptions for class COMMANDSTATION: {}", subscriptionDataList);
if (!subscriptionDataList.isEmpty()) {
final CommandStationState csState = me.getCommandStationState();
byte[] nodeAddress = me.getAddress(); // the address of the command station node
int messageNum = me.getMessageNum();
final NodeInterface originNode = nodeProvider.findNodeByAddress(me.getAddress());
final BidibMessageInterface csStateResponse =
requestFactory.createCommandStationStateResponse(nodeAddress, messageNum, csState);
publishAsGuestResponseNotifyMessage(originNode, csStateResponse, subscriptionDataList);
}
}
catch (ProtocolException ex) {
LOGGER.warn("Publish guestResponseNotifyMessage failed.", ex);
}
}
private void notifyCommandStationSubscribers(final CommandStationDriveAcknowledgeMessageEvent me) {
try {
final List subscriptionDataList =
getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_COMMANDSTATION);
LOGGER.info("Fetch subscriptions for class COMMANDSTATION: {}", subscriptionDataList);
if (!subscriptionDataList.isEmpty()) {
byte[] nodeAddress = me.getAddress();
int messageNum = me.getMessageNum();
final NodeInterface originNode = nodeProvider.findNodeByAddress(me.getAddress());
final DriveAcknowledge driveAckn = me.getDriveAcknowledge();
final int dccAddress = me.getDccAddress();
final Integer acknowledgedMessageNumber = me.getAcknowledgedMessageNumber();
final BidibMessageInterface csStateResponse =
requestFactory
.createCommandStationDriveAckResponse(nodeAddress, messageNum,
new AddressData(dccAddress, null), driveAckn, acknowledgedMessageNumber);
publishAsGuestResponseNotifyMessage(originNode, csStateResponse, subscriptionDataList);
}
}
catch (ProtocolException ex) {
LOGGER.warn("Publish guestResponseNotifyMessage failed.", ex);
}
}
private void notifyCommandStationSubscribers(final CommandStationDriveManualMessageEvent me) {
try {
byte[] nodeAddress = me.getAddress();
int messageNum = me.getMessageNum();
final NodeInterface originNode = nodeProvider.findNodeByAddress(me.getAddress());
final DriveState driveState = me.getDriveState();
final BidibMessageInterface csStateResponse =
requestFactory.createCommandStationDriveManualResponse(nodeAddress, messageNum, driveState);
final List subscriptionDataList =
getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_COMMANDSTATION);
LOGGER.info("Fetch subscriptions for class COMMANDSTATION: {}", subscriptionDataList);
publishAsGuestResponseNotifyMessage(originNode, csStateResponse, subscriptionDataList);
}
catch (ProtocolException ex) {
LOGGER.warn("Publish guestResponseNotifyMessage failed.", ex);
}
}
private void notifyCommandStationSubscribers(final CommandStationDriveStateMessageEvent me) {
try {
byte[] nodeAddress = me.getAddress();
int messageNum = me.getMessageNum();
final NodeInterface originNode = nodeProvider.findNodeByAddress(me.getAddress());
final int opCode = me.getOpCode();
final DriveState driveState = me.getDriveState();
final BidibMessageInterface csStateResponse =
requestFactory.createCommandStationDriveStateResponse(nodeAddress, messageNum, opCode, driveState);
final List subscriptionDataList =
getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_COMMANDSTATION);
LOGGER.info("Fetch subscriptions for class COMMANDSTATION: {}", subscriptionDataList);
publishAsGuestResponseNotifyMessage(originNode, csStateResponse, subscriptionDataList);
}
catch (ProtocolException ex) {
LOGGER.warn("Publish guestResponseNotifyMessage failed.", ex);
}
}
private void notifyAccessorySubscribers(final AccessoryStateMessageEvent me) {
try {
final List subscriptionDataList =
getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_ACCESSORY);
LOGGER.info("Fetch subscriptions for class ACCESSORY: {}", subscriptionDataList);
if (!subscriptionDataList.isEmpty()) {
final AccessoryState accessoryState = me.getAccessoryState();
byte[] nodeAddress = me.getAddress();
int messageNum = me.getMessageNum();
final NodeInterface originNode = nodeProvider.findNodeByAddress(me.getAddress());
int accessoryNumber = me.getAccessoryNumber();
Integer aspect = accessoryState.getActiveAspect();
byte[] value =
new byte[] { accessoryState.getTotal(), accessoryState.getExecute(),
ByteUtils.getLowByte(accessoryState.getWait()) };
final BidibMessageInterface accessoryStateResponse =
requestFactory
.createAccessoryStateResponse(nodeAddress, messageNum, accessoryNumber, aspect, value);
publishAsGuestResponseNotifyMessage(originNode, accessoryStateResponse, subscriptionDataList);
}
}
catch (ProtocolException ex) {
LOGGER.warn("Publish guestResponseNotifyMessage failed.", ex);
}
}
private void notifyBoosterSubscribers(final BoosterStateMessageEvent me) {
try {
final BoosterState boosterState = me.getBoosterState();
byte[] nodeAddress = me.getAddress();
int messageNum = me.getMessageNum();
final NodeInterface originNode = nodeProvider.findNodeByAddress(me.getAddress());
final BidibMessageInterface boostStateResponse =
requestFactory.createBoosterStateResponse(nodeAddress, messageNum, boosterState);
final List subscriptionDataList =
getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_BOOSTER);
LOGGER.info("Fetch subscriptions for class BOOSTER: {}", subscriptionDataList);
publishAsGuestResponseNotifyMessage(originNode, boostStateResponse, subscriptionDataList);
}
catch (ProtocolException ex) {
LOGGER.warn("Publish guestResponseNotifyMessage failed.", ex);
}
}
private void notifyBoosterSubscribers(final BoosterDiagMessageEvent me) {
try {
byte[] nodeAddress = me.getAddress();
int messageNum = me.getMessageNum();
final NodeInterface originNode = nodeProvider.findNodeByAddress(me.getAddress());
int current = me.getCurrent();
int voltage = me.getVoltage();
int temperature = me.getTemperature();
final BidibMessageInterface boostDiagResponse =
requestFactory.createBoosterDiagnosticResponse(nodeAddress, messageNum, current, voltage, temperature);
final List subscriptionDataList =
getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_BOOSTER);
publishAsGuestResponseNotifyMessage(originNode, boostDiagResponse, subscriptionDataList);
}
catch (ProtocolException ex) {
LOGGER.warn("Publish guestResponseNotifyMessage failed.", ex);
}
}
/**
* Publish the message as guest response notify message.
*
* @param bidibMessage
* the original bidib message
* @param nodeAddress
* the node address of the sender node
* @param subscriptionDataList
* the subscription data list
* @throws ProtocolException
* thrown if create {@code GuestResponseNotifyMessage} failed
*/
@Override
public void publishAsGuestResponseNotifyMessage(
final NodeInterface originNode, final BidibMessageInterface bidibMessage,
final List subscriptionDataList) throws ProtocolException {
if (CollectionUtils.isNotEmpty(subscriptionDataList)) {
for (NodeSubscriptionData nodeSubscriptionData : subscriptionDataList) {
byte[] nodeAddress = nodeSubscriptionData.getSubscriberAddress();
TargetModeEnum targetMode = nodeSubscriptionData.getTargetMode();
LOGGER
.info("Prepared nodeAddress: {}, targetMode: {}, bidibMessage: {}",
NodeUtils.formatAddress(nodeAddress), targetMode, bidibMessage);
// send the MSG_GUEST_RESP_NOTIFY to the node
final GuestResponseNotifyMessage gateResponseNotifyMessage =
new GuestResponseNotifyMessage(nodeAddress, 0, TargetModeEnum.BIDIB_TARGET_MODE_UID,
NodeUtils.getTargetModeUniqueId(TargetModeEnum.BIDIB_TARGET_MODE_UID, originNode.getUniqueId()),
bidibMessage.getNum(), bidibMessage.getMessageContent());
publishMessage(nodeSubscriptionData, gateResponseNotifyMessage);
}
}
else {
LOGGER.info("No subscribers for bidibMessage: {}", bidibMessage);
}
}
/**
* Publish the message to the node with the provided nodeAddress.
*
* @param nodeSubscriptionData
* the node subscription data
* @param bidibMessage
* the message
*/
@Override
public void publishMessage(
final NodeSubscriptionData nodeSubscriptionData, final BidibMessageInterface bidibMessage) {
LOGGER
.info("Publish message, current subscriber address: {}, subscriber uniqueId: {}",
NodeUtils.formatAddress(nodeSubscriptionData.getSubscriberAddress()),
ByteUtils.getUniqueIdAsString(nodeSubscriptionData.getSubscriberUniqueId()));
// TODO must evaluate if the addresses are subscribed to the current node address and filter out the not
// matching addresses
// send the message to the connection
try {
final NodeInterface node = nodeProvider.findNodeByAddress(nodeSubscriptionData.getSubscriberAddress());
final GatewayConnection gatewayConnection = (GatewayConnection) this.connection;
if (node != null) {
bidibMessage.setAddr(nodeSubscriptionData.getSubscriberAddress());
gatewayConnection.publishBidibMessage(node, bidibMessage);
}
else {
LOGGER.warn("The node is not available, node address: {}", nodeSubscriptionData.getSubscriberAddress());
}
}
catch (Exception ex) {
LOGGER.warn("Publish bidib message to connection failed: {}", bidibMessage, ex);
}
}
public Disposable subscribeSubscriberEvents(Consumer onNext, Consumer onError) {
return subjectSubscriberEvents
.subscribeOn(Schedulers.single()).observeOn(Schedulers.computation()).subscribe(onNext, onError);
}
public Map> getSubscriptions() {
return MapUtils.unmodifiableMap(subscriptions);
}
}