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

org.bidib.wizard.gateway.service.ProxyNetBidibConnectionAdapter Maven / Gradle / Ivy

package org.bidib.wizard.gateway.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IterableUtils;
import org.bidib.api.json.types.NodeAddress;
import org.bidib.jbidibc.messages.AccessoryState;
import org.bidib.jbidibc.messages.BidibLibrary;
import org.bidib.jbidibc.messages.BidibPort;
import org.bidib.jbidibc.messages.Feature;
import org.bidib.jbidibc.messages.FeedbackConfidenceData;
import org.bidib.jbidibc.messages.FeedbackTimestampData;
import org.bidib.jbidibc.messages.HostAdapter;
import org.bidib.jbidibc.messages.LcConfigX;
import org.bidib.jbidibc.messages.LcMacro;
import org.bidib.jbidibc.messages.Node;
import org.bidib.jbidibc.messages.ProtocolVersion;
import org.bidib.jbidibc.messages.SoftwareVersion;
import org.bidib.jbidibc.messages.StringData;
import org.bidib.jbidibc.messages.enums.CommandStationState;
import org.bidib.jbidibc.messages.enums.InputPortEnum;
import org.bidib.jbidibc.messages.enums.LcOutputType;
import org.bidib.jbidibc.messages.enums.LightPortEnum;
import org.bidib.jbidibc.messages.enums.OccupationState;
import org.bidib.jbidibc.messages.enums.PortModelEnum;
import org.bidib.jbidibc.messages.enums.SwitchPortEnum;
import org.bidib.jbidibc.messages.exception.ProtocolException;
import org.bidib.jbidibc.messages.message.AccessoryGetMessage;
import org.bidib.jbidibc.messages.message.AccessoryParaGetMessage;
import org.bidib.jbidibc.messages.message.BidibMessage;
import org.bidib.jbidibc.messages.message.BidibMessageInterface;
import org.bidib.jbidibc.messages.message.BidibResponseFactory;
import org.bidib.jbidibc.messages.message.CommandStationSetStateMessage;
import org.bidib.jbidibc.messages.message.FeatureGetAllMessage;
import org.bidib.jbidibc.messages.message.FeatureNotAvailableResponse;
import org.bidib.jbidibc.messages.message.FeatureResponse;
import org.bidib.jbidibc.messages.message.FeatureSetMessage;
import org.bidib.jbidibc.messages.message.FeedbackGetRangeMessage;
import org.bidib.jbidibc.messages.message.LcConfigXGetAllMessage;
import org.bidib.jbidibc.messages.message.LcConfigXResponse;
import org.bidib.jbidibc.messages.message.LcConfigXSetMessage;
import org.bidib.jbidibc.messages.message.LcMacroGetMessage;
import org.bidib.jbidibc.messages.message.LcMacroParaGetMessage;
import org.bidib.jbidibc.messages.message.LcOutputMessage;
import org.bidib.jbidibc.messages.message.LcPortQueryAllMessage;
import org.bidib.jbidibc.messages.message.NodeTabCountResponse;
import org.bidib.jbidibc.messages.message.NodeTabResponse;
import org.bidib.jbidibc.messages.message.StringGetMessage;
import org.bidib.jbidibc.messages.message.StringResponse;
import org.bidib.jbidibc.messages.port.PortConfigUtils;
import org.bidib.jbidibc.messages.port.PortConfigValue;
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.Accessory;
import org.bidib.wizard.api.model.Macro;
import org.bidib.wizard.api.model.MacroRepeatDay;
import org.bidib.wizard.api.model.MacroRepeatTime;
import org.bidib.wizard.api.model.NodeInterface;
import org.bidib.wizard.api.model.StartCondition;
import org.bidib.wizard.api.model.TimeStartCondition;
import org.bidib.wizard.api.model.connection.AbstractMessageEvent;
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.CommandStationStateMessageEvent;
import org.bidib.wizard.api.model.connection.event.LcConfigXMessageEvent;
import org.bidib.wizard.api.model.connection.event.LcNaMessageEvent;
import org.bidib.wizard.api.model.connection.event.LcStatMessageEvent;
import org.bidib.wizard.api.model.connection.event.NodeLostMessageEvent;
import org.bidib.wizard.api.model.connection.event.NodeNewMessageEvent;
import org.bidib.wizard.api.model.connection.event.OccupancyStateMessageEvent;
import org.bidib.wizard.api.model.connection.event.StringMessageEvent;
import org.bidib.wizard.api.model.connection.event.SysMagicMessageEvent;
import org.bidib.wizard.api.model.connection.event.SysProtocolVersionMessageEvent;
import org.bidib.wizard.api.model.connection.event.SysSoftwareVersionMessageEvent;
import org.bidib.wizard.api.model.event.NodeStatusEvent.StatusIdentifier;
import org.bidib.wizard.api.model.function.Function;
import org.bidib.wizard.api.model.function.SystemFunction;
import org.bidib.wizard.api.service.node.BoosterService;
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.api.utils.JsonNodeUtils;
import org.bidib.wizard.api.utils.PortListUtils;
import org.bidib.wizard.gateway.model.connection.ProxyBidibConnection;
import org.bidib.wizard.gateway.model.node.ProxyInterfaceNode;
import org.bidib.wizard.gateway.model.node.ProxyNode;
import org.bidib.wizard.model.ports.BacklightPort;
import org.bidib.wizard.model.ports.FeedbackPort;
import org.bidib.wizard.model.ports.GenericPort;
import org.bidib.wizard.model.ports.InputPort;
import org.bidib.wizard.model.ports.LightPort;
import org.bidib.wizard.model.ports.Port;
import org.bidib.wizard.model.ports.ServoPort;
import org.bidib.wizard.model.ports.SwitchPairPort;
import org.bidib.wizard.model.ports.SwitchPort;
import org.bidib.wizard.model.status.BidibStatus;
import org.bidib.wizard.model.status.BoosterStatus;
import org.bidib.wizard.model.status.CommandStationStatus;
import org.bidib.wizard.model.status.FeedbackConfidenceStatus;
import org.bidib.wizard.model.status.InputPortStatus;
import org.bidib.wizard.model.status.LightPortStatus;
import org.bidib.wizard.model.status.SwitchPortStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * The {@code ProxyNetBidibConnectionAdapter} is used to transform the bidib messages to calls on the
 * {@code ProxyBidibConnection} that will call the backend.
 */
public class ProxyNetBidibConnectionAdapter implements ProxyConnectionAdapter {

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

    private static final Logger LOGGER_LCCONFIGX = LoggerFactory.getLogger(LcConfigX.class);

    private final BidibResponseFactory responseFactory;

    @Autowired
    private NodeService nodeService;

    @Autowired
    private SwitchingNodeService switchingNodeService;

    @Autowired
    private CommandStationService commandStationService;

    @Autowired
    private BoosterService boosterService;

    private boolean mimicGateway = true;

    private final org.bidib.jbidibc.messages.logger.Logger messageLogger;

    private final ScheduledExecutorService serviceWorker;

    public ProxyNetBidibConnectionAdapter(final BidibResponseFactory responseFactory) {
        this.responseFactory = responseFactory;

        this.messageLogger = new org.bidib.jbidibc.messages.logger.Logger() {

            @Override
            public void debug(String format, Object... arguments) {
                LOGGER_LCCONFIGX.debug(format, arguments);
            }

            @Override
            public void info(String format, Object... arguments) {
                LOGGER_LCCONFIGX.info(format, arguments);
            }

            @Override
            public void warn(String format, Object... arguments) {
                LOGGER_LCCONFIGX.warn(format, arguments);
            }

            @Override
            public void error(String format, Object... arguments) {
                LOGGER_LCCONFIGX.error(format, arguments);
            }
        };

        this.serviceWorker =
            Executors
                .newScheduledThreadPool(1,
                    new ThreadFactoryBuilder().setNameFormat("proxyConnectionServiceWorkers-thread-%d").build());
    }

    @Override
    public void processBidibMessageFromGuest(
        final ProxyBidibConnection bidibProxyConnection,
        final HostAdapter distributedHostAdapter, final BidibMessageInterface bidibMessage)
        throws ProtocolException {

        LOGGER.info("Process the bidibMessage: {}", bidibMessage);

        try {
            int messageType = ByteUtils.getInt(bidibMessage.getType());

            doProcessBidibMessageFromGuest(bidibProxyConnection, distributedHostAdapter, bidibMessage, messageType);
        }
        catch (Exception ex) {
            LOGGER.warn("Process bidibMessage failed: {}", bidibMessage, ex);

            throw new ProtocolException("Process message failed.");
        }

    }

    private void doProcessBidibMessageFromGuest(
        final ProxyBidibConnection bidibProxyConnection,
        final HostAdapter distributedHostAdapter, final BidibMessageInterface bidibMessage,
        int messageType) throws ProtocolException {

        try {

            // get the node by address
            final NodeInterface node =
                Optional
                    .ofNullable(bidibProxyConnection.getNodeProvider().findNodeByAddress(bidibMessage.getAddr()))
                    .orElseThrow(() -> new ProtocolException(
                        "No node available with address: " + NodeUtils.formatAddress(bidibMessage.getAddr())));

            final ProxyNode proxyNode = ProxyNode.getProxyNode(node);

            switch (messageType) {
                case BidibLibrary.MSG_SYS_GET_UNIQUE_ID:
                    Long uniqueId = proxyNode.getUniqueId();

                    BidibMessageInterface sysUniqueIdResponse =
                        responseFactory.createSysUniqueIdResponse(bidibMessage.getAddr(), 0, uniqueId);

                    distributedHostAdapter.forwardMessageToGuest(proxyNode, sysUniqueIdResponse);
                    break;
                case BidibLibrary.MSG_SYS_GET_MAGIC:
                    Integer magic = proxyNode.getNode().getMagic();
                    LOGGER.info("Current magic of node: {}", magic);

                    if (mimicGateway) {
                        magic = BidibLibrary.BIDIB_GATEWAY_MAGIC;
                        LOGGER.info("Changed current magic of node to BIDIB_GATEWAY_MAGIC: {}", magic);
                    }

                    if (magic != null) {
                        BidibMessageInterface sysMagicResponse =
                            responseFactory.createSysMagicResponse(bidibMessage.getAddr(), 0, magic);

                        distributedHostAdapter.forwardMessageToGuest(proxyNode, sysMagicResponse);
                    }
                    else {
                        LOGGER.warn("No magic available for node: {}", proxyNode);
                    }
                    break;
                case BidibLibrary.MSG_SYS_ENABLE:
                    processSysEnableOrDisableRequest(bidibProxyConnection.getConnectionId(), bidibMessage, proxyNode,
                        true);
                    break;
                case BidibLibrary.MSG_SYS_DISABLE:
                    processSysEnableOrDisableRequest(bidibProxyConnection.getConnectionId(), bidibMessage, proxyNode,
                        false);
                    break;
                case BidibLibrary.MSG_SYS_GET_SW_VERSION:
                    SoftwareVersion softwareVersion = proxyNode.getNode().getSoftwareVersion();
                    LOGGER.info("Current software version of node: {}", softwareVersion);
                    if (softwareVersion != null) {
                        BidibMessageInterface sysSwVersionResponse =
                            responseFactory.createSysSwVersionResponse(bidibMessage.getAddr(), 0, softwareVersion);

                        distributedHostAdapter.forwardMessageToGuest(proxyNode, sysSwVersionResponse);
                    }
                    else {
                        LOGGER.warn("No software version available for node: {}", proxyNode);
                    }
                    break;
                case BidibLibrary.MSG_SYS_GET_P_VERSION:
                    ProtocolVersion protocolVersion = proxyNode.getNode().getProtocolVersion();
                    LOGGER.info("Current protocol version of node: {}", protocolVersion);
                    if (protocolVersion != null) {
                        BidibMessageInterface sysProtocolVersionResponse =
                            responseFactory
                                .createSysProtocolVersionResponse(bidibMessage.getAddr(), 0, protocolVersion);

                        distributedHostAdapter.forwardMessageToGuest(proxyNode, sysProtocolVersionResponse);
                    }
                    else {
                        LOGGER.warn("No protocol version available for node: {}", proxyNode);
                    }
                    break;
                case BidibLibrary.MSG_FEATURE_GETALL:
                    processFeatureGetAllRequest(distributedHostAdapter, bidibMessage, proxyNode);
                    break;
                case BidibLibrary.MSG_FEATURE_GETNEXT:
                    processFeatureGetNextRequest(distributedHostAdapter, bidibMessage, proxyNode);
                    break;
                case BidibLibrary.MSG_FEATURE_SET:
                    processFeatureSetRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter,
                        bidibMessage, proxyNode);
                    break;

                case BidibLibrary.MSG_NODETAB_GETALL:
                    processNodetabGetAllRequest(bidibProxyConnection, distributedHostAdapter, bidibMessage, proxyNode);
                    break;
                case BidibLibrary.MSG_NODETAB_GETNEXT:
                    processNodetabGetNextRequest(bidibProxyConnection, distributedHostAdapter, bidibMessage, proxyNode);
                    break;

                case BidibLibrary.MSG_STRING_GET:
                    processStringGetRequest(distributedHostAdapter, bidibMessage, proxyNode);
                    break;

                case BidibLibrary.MSG_CS_SET_STATE:
                    processCommandStationSetStateRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter,
                        bidibMessage, proxyNode);
                    break;

                case BidibLibrary.MSG_BOOST_QUERY:
                    processBoosterQueryRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter,
                        bidibMessage, proxyNode);
                    break;
                case BidibLibrary.MSG_BOOST_OFF:
                case BidibLibrary.MSG_BOOST_ON:
                    processBoosterStateRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter,
                        bidibMessage, proxyNode);
                    break;

                case BidibLibrary.MSG_BM_GET_RANGE:
                    processFeedbackGetRangeRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter,
                        bidibMessage, proxyNode);
                    break;
                case BidibLibrary.MSG_BM_GET_CONFIDENCE:
                    processFeedbackGetConfidenceRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter,
                        bidibMessage, proxyNode);
                    break;
                case BidibLibrary.MSG_BM_ADDR_GET_RANGE:
                    processFeedbackAddressGetRangeRequest(bidibProxyConnection.getConnectionId(), bidibMessage,
                        proxyNode);
                    break;

                case BidibLibrary.MSG_LOCAL_PING:
                    // TODO forward the local ping to the real node

                    // bidibProxyConnection.
                    break;

                case BidibLibrary.MSG_NODE_CHANGED_ACK:
                    LOGGER.warn("The message MSG_NODE_CHANGED_ACK is not signaled to the proxy connection");

                    break;

                case BidibLibrary.MSG_LC_PORT_QUERY_ALL:
                    processLcPortQueryAllRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter,
                        bidibMessage, proxyNode);
                    break;

                case BidibLibrary.MSG_LC_CONFIGX_GET_ALL:
                    processLcConfigXGetAllRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter,
                        bidibMessage, proxyNode);
                    break;
                case BidibLibrary.MSG_LC_CONFIGX_SET:
                    processLcConfigXSetRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter,
                        bidibMessage, proxyNode);
                    break;
                case BidibLibrary.MSG_LC_OUTPUT:
                    processLcOutputRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter, bidibMessage,
                        proxyNode);
                    break;
                case BidibLibrary.MSG_LC_MACRO_GET:
                    processLcMacroGetRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter,
                        bidibMessage, proxyNode);
                    break;
                case BidibLibrary.MSG_LC_MACRO_PARA_GET:
                    processLcMacroParaGetRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter,
                        bidibMessage, proxyNode);
                    break;
                case BidibLibrary.MSG_ACCESSORY_PARA_GET:
                    processAccessoryParaGetRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter,
                        bidibMessage, proxyNode);
                    break;
                case BidibLibrary.MSG_ACCESSORY_GET:
                    processAccessoryGetRequest(bidibProxyConnection.getConnectionId(), distributedHostAdapter,
                        bidibMessage, proxyNode);
                    break;

                default:
                    LOGGER.warn("Unsupported message detected: {}", bidibMessage);
                    break;
            }
        }
        catch (Exception ex) {
            LOGGER.warn("Process bidibMessage failed: {}", bidibMessage, ex);

            throw new ProtocolException("Process message failed.");
        }
    }

    @Override
    public void handleMessageEventFromBus(
        final ProxyBidibConnection bidibProxyConnection,
        final HostAdapter distributedHostAdapter, final AbstractMessageEvent event) {
        LOGGER.info("Handle the message event: {}", event);

        try {
            final NodeInterface node =
                Optional
                    .ofNullable(bidibProxyConnection.getNodeProvider().findNodeByAddress(event.getAddress()))
                    .orElseThrow(() -> new ProtocolException(
                        "No node available with address: " + NodeUtils.formatAddress(event.getAddress())));

            final ProxyNode proxyNode = ProxyNode.getProxyNode(node);

            switch (event.getMessageType()) {
                case BidibLibrary.MSG_CS_STATE:
                    final CommandStationStateMessageEvent commandStationStateMessageEvent =
                        (CommandStationStateMessageEvent) event;
                    final CommandStationState csState = commandStationStateMessageEvent.getCommandStationState();

                    // TODO we must synchronize the access to the sending
                    final BidibMessage csStateResponse =
                        responseFactory.createCommandStationStateResponse(event.getAddress(), 0, csState);

                    LOGGER
                        .info(">>> MSG_CS_STATE: Current messageContent: {}",
                            ByteUtils.bytesToHex(csStateResponse.getMessageContent()));

                    distributedHostAdapter.forwardMessageToGuest(proxyNode, csStateResponse);
                    break;

                case BidibLibrary.MSG_BM_FREE:
                case BidibLibrary.MSG_BM_OCC:
                    if (!proxyNode.isEnabled()) {
                        LOGGER.info("Discard the occupancy message because the node is not enabled: {}", proxyNode);
                        break;
                    }

                    LOGGER.info("Current message event: {}", event);
                    OccupancyStateMessageEvent occupancyStateMessageEvent = (OccupancyStateMessageEvent) event;
                    int detectorNumber = occupancyStateMessageEvent.getDetectorNumber();
                    OccupationState occupationState = occupancyStateMessageEvent.getOccupationState();
                    Integer timestamp = occupancyStateMessageEvent.getTimestamp();

                    final BidibMessage feedbackStateResponse =
                        responseFactory
                            .createOccupancyStateResponse(event.getAddress(), 0, detectorNumber, occupationState,
                                timestamp);

                    LOGGER
                        .info(">>> MSG_BM_FREE/MSG_BM_OCC: Current messageContent: {}",
                            ByteUtils.bytesToHex(feedbackStateResponse.getMessageContent()));

                    distributedHostAdapter.forwardMessageToGuest(proxyNode, feedbackStateResponse);

                    break;

                case BidibLibrary.MSG_BM_CONFIDENCE:
                case BidibLibrary.MSG_BM_MULTIPLE:

                    if (!proxyNode.isEnabled()) {
                        LOGGER.info("Discard the occupancy message because the node is not enabled: {}", proxyNode);
                        break;
                    }

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

                    // TODO

                    break;
                case BidibLibrary.MSG_BOOST_STAT:
                    final BoosterStateMessageEvent boosterStateMessageEvent = (BoosterStateMessageEvent) event;
                    final BidibMessage boosterStateResponse =
                        responseFactory
                            .createBoosterStateResponse(event.getAddress(), 0,
                                boosterStateMessageEvent.getBoosterState());

                    LOGGER
                        .info(">>> MSG_BOOST_STAT: Current messageContent: {}",
                            ByteUtils.bytesToHex(boosterStateResponse.getMessageContent()));

                    distributedHostAdapter.forwardMessageToGuest(proxyNode, boosterStateResponse);
                    break;

                case BidibLibrary.MSG_BOOST_DIAGNOSTIC:

                    if (!proxyNode.isEnabled()) {
                        LOGGER.info("Discard the booster diag message because the node is not enabled: {}", proxyNode);
                        break;
                    }

                    final BoosterDiagMessageEvent boosterDiagMessageEvent = (BoosterDiagMessageEvent) event;

                    final BidibMessage boosterDiagResponse =
                        responseFactory
                            .createBoosterDiagnosticResponse(event.getAddress(), 0,
                                boosterDiagMessageEvent.getCurrent(), boosterDiagMessageEvent.getVoltage(),
                                boosterDiagMessageEvent.getTemperature());
                    LOGGER
                        .info(">>> MSG_BOOST_DIAGNOSTIC: Current messageContent: {}",
                            ByteUtils.bytesToHex(boosterDiagResponse.getMessageContent()));

                    distributedHostAdapter.forwardMessageToGuest(proxyNode, boosterDiagResponse);

                    break;
                case BidibLibrary.MSG_LOCAL_PONG:
                    // final LocalPongMessageEvent localPongMessageEvent = (LocalPongMessageEvent) event;
                    final BidibMessage localPongResponse = responseFactory.createLocalPongResponse(event.getAddress());
                    LOGGER
                        .info(">>> MSG_LOCAL_PONG: Current messageContent: {}",
                            ByteUtils.bytesToHex(localPongResponse.getMessageContent()));

                    distributedHostAdapter.forwardMessageToGuest(proxyNode, localPongResponse);
                    break;
                case BidibLibrary.MSG_NODE_NEW:
                    final NodeNewMessageEvent nodeNewMessageEvent = (NodeNewMessageEvent) event;
                    final Node newCoreNode = nodeNewMessageEvent.getNode();

                    try {
                        byte[] addr = newCoreNode.getAddr();
                        int localAddr = addr[addr.length - 1];
                        long uniqueId = newCoreNode.getUniqueId();

                        LOGGER
                            .info("handle nodeNewMessageEvent: {}, proxyNode: {}, coreNode: {}, localAddr: {}",
                                nodeNewMessageEvent, proxyNode, newCoreNode, localAddr);

                        final BidibMessage nodeNewResponse =
                            responseFactory
                                .createNodeNewResponse(event.getAddress(), 0, newCoreNode.getVersion(), localAddr,
                                    uniqueId);

                        distributedHostAdapter.forwardMessageToGuest(proxyNode, nodeNewResponse);
                    }
                    catch (Exception ex) {
                        LOGGER
                            .warn("Forward NodeNewResponse to guest failed. Current nodeNewMessageEvent: {}",
                                nodeNewMessageEvent, ex);
                    }
                    break;

                case BidibLibrary.MSG_NODE_LOST:
                    final NodeLostMessageEvent nodeLostMessageEvent = (NodeLostMessageEvent) event;
                    final Node lostCoreNode = nodeLostMessageEvent.getNode();

                    try {
                        byte[] addr = lostCoreNode.getAddr();
                        int localAddr = addr[addr.length - 1];
                        long uniqueId = lostCoreNode.getUniqueId();

                        LOGGER
                            .info("handle nodeLostMessageEvent: {}, proxyNode: {}, coreNode: {}, localAddr: {}",
                                nodeLostMessageEvent, proxyNode, lostCoreNode, localAddr);

                        final BidibMessage nodeLostResponse =
                            responseFactory
                                .createNodeLostResponse(event.getAddress(), 0, lostCoreNode.getVersion(), localAddr,
                                    uniqueId);

                        distributedHostAdapter.forwardMessageToGuest(proxyNode, nodeLostResponse);
                    }
                    catch (Exception ex) {
                        LOGGER
                            .warn("Forward NodeLostResponse to guest failed. Current nodeLostMessageEvent: {}",
                                nodeLostMessageEvent, ex);
                    }
                    break;

                case BidibLibrary.MSG_SYS_MAGIC:
                    final SysMagicMessageEvent sysMagicMessageEvent = (SysMagicMessageEvent) event;
                    int magic = sysMagicMessageEvent.getMagic();

                    final BidibMessage sysMagicResponse =
                        responseFactory.createSysMagicResponse(event.getAddress(), 0, magic);
                    LOGGER.info("Publish the sysMagicResponse: {}", sysMagicResponse);

                    distributedHostAdapter.forwardMessageToGuest(proxyNode, sysMagicResponse);
                    break;

                case BidibLibrary.MSG_SYS_SW_VERSION:
                    final SysSoftwareVersionMessageEvent sysSoftwareVersionMessageEvent =
                        (SysSoftwareVersionMessageEvent) event;
                    SoftwareVersion softwareVersion = sysSoftwareVersionMessageEvent.getSoftwareVersion();

                    final BidibMessage sysSoftwareVersionResponse =
                        responseFactory.createSysSwVersionResponse(event.getAddress(), 0, softwareVersion);
                    LOGGER.info("Publish the sysSoftwareVersionResponse: {}", sysSoftwareVersionResponse);

                    distributedHostAdapter.forwardMessageToGuest(proxyNode, sysSoftwareVersionResponse);
                    break;

                case BidibLibrary.MSG_SYS_P_VERSION:
                    final SysProtocolVersionMessageEvent sysProtocolVersionMessageEvent =
                        (SysProtocolVersionMessageEvent) event;
                    ProtocolVersion protocolVersion = sysProtocolVersionMessageEvent.getProtocolVersion();

                    final BidibMessage sysProtocolVersionResponse =
                        responseFactory.createSysProtocolVersionResponse(event.getAddress(), 0, protocolVersion);
                    LOGGER.info("Publish the sysProtocolVersionResponse: {}", sysProtocolVersionResponse);

                    distributedHostAdapter.forwardMessageToGuest(proxyNode, sysProtocolVersionResponse);
                    break;

                case BidibLibrary.MSG_STRING:
                    final StringMessageEvent stringMessageEvent = (StringMessageEvent) event;
                    final StringData stringData = stringMessageEvent.getStringData();

                    final BidibMessage stringResponse =
                        responseFactory.createStringResponse(event.getAddress(), 0, stringData);

                    distributedHostAdapter.forwardMessageToGuest(proxyNode, stringResponse);
                    break;

                case BidibLibrary.MSG_LC_CONFIGX:
                    final LcConfigXMessageEvent lcConfigXMessageEvent = (LcConfigXMessageEvent) event;
                    final LcConfigX lcConfigX = lcConfigXMessageEvent.getLcConfigX();

                    final PortModelEnum portModelEnum = PortModelEnum.getPortModel(node.getNode());

                    LOGGER.info("Publish lcConfigX: {}", lcConfigX);

                    final BidibMessage lcConfigXResponse =
                        responseFactory.createLcConfigXResponse(event.getAddress(), 0, lcConfigX, portModelEnum);

                    distributedHostAdapter.forwardMessageToGuest(proxyNode, lcConfigXResponse);
                    break;

                case BidibLibrary.MSG_LC_STAT:
                    final LcStatMessageEvent lcStatMessageEvent = (LcStatMessageEvent) event;
                    try {
                        final BidibPort bidibPort = lcStatMessageEvent.getBidibPort();
                        final int portStatus = lcStatMessageEvent.getPortStatus();

                        final BidibMessage lcStatResponse =
                            responseFactory.createLcStatResponse(event.getAddress(), 0, bidibPort, portStatus);

                        distributedHostAdapter.forwardMessageToGuest(proxyNode, lcStatResponse);
                    }
                    catch (Exception ex) {
                        LOGGER
                            .warn("Forward LcStatResponse to guest failed. Current lcStatMessageEvent: {}",
                                lcStatMessageEvent, ex);
                    }
                    break;

                case BidibLibrary.MSG_LC_NA:
                    final LcNaMessageEvent lcNaMessageEvent = (LcNaMessageEvent) event;
                    final BidibPort bidibPort = lcNaMessageEvent.getBidibPort();

                    final BidibMessage lcNaResponse =
                        responseFactory.createLcNaResponse(event.getAddress(), 0, bidibPort);
                    distributedHostAdapter.forwardMessageToGuest(proxyNode, lcNaResponse);
                    break;

                case BidibLibrary.MSG_ACCESSORY_STATE:
                    final AccessoryStateMessageEvent accessoryStateMessageEvent = (AccessoryStateMessageEvent) event;

                    final AccessoryState accessoryState = accessoryStateMessageEvent.getAccessoryState();
                    int accessoryNumber = accessoryState.getAccessoryNumber();
                    Integer aspect = accessoryState.getActiveAspect();

                    byte[] value =
                        new byte[] { accessoryState.getTotal(), accessoryState.getExecute(),
                            ByteUtils.getLowByte(accessoryState.getWait()) };

                    final BidibMessage accessoryStateResponse =
                        responseFactory
                            .createAccessoryStateResponse(event.getAddress(), 0, accessoryNumber, aspect, value);
                    distributedHostAdapter.forwardMessageToGuest(proxyNode, accessoryStateResponse);
                    break;

                default:
                    LOGGER.info("Unprocessed event: {}", event);
                    break;
            }
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Publish message from event failed: {}", event, ex);
        }
    }

    private void processFeatureGetAllRequest(
        final HostAdapter distributedHostAdapter, final BidibMessageInterface bidibMessage,
        final NodeInterface node) throws ProtocolException {

        FeatureGetAllMessage featureGetAllMessage = (FeatureGetAllMessage) bidibMessage;
        Integer startStreaming = featureGetAllMessage.getStartStreaming();

        ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        if (StatusIdentifier.InitialLoadFinished == proxyNode.getNodeLoadStatusIdentifier()) {

            // reset the feature
            proxyNode.setCurrentFeature(0);

            int featureCount = node.getNode().getFeatures().size();

            if (startStreaming == null || startStreaming.intValue() == 0) {
                BidibMessageInterface featureCountResponse =
                    responseFactory.createFeatureCountResponse(bidibMessage.getAddr(), 0, featureCount, 0);

                distributedHostAdapter.forwardMessageToGuest(proxyNode, featureCountResponse);
            }
            else {
                LOGGER.info("Provide streaming support for features. Current featureCount: {}", featureCount);
                BidibMessageInterface featureCountResponse =
                    responseFactory.createFeatureCountResponse(bidibMessage.getAddr(), 0, featureCount, 1);

                distributedHostAdapter.forwardMessageToGuest(proxyNode, featureCountResponse);

                // stream features
                for (Feature feature : node.getNode().getFeatures()) {
                    FeatureResponse featureResponse =
                        new FeatureResponse(bidibMessage.getAddr(), 0, feature.getType(), feature.getValue());
                    distributedHostAdapter.forwardMessageToGuest(proxyNode, featureResponse);
                }

                // terminating
                FeatureNotAvailableResponse featureResponse =
                    new FeatureNotAvailableResponse(bidibMessage.getAddr(), 0, 255);
                distributedHostAdapter.forwardMessageToGuest(proxyNode, featureResponse);
            }
        }
        else {
            LOGGER.warn("The initial load has not finished for node: {}. Set the publish features marker.", proxyNode);
            proxyNode.setPublishFeatures(true);
            // TODO evaluate the streaming support
        }
    }

    private void processFeatureGetNextRequest(
        final HostAdapter distributedHostAdapter, final BidibMessageInterface bidibMessage,
        final NodeInterface node) {

        int featureCount = node.getNode().getFeatures().size();

        ProxyNode proxyNode = ProxyNode.getProxyNode(node);
        int currentFeature = proxyNode.getNextFeature();

        if (currentFeature >= featureCount) {
            try {
                FeatureNotAvailableResponse featureResponse =
                    new FeatureNotAvailableResponse(bidibMessage.getAddr(), 0, 255);
                distributedHostAdapter.forwardMessageToGuest(proxyNode, featureResponse);
            }
            catch (ProtocolException ex) {
                LOGGER.warn("Create feature N/A response failed.", ex);
            }
        }
        else {
            try {
                Feature feature = IterableUtils.get(node.getNode().getFeatures(), currentFeature);
                FeatureResponse featureResponse =
                    new FeatureResponse(bidibMessage.getAddr(), 0, feature.getType(), feature.getValue());
                distributedHostAdapter.forwardMessageToGuest(proxyNode, featureResponse);
            }
            catch (ProtocolException ex) {
                LOGGER.warn("Create feature response failed.", ex);
            }
            catch (Exception ex) {
                LOGGER.warn("Create feature response failed.", ex);
            }
        }
    }

    private void processFeatureSetRequest(
        String connectionId, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage, final NodeInterface node) {

        FeatureSetMessage featureSetMessage = (FeatureSetMessage) bidibMessage;
        LOGGER.info("Write feature to node: {}", featureSetMessage);

        ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        // write feature to node
        final NodeAddress nodeAddress = JsonNodeUtils.prepareNodeAddress(proxyNode);

        List features =
            Arrays.asList(new Feature(featureSetMessage.getNumber(), featureSetMessage.getValue()));
        nodeService.setFeatures(connectionId, nodeAddress, features);
    }

    private void processNodetabGetAllRequest(
        final ProxyBidibConnection bidibProxyConnection,
        final HostAdapter distributedHostAdapter, final BidibMessageInterface bidibMessage,
        final NodeInterface node) throws ProtocolException {

        ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        int subNodeCount = 1;
        if (proxyNode instanceof ProxyInterfaceNode) {
            ProxyInterfaceNode proxyInterfaceNode = (ProxyInterfaceNode) proxyNode;
            proxyInterfaceNode.resetSubNodeIndex();

            List subNodes =
                Optional
                    .ofNullable(bidibProxyConnection.getNodeProvider().findSubNodes(proxyNode))
                    .orElseThrow(() -> new ProtocolException(
                        "No subnodes available with address: " + NodeUtils.formatAddress(proxyNode.getAddr())));
            subNodeCount = subNodes.size() + 1;
        }

        LOGGER.info("Return subNodeCount: {}", subNodeCount);

        NodeTabCountResponse nodeTabCountResponse =
            new NodeTabCountResponse(bidibMessage.getAddr(), 0, ByteUtils.getLowByte(subNodeCount));
        distributedHostAdapter.forwardMessageToGuest(proxyNode, nodeTabCountResponse);
    }

    private void processNodetabGetNextRequest(
        final ProxyBidibConnection bidibProxyConnection,
        final HostAdapter distributedHostAdapter, final BidibMessageInterface bidibMessage,
        final NodeInterface node) throws ProtocolException {

        ProxyNode proxyNode = ProxyNode.getProxyNode(node);
        NodeTabResponse nodeTabResponse = null;
        if (proxyNode instanceof ProxyInterfaceNode) {
            ProxyInterfaceNode proxyInterfaceNode = (ProxyInterfaceNode) proxyNode;

            int subNodeIndex = proxyInterfaceNode.getNextSubNodeIndex();

            List subNodes =
                Optional
                    .ofNullable(bidibProxyConnection.getNodeProvider().findSubNodes(proxyNode))
                    .orElseThrow(() -> new ProtocolException(
                        "No subnodes available with address: " + NodeUtils.formatAddress(proxyNode.getAddr())));

            // add the interface node itself
            subNodes.add(0, proxyNode);

            NodeInterface subNode = subNodes.get(subNodeIndex);
            byte[] address = subNode.getAddr();
            int localAddress = address[0];

            LOGGER
                .info("Return subNode: {}, address: {}, localAddress: {}", subNode, ByteUtils.bytesToHex(address),
                    localAddress);

            nodeTabResponse =
                new NodeTabResponse(bidibMessage.getAddr(), 0, proxyNode.getNodeTabVersion(), localAddress,
                    subNode.getUniqueId());
        }
        else {
            nodeTabResponse =
                new NodeTabResponse(bidibMessage.getAddr(), 0, proxyNode.getNodeTabVersion(),
                    proxyNode.getLocalAddress(), node.getUniqueId());
        }
        distributedHostAdapter.forwardMessageToGuest(proxyNode, nodeTabResponse);
    }

    private void processStringGetRequest(
        final HostAdapter distributedHostAdapter, final BidibMessageInterface bidibMessage,
        final NodeInterface node) throws ProtocolException {

        StringGetMessage stringGetMessage = (StringGetMessage) bidibMessage;

        ProxyNode proxyNode = ProxyNode.getProxyNode(node);
        String stringData = null;
        switch (stringGetMessage.getNamespace()) {
            case StringData.NAMESPACE_NODE:
                stringData = proxyNode.getNode().getStoredString(stringGetMessage.getStringId());
                break;
            case StringData.NAMESPACE_DEBUG:
                break;
            default:
                break;
        }

        if (stringData != null) {
            StringResponse stringResponse =
                new StringResponse(bidibMessage.getAddr(), 0, ByteUtils.getLowByte(stringGetMessage.getNamespace()),
                    ByteUtils.getLowByte(stringGetMessage.getStringId()), stringData);
            distributedHostAdapter.forwardMessageToGuest(proxyNode, stringResponse);
        }
    }

    private void processLcPortQueryAllRequest(
        String connectionId, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage, final NodeInterface node) throws ProtocolException {

        final LcPortQueryAllMessage lcPortQueryAllMessage = (LcPortQueryAllMessage) bidibMessage;

        final ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        final PortModelEnum portModel = PortModelEnum.getPortModel(proxyNode.getNode());
        int portRangeFrom = lcPortQueryAllMessage.getPortRangeFrom(portModel);
        int portRangeTo = lcPortQueryAllMessage.getPortRangeTo(portModel);
        int portTypeMask = lcPortQueryAllMessage.getPortTypeMask();

        LOGGER
            .info("Query all port states, portRangeFrom: {}, portRangeTo: {}, portModel: {}, portTypeMask: {}",
                portRangeFrom, portRangeTo, portModel, portTypeMask);

        if (proxyNode.isFlatPortModel()) {
            if (!proxyNode.isGenericPortsSet()) {
                queryAndApplyGenericPortsToNode(connectionId, proxyNode);
            }
            List genericPorts = new LinkedList<>();
            genericPorts.addAll(proxyNode.getGenericPorts());
            for (GenericPort genericPort : genericPorts) {
                LcOutputType currentPortType = genericPort.getCurrentPortType();
                LOGGER.info("Current currentPortType: {}", currentPortType);
                if (PortConfigUtils.isSupportsPortType(currentPortType, portTypeMask)
                    && (genericPort.getPortNumber() >= portRangeFrom && genericPort.getPortNumber() < portRangeTo)) {
                    try {
                        byte portValue = 0;
                        switch (genericPort.getCurrentPortType()) {
                            case BACKLIGHTPORT:
                            case SERVOPORT:
                                Integer portVal = genericPort.getPortValue();
                                if (portVal != null) {
                                    portValue = ByteUtils.getLowByte(portVal.intValue());
                                }
                                break;

                            default:
                                portValue = genericPort.getPortStatus();
                                break;
                        }

                        // byte portStatus = genericPort.getPortStatus();
                        publishPortState(proxyNode, distributedHostAdapter, bidibMessage, currentPortType,
                            genericPort.getPortNumber(), portValue, portModel);
                    }
                    catch (Exception ex) {
                        LOGGER.warn("Publish port state failed for port: {}", genericPort, ex);
                    }
                }
                else {
                    LOGGER.info("Skip current port that is out of port range or wrong port type: {}", genericPort);
                }
            }
        }
        else {
            List> nodePorts = node.getPorts();
            if (nodePorts.isEmpty()) {
                queryAndApplyTypedPortsToNode(connectionId, proxyNode);
            }

            if (PortConfigUtils.isSupportsInputPort(portTypeMask)) {
                List inputPorts = node.getInputPorts();
                if (CollectionUtils.isNotEmpty(inputPorts)) {
                    for (InputPort inputPort : inputPorts) {

                        if (inputPort.getId() >= portRangeFrom && inputPort.getId() < portRangeTo) {
                            try {
                                InputPortStatus inputPortStatus = inputPort.getStatus();
                                byte portStatus = inputPortStatus.getType().getType();

                                publishPortState(proxyNode, distributedHostAdapter, bidibMessage,
                                    LcOutputType.INPUTPORT, inputPort.getId(), portStatus, portModel);
                            }
                            catch (Exception ex) {
                                LOGGER.warn("Publish port state failed for port: {}", inputPort, ex);
                            }
                        }
                        else {
                            LOGGER.info("Skip input port that is out of port range: {}", inputPort);
                        }
                    }
                }
            }

            if (PortConfigUtils.isSupportsLightPort(portTypeMask)) {
                List lightPorts = node.getLightPorts();
                if (CollectionUtils.isNotEmpty(lightPorts)) {
                    for (LightPort lightPort : lightPorts) {
                        if (lightPort.getId() >= portRangeFrom && lightPort.getId() < portRangeTo) {
                            try {
                                LightPortStatus lightPortStatus = lightPort.getStatus();
                                byte portStatus = lightPortStatus.getType().getType();

                                publishPortState(proxyNode, distributedHostAdapter, bidibMessage,
                                    LcOutputType.LIGHTPORT, lightPort.getId(), portStatus, portModel);
                            }
                            catch (Exception ex) {
                                LOGGER.warn("Publish port state failed for port: {}", lightPort, ex);
                            }
                        }
                        else {
                            LOGGER.info("Skip light port that is out of port range: {}", lightPort);
                        }
                    }
                }
            }

            if (PortConfigUtils.isSupportsSwitchPort(portTypeMask)) {
                List switchPorts = node.getSwitchPorts();
                if (CollectionUtils.isNotEmpty(switchPorts)) {
                    for (SwitchPort switchPort : switchPorts) {
                        if (switchPort.getId() >= portRangeFrom && switchPort.getId() < portRangeTo) {
                            try {
                                SwitchPortStatus switchPortStatus = switchPort.getStatus();
                                byte portStatus = switchPortStatus.getType().getType();

                                publishPortState(proxyNode, distributedHostAdapter, bidibMessage,
                                    LcOutputType.SWITCHPORT, switchPort.getId(), portStatus, portModel);
                            }
                            catch (Exception ex) {
                                LOGGER.warn("Publish port state failed for port: {}", switchPort, ex);
                            }
                        }
                        else {
                            LOGGER.info("Skip switch port that is out of port range: {}", switchPort);
                        }
                    }
                }
            }

            if (PortConfigUtils.isSupportsSwitchPairPort(portTypeMask)) {
                List switchPairPorts = node.getSwitchPairPorts();
                if (CollectionUtils.isNotEmpty(switchPairPorts)) {
                    for (SwitchPairPort switchPairPort : switchPairPorts) {
                        if (switchPairPort.getId() >= portRangeFrom && switchPairPort.getId() < portRangeTo) {
                            try {
                                SwitchPortStatus switchPortStatus = switchPairPort.getStatus();
                                byte portStatus = switchPortStatus.getType().getType();

                                publishPortState(proxyNode, distributedHostAdapter, bidibMessage,
                                    LcOutputType.SWITCHPAIRPORT, switchPairPort.getId(), portStatus, portModel);
                            }
                            catch (Exception ex) {
                                LOGGER.warn("Publish port state failed for port: {}", switchPairPort, ex);
                            }
                        }
                        else {
                            LOGGER.info("Skip switchPair port that is out of port range: {}", switchPairPort);
                        }
                    }
                }
            }

            if (PortConfigUtils.isSupportsServoPort(portTypeMask)) {
                List servoPorts = node.getServoPorts();
                if (CollectionUtils.isNotEmpty(servoPorts)) {
                    for (ServoPort servoPort : servoPorts) {
                        if (servoPort.getId() >= portRangeFrom && servoPort.getId() < portRangeTo) {
                            try {
                                byte portStatus = ByteUtils.getLowByte(servoPort.getValue());

                                // ServoPortStatus servoPortStatus = servoPort.getStatus();
                                // byte portStatus = servoPortStatus.getType().getType();

                                publishPortState(proxyNode, distributedHostAdapter, bidibMessage,
                                    LcOutputType.SERVOPORT, servoPort.getId(), portStatus, portModel);
                            }
                            catch (Exception ex) {
                                LOGGER.warn("Publish port state failed for port: {}", servoPort, ex);
                            }
                        }
                        else {
                            LOGGER.info("Skip servo port that is out of port range: {}", servoPort);
                        }
                    }
                }
            }

        }

        // publish end of status
        publishEndOfPortList(proxyNode, distributedHostAdapter, bidibMessage);
    }

    private void publishEndOfPortList(
        final ProxyNode proxyNode, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage) throws ProtocolException {

        final BidibPort bidibPort = BidibPort.prepareBidibPortNa();

        final BidibMessage lcNaResponse = responseFactory.createLcNaResponse(bidibMessage.getAddr(), 0, bidibPort);

        LOGGER.info("Prepared LcNaResponse: {}", lcNaResponse);

        distributedHostAdapter.forwardMessageToGuest(proxyNode, lcNaResponse);
    }

    private void publishPortState(
        final ProxyNode proxyNode, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage, LcOutputType outputType, int outputNumber, byte portStatus,
        final PortModelEnum portModel) throws ProtocolException {

        final BidibPort bidibPort = prepareBidibPort(portModel, outputType, outputNumber);

        final BidibMessage lcStatResponse =
            responseFactory.createLcStatResponse(bidibMessage.getAddr(), 0, bidibPort, portStatus);

        LOGGER.info("Prepared LcStatResponse: {}", lcStatResponse);

        distributedHostAdapter.forwardMessageToGuest(proxyNode, lcStatResponse);
    }

    private void processLcConfigXGetAllRequest(
        final String connectionId, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage, final NodeInterface node) throws ProtocolException {

        final LcConfigXGetAllMessage lcConfigXGetAllMessage = (LcConfigXGetAllMessage) bidibMessage;

        final ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        final PortModelEnum portModel = PortModelEnum.getPortModel(proxyNode.getNode());
        LOGGER.info("processLcConfigXGetAllRequest, current portModel: {}", portModel);

        LcOutputType outputType = lcConfigXGetAllMessage.getPortTypeFrom(portModel);
        int rangeFrom = lcConfigXGetAllMessage.getPortRangeFrom(portModel);
        int rangeTo = lcConfigXGetAllMessage.getPortRangeTo(portModel);

        LOGGER.info("Get the configX for type: {}, rangeFrom: {}, rangeTo: {}", outputType, rangeFrom, rangeTo);

        if (portModel == PortModelEnum.type) {

            // TODO fix the missing types
            if (outputType == null) {
                LOGGER.warn("Set the requested LcOutputType to INPUTPORT because none is requested.");

                LcOutputType[] outputTypes =
                    new LcOutputType[] { LcOutputType.BACKLIGHTPORT, LcOutputType.INPUTPORT, LcOutputType.LIGHTPORT,
                        LcOutputType.MOTORPORT, LcOutputType.SERVOPORT, LcOutputType.SOUNDPORT, LcOutputType.SWITCHPORT,
                        LcOutputType.SWITCHPAIRPORT };

                for (LcOutputType currentOutputType : outputTypes) {
                    publishLcConfigXResponse(connectionId, distributedHostAdapter, bidibMessage.getAddr(), proxyNode,
                        currentOutputType, portModel);
                }
            }
            else {
                publishLcConfigXResponse(connectionId, distributedHostAdapter, bidibMessage.getAddr(), proxyNode,
                    outputType, portModel);
            }
        }
        else {
            // handle flat port model

            outputType = LcOutputType.SWITCHPORT;

            if (!proxyNode.isGenericPortsSet()) {

                queryAndApplyGenericPortsToNode(connectionId, proxyNode);
            }

            final List genericPorts = proxyNode.getGenericPorts();

            try {
                for (GenericPort port : genericPorts) {
                    int portNumber = port.getPortNumber();
                    LOGGER.info("Prepare lcConfigXResponse for port number: {}, port: {}", portNumber, port);

                    BidibPort bidibPort = prepareBidibPort(portModel, outputType, portNumber);
                    LcConfigX lcConfigX = new LcConfigX(bidibPort, port.getPortConfigX());

                    LcConfigXResponse lcConfigXResponse =
                        new LcConfigXResponse(bidibMessage.getAddr(), 0,
                            LcConfigX.getCodedPortConfig(messageLogger, lcConfigX, portModel));

                    LOGGER.info("Publish lcConfigXResponse: {}", lcConfigXResponse);

                    distributedHostAdapter.forwardMessageToGuest(proxyNode, lcConfigXResponse);
                }
            }
            catch (ProtocolException ex) {
                LOGGER.warn("Create LcConfigXResponse response failed.", ex);
            }

        }
    }

    private void publishLcConfigXResponse(
        final String connectionId, final HostAdapter distributedHostAdapter, byte[] nodeAddr,
        final ProxyNode proxyNode, final LcOutputType outputType, final PortModelEnum portModel)
        throws ProtocolException {
        final List> ports = new LinkedList<>();

        // TODO implement
        switch (outputType) {
            case BACKLIGHTPORT:
                ports.addAll(proxyNode.getBacklightPorts());

                if (ports.isEmpty()) {
                    queryAndApplyTypedPortsToNode(connectionId, proxyNode);
                    ports.addAll(proxyNode.getBacklightPorts());
                }
                break;
            case INPUTPORT:
                ports.addAll(proxyNode.getInputPorts());

                if (ports.isEmpty()) {
                    queryAndApplyTypedPortsToNode(connectionId, proxyNode);
                    ports.addAll(proxyNode.getInputPorts());
                }

                break;
            case LIGHTPORT:
                ports.addAll(proxyNode.getLightPorts());

                if (ports.isEmpty()) {
                    queryAndApplyTypedPortsToNode(connectionId, proxyNode);
                    ports.addAll(proxyNode.getLightPorts());
                }
                break;
            case SERVOPORT:
                ports.addAll(proxyNode.getServoPorts());

                if (ports.isEmpty()) {
                    queryAndApplyTypedPortsToNode(connectionId, proxyNode);
                    ports.addAll(proxyNode.getServoPorts());
                }
                break;
            case SWITCHPORT:
                ports.addAll(proxyNode.getSwitchPorts());

                if (ports.isEmpty()) {
                    queryAndApplyTypedPortsToNode(connectionId, proxyNode);
                    ports.addAll(proxyNode.getSwitchPorts());
                }
                break;
            case SWITCHPAIRPORT:
                ports.addAll(proxyNode.getSwitchPairPorts());

                if (ports.isEmpty()) {
                    queryAndApplyTypedPortsToNode(connectionId, proxyNode);
                    ports.addAll(proxyNode.getSwitchPairPorts());
                }
                break;
            default:
                break;
        }

        LOGGER.info("Process the ports, outputType: {}, ports: {}", outputType, ports);

        for (Port port : ports) {

            Map> portConfigX = port.getPortConfigX();
            BidibPort bidibPort = prepareBidibPort(portModel, outputType, port.getId());
            LcConfigX lcConfigX = new LcConfigX(bidibPort, portConfigX);

            final BidibMessage lcConfigXResponse =
                responseFactory.createLcConfigXResponse(nodeAddr, 0, lcConfigX, portModel);

            LOGGER.info("Publish lcConfigXResponse: {}", lcConfigXResponse);

            distributedHostAdapter.forwardMessageToGuest(proxyNode, lcConfigXResponse);
        }

    }

    private void processLcConfigXSetRequest(
        String connectionId, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage, final NodeInterface node) throws ProtocolException {

        final LcConfigXSetMessage lcConfigXSetMessage = (LcConfigXSetMessage) bidibMessage;

        final ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        final PortModelEnum portModel = PortModelEnum.getPortModel(proxyNode.getNode());
        LOGGER.info("processLcConfigXSetRequest, current portModel: {}, proxyNode: {}", portModel, proxyNode);

        final LcConfigX lcConfigX = lcConfigXSetMessage.getLcConfigX(this.messageLogger);
        final Map> values = lcConfigX.getPortConfig();
        final LcOutputType portType = lcConfigX.getOutputType(portModel);
        int portNumber = lcConfigX.getOutputNumber(portModel);

        final NodeAddress nodeAddress = JsonNodeUtils.prepareNodeAddress(proxyNode);

        switchingNodeService.setPortConfig(connectionId, nodeAddress, portType, portNumber, null, values);
    }

    private void processLcOutputRequest(
        String connectionId, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage, final NodeInterface node) throws ProtocolException {

        final LcOutputMessage lcOutputMessage = (LcOutputMessage) bidibMessage;

        final ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        final PortModelEnum portModel = PortModelEnum.getPortModel(proxyNode.getNode());
        LOGGER.info("processLcOutputRequest, current portModel: {}, proxyNode: {}", portModel, proxyNode);

        final LcOutputType portType = lcOutputMessage.getOutputType(portModel);
        int portNumber = lcOutputMessage.getOutputNumber(portModel);
        byte outputStatus = lcOutputMessage.getOutputStatus();

        final NodeAddress nodeAddress = JsonNodeUtils.prepareNodeAddress(proxyNode);

        Port port = getPortInstance(proxyNode, portModel, portType, portNumber, outputStatus);

        switchingNodeService.setPortStatus(connectionId, nodeAddress, port);
    }

    private Port getPortInstance(
        final ProxyNode proxyNode, final PortModelEnum portModel, LcOutputType portType, int portNumber,
        byte outputStatus) {

        if (proxyNode.isFlatPortModel()) {
            // get the current port type
            GenericPort genericPort =
                PortListUtils.findGenericPortByPortNumber(proxyNode.getGenericPorts(), portNumber);
            portType = genericPort.getCurrentPortType();
            LOGGER.info("Changed the port type from the generic port to: {}", portType);
        }

        Port port = null;
        switch (portType) {
            case BACKLIGHTPORT:
                BacklightPort backlightPort = new BacklightPort();
                backlightPort.setId(portNumber);
                backlightPort.setValue(ByteUtils.getInt(outputStatus));
                port = backlightPort;
                break;
            case INPUTPORT:
                InputPort inputPort = new InputPort();
                inputPort.setId(portNumber);
                inputPort.setStatus(InputPortStatus.valueOf(InputPortEnum.valueOf(outputStatus)));
                port = inputPort;
                break;
            case LIGHTPORT:
                LightPort lightPort = new LightPort();
                lightPort.setId(portNumber);
                lightPort.setStatus(LightPortStatus.valueOf(LightPortEnum.valueOf(outputStatus)));
                port = lightPort;
                break;
            case SERVOPORT:
                ServoPort servoPort = new ServoPort();
                servoPort.setId(portNumber);
                servoPort.setValue(ByteUtils.getInt(outputStatus));
                port = servoPort;
                break;
            case SWITCHPORT:
                SwitchPort switchPort = new SwitchPort();
                switchPort.setId(portNumber);
                switchPort.setStatus(SwitchPortStatus.valueOf(SwitchPortEnum.valueOf(outputStatus)));
                port = switchPort;
                break;
            case SWITCHPAIRPORT:
                SwitchPairPort switchPairPort = new SwitchPairPort();
                switchPairPort.setId(portNumber);
                switchPairPort.setStatus(SwitchPortStatus.valueOf(SwitchPortEnum.valueOf(outputStatus)));
                port = switchPairPort;
                break;

            default:
                LOGGER.warn("Unsupported port type detected: {}, portNumber: {}", portType, portNumber);
                break;
        }

        return port;
    }

    private void queryAndApplyGenericPortsToNode(String connectionId, final ProxyNode proxyNode) {
        final NodeAddress nodeAddress = JsonNodeUtils.prepareNodeAddress(proxyNode);
        List sourceGenericPorts = switchingNodeService.queryAllGenericPorts(connectionId, nodeAddress);
        LOGGER.info("queryAndApplyGenericPortsToNode, genericPorts: {}", sourceGenericPorts);

        List genericPorts = new LinkedList<>();

        for (GenericPort sgp : sourceGenericPorts) {
            // @formatter:off

            GenericPort genericPort =
                GenericPort
                    .builder().withPortNumber(sgp.getPortNumber())
                    .withConfigStatus(sgp.getConfigStatus())
                    .withIsInactive(sgp.isInactive())
                    .withKnownPortConfigKeys(sgp.getKnownPortConfigKeys())
                    .withPairedPortMaster(sgp.getPairedPortMaster())
                    .withPortConfig(sgp.getPortConfigX())
                    .withPortConfigErrorCode(sgp.getPortConfigErrorCode())
                    .withPortStatus(sgp.getPortStatus())
                    .withPortValue(sgp.getPortValue())
                    .build();
            
            // @formatter:on
            genericPorts.add(genericPort);
        }

        proxyNode.setGenericPorts(genericPorts);
    }

    private void queryAndApplyTypedPortsToNode(String connectionId, final ProxyNode proxyNode) {
        final NodeAddress nodeAddress = JsonNodeUtils.prepareNodeAddress(proxyNode);

        if (proxyNode.isFlatPortModel()) {
            List ports = switchingNodeService.queryAllGenericPorts(connectionId, nodeAddress);
            proxyNode.setGenericPorts(ports);
        }
        else {
            List> ports = switchingNodeService.queryAllPorts(connectionId, nodeAddress);

            LOGGER.info("queryAndApplyPortsToNode, ports: {}", ports);

            List sourceInputPorts =
                ports
                    .stream().filter(p -> p.getPortType() == LcOutputType.INPUTPORT).map(p -> (InputPort) p)
                    .collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(sourceInputPorts)) {
                List inputPorts = new LinkedList<>();

                for (InputPort ip : sourceInputPorts) {
                    InputPort inputPort = InputPort.clonePort(ip);
                    inputPorts.add(inputPort);
                }
                proxyNode.setInputPorts(inputPorts);
            }

            List sourceServoPorts =
                ports
                    .stream().filter(p -> p.getPortType() == LcOutputType.SERVOPORT).map(p -> (ServoPort) p)
                    .collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(sourceServoPorts)) {
                List servoPorts = new LinkedList<>();

                for (ServoPort sp : sourceServoPorts) {
                    ServoPort servoPort = ServoPort.clonePort(sp);
                    servoPorts.add(servoPort);
                }
                proxyNode.setServoPorts(servoPorts);
            }

            List sourceBacklightPorts =
                ports
                    .stream().filter(p -> p.getPortType() == LcOutputType.BACKLIGHTPORT).map(p -> (BacklightPort) p)
                    .collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(sourceBacklightPorts)) {
                List backlightPorts = new LinkedList<>();

                for (BacklightPort bp : sourceBacklightPorts) {
                    BacklightPort backlightPort = BacklightPort.clonePort(bp);
                    backlightPorts.add(backlightPort);
                }
                proxyNode.setBacklightPorts(backlightPorts);
            }

            List sourceLightPorts =
                ports
                    .stream().filter(p -> p.getPortType() == LcOutputType.LIGHTPORT).map(p -> (LightPort) p)
                    .collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(sourceLightPorts)) {
                List lightPorts = new LinkedList<>();

                for (LightPort lp : sourceLightPorts) {
                    LightPort lightPort = LightPort.clonePort(lp);
                    lightPorts.add(lightPort);
                }
                proxyNode.setLightPorts(lightPorts);
            }

            List sourceSwitchPorts =
                ports
                    .stream().filter(p -> p.getPortType() == LcOutputType.SWITCHPORT).map(p -> (SwitchPort) p)
                    .collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(sourceSwitchPorts)) {
                List switchPorts = new LinkedList<>();

                for (SwitchPort sp : sourceSwitchPorts) {
                    SwitchPort switchPort = SwitchPort.clonePort(sp);
                    switchPorts.add(switchPort);
                }
                proxyNode.setSwitchPorts(switchPorts);
            }

            List sourceSwitchPairPorts =
                ports
                    .stream().filter(p -> p.getPortType() == LcOutputType.SWITCHPAIRPORT).map(p -> (SwitchPairPort) p)
                    .collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(sourceSwitchPairPorts)) {
                List switchPairPorts = new LinkedList<>();

                for (SwitchPairPort sp : sourceSwitchPairPorts) {
                    SwitchPairPort switchPairPort = SwitchPairPort.clonePort(sp);
                    switchPairPorts.add(switchPairPort);
                }
                proxyNode.setSwitchPairPorts(switchPairPorts);
            }
        }
    }

    private BidibPort prepareBidibPort(PortModelEnum portModelEnum, LcOutputType lcOutputType, int port) {
        BidibPort bidibPort = null;
        if (portModelEnum == PortModelEnum.type) {
            bidibPort = new BidibPort(new byte[] { lcOutputType.getType(), ByteUtils.getLowByte(port) });
        }
        else {
            bidibPort = new BidibPort(new byte[] { ByteUtils.getLowByte(port), ByteUtils.getHighByte(port) });
        }
        return bidibPort;
    }

    private void processLcMacroGetRequest(
        String connectionId, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage, final NodeInterface node) throws ProtocolException {

        final LcMacroGetMessage lcMacroGetMessage = (LcMacroGetMessage) bidibMessage;

        final ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        int macroNumber = lcMacroGetMessage.getMacroNumber();
        int stepNumber = lcMacroGetMessage.getStep();

        LOGGER.info("Get the macro for macro id: {}, step: {}", macroNumber, stepNumber);

        if (CollectionUtils.isEmpty(node.getMacros())) {
            queryAndApplyMacrosToNode(connectionId, proxyNode);
        }

        BidibMessageInterface lcMacroResponse = null;

        final Macro macro =
            proxyNode.getMacros().stream().filter(m -> m.getId() == macroNumber).findFirst().orElse(null);
        if (macro != null) {

            Function function = macro.getFunction(stepNumber);
            LcMacro lcMacro = null;

            // TODO prepare the macro for the transfer
            LOGGER.warn("Loading the macro is not implemented yet!");

            // function.getAction();
            // int delay = 0;
            if (function instanceof SystemFunction) {
                SystemFunction systemFunction = (SystemFunction) function;
                // int delay = delay = 0xFF;
                // TODO
                // lcMacro = new LcMacro(macroNumber, stepNumber, delay, outputType, portNumber, value);
            }
            // function.
            // LcMacro lcMacro = new LcMacro();

            lcMacro = new LcMacro(macroNumber, stepNumber, 0xFF, BidibLibrary.BIDIB_MSYS_END_OF_MACRO, 0x00, 0x00);

            lcMacroResponse =
                responseFactory.createLcMacroResponse(bidibMessage.getAddr(), 0, macroNumber, stepNumber, lcMacro);
        }

        LOGGER.info("Publish lcMacroResponse: {}", lcMacroResponse);

        distributedHostAdapter.forwardMessageToGuest(proxyNode, lcMacroResponse);
    }

    private void processLcMacroParaGetRequest(
        String connectionId, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage, final NodeInterface node) throws ProtocolException {

        final LcMacroParaGetMessage lcMacroParaGetMessage = (LcMacroParaGetMessage) bidibMessage;

        final ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        int macroNumber = lcMacroParaGetMessage.getMacroNumber();
        int parameterIndex = lcMacroParaGetMessage.getParameterIndex();

        LOGGER.info("Get the macro parameter for macro id: {}, paramIndex: {}", macroNumber, parameterIndex);

        if (CollectionUtils.isEmpty(node.getMacros())) {
            queryAndApplyMacrosToNode(connectionId, proxyNode);
        }

        // LOGGER.warn("processLcMacroParaGetRequest: Not implemented!");
        BidibMessageInterface lcMacroParaResponse = null;

        byte[] macroParam =
            new byte[] { ByteUtils.getLowByte(0xFF), ByteUtils.getLowByte(0xFF), ByteUtils.getLowByte(0xFF),
                ByteUtils.getLowByte(0xFF) };

        final Macro macro =
            proxyNode.getMacros().stream().filter(m -> m.getId() == macroNumber).findFirst().orElse(null);
        if (macro != null) {

            switch (parameterIndex) {
                case BidibLibrary.BIDIB_MACRO_PARA_REPEAT:
                    int cycles = macro.getCycles();
                    macroParam[0] = ByteUtils.getLowByte(cycles);
                    break;
                case BidibLibrary.BIDIB_MACRO_PARA_SLOWDOWN:
                    int speed = macro.getSpeed();
                    macroParam[0] = ByteUtils.getLowByte(speed);
                    break;
                case BidibLibrary.BIDIB_MACRO_PARA_START_CLK:
                    Collection startConditions = macro.getStartConditions();
                    if (CollectionUtils.isNotEmpty(startConditions)) {
                        TimeStartCondition timeStartCondition =
                            startConditions
                                .stream().filter(sc -> sc instanceof TimeStartCondition)
                                .map(sc -> (TimeStartCondition) sc).findFirst().orElse(null);
                        if (timeStartCondition != null) {
                            // TODO
                            MacroRepeatDay repeatDay = timeStartCondition.getRepeatDay();
                            MacroRepeatTime repeatTime = timeStartCondition.getRepeatTime();
                            final Calendar cal = timeStartCondition.getTime();

                            int minute = cal.get(Calendar.MINUTE);
                            int hour = cal.get(Calendar.HOUR_OF_DAY);
                            int day = 0;
                            if (repeatTime != null) {
                                switch (repeatTime) {
                                    case WORKING_HOURLY:
                                        hour = 25;
                                        break;
                                    case HOURLY:
                                        hour = 24;
                                        break;
                                    case MINUTELY:
                                        minute = 60;
                                        break;
                                    case HALF_HOURLY:
                                        minute = 61;
                                        break;
                                    case QUARTER_HOURLY:
                                        minute = 62;
                                        break;
                                    default:
                                        break;
                                }
                            }

                            if (repeatDay != null) {
                                day = repeatDay.getValue();
                            }

                            macroParam[0] = ByteUtils.getLowByte(minute);
                            macroParam[1] = ByteUtils.getLowByte(hour);
                            macroParam[2] = ByteUtils.getLowByte(day);
                        }
                    }
                    break;
                default:
                    break;
            }

            lcMacroParaResponse =
                responseFactory
                    .createLcMacroParaResponse(bidibMessage.getAddr(), 0, macroNumber, parameterIndex, macroParam);
        }
        else {
            LOGGER.warn("No macro available, macroNumber: {}", macroNumber);

            lcMacroParaResponse =
                responseFactory
                    .createLcMacroParaResponse(bidibMessage.getAddr(), 0, macroNumber, parameterIndex, macroParam);

        }

        LOGGER.info("Publish lcMacroParaResponse: {}", lcMacroParaResponse);

        distributedHostAdapter.forwardMessageToGuest(proxyNode, lcMacroParaResponse);
    }

    private void queryAndApplyMacrosToNode(String connectionId, final ProxyNode proxyNode) {
        final NodeAddress nodeAddress = JsonNodeUtils.prepareNodeAddress(proxyNode);
        List macros = switchingNodeService.queryAllMacros(connectionId, nodeAddress);

        LOGGER.info("queryAndApplyMacrosToNode, macros: {}", macros);

        // TODO clone the data from the macros

        proxyNode.setMacros(macros);
    }

    private void processAccessoryGetRequest(
        String connectionId, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage, final NodeInterface node) throws ProtocolException {

        final AccessoryGetMessage accessoryGetMessage = (AccessoryGetMessage) bidibMessage;

        final ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        int accessoryNumber = accessoryGetMessage.getAccessoryNumber();

        LOGGER.info("Get the accessory for accessory id: {}", accessoryNumber);

        if (CollectionUtils.isEmpty(node.getAccessories())) {
            queryAndApplyAccessoriesToNode(connectionId, proxyNode);
        }

        BidibMessageInterface accessoryResponse = null;
        final Accessory accessory =
            proxyNode.getAccessories().stream().filter(acc -> acc.getId() == accessoryNumber).findFirst().orElse(null);
        if (accessory != null && accessory.getAccessoryState() != null) {

            final AccessoryState accessoryState = accessory.getAccessoryState();
            Integer aspect = accessoryState.getActiveAspect();

            byte[] value =
                new byte[] { accessoryState.getTotal(), accessoryState.getExecute(),
                    ByteUtils.getLowByte(accessoryState.getWait()) };

            accessoryResponse =
                responseFactory.createAccessoryStateResponse(bidibMessage.getAddr(), 0, accessoryNumber, aspect, value);
        }

        if (accessoryResponse != null) {
            LOGGER.info("Publish accessoryResponse: {}", accessoryResponse);

            distributedHostAdapter.forwardMessageToGuest(proxyNode, accessoryResponse);
        }
        else {
            LOGGER.warn("No accessory available with accessory id: {}", accessoryNumber);
        }
    }

    private void processAccessoryParaGetRequest(
        String connectionId, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage, final NodeInterface node) throws ProtocolException {

        final AccessoryParaGetMessage accessoryParaGetMessage = (AccessoryParaGetMessage) bidibMessage;

        final ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        int accessoryNumber = accessoryParaGetMessage.getAccessoryNumber();
        int paramNumber = accessoryParaGetMessage.getParaNumber();

        LOGGER.info("Get the accessory parameter for accessory id: {}, paramNumber: {}", accessoryNumber, paramNumber);

        if (CollectionUtils.isEmpty(node.getAccessories())) {
            queryAndApplyAccessoriesToNode(connectionId, proxyNode);
        }
        else {
            LOGGER.info("Use existing accessories: {}", node.getAccessories());
        }

        BidibMessageInterface accessoryParaResponse = null;

        final Accessory accessory =
            proxyNode.getAccessories().stream().filter(acc -> acc.getId() == accessoryNumber).findFirst().orElse(null);
        if (accessory != null) {

            // prepare the response
            int[] accessoryParam = accessory.getParam(paramNumber);
            if (accessoryParam != null && accessoryParam.length > 0) {

                LOGGER
                    .info("Prepare AccessoryParaResponse for accessoryNumber: {}, paramNumber: {}, accessoryParam: {}",
                        accessoryNumber, paramNumber, accessoryParam);
                accessoryParaResponse =
                    responseFactory
                        .createAccessoryParaResponse(bidibMessage.getAddr(), 0, accessoryNumber, paramNumber,
                            accessoryParam);
            }
            else {
                LOGGER
                    .warn("No accessory params available for accessoryNumber: {}, paramNumber: {}", accessoryNumber,
                        paramNumber);
            }
        }
        else {
            LOGGER.warn("No accessory available for accessoryNumber: {}", accessoryNumber);
        }

        if (accessoryParaResponse == null) {
            accessoryParaResponse =
                responseFactory
                    .createAccessoryParaResponse(bidibMessage.getAddr(), 0, accessoryNumber,
                        BidibLibrary.BIDIB_ACCESSORY_PARA_NOTEXIST, new int[] { paramNumber });
        }

        LOGGER.info("Publish accessoryParaResponse: {}", accessoryParaResponse);

        distributedHostAdapter.forwardMessageToGuest(proxyNode, accessoryParaResponse);
    }

    private void queryAndApplyAccessoriesToNode(String connectionId, final ProxyNode proxyNode) {
        final NodeAddress nodeAddress = JsonNodeUtils.prepareNodeAddress(proxyNode);
        List accessories = switchingNodeService.queryAllAccessories(connectionId, nodeAddress);

        LOGGER.info("queryAndApplyAccessoriesToNode, accessories: {}", accessories);

        // TODO clone the data from the accessories

        proxyNode.setAccessories(accessories);

    }

    private void processCommandStationSetStateRequest(
        String connectionId, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage, final NodeInterface node) throws ProtocolException {

        final CommandStationSetStateMessage commandStationSetStateMessage =
            (CommandStationSetStateMessage) bidibMessage;

        final ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        CommandStationState state = commandStationSetStateMessage.getState();
        LOGGER.info("The requested command station state is: {}", state);
        final NodeAddress nodeAddress = JsonNodeUtils.prepareNodeAddress(proxyNode);

        switch (state) {
            case OFF:
            case STOP:
            case SOFTSTOP:
            case GO:
            case GO_IGN_WD:
            case PROG:
                serviceWorker.schedule(() -> {
                    LOGGER.info("Set the new command station state: {}", state);
                    commandStationService
                        .setCommandStationState(connectionId, nodeAddress, CommandStationStatus.valueOf(state));
                }, 5, TimeUnit.MILLISECONDS);
                break;
            case QUERY:
                serviceWorker.schedule(() -> {
                    LOGGER.warn("Query command station state requested");
                    commandStationService.queryCommandStationState(connectionId, nodeAddress);
                }, 5, TimeUnit.MILLISECONDS);
                break;
            default:
                LOGGER.warn("Unprocessed command station state: {}", state);
                break;
        }
    }

    private void processBoosterQueryRequest(
        String connectionId, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage, final NodeInterface node) throws ProtocolException {
        LOGGER.info("Query the booster state.");

        ProxyNode proxyNode = ProxyNode.getProxyNode(node);
        final NodeAddress nodeAddress = JsonNodeUtils.prepareNodeAddress(proxyNode);

        serviceWorker.schedule(() -> {
            LOGGER.info("Query the new booster state");
            boosterService.queryBoosterState(connectionId, nodeAddress);
        }, 5, TimeUnit.MILLISECONDS);
    }

    private void processBoosterStateRequest(
        String connectionId, final HostAdapter distributedHostAdapter,
        final BidibMessageInterface bidibMessage, final NodeInterface node) throws ProtocolException {

        BoosterStatus boosterState = null;
        switch (ByteUtils.getInt(bidibMessage.getType())) {
            case BidibLibrary.MSG_BOOST_OFF:
                boosterState = BoosterStatus.OFF;
                break;
            case BidibLibrary.MSG_BOOST_ON:
                boosterState = BoosterStatus.ON;
                break;
        }

        final BoosterStatus boosterStatus = boosterState;
        LOGGER.info("Set the booster state: {}", boosterStatus);

        ProxyNode proxyNode = ProxyNode.getProxyNode(node);
        final NodeAddress nodeAddress = JsonNodeUtils.prepareNodeAddress(proxyNode);

        serviceWorker.schedule(() -> {
            // LOGGER.info("Set the new booster state: {}", boosterStatus);
            boosterService.setBoosterState(connectionId, nodeAddress, boosterStatus);
        }, 5, TimeUnit.MILLISECONDS);
    }

    private void processSysEnableOrDisableRequest(
        String connectionId, final BidibMessageInterface bidibMessage, final NodeInterface node, boolean enableNode)
        throws ProtocolException {
        LOGGER.info("Enable/Disable the node: {}, enableNode: {}", node, enableNode);

        ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        if (enableNode != proxyNode.isEnabled()) {

            proxyNode.setEnabled(enableNode);

            final NodeAddress nodeAddress = JsonNodeUtils.prepareNodeAddress(proxyNode);

            serviceWorker.schedule(() -> {
                LOGGER.info("Enable the node: {}", node);
                if (enableNode) {
                    nodeService.enable(connectionId, nodeAddress);
                }
                else {
                    // TODO prevent disable spontaneous events from the node
                    // nodeService.disable(connectionId, nodeAddress);
                }
            }, 5, TimeUnit.MILLISECONDS);
        }

    }

    private void processFeedbackGetRangeRequest(
        String connectionId, final HostAdapter distributedHostAdapter,
        BidibMessageInterface bidibMessage, NodeInterface node) throws ProtocolException {
        LOGGER.info("Get feedback state, node: {}", node);

        FeedbackGetRangeMessage feedbackGetRangeMessage = (FeedbackGetRangeMessage) bidibMessage;
        final ProxyNode proxyNode = ProxyNode.getProxyNode(node);
        final NodeAddress nodeAddress = JsonNodeUtils.prepareNodeAddress(proxyNode);

        if (CollectionUtils.isEmpty(proxyNode.getFeedbackPorts())) {
            // set the feedback ports on the proxy node

            LOGGER.info("Get the feedback ports.");

            List nodeFeedbackPorts = nodeService.queryAllOccupancyPorts(connectionId, nodeAddress);
            List ports = new ArrayList<>();
            for (FeedbackPort nodeFeedbackPort : nodeFeedbackPorts) {

                FeedbackPort feedbackPort = new FeedbackPort();
                feedbackPort.setId(nodeFeedbackPort.getId());
                feedbackPort.setEnabled(nodeFeedbackPort.isEnabled());

                feedbackPort.setConfigStatus(nodeFeedbackPort.getConfigStatus());

                feedbackPort.setStatus(nodeFeedbackPort.getStatus());
                FeedbackTimestampData feedbackTimestampData = nodeFeedbackPort.getTimestamp();
                if (feedbackTimestampData != null) {
                    feedbackPort.setTimestamp(new FeedbackTimestampData(feedbackTimestampData.getTimestamp()));
                }
                FeedbackConfidenceData confidenceData = nodeFeedbackPort.getConfidence();
                if (confidenceData != null) {
                    feedbackPort
                        .setConfidence(new FeedbackConfidenceData(confidenceData.isInvalid(), confidenceData.isFreeze(),
                            confidenceData.isNoSignal()));
                }

                ports.add(feedbackPort);
            }

            LOGGER.info("Prepared the feedback ports for the proxyNode: {}", ports);
            proxyNode.setFeedbackPorts(ports);
        }

        // nodeService
        // .queryFeedbackPortStatus(connectionId, nodeAddress, feedbackGetRangeMessage.getBeginRange(),
        // feedbackGetRangeMessage.getEndRange());

        // TODO publish the MSG_BM_MULTIPLE

        final List feedbackPorts = proxyNode.getFeedbackPorts();

        int baseAddress = feedbackGetRangeMessage.getBeginRange();
        int end = feedbackGetRangeMessage.getEndRange();
        int feedbackSize = feedbackGetRangeMessage.getEndRange() - feedbackGetRangeMessage.getBeginRange();

        byte value = 0x00;
        int index = 0;

        int feedbackByteSize = feedbackSize / 8 + (feedbackSize % 8 > 0 ? 1 : 0);

        byte[] feedbackMultiple = new byte[feedbackByteSize];
        int position = feedbackMultiple.length;

        for (int portNum = end; portNum > baseAddress; portNum--) {
            value = (byte) ((value & 0xFF) << 1);

            FeedbackPort fbp = PortListUtils.findPortByPortNumber(feedbackPorts, portNum - 1);
            byte status = 0;
            if (fbp != null) {
                status = ByteUtils.getLowByte(fbp.getStatus().getType().getType(), 0x01);
            }
            value |= status;
            feedbackMultiple[position - 1] = value;

            index++;
            if (index > 7) {
                value = 0;
                index = 0;
                position--;
            }
        }

        LOGGER.info("Prepared feedback multiple: {}", ByteUtils.bytesToHex(feedbackMultiple));

        final BidibMessage occupancyMultipleResponse =
            responseFactory
                .createOccupancyMultipleResponse(bidibMessage.getAddr(), 0, baseAddress, feedbackSize,
                    feedbackMultiple);
        LOGGER.info("Publish occupancyMultipleResponse: {}", occupancyMultipleResponse);

        distributedHostAdapter.forwardMessageToGuest(proxyNode, occupancyMultipleResponse);
    }

    private void processFeedbackGetConfidenceRequest(
        String connectionId, final HostAdapter distributedHostAdapter,
        BidibMessageInterface bidibMessage, NodeInterface node) throws ProtocolException {
        LOGGER.info("Get feedback confidence, node: {}", node);

        // FeedbackGetConfidenceMessage feedbackGetConfidenceMessage = (FeedbackGetConfidenceMessage) bidibMessage;
        ProxyNode proxyNode = ProxyNode.getProxyNode(node);

        FeedbackConfidenceStatus feedbackConfidenceStatus = proxyNode.getFeedbackPortGroupConfidence();

        // final NodeAddress nodeAddress = JsonNodeUtils.prepareNodeAddress(proxyNode);
        // nodeService.queryFeedbackConfidence(connectionId, nodeAddress);

        int valid = feedbackConfidenceStatus.getInvalid();
        int freeze = feedbackConfidenceStatus.getFreeze();
        int signal = feedbackConfidenceStatus.getNoSignal();

        final BidibMessage occupancyConfidenceResponse =
            responseFactory.createOccupancyConfidenceResponse(bidibMessage.getAddr(), 0, valid, freeze, signal);
        LOGGER.info("Publish occupancyConfidenceResponse: {}", occupancyConfidenceResponse);

        distributedHostAdapter.forwardMessageToGuest(proxyNode, occupancyConfidenceResponse);

    }

    private void processFeedbackAddressGetRangeRequest(
        String connectionId, BidibMessageInterface bidibMessage, NodeInterface node) {
        LOGGER.info("Get feedback addresses, node: {}", node);
        // TODO Auto-generated method stub

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy