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

org.bidib.wizard.localhost.LocalHostBidibDistributedMessageHandler Maven / Gradle / Ivy

package org.bidib.wizard.localhost;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
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.collections4.Predicate;
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.helpers.Context;
import org.bidib.jbidibc.messages.message.AccessorySetMessage;
import org.bidib.jbidibc.messages.message.BidibGuestMessage;
import org.bidib.jbidibc.messages.message.BidibMessageInterface;
import org.bidib.jbidibc.messages.message.BidibResponseFactory;
import org.bidib.jbidibc.messages.message.CommandStationDriveMessage;
import org.bidib.jbidibc.messages.message.CommandStationSetStateMessage;
import org.bidib.jbidibc.messages.message.GuestRequestSendMessage;
import org.bidib.jbidibc.messages.message.GuestRequestSubscribeMessage;
import org.bidib.jbidibc.messages.message.GuestResponseNotifyMessage;
import org.bidib.jbidibc.messages.message.GuestResponseSentMessage;
import org.bidib.jbidibc.messages.message.GuestResponseSubscriptionMessage;
import org.bidib.jbidibc.messages.message.NodeTabGetAllMessage;
import org.bidib.jbidibc.messages.message.NodeTabGetNextMessage;
import org.bidib.jbidibc.messages.message.RequestFactory;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.ConversionUtils;
import org.bidib.jbidibc.messages.utils.NodeUtils;
import org.bidib.jbidibc.messages.utils.ThreadFactoryBuilder;
import org.bidib.wizard.api.model.Accessory;
import org.bidib.wizard.api.model.CommandStationNodeInterface;
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.SwitchingNodeService;
import org.bidib.wizard.api.utils.AccessoryListUtils;
import org.bidib.wizard.localhost.event.SubscriberEvent;
import org.bidib.wizard.localhost.event.SubscriberEvent.SubscribeAction;
import org.bidib.wizard.localhost.exception.InvalidNodeClassException;
import org.bidib.wizard.localhost.exception.InvalidSubscriptionException;
import org.bidib.wizard.model.status.CommandStationStatus;
import org.bidib.wizard.model.status.DirectionStatus;
import org.bidib.wizard.model.status.SpeedSteps;
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;

public class LocalHostBidibDistributedMessageHandler implements BidibDistributedMessageListener {

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

    private final BidibConnection connection;

    private final NodeProvider nodeProvider;

    private final BidibResponseFactory responseFactory;

    private final CommandStationService commandStationService;

    private final SwitchingNodeService switchingNodeService;

    private CompositeDisposable compDispMessages = new CompositeDisposable();

    private 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 static final class ReceiveMessgeContainer {
        private final BidibNode bidibNode;

        private final BidibMessageInterface message;

        /**
         * Create a new container to transport the node and the message that is releated to the node.
         * 
         * @param bidibNode
         *            the node that sent the message (origin of message)
         * @param message
         *            the message
         */
        public ReceiveMessgeContainer(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 BidibResponseFactory responseFactory, final CommandStationService commandStationService,
        final SwitchingNodeService switchingNodeService) {
        LOGGER.info("Create new instance of LocalHostBidibDistributedMessageHandler.");

        this.connection = connection;
        this.nodeProvider = nodeProvider;
        this.responseFactory = responseFactory;
        this.commandStationService = commandStationService;
        this.switchingNodeService = switchingNodeService;

        this.subjectSubscriberEvents = PublishSubject.create();

        // subscribe to messages of the connection
        this.computationScheduler =
            RxJavaPlugins
                .createComputationScheduler(
                    new ThreadFactoryBuilder().setNameFormat("localHostSubjectMessagesWorkers-thread-%d").build());

        Disposable dispMessages =
            this.connection.getSubjectMessages().subscribeOn(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 ReceiveMessgeContainer messageContainer =
                        this.receiveQueue.poll(receiveTimeout, TimeUnit.MILLISECONDS);
                    if (messageContainer != null) {
                        handleDistributedMessage(messageContainer);
                    }
                }
                catch (InterruptedException ex) {
                    LOGGER.warn("Process receive queue was interrupted.");
                    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 ReceiveMessgeContainer messgeContainer = new ReceiveMessgeContainer(bidibNode, message);
        this.receiveQueue.add(messgeContainer);
    }

    /**
     * Process the distributed message.
     * 
     * @param messgeContainer
     *            the container with the node and the message
     */
    private void handleDistributedMessage(final ReceiveMessgeContainer messgeContainer) {

        final BidibNode bidibNode = messgeContainer.getBidibNode();
        final BidibMessageInterface message = messgeContainer.getMessage();

        final RequestFactory requestFactory = bidibNode.getRequestFactory();

        int type = ByteUtils.getInt(message.getType());
        switch (type) {
            case BidibLibrary.MSG_GATE_REQ_SUBSCRIBE:
            case BidibLibrary.MSG_GATE_REQ_SEND:
            case BidibLibrary.MSG_GATE_REQ_TAKE_RESPONSIBILITY:
            case BidibLibrary.MSG_GATE_REQ_PASS_RESPONSIBILITY:
            case BidibLibrary.MSG_GUEST_RESP_SUBSCRIPTION:
            case BidibLibrary.MSG_GUEST_RESP_SENT:
            case BidibLibrary.MSG_GUEST_RESP_NOTIFY:
            case BidibLibrary.MSG_GUEST_RESP_RESPONSIBILITY:
                break;
            case BidibLibrary.MSG_GUEST_REQ_SUBSCRIBE:
                handleGuestReqSubscribe(requestFactory, (GuestRequestSubscribeMessage) message, this.connection,
                    bidibNode);
                break;
            case BidibLibrary.MSG_GUEST_REQ_SEND:
                handleGuestReqSend(requestFactory, (GuestRequestSendMessage) message, this.connection, bidibNode);
                break;
            case BidibLibrary.MSG_GUEST_REQ_TAKE_RESPONSIBILITY:
            case BidibLibrary.MSG_GUEST_REQ_PASS_RESPONSIBILITY:
            case BidibLibrary.MSG_GATE_RESP_SUBSCRIPTION:
            case BidibLibrary.MSG_GATE_RESP_SENT:
            case BidibLibrary.MSG_GATE_RESP_FYI:
            case BidibLibrary.MSG_GATE_RESP_RESPONSIBILITY:
                break;
            default:
                break;
        }
    }

    /**
     *
     * @param requestFactory
     *            the request factory
     * @param guestRequestSubscribeMessage
     *            the message
     * @param connection
     *            the connection
     * @param bidibNode
     *            the bidib node that sent the message
     */
    protected void handleGuestReqSubscribe(
        final RequestFactory requestFactory, 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();

        // TODO should we better resolve the address to the uniqueId of the origin node?

        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()), 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);

        byte[] targetModeUniqueId = null;
        NodeInterface node = null;

        try {
            // TODO we must provide the response for all targetModes

            switch (targetMode) {
                case BIDIB_TARGET_MODE_UID:
                    byte[] uniqueId = guestRequestSubscribeMessage.getUniqueId();
                    LOGGER.info("Publish gateway response, uniqueId: {}", ByteUtils.convertUniqueIdToString(uniqueId));

                    node = nodeProvider.findNodeByUniqueIdWithoutClass(uniqueId);
                    if (node == null) {
                        LOGGER.warn("No node found with uniqueId: {}", ByteUtils.convertUniqueIdToString(uniqueId));
                        acknowledge = 0xFF;
                    }
                    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.");
                        acknowledge = 0xFF;
                    }
                    else {
                        node = dccGenNodes.get(0);
                    }
                    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.");
                        acknowledge = 0xFF;
                    }
                    else {
                        node = boosterNodes.get(0);
                    }
                    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.");
                        acknowledge = 0xFF;
                    }
                    else {
                        node = accessoryNodes.get(0);
                    }
                    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.");
                        acknowledge = 0xFF;
                    }
                    else {
                        node = switchNodes.get(0);
                    }
                    break;
                case BIDIB_TARGET_MODE_TOP:
                    node = nodeProvider.findNodeByAddress(Node.ROOTNODE_ADDR);
                    if (node == null) {
                        LOGGER.warn("No root node found!");
                        acknowledge = 0xFF;
                    }
                    break;
                default:
                    LOGGER.warn("Unsupported targetMode detected: {}", targetMode);

                    throw new InvalidSubscriptionException("Unsupported targetMode detected: " + targetMode);
            }

            if (node != null) {
                targetModeUniqueId = NodeUtils.getTargetModeUniqueId(targetMode, node.getUniqueId());
            }

            // send the MSG_GUEST_RESP_SUBSCRIPTION to the node
            final GuestResponseSubscriptionMessage gateResponseSubscriptionMessage =
                new GuestResponseSubscriptionMessage(nodeSubscriptionData.getSubscriberAddress(), 0,
                    TargetModeEnum.BIDIB_TARGET_MODE_UID, targetModeUniqueId, acknowledge, subscription);

            publishMessage(nodeSubscriptionData.getSubscriberAddress(), gateResponseSubscriptionMessage);
        }
        catch (InvalidSubscriptionException ex) {
            LOGGER.warn("The subscription is not supported.", ex);

            acknowledge = 0x80;

            // if (node != null) {
            // targetModeUniqueId = NodeUtils.getTargetModeUniqueId(node.getUniqueId());
            // }

            // send the MSG_GUEST_RESP_SUBSCRIPTION to the node
            try {
                final GuestResponseSubscriptionMessage gateResponseSubscriptionMessage =
                    new GuestResponseSubscriptionMessage(nodeSubscriptionData.getSubscriberAddress(), 0,
                        TargetModeEnum.BIDIB_TARGET_MODE_UID, targetModeUniqueId, acknowledge, subscription);

                publishMessage(nodeSubscriptionData.getSubscriberAddress(), gateResponseSubscriptionMessage);
            }
            catch (ProtocolException ex1) {
                LOGGER.warn("Publish gateResponseSubscriptionMessage failed.", ex1);
            }
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Publish gateResponseSubscriptionMessage failed.", ex);
        }
    }

    protected void prepareSubscription(int subscriptionClasses, final NodeSubscriptionData nodeSubscriptionData) {

        for (MessageClassEnum messageClass : MessageClassEnum.values()) {

            if (messageClass == MessageClassEnum.BIDIB_MESSAGE_CLASS_NOTHING) {
                continue;
            }

            boolean isActive = MessageClassEnum.isActive(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, new Predicate() {

                            @Override
                            public boolean evaluate(NodeSubscriptionData data) {
                                return 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
            }
        }
    }

    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 RequestFactory requestFactory, final GuestRequestSendMessage guestRequestSendMessage,
        final BidibConnection connection, final BidibNode bidibNode) {
        LOGGER.info("Handle the GuestRequestSendMessage: {}", guestRequestSendMessage);

        final BidibNodeContainer bidibNodeContainer = getBidibNodeContainer(bidibNode.getCachedUniqueId());

        final byte[] originNodeAddress = guestRequestSendMessage.getAddr();
        final TargetModeEnum targetMode = guestRequestSendMessage.getTargetMode();

        final NodeInterface node;
        try {
            node = findNodeByTargetMode(guestRequestSendMessage, 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 =
                    new GuestResponseSentMessage(originNodeAddress, 0, targetMode, new byte[0],
                        new byte[] { ByteUtils.getLowByte(ackSequence), ByteUtils.getLowByte(result) });

                publishMessage(originNodeAddress, guestResponseSentMessage);
            }
            catch (ProtocolException ex1) {
                LOGGER.info("Process command from wrapped message failed.", ex1);
            }
            return;
        }

        // TODO force subscription on addressed node

        final NodeSubscriptionData nodeSubscriptionData =
            new NodeSubscriptionData(originNodeAddress, targetMode, node.getUniqueId(), null);
        // new NodeSubscriptionData(originNodeAddress, targetMode, bidibNode.getCachedUniqueId(), null);

        int subscription = 0xFFFF;
        prepareSubscription(subscription, nodeSubscriptionData);
        byte[] targetNodeAddr = node.getAddr();

        LOGGER
            .info("Current originNodeAddress: {}, targetMode: {}, targetNodeAddr: {}", originNodeAddress, targetMode,
                targetNodeAddr);

        try {
            byte[] wrappedContent = guestRequestSendMessage.getWrappedContent();
            LOGGER.info("Extracted wrappedContent: {}", ByteUtils.bytesToHex(wrappedContent));

            // prepare the message
            int addrLen = targetNodeAddr.length;
            if (!Arrays.equals(targetNodeAddr, Node.ROOTNODE_ADDR)) {
                addrLen += 1; /* term 0 */
            }
            // len, addr, sequenceNum, wrappedContent
            int len = addrLen + 1 /* seq */ + wrappedContent.length;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            baos.write(len & 0xFF);
            baos.write(targetNodeAddr);
            if (!Arrays.equals(targetNodeAddr, Node.ROOTNODE_ADDR)) {
                baos.write(0); // term 0
            }
            baos.write(0); // seq
            baos.write(wrappedContent);

            baos.write(ByteUtils.getLowByte(BidibLibrary.BIDIB_PKT_MAGIC));

            wrappedContent = baos.toByteArray();

            LOGGER.info("Process wrappedContent: {}", ByteUtils.bytesToHex(wrappedContent));

            requestFactory.create(wrappedContent).forEach(command -> {
                LOGGER.info("Current command: {}", command);

                try {
                    // NodeInterface node = findNodeByTargetMode(guestRequestSendMessage, targetMode);
                    switch (ByteUtils.getInt(command.getType())) {
                        case BidibLibrary.MSG_SYS_GET_MAGIC:
                            handleSysGetMagicCommand(command, nodeSubscriptionData, node, result -> {
                                sendGuestResponse(nodeSubscriptionData, getTargetModeUniqueId(targetMode, node),
                                    guestRequestSendMessage.getNum(), result, null);
                            });
                            break;
                        case BidibLibrary.MSG_CS_SET_STATE:

                            handleCsSetCommand(command, nodeSubscriptionData, node, result -> {
                                sendGuestResponse(nodeSubscriptionData, getTargetModeUniqueId(targetMode, node),
                                    guestRequestSendMessage.getNum(), result, null);
                            });
                            break;
                        case BidibLibrary.MSG_CS_DRIVE:

                            handleCsDriveCommand(command, nodeSubscriptionData, node, result -> {
                                sendGuestResponse(nodeSubscriptionData, getTargetModeUniqueId(targetMode, node),
                                    guestRequestSendMessage.getNum(), result, null);
                            });
                            break;
                        case BidibLibrary.MSG_ACCESSORY_SET:

                            handleAccessorySetCommand(command, nodeSubscriptionData, node, result -> {
                                sendGuestResponse(nodeSubscriptionData, getTargetModeUniqueId(targetMode, node),
                                    guestRequestSendMessage.getNum(), result, null);
                            });
                            break;

                        case BidibLibrary.MSG_NODETAB_GETALL:

                            handleNodeTabGetAllCommand(command, nodeSubscriptionData, node, result -> {
                                sendGuestResponse(nodeSubscriptionData, getTargetModeUniqueId(targetMode, node),
                                    guestRequestSendMessage.getNum(), result, null);
                            }, bidibNodeContainer);
                            break;

                        case BidibLibrary.MSG_NODETAB_GETNEXT:

                            handleNodeTabGetNextCommand(command, nodeSubscriptionData, node, result -> {
                                sendGuestResponse(nodeSubscriptionData, getTargetModeUniqueId(targetMode, node),
                                    guestRequestSendMessage.getNum(), result, null);
                            }, bidibNodeContainer);
                            break;

                        default:
                            break;
                    }
                }
                catch (InvalidNodeClassException ex) {
                    LOGGER.info("Process command failed.", ex);
                    // handle exception
                    try {
                        handleInvalidNodeClassError(nodeSubscriptionData, getTargetModeUniqueId(targetMode, node),
                            guestRequestSendMessage);
                    }
                    catch (ProtocolException ex1) {
                        LOGGER.info("Publish the error message failed.", ex1);
                    }
                }
                catch (ProtocolException ex) {
                    LOGGER.info("Process command failed.", ex);
                }
                catch (Exception ex) {
                    LOGGER.info("Process command failed.", ex);
                }

            });
        }
        catch (ProtocolException ex) {
            LOGGER.info("Process command from wrapped message failed.", ex);
        }
        catch (Exception ex) {
            LOGGER.info("Process command from wrapped message failed.", ex);
        }
    }

    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.getUniqueId());
                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;
    }

    private byte[] getTargetModeUniqueId(final TargetModeEnum targetMode, final NodeInterface node) {
        byte[] targetModeUniqueId = NodeUtils.getTargetModeUniqueId(targetMode, node.getUniqueId());
        return targetModeUniqueId;
    }

    private void handleInvalidNodeClassError(
        final NodeSubscriptionData nodeSubscriptionData, final byte[] targetModeUniqueId,
        final BidibGuestMessage guestMessage) throws ProtocolException {
        LOGGER
            .error("Handle the invalid node class error. Provided addressNodeData: {}, guestMessage: {}",
                nodeSubscriptionData, guestMessage);

        // byte[] targetModeUniqueId = getTargetModeUniqueId(targetMode, guestMessage);

        int result = 0xFF; // no existing node
        sendGuestResponse(nodeSubscriptionData, targetModeUniqueId, guestMessage.getNum(), result, null);

    }

    private void sendGuestResponse(
        final NodeSubscriptionData nodeSubscriptionData, byte[] targetModeUniqueId, int ackSequence, int result,
        byte[] details) throws ProtocolException {

        // final TargetModeEnum targetMode = nodeSubscriptionData.getTargetMode();
        final TargetModeEnum targetMode = TargetModeEnum.BIDIB_TARGET_MODE_UID;

        LOGGER
            .info("Send the GuestResponse, ackSequence: {}, targetMode: {}, targetModeUniqueId: {}", ackSequence,
                targetMode, ByteUtils.bytesToHex(targetModeUniqueId));

        // send the MSG_GUEST_RESP_SENT to the node
        final GuestResponseSentMessage guestResponseSentMessage =
            new GuestResponseSentMessage(nodeSubscriptionData.getSubscriberAddress(), 0, targetMode, targetModeUniqueId,
                ByteUtils
                    .concat(new byte[] { ByteUtils.getLowByte(ackSequence), ByteUtils.getLowByte(result) }, details));

        publishMessage(nodeSubscriptionData.getSubscriberAddress(), guestResponseSentMessage);

    }

    private static void sendGuestResponse(final Consumer sendGuestResponse, Integer result) {

        // send the RESP_SENT
        try {
            sendGuestResponse.accept(result);
        }
        catch (Throwable ex) {
            LOGGER.warn("Send guest response failed", ex);
        }
    }

    private void handleSysGetMagicCommand(
        final BidibMessageInterface command, final NodeSubscriptionData nodeSubscriptionData, final NodeInterface node,
        final Consumer sendGuestResponse) throws ProtocolException {

        // send the RESP_SENT
        sendGuestResponse(sendGuestResponse, node != null ? Integer.valueOf(0x00) : Integer.valueOf(0xFF));

        if (node != null) {
            // final NodeSubscriptionData originNodeAddress = nodeSubscriptionData;

            final BidibMessageInterface sysMagicResponse =
                responseFactory.createSysMagicResponse(node.getAddr(), 0, node.getNode().getMagic());
            LOGGER.info("Prepared sysMagicResponse: {}", sysMagicResponse);
            publishAsGuestResponseNotifyMessage(sysMagicResponse, Arrays.asList(nodeSubscriptionData));
        }
    }

    private void handleNodeTabGetAllCommand(
        final BidibMessageInterface command, final NodeSubscriptionData nodeSubscriptionData, final NodeInterface node,
        final Consumer sendGuestResponse, final BidibNodeContainer bidibNodeContainer)
        throws ProtocolException {
        final NodeTabGetAllMessage nodeTabGetAllMessage = (NodeTabGetAllMessage) command;

        // send the RESP_SENT
        sendGuestResponse(sendGuestResponse, node != null ? Integer.valueOf(0x00) : Integer.valueOf(0xFF));

        if (node != null) {

            // reset the current nodeTab index
            bidibNodeContainer.setCurrentNodeTabIndex(0);

            int tabCount = 1;
            if (NodeUtils.hasSubNodesFunctions(node.getUniqueId())) {
                // get the subnodes

                final List subNodes = this.nodeProvider.findSubNodes(node);
                if (CollectionUtils.isNotEmpty(subNodes)) {

                    subNodes.add(0, node);
                    // keep the node info
                    bidibNodeContainer.setSubNodes(subNodes);

                }
                else {
                    subNodes.add(node);
                }
                tabCount = subNodes.size();
            }

            LOGGER.info("Current tabCount: {}", tabCount);

            final BidibMessageInterface sysMagicResponse =
                responseFactory.createNodeTabCountResponse(node.getAddr(), 0, tabCount);
            LOGGER.info("Prepared sysMagicResponse: {}", sysMagicResponse);
            publishAsGuestResponseNotifyMessage(sysMagicResponse, Arrays.asList(nodeSubscriptionData));
        }
    }

    private void handleNodeTabGetNextCommand(
        final BidibMessageInterface command, final NodeSubscriptionData nodeSubscriptionData, final NodeInterface node,
        final Consumer sendGuestResponse, final BidibNodeContainer bidibNodeContainer)
        throws ProtocolException {
        final NodeTabGetNextMessage nodeTabGetNextMessage = (NodeTabGetNextMessage) command;

        // send the RESP_SENT
        sendGuestResponse(sendGuestResponse, node != null ? Integer.valueOf(0x00) : Integer.valueOf(0xFF));

        if (node != null) {

            final NodeInterface subNode = bidibNodeContainer.getNextSubNode();
            BidibMessageInterface nodeTabResponse = null;
            if (subNode != null) {
                LOGGER.info("Current subNode: {}", subNode);
                // get the correct nodeTabVersion
                int nodeTabVersion = subNode.getNode().getVersion();
                int localAddress = NodeUtils.getLocalAddress(subNode.getAddr());
                long uniqueId = subNode.getUniqueId();

                nodeTabResponse =
                    responseFactory.createNodeTabResponse(node.getAddr(), 0, nodeTabVersion, localAddress, uniqueId);
            }
            else {
                LOGGER.info("Next node not found.");

                // we are done
                bidibNodeContainer.clearSubNodes();

                int localAddress = 255;
                nodeTabResponse = responseFactory.createNodeNotAvailableResponse(node.getAddr(), 0, localAddress);
            }
            LOGGER.info("Prepared nodeTabResponse: {}", nodeTabResponse);
            publishAsGuestResponseNotifyMessage(nodeTabResponse, Arrays.asList(nodeSubscriptionData));
        }
    }

    private void handleCsSetCommand(
        final BidibMessageInterface command, final NodeSubscriptionData nodeSubscriptionData, final NodeInterface node,
        final Consumer sendGuestResponse) throws ProtocolException {
        final CommandStationSetStateMessage csSetStateMessage = (CommandStationSetStateMessage) command;

        final CommandStationState commandStationState = csSetStateMessage.getState();
        LOGGER.info("The requested CS state: {}", commandStationState);

        switch (commandStationState) {
            case QUERY:
                // send the RESP_SENT
                sendGuestResponse(sendGuestResponse, Integer.valueOf(0x00));
                publishCommandStationStateDirect(nodeSubscriptionData, node);
                break;
            case GO:
            case GO_IGN_WD:
            case STOP:
            case SOFTSTOP:
            case OFF:
                // send the command station state to the node
                if (node != null && node.getCommandStationNode() != null) {

                    final CommandStationNodeInterface commandStationNode = node.getCommandStationNode();

                    LOGGER
                        .info("The requested CS state: {}, current CS state: {}", commandStationState,
                            commandStationNode.getCommandStationState());

                    // send the RESP_SENT
                    sendGuestResponse(sendGuestResponse, Integer.valueOf(0x00));

                    if (commandStationNode.getCommandStationState() == commandStationState
                        || (CommandStationState.isOffState(commandStationNode.getCommandStationState())
                            && commandStationState == CommandStationState.STOP)) {
                        LOGGER
                            .info(
                                "The command station is in the requested state already. Do not send the requested status.");

                        publishCommandStationStateDirect(nodeSubscriptionData, node);
                    }
                    else {
                        commandStationService
                            .setCommandStationState(connection.getConnectionId(), node.getCommandStationNode(),
                                CommandStationStatus.valueOf(commandStationState));
                    }
                }
                else {
                    LOGGER.warn("No command station node available.");
                    // send the RESP_SENT
                    sendGuestResponse(sendGuestResponse, Integer.valueOf(0xFF));
                }
                break;
            default:
                break;
        }
    }

    private void handleCsDriveCommand(
        final BidibMessageInterface command, final NodeSubscriptionData nodeSubscriptionData, final NodeInterface node,
        final Consumer sendGuestResponse) throws ProtocolException {
        final CommandStationDriveMessage csDriveMessage = (CommandStationDriveMessage) command;
        LOGGER.info("The requested CS drive message: {}", csDriveMessage);

        // send the command station state to the node
        if (node != null && node.getCommandStationNode() != null) {

            // send the RESP_SENT
            sendGuestResponse(sendGuestResponse, Integer.valueOf(0x00));

            int locoAddress = csDriveMessage.getDecoderAddress().getAddress();
            SpeedSteps speedSteps = SpeedSteps.valueOf(csDriveMessage.getSpeedSteps());
            Integer speed = csDriveMessage.getSpeed();

            int outputActive = csDriveMessage.getOutputActiveBits();
            if (ByteUtils.isBitSetEqual(outputActive, 0, 0)) {
                speed = null;
            }

            final DirectionStatus direction = DirectionStatus.valueOf(csDriveMessage.getDriveState().getDirection());
            final Context context = null;

            LOGGER
                .info("The loco address: {}, speedSteps: {}, speed: {}, direction: {}, outputActive: {}", locoAddress,
                    speedSteps, speed, direction, outputActive);

            // prepare the active functions
            BitSet activeFunctions = new BitSet(8);

            // bit 0 is the speed output
            outputActive = outputActive >> 1;

            for (int bit = 0; bit < 8; bit++) {
                int bitVal = ByteUtils.getBit(outputActive, bit);
                LOGGER.debug("Current bit: {}, bitVal: {}", bit, bitVal);

                activeFunctions.set(bit, ByteUtils.getBit(outputActive, bit) == 1);
            }
            BitSet functions = ConversionUtils.convertFunctions(csDriveMessage.getDriveState().getFunctions());

            commandStationService
                .setSpeed(connection.getConnectionId(), node.getCommandStationNode(), locoAddress, speedSteps, speed,
                    direction, activeFunctions, functions, context);
        }
        else {
            LOGGER.warn("No command station node available.");

            // send the RESP_SENT
            sendGuestResponse(sendGuestResponse, Integer.valueOf(0xFF));
        }
    }

    private void handleAccessorySetCommand(
        final BidibMessageInterface command, final NodeSubscriptionData nodeSubscriptionData, final NodeInterface node,
        final Consumer sendGuestResponse) throws ProtocolException, InvalidNodeClassException {
        final AccessorySetMessage accessorySetMessage = (AccessorySetMessage) command;

        int accessoryNumber = accessorySetMessage.getAccessoryNumber();
        int aspectNumber = accessorySetMessage.getAspect();
        LOGGER.info("Set the accessory: {}, aspect: {}, node: {}", accessoryNumber, aspectNumber, node);

        if (node != null && node.getSwitchingNode() != null) {

            final Accessory accessory =
                AccessoryListUtils.findAccessoryByAccessoryNumber(node.getAccessories(), accessoryNumber);
            if (accessory != null) {
                // send the RESP_SENT
                sendGuestResponse(sendGuestResponse, Integer.valueOf(0x00));

                this.switchingNodeService
                    .setAccessoryAspect(connection.getConnectionId(), node.getSwitchingNode(), accessory, aspectNumber);
            }
            else {
                // send the RESP_SENT
                sendGuestResponse(sendGuestResponse, Integer.valueOf(0x83));

                throw new ProtocolException("Accessory not found, accessoryNumber: " + accessoryNumber);
            }
        }
        else {
            throw new InvalidNodeClassException("Node not found or not a switching node, node: " + 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) {
        try {
            final Feature feature = me.getFeature();
            byte[] nodeAddress = me.getAddress(); // the address of the node

            final BidibMessageInterface csStateResponse =
                responseFactory.createFeatureResponse(nodeAddress, 0, feature);

            // 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);
            publishAsGuestResponseNotifyMessage(csStateResponse, subscriptionDataList);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Publish gateResponseFyiMessage failed.", ex);
        }
    }

    private void notifySystemSubscribers(final SysIdentifyStateMessageEvent me) {
        try {
            final IdentifyState identifyState = me.getIdentifyState();
            byte[] nodeAddress = me.getAddress(); // the address of the node

            final BidibMessageInterface csStateResponse =
                responseFactory.createSysIdentifyStateResponse(nodeAddress, 0, identifyState);

            // 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);
            publishAsGuestResponseNotifyMessage(csStateResponse, subscriptionDataList);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Publish gateResponseFyiMessage failed.", ex);
        }
    }

    private void notifyCommandStationSubscribers(final CommandStationStateMessageEvent me) {

        try {
            final CommandStationState csState = me.getCommandStationState();
            byte[] nodeAddress = me.getAddress(); // the address of the command station node

            final BidibMessageInterface csStateResponse =
                responseFactory.createCommandStationStateResponse(nodeAddress, 0, csState);

            // 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);
            publishAsGuestResponseNotifyMessage(csStateResponse, subscriptionDataList);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Publish gateResponseFyiMessage failed.", ex);
        }
    }

    private void notifyCommandStationSubscribers(final CommandStationDriveAcknowledgeMessageEvent me) {

        try {
            byte[] nodeAddress = me.getAddress();

            final DriveAcknowledge driveAckn = me.getDriveAcknowledge();
            final int dccAddress = me.getDccAddress();
            final Integer acknowledgedMessageNumber = me.getAcknowledgedMessageNumber();

            final BidibMessageInterface csStateResponse =
                responseFactory
                    .createCommandStationDriveAckResponse(nodeAddress, 0, new AddressData(dccAddress, null), driveAckn,
                        acknowledgedMessageNumber);

            final List subscriptionDataList =
                getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_COMMANDSTATION);
            LOGGER.info("Fetch subscriptions for class COMMANDSTATION: {}", subscriptionDataList);
            publishAsGuestResponseNotifyMessage(csStateResponse, subscriptionDataList);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Publish gateResponseFyiMessage failed.", ex);
        }
    }

    private void notifyCommandStationSubscribers(final CommandStationDriveManualMessageEvent me) {

        try {
            byte[] nodeAddress = me.getAddress();

            final DriveState driveState = me.getDriveState();

            final BidibMessageInterface csStateResponse =
                responseFactory.createCommandStationDriveManualResponse(nodeAddress, 0, driveState);

            final List subscriptionDataList =
                getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_COMMANDSTATION);
            LOGGER.info("Fetch subscriptions for class COMMANDSTATION: {}", subscriptionDataList);
            publishAsGuestResponseNotifyMessage(csStateResponse, subscriptionDataList);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Publish gateResponseFyiMessage failed.", ex);
        }
    }

    private void notifyCommandStationSubscribers(final CommandStationDriveStateMessageEvent me) {

        try {
            byte[] nodeAddress = me.getAddress();

            final int opCode = me.getOpCode();
            final DriveState driveState = me.getDriveState();

            final BidibMessageInterface csStateResponse =
                responseFactory.createCommandStationDriveStateResponse(nodeAddress, 0, opCode, driveState);

            final List subscriptionDataList =
                getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_COMMANDSTATION);
            LOGGER.info("Fetch subscriptions for class COMMANDSTATION: {}", subscriptionDataList);
            publishAsGuestResponseNotifyMessage(csStateResponse, subscriptionDataList);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Publish gateResponseFyiMessage failed.", ex);
        }
    }

    private void notifyAccessorySubscribers(final AccessoryStateMessageEvent me) {

        try {
            final AccessoryState accessoryState = me.getAccessoryState();
            byte[] nodeAddress = me.getAddress();

            int accessoryNumber = me.getAccessoryNumber();
            Integer aspect = accessoryState.getAspect();
            byte[] value =
                new byte[] { accessoryState.getTotal(), accessoryState.getExecute(),
                    ByteUtils.getLowByte(accessoryState.getWait()) };

            final BidibMessageInterface accessoryStateResponse =
                responseFactory.createAccessoryStateResponse(nodeAddress, 0, accessoryNumber, aspect, value);

            final List subscriptionDataList =
                getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_ACCESSORY);
            LOGGER.info("Fetch subscriptions for class ACCESSORY: {}", subscriptionDataList);
            publishAsGuestResponseNotifyMessage(accessoryStateResponse, subscriptionDataList);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Publish gateResponseFyiMessage failed.", ex);
        }
    }

    private void notifyBoosterSubscribers(final BoosterStateMessageEvent me) {

        try {
            final BoosterState boosterState = me.getBoosterState();
            byte[] nodeAddress = me.getAddress();

            final BidibMessageInterface boostStateResponse =
                responseFactory.createBoosterStateResponse(nodeAddress, 0, boosterState);

            final List subscriptionDataList =
                getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_BOOSTER);
            LOGGER.info("Fetch subscriptions for class BOOSTER: {}", subscriptionDataList);
            publishAsGuestResponseNotifyMessage(boostStateResponse, subscriptionDataList);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Publish gateResponseFyiMessage failed.", ex);
        }
    }

    private void notifyBoosterSubscribers(final BoosterDiagMessageEvent me) {

        try {
            byte[] nodeAddress = me.getAddress();

            int current = me.getCurrent();
            int voltage = me.getVoltage();
            int temperature = me.getTemperature();

            final BidibMessageInterface boostDiagResponse =
                responseFactory.createBoosterDiagnosticResponse(nodeAddress, 0, current, voltage, temperature);

            final List subscriptionDataList =
                getSubscriptionDataList(MessageClassEnum.BIDIB_MESSAGE_CLASS_BOOSTER);
            publishAsGuestResponseNotifyMessage(boostDiagResponse, subscriptionDataList);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Publish gateResponseNotifyMessage failed.", ex);
        }
    }

    /**
     * Publish the messahe 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
     */
    private void publishAsGuestResponseNotifyMessage(
        final BidibMessageInterface bidibMessage, final List subscriptionDataList)
        throws ProtocolException {

        if (CollectionUtils.isNotEmpty(subscriptionDataList)) {
            for (NodeSubscriptionData nodeSubscriptionData : subscriptionDataList) {

                byte[] nodeAddress = nodeSubscriptionData.getSubscriberAddress();
                // NodeInterface node = this.nodeProvider.findNodeByAddress(nodeAddress);

                // 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, nodeSubscriptionData.getSubscriberUniqueId()),
                        bidibMessage.getNum(), bidibMessage.getMessageContent());

                publishMessage(nodeSubscriptionData, gateResponseNotifyMessage);
            }
        }

    }

    private 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);
            }
        }
        catch (Exception ex) {
            LOGGER.warn("Publish bidib message to connection failed: {}", bidibMessage, ex);
        }
    }

    /**
     * Publish the message to the node with the provided nodeAddress.
     * 
     * @param nodeAddress
     *            the node address
     * @param bidibMessage
     *            the message
     */
    private void publishMessage(byte[] nodeAddress, final BidibMessageInterface bidibMessage) {
        LOGGER
            .info("Publish message, current nodeAddress: {}, bidibMessage: {}", NodeUtils.formatAddress(nodeAddress),
                bidibMessage);
        // send the message to the connection
        try {
            final NodeInterface node = nodeProvider.findNodeByAddress(nodeAddress);
            final GatewayConnection gatewayConnection = (GatewayConnection) this.connection;

            gatewayConnection.publishBidibMessage(node, bidibMessage);
        }
        catch (Exception ex) {
            LOGGER.warn("Publish bidib message to connection failed: {}", bidibMessage, ex);
        }
    }

    /**
     * Publish the command station state directly to the provided originNodeAddress
     * 
     * @param originNodeAddress
     *            the origin node address
     * @param node
     *            the addressed node
     * @throws ProtocolException
     */
    private void publishCommandStationStateDirect(
        final NodeSubscriptionData nodeSubscriptionData, final NodeInterface node) throws ProtocolException {
        CommandStationState csState = node.getCommandStationNode().getCommandStationState();

        LOGGER.info("Publish the initial command station status: {}, node: {}", csState, node);

        final BidibMessageInterface csStateResponse =
            responseFactory.createCommandStationStateResponse(node.getAddr(), 0, csState);

        publishAsGuestResponseNotifyMessage(csStateResponse, Arrays.asList(nodeSubscriptionData));
    }

    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);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy