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

org.bidib.wizard.simulation.RFBasisNodeSimulator Maven / Gradle / Ivy

There is a newer version: 2.0.34
Show newest version
package org.bidib.wizard.simulation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.collections4.CollectionUtils;
import org.bidib.jbidibc.messages.AddressData;
import org.bidib.jbidibc.messages.BidibLibrary;
import org.bidib.jbidibc.messages.Feature;
import org.bidib.jbidibc.messages.FeedbackAddressData;
import org.bidib.jbidibc.messages.enums.AddressTypeEnum;
import org.bidib.jbidibc.messages.enums.CommandStationPom;
import org.bidib.jbidibc.messages.enums.CommandStationProgState;
import org.bidib.jbidibc.messages.enums.CommandStationPt;
import org.bidib.jbidibc.messages.enums.CommandStationState;
import org.bidib.jbidibc.messages.enums.CsQueryTypeEnum;
import org.bidib.jbidibc.messages.enums.EnrailmentDirectionEnum;
import org.bidib.jbidibc.messages.enums.FeatureEnum;
import org.bidib.jbidibc.messages.enums.PositionLocationEnum;
import org.bidib.jbidibc.messages.enums.SpeedStepsEnum;
import org.bidib.jbidibc.messages.exception.ProtocolException;
import org.bidib.jbidibc.messages.message.BidibMessage;
import org.bidib.jbidibc.messages.message.BidibMessageInterface;
import org.bidib.jbidibc.messages.message.BidibRequestFactory;
import org.bidib.jbidibc.messages.message.CommandStationAccessoryAcknowledgeResponse;
import org.bidib.jbidibc.messages.message.CommandStationAccessoryMessage;
import org.bidib.jbidibc.messages.message.CommandStationBinaryStateMessage;
import org.bidib.jbidibc.messages.message.CommandStationDriveAcknowledgeResponse;
import org.bidib.jbidibc.messages.message.CommandStationDriveMessage;
import org.bidib.jbidibc.messages.message.CommandStationDriveStateResponse;
import org.bidib.jbidibc.messages.message.CommandStationPomAcknowledgeResponse;
import org.bidib.jbidibc.messages.message.CommandStationPomMessage;
import org.bidib.jbidibc.messages.message.CommandStationProgMessage;
import org.bidib.jbidibc.messages.message.CommandStationProgStateResponse;
import org.bidib.jbidibc.messages.message.CommandStationQueryMessage;
import org.bidib.jbidibc.messages.message.CommandStationSetStateMessage;
import org.bidib.jbidibc.messages.message.CommandStationStateResponse;
import org.bidib.jbidibc.messages.message.FeedbackAddressResponse;
import org.bidib.jbidibc.messages.message.FeedbackConfidenceResponse;
import org.bidib.jbidibc.messages.message.FeedbackCvResponse;
import org.bidib.jbidibc.messages.message.FeedbackFreeResponse;
import org.bidib.jbidibc.messages.message.FeedbackGetRangeMessage;
import org.bidib.jbidibc.messages.message.FeedbackMultipleResponse;
import org.bidib.jbidibc.messages.message.FeedbackOccupiedResponse;
import org.bidib.jbidibc.messages.message.FeedbackPositionResponse;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.NodeUtils;
import org.bidib.jbidibc.simulation.SimulationBidibMessageProcessor;
import org.bidib.jbidibc.simulation.annotation.BidibNodeSimulator;
import org.bidib.jbidibc.simulation.annotation.BidibNodeSimulators;
import org.bidib.jbidibc.simulation.nodes.DefaultNodeSimulator;
import org.bidib.wizard.model.ports.FeedbackPort;
import org.bidib.wizard.model.ports.Port;
import org.bidib.wizard.model.status.FeedbackPortStatus;
import org.bidib.wizard.simulation.events.FeedbackConfidenceSetEvent;
import org.bidib.wizard.simulation.events.FeedbackConfidenceStatusEvent;
import org.bidib.wizard.simulation.events.FeedbackPortSetStatusEvent;
import org.bidib.wizard.simulation.events.FeedbackPortStatusEvent;
import org.bushe.swing.event.EventBus;
import org.bushe.swing.event.annotation.AnnotationProcessor;
import org.bushe.swing.event.annotation.EventSubscriber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@BidibNodeSimulators({ @BidibNodeSimulator(vid = "13", pid = "32770"), @BidibNodeSimulator(vid = "251", pid = "302") })
public class RFBasisNodeSimulator extends DefaultNodeSimulator {
    private static final Logger LOGGER = LoggerFactory.getLogger(RFBasisNodeSimulator.class);

    private static final String SIMULATION_PANEL_CLASS =
        "org.bidib.wizard.simulation.client.view.panel.GBMboostMasterPanel";

    private final Map feedbackPorts = new HashMap();

    private final AtomicBoolean statusFreeze = new AtomicBoolean();

    private final AtomicBoolean statusValid = new AtomicBoolean();

    private final AtomicBoolean statusSignal = new AtomicBoolean();

    private CommandStationState commandStationState = CommandStationState.OFF;

    private Map mapLocoCV = new LinkedHashMap();

    protected static final int NUM_OF_FEEDBACK_PORTS = 16;

    protected final ScheduledExecutorService positionWorker = Executors.newScheduledThreadPool(1);

    public RFBasisNodeSimulator(byte[] nodeAddress, long uniqueId, boolean autoAddFeature,
        SimulationBidibMessageProcessor messageReceiver, final BidibRequestFactory bidibRequestFactory) {
        super(nodeAddress, uniqueId, autoAddFeature, messageReceiver, bidibRequestFactory);
    }

    @Override
    protected void prepareFeatures() {
        super.prepareFeatures();

        features.add(new Feature(BidibLibrary.FEATURE_BM_SIZE, NUM_OF_FEEDBACK_PORTS));
        features.add(new Feature(BidibLibrary.FEATURE_BM_ON, 1));
        features.add(new Feature(BidibLibrary.FEATURE_BM_SECACK_AVAILABLE, 1));
        features.add(new Feature(BidibLibrary.FEATURE_BM_SECACK_ON, 100));
        features.add(new Feature(BidibLibrary.FEATURE_BM_ADDR_DETECT_AVAILABLE, 1));
        features.add(new Feature(BidibLibrary.FEATURE_BM_ADDR_DETECT_ON, 1));
        features.add(new Feature(BidibLibrary.FEATURE_BM_ADDR_AND_DIR, 1));
        // features.add(new Feature(BidibLibrary.FEATURE_BM_TIMESTAMP_ON, 1));
        features.add(new Feature(BidibLibrary.FEATURE_BM_ISTSPEED_AVAILABLE, 1));
        features.add(new Feature(BidibLibrary.FEATURE_BM_ISTSPEED_INTERVAL, 5));

        features.add(new Feature(BidibLibrary.FEATURE_BM_CV_AVAILABLE, 1));
        features.add(new Feature(BidibLibrary.FEATURE_BM_CV_ON, 1));
        features.add(new Feature(BidibLibrary.FEATURE_BM_DYN_STATE_INTERVAL, 100));

        features.add(new Feature(BidibLibrary.FEATURE_BM_POSITION_ON, 1));
        features.add(new Feature(BidibLibrary.FEATURE_BM_POSITION_SECACK, 30));

        features.add(new Feature(BidibLibrary.FEATURE_GEN_WATCHDOG, 20));
        features.add(new Feature(BidibLibrary.FEATURE_GEN_POM_REPEAT, 3));

        features.add(new Feature(BidibLibrary.FEATURE_GEN_DRIVE_BUS, 1));
        features.add(new Feature(BidibLibrary.FEATURE_GEN_LOK_LOST_DETECT, 1));
        features.add(new Feature(BidibLibrary.FEATURE_GEN_NOTIFY_DRIVE_MANUAL, 1));

        features.add(new Feature(BidibLibrary.FEATURE_GEN_START_STATE, 1));
    }

    @Override
    public String getSimulationPanelClass() {
        return SIMULATION_PANEL_CLASS;
    }

    @Override
    public void start() {
        LOGGER.info("Start the simulator for address: {}", getAddress());

        AnnotationProcessor.process(this);

        // setup loco decoder CV
        mapLocoCV.put(1, Integer.valueOf(3));
        mapLocoCV.put(29, Integer.valueOf(0));
        mapLocoCV.put(28, Integer.valueOf(3)); // set the railcom config

        // prepare the feedback ports
        setupFeedbackPorts();

        super.start();
    }

    @Override
    public void stop() {
        AnnotationProcessor.unprocess(this);

        if (positionWorker != null) {
            LOGGER.info("Stop the position worker.");
            positionWorker.shutdownNow();
        }

        super.stop();
    }

    private void setupFeedbackPorts() {
        for (int id = 0; id < NUM_OF_FEEDBACK_PORTS; id++) {
            FeedbackPort port = new FeedbackPort();

            port.setId(id);
            // port.setStatus(id % 3 == 0 ? FeedbackPortStatus.FREE : FeedbackPortStatus.OCCUPIED);
            port.setStatus(FeedbackPortStatus.FREE);
            feedbackPorts.put(id, port);
        }
    }

    @Override
    protected byte[] prepareResponse(BidibMessageInterface bidibMessage) {

        byte[] response = null;
        switch (ByteUtils.getInt(bidibMessage.getType())) {
            case BidibLibrary.MSG_BM_GET_RANGE:
                response = processBmGetRangeRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_BM_MIRROR_MULTIPLE:
                processBmMirrorMultipleRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_BM_MIRROR_OCC:
                processBmMirrorOccupiedRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_BM_MIRROR_FREE:
                processBmMirrorFreeRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_BM_MIRROR_POSITION:
                processBmMirrorPositionRequest(bidibMessage);
                break;

            case BidibLibrary.MSG_BM_ADDR_GET_RANGE:
                processBmAddrGetRangeRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_BM_GET_CONFIDENCE:
                response = processBmGetConfidenceRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_CS_SET_STATE:
                response = processCsSetStateRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_CS_POM:
                response = processCsPomRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_CS_PROG:
                response = processCsProgRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_CS_DRIVE:
                response = processCsDriveRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_CS_ACCESSORY:
                response = processCsAccessoryRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_CS_QUERY:
                response = processCsQueryRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_CS_BIN_STATE:
                response = processCsBinStateRequest(bidibMessage);
                break;
            default:
                response = super.prepareResponse(bidibMessage);
                break;
        }
        return response;
    }

    private ScheduledFuture futureTriggerPositionWorker;

    protected byte[] processCsSetStateRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the CsSetState request: {}", bidibMessage);
        byte[] response = null;
        try {
            CommandStationSetStateMessage commandStationSetStateMessage = (CommandStationSetStateMessage) bidibMessage;
            CommandStationState state = commandStationSetStateMessage.getState();
            LOGGER.info("The requested command station state is: {}", state);

            switch (state) {
                case OFF:
                    commandStationState = CommandStationState.OFF;
                    break;
                case STOP:
                case SOFTSTOP:
                    commandStationState = CommandStationState.STOP;
                    break;
                case GO:
                case GO_IGN_WD:
                    commandStationState = CommandStationState.GO;
                    break;
                case PROG:
                    commandStationState = CommandStationState.PROG;
                    break;
                case QUERY:
                    LOGGER.warn("Query command station state requested");
                    break;
                default:
                    LOGGER.warn("Unprocessed command station state: {}", state);
                    break;
            }

            LOGGER.info("Return current command station state: {}", commandStationState);
            CommandStationStateResponse commandStationStateResponse =
                new CommandStationStateResponse(bidibMessage.getAddr(), getNextSendNum(),
                    commandStationState.getType());
            response = commandStationStateResponse.getContent();

            // publish response now
            publishResponse(response);
            response = null;

            // handle state
            handleCsState(commandStationState);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create CommandStationState response failed.", ex);
        }
        return response;
    }

    private void handleCsState(final CommandStationState commandStationState) {

        // use executor to send response
        switch (commandStationState) {
            case GO:
            case GO_IGN_WD:
                if (futureTriggerPositionWorker == null) {

                    LOGGER.info("Schedule the position trigger");

                    int interval = 4000;

                    futureTriggerPositionWorker = positionWorker.scheduleAtFixedRate(() -> {
                        LOGGER.info("Trigger position");
                        try {
                            triggerPositionResponse();
                        }
                        catch (Exception ex) {
                            LOGGER.warn("Trigger the position failed.", ex);
                        }
                        LOGGER.info("Trigger position has finished.");
                    }, 500, interval, TimeUnit.MILLISECONDS);
                }
                break;
            case STOP:
            case SOFTSTOP:
            case OFF:
                if (futureTriggerPositionWorker != null) {
                    LOGGER.info("Stop the position trigger.");
                    futureTriggerPositionWorker.cancel(false);

                    futureTriggerPositionWorker = null;
                }
                break;
            default:
                break;
        }
    }

    protected static final int MAX_NUM_OF_FEEDBACK_PORTS = 16;

    private int currentCarPosition = -1;

    private int carAddress = 3;

    protected void triggerPositionResponse() {
        try {

            int portNum = currentCarPosition;
            LOGGER.info("Trigger position address, portNum: {}", portNum);

            if (portNum > -1) {
                portNum = currentCarPosition;
                if (portNum < 0) {
                    portNum = MAX_NUM_OF_FEEDBACK_PORTS - 1;
                }
                // make the previous active position free
            }

            currentCarPosition++;
            if (currentCarPosition >= MAX_NUM_OF_FEEDBACK_PORTS) {
                currentCarPosition = 0;
            }

            int offset = getLocalAddr();
            // TODO set the minimum offset number correct
            if (offset < 12) {
                offset = 12;
            }

            int locationType = ByteUtils.getInt(PositionLocationEnum.LOCATOR_BADGE.getType());

            triggerPositionAddressResponse(currentCarPosition + ((offset - 12) * 100), carAddress + (offset * 2),
                locationType, currentCarPosition);

            int delayValue = ThreadLocalRandom.current().nextInt(300, 800);
            LOGGER.info("Wait for next execution, delayValue: {}", delayValue);
            Thread.sleep(delayValue);

            triggerPositionAddressResponse(currentCarPosition + ((offset - 12) * 100), carAddress + 1 + (offset * 2),
                locationType, currentCarPosition);

        }
        catch (Exception ex) {
            LOGGER.warn("Trigger the feedback address failed.", ex);
        }
        LOGGER.info("Trigger feedback address has finished.");

    }

    private void triggerPositionAddressResponse(int portNum, int carAddress, int locationType, int carPosition) {
        LOGGER.info("Trigger the feedback address repsonse, portNum: {}", portNum);
        byte[] response = null;

        try {

            // int locationType = 0;
            FeedbackPositionResponse feedbackPositionEvent =
                new FeedbackPositionResponse(nodeAddress, getNextSendNum(), carAddress, locationType, carPosition);

            response = feedbackPositionEvent.getContent();
            sendSpontanousResponse(response);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create FeedbackPositionResponse failed.", ex);
        }
    }

    protected byte[] processCsPomRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the CsPom request: {}", bidibMessage);
        byte[] response = null;

        // prepare the POM acknowledge
        try {
            CommandStationPomMessage commandStationPomMessage = (CommandStationPomMessage) bidibMessage;
            org.bidib.jbidibc.messages.PomAddressData addressData = commandStationPomMessage.getDecoderAddress();
            LOGGER.info("Received addressData: {}", addressData);
            CommandStationPomAcknowledgeResponse commandStationPomAckResponse =
                new CommandStationPomAcknowledgeResponse(bidibMessage.getAddr(), getNextSendNum(), addressData,
                    (byte) 1);
            response = commandStationPomAckResponse.getContent();

            LOGGER.info("Publish the running response: {}", commandStationPomAckResponse);
            publishResponse(response);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create CommandStationPomAck response failed.", ex);
        }

        // prepare the MSG_BM_CV that contains the real data
        try {
            LOGGER.info("prepare the MSG_BM_CV that contains the real data.");
            CommandStationPomMessage commandStationPomMessage = (CommandStationPomMessage) bidibMessage;
            org.bidib.jbidibc.messages.PomAddressData addressData = commandStationPomMessage.getDecoderAddress();
            int cvNumber = commandStationPomMessage.getCvNumber();

            LOGGER.info("Current CV number: {}", cvNumber);
            byte cvValue = 12;

            if (cvNumber == 29) {
                cvValue = 0;
            }

            CommandStationPom opCode =
                CommandStationPom.valueOf(ByteUtils.getLowByte(commandStationPomMessage.getOpCode()));
            switch (opCode) {
                case WR_BYTE:
                    cvValue = ByteUtils.getLowByte(commandStationPomMessage.getCvValue());
                    break;
                default:
                    break;
            }

            FeedbackCvResponse feedbackCvResponse =
                new FeedbackCvResponse(bidibMessage.getAddr(), getNextSendNum(), addressData.getAddress(), cvNumber - 1,
                    cvValue);
            response = feedbackCvResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create CommandStationPomAck response failed.", ex);
        }

        return response;
    }

    protected byte[] processCsProgRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the CsProg request: {}", bidibMessage);
        byte[] response = null;
        try {
            CommandStationProgMessage commandStationProgMessage = (CommandStationProgMessage) bidibMessage;
            CommandStationPt opCode = commandStationProgMessage.getOpCode();
            int cvNumber = commandStationProgMessage.getCvNumber();
            int cvData = commandStationProgMessage.getCvData();
            LOGGER.info("Received opCode: {}, cvNumber: {}, cvData: {}", opCode, cvNumber, cvData);
            boolean sendTimeoutResponse = false;
            switch (opCode) {
                case BIDIB_CS_PROG_BREAK:
                    break;
                case BIDIB_CS_PROG_QUERY:
                    break;
                case BIDIB_CS_PROG_RDWR_BIT:
                    if ((cvData & 0x10) == 0x10) {
                        Integer storedValue = mapLocoCV.get(cvNumber);
                        // write operation
                        if (storedValue != null) {
                            byte byteValue = ByteUtils.getLowByte(storedValue.intValue());
                            byteValue =
                                ByteUtils.setBit(byteValue, (cvData & 0x08) == 0x08 ? true : false, cvData & 0x07);
                            LOGGER.info("Changed CV value: {}", ByteUtils.byteToHex(byteValue));
                            mapLocoCV.put(cvNumber, Integer.valueOf(byteValue));
                        }
                        else {
                            byte byteValue = 0;
                            byteValue =
                                ByteUtils.setBit(byteValue, (cvData & 0x08) == 0x08 ? true : false, cvData & 0x07);
                            LOGGER.info("Changed CV value: {}", ByteUtils.byteToHex(byteValue));
                            mapLocoCV.put(cvNumber, Integer.valueOf(byteValue));
                        }
                    }
                    else {
                        // read operation
                        Integer storedValue = mapLocoCV.get(cvNumber);
                        if (storedValue != null) {
                            byte byteValue = ByteUtils.getLowByte(storedValue.intValue());
                            boolean bitIsSetEqual =
                                ByteUtils.isBitSetEqual(byteValue, ByteUtils.getBit(cvData, 3), cvData & 0x07);
                            LOGGER.info("Verify bitIsSetEqual: {}, byteValue: {}", bitIsSetEqual, byteValue);
                            if (!bitIsSetEqual) {
                                LOGGER.warn("Send timeout response!");
                                sendTimeoutResponse = true;
                            }
                        }
                        else {
                            LOGGER.warn("The requested CV value is not stored, cvNumber: {}", cvNumber);
                            sendTimeoutResponse = true;
                        }
                    }
                    break;
                case BIDIB_CS_PROG_WR_BYTE:
                    mapLocoCV.put(cvNumber, cvData);
                    break;
                default:
                    Integer storedValue = mapLocoCV.get(cvNumber);
                    if (storedValue != null) {
                        cvData = ByteUtils.getLowByte(storedValue.intValue());
                    }
                    else {
                        LOGGER.warn("The requested CV value is not stored, cvNumber: {}", cvNumber);
                        sendTimeoutResponse = true;
                    }
                    break;
            }

            CommandStationProgStateResponse commandStationProgStateResponse =
                new CommandStationProgStateResponse(bidibMessage.getAddr(), getNextSendNum(),
                    CommandStationProgState.PROG_RUNNING, 1, cvNumber, cvData);
            response = commandStationProgStateResponse.getContent();
            LOGGER.info("Publish the running response: {}", commandStationProgStateResponse);
            publishResponse(response);

            LOGGER.info("Sleep a second.");
            // sleep some time
            try {
                Thread.sleep(1000);
            }
            catch (InterruptedException e) {
                LOGGER.warn("Sleep thread was interrupted.", e);
            }

            if (!sendTimeoutResponse) {
                LOGGER.info("Prepare the OKAY state response.");
                commandStationProgStateResponse =
                    new CommandStationProgStateResponse(bidibMessage.getAddr(), getNextSendNum(),
                        CommandStationProgState.PROG_OKAY, 1, cvNumber, cvData);
            }
            else {
                LOGGER.info("Prepare the NO_ANSWER state response.");
                commandStationProgStateResponse =
                    new CommandStationProgStateResponse(bidibMessage.getAddr(), getNextSendNum(),
                        CommandStationProgState.PROG_NO_ANSWER, 1, cvNumber, 0xFF);
            }
            response = commandStationProgStateResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create CommandStationProgState response failed.", ex);
        }
        return response;
    }

    protected byte[] processCsDriveRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the CsDrive request: {}", bidibMessage);
        byte[] response = null;
        try {
            CommandStationDriveMessage commandStationDriveMessage = (CommandStationDriveMessage) bidibMessage;
            org.bidib.jbidibc.messages.AddressData addressData = commandStationDriveMessage.getDecoderAddress();

            int messageNumber = commandStationDriveMessage.getNum();

            LOGGER.info("Received addressData: {}", addressData);
            CommandStationDriveAcknowledgeResponse commandStationDriveAckResponse =
                new CommandStationDriveAcknowledgeResponse(bidibMessage.getAddr(), getNextSendNum(), addressData,
                    (byte) 1, Integer.valueOf(messageNumber));
            response = commandStationDriveAckResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create CommandStationDriveAck response failed.", ex);
        }
        return response;
    }

    private byte[] processCsBinStateRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the CsBinState request: {}", bidibMessage);

        Integer requestedCellNumber = null;
        Integer carAddress = null;

        byte[] response = null;
        try {
            CommandStationBinaryStateMessage commandStationBinStateMessage =
                (CommandStationBinaryStateMessage) bidibMessage;
            org.bidib.jbidibc.messages.AddressData addressData = commandStationBinStateMessage.getDecoderAddress();
            int binStateNumber = commandStationBinStateMessage.getBinaryStateNumber();
            int binStateValue = commandStationBinStateMessage.getBinaryStateValue();
            int messageNumber = commandStationBinStateMessage.getNum();

            LOGGER
                .info("Received addressData: {}, binStateNumber: {}, binStateValue: {}", addressData, binStateNumber,
                    binStateValue);
            CommandStationDriveAcknowledgeResponse commandStationDriveAckResponse =
                new CommandStationDriveAcknowledgeResponse(bidibMessage.getAddr(), getNextSendNum(), addressData,
                    (byte) 1, Integer.valueOf(messageNumber));
            response = commandStationDriveAckResponse.getContent();

            sendSpontanousResponse(response);

            if (binStateValue > 0 && binStateNumber >= 512 && binStateNumber <= 519) {
                requestedCellNumber = Integer.valueOf(binStateNumber - 511);
                carAddress = addressData.getAddress();
                LOGGER.info("The requested cell number: {}, carAddress: {}", requestedCellNumber, carAddress);
            }

        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create CommandStationDriveAck response failed.", ex);
        }

        response = null;
        // signal the MSG_BM_POSITION for the decoder with the new cell identifier
        final Feature featureCellNumber = getFeature(FeatureEnum.FEATURE_CELL_NUMBER.getNumber());
        if (featureCellNumber != null && requestedCellNumber != null
            && featureCellNumber.getValue() == requestedCellNumber.intValue()) {

            int offset = getLocalAddr();
            // TODO set the minimum offset number correct
            if (offset < 12) {
                offset = 12;
            }

            LOGGER.info("The cell number has changed, requested cellnumber: {}", requestedCellNumber);

            triggerPositionAddressResponse(currentCarPosition + ((offset - 12) * 100), carAddress, 1,
                requestedCellNumber.intValue());

        }

        return response;
    }

    protected byte[] processCsQueryRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the CsQuery request: {}", bidibMessage);
        byte[] response = null;
        try {
            CommandStationQueryMessage commandStationQueryMessage = (CommandStationQueryMessage) bidibMessage;
            CsQueryTypeEnum csQueryType = commandStationQueryMessage.getCsQueryType();

            switch (csQueryType) {
                case LOCO_LIST:
                    // prepare some locos

                    // TODO check the real csQuery value to check if a single loco is requested
                    org.bidib.jbidibc.messages.AddressData addressData =
                        new AddressData(13, AddressTypeEnum.LOCOMOTIVE_FORWARD);
                    byte[] functions = new byte[] { (byte) 0x80, 0x00, 0x72, (byte) 0x85 };
                    CommandStationDriveStateResponse driveStateResponse =
                        new CommandStationDriveStateResponse(bidibMessage.getAddr(), getNextSendNum(), 0x41,
                            addressData, SpeedStepsEnum.DCC128, 39, functions);

                    response = driveStateResponse.getContent();
                    break;
                default:
                    LOGGER.warn("The CsQueryRequest is not implemented for type: {}", csQueryType);
                    break;
            }

        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create CommandStationDriveAck response failed.", ex);
        }
        return response;
    }

    protected byte[] processCsAccessoryRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the CsAccessory request: {}", bidibMessage);
        byte[] response = null;
        try {
            CommandStationAccessoryMessage commandStationAccessoryMessage =
                (CommandStationAccessoryMessage) bidibMessage;
            org.bidib.jbidibc.messages.AddressData addressData = commandStationAccessoryMessage.getDecoderAddress();
            LOGGER.info("Received addressData: {}", addressData);
            CommandStationAccessoryAcknowledgeResponse commandStationAccessoryAckResponse =
                new CommandStationAccessoryAcknowledgeResponse(bidibMessage.getAddr(), getNextSendNum(), addressData,
                    (byte) 1);
            response = commandStationAccessoryAckResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create CommandStationAccessoryAck response failed.", ex);
        }
        return response;
    }

    protected byte[] processBmGetRangeRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the FeedbackGetRangeMessage: {}", bidibMessage);

        byte[] response = null;
        try {
            FeedbackGetRangeMessage feedbackGetRangeMessage = (FeedbackGetRangeMessage) bidibMessage;
            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 = feedbackPorts.get(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));

            FeedbackMultipleResponse feedbackMultipleResponse =
                new FeedbackMultipleResponse(bidibMessage.getAddr(), getNextSendNum(),
                    ByteUtils.getLowByte(baseAddress), ByteUtils.getLowByte(feedbackSize), feedbackMultiple);
            response = feedbackMultipleResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create feedbackMultiple response failed.", ex);
        }
        return response;
    }

    protected void processBmMirrorMultipleRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the FeedbackMirrorMultipleMessage: {}, do nothing ...", bidibMessage);
    }

    protected void processBmMirrorOccupiedRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the processBmMirrorOccupiedRequest: {}, do nothing ...", bidibMessage);
    }

    protected void processBmMirrorFreeRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the processBmMirrorFreeRequest: {}, do nothing ...", bidibMessage);
    }

    protected void processBmMirrorPositionRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the processBmMirrorPositionRequest: {}, do nothing ...", bidibMessage);
    }

    protected void processBmAddrGetRangeRequest(BidibMessageInterface bidibMessage) {

        try {
            for (FeedbackPort port : feedbackPorts.values()) {

                int detectorNumber = port.getId();
                List bidibAddresses = new ArrayList<>();
                List addresses = port.getAddresses();
                if (CollectionUtils.isNotEmpty(addresses)) {
                    for (FeedbackAddressData addressData : addresses) {
                        final EnrailmentDirectionEnum enrailmentDirection = addressData.getType();
                        AddressTypeEnum addressType = null;
                        switch (enrailmentDirection) {
                            case LOCOMOTIVE_LEFT:
                            case LOCOMOTIVE_RIGHT:
                                addressType = AddressTypeEnum.LOCOMOTIVE_FORWARD;
                                break;
                            case BASIC_ACCESSORY:
                                addressType = AddressTypeEnum.ACCESSORY;
                                break;
                            case EXTENDED_ACCESSORY:
                                addressType = AddressTypeEnum.EXTENDED_ACCESSORY;
                                break;
                            default:
                                break;
                        }
                        AddressData bidibAddress = new AddressData(addressData.getAddress(), addressType);
                        bidibAddresses.add(bidibAddress);
                    }
                }
                FeedbackAddressResponse feedbackAddressResponse =
                    new FeedbackAddressResponse(bidibMessage.getAddr(), getNextSendNum(), detectorNumber,
                        bidibAddresses);
                byte[] response = feedbackAddressResponse.getContent();
                LOGGER.info("Prepare feedbackAddressResponse: {}", ByteUtils.bytesToHex(response));
                sendSpontanousResponse(response);
            }
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create feedbackAddress response failed.", ex);
        }
    }

    protected byte[] processBmGetConfidenceRequest(BidibMessageInterface bidibMessage) {
        byte[] response = null;
        try {

            byte valid = (byte) (statusValid.get() ? 1 : 0);
            byte freeze = (byte) (statusFreeze.get() ? 1 : 0);
            byte signal = (byte) (statusSignal.get() ? 1 : 0);

            // TODO if more than a single GBM16T is attached we must set more bits? See 4.7.4. Uplink: Nachrichten für
            // Belegtmelder --> MSG_BM_CONFIDENCE
            // Test with real system: See MainMessageListener.confidence()

            FeedbackConfidenceResponse feedbackConfidenceResponse =
                new FeedbackConfidenceResponse(bidibMessage.getAddr(), getNextSendNum(), valid, freeze, signal);
            response = feedbackConfidenceResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create feedbackConfidence response failed.", ex);
        }
        return response;
    }

    private void publishFeedbackPortChange(Port port) {
        FeedbackPort feedbackPort = (FeedbackPort) port;
        FeedbackPortStatus status = feedbackPort.getStatus();

        LOGGER.info("The feedbackport status has changed, notify the listeners, nodeAddress: {}", nodeAddress);
        EventBus
            .publish(new FeedbackPortStatusEvent(NodeUtils.formatAddress(nodeAddress), feedbackPort.getId(), status));
    }

    @Override
    public void queryStatus(Class portClass) {
        if (FeedbackPort.class.equals(portClass)) {
            for (FeedbackPort feedbackPort : feedbackPorts.values()) {
                publishFeedbackPortChange(feedbackPort);
            }

            // publish the confidence
            publishFeedbackConfidenceStatusEvent(statusValid.get(), statusFreeze.get(), statusSignal.get());
        }
    }

    @EventSubscriber(eventClass = FeedbackConfidenceSetEvent.class)
    public void feedbackConfidenceSetEvent(FeedbackConfidenceSetEvent feedbackConfidenceEvent) {
        String nodeAddress = feedbackConfidenceEvent.getNodeAddr();
        LOGGER.info("The change of the feedback confidence was requested, nodeAddress: {}", nodeAddress);

        // check if the node is addressed
        if (!isAddressEqual(nodeAddress)) {
            LOGGER.trace("Another node is addressed.");
            return;
        }

        statusValid.set(feedbackConfidenceEvent.getValid());
        statusFreeze.set(feedbackConfidenceEvent.getFreeze());
        statusSignal.set(feedbackConfidenceEvent.getSignal());

        byte valid = (byte) (statusValid.get() ? 1 : 0);
        byte freeze = (byte) (statusFreeze.get() ? 1 : 0);
        byte signal = (byte) (statusSignal.get() ? 1 : 0);

        try {
            FeedbackConfidenceResponse feedbackConfidenceResponse =
                new FeedbackConfidenceResponse(this.nodeAddress, getNextSendNum(), valid, freeze, signal);
            LOGGER.info("Prepared feedbackConfidenceResponse: {}", feedbackConfidenceResponse);
            sendSpontanousResponse(feedbackConfidenceResponse.getContent());
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Send feedbackConfidenceResponse failed.", ex);
        }

        publishFeedbackConfidenceStatusEvent(statusValid.get(), statusFreeze.get(), statusSignal.get());
    }

    private void publishFeedbackConfidenceStatusEvent(boolean valid, boolean freeze, boolean signal) {

        LOGGER
            .info("The feedbackport confidence status has changed, notify the listeners, nodeAddress: {}", nodeAddress);
        EventBus
            .publish(new FeedbackConfidenceStatusEvent(NodeUtils.formatAddress(nodeAddress), valid, freeze, signal));
    }

    @EventSubscriber(eventClass = FeedbackPortSetStatusEvent.class)
    public void feedbackPortSetStatus(FeedbackPortSetStatusEvent setStatusEvent) {
        LOGGER.info("The change of the feedback port was requested.");
        String nodeAddress = setStatusEvent.getNodeAddr();

        // check if the node is addressed
        if (!isAddressEqual(nodeAddress)) {
            LOGGER.trace("Another node is addressed.");
            return;
        }

        int portNum = setStatusEvent.getPortNum();
        try {
            changeFeedbackPortStatus(portNum);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Publish feedback status failed.", ex);
        }
    }

    private int timestamp;

    private int getTimestamp() {
        timestamp += 10;
        if (timestamp > 65000) {
            timestamp = 0;
        }
        return timestamp;
    }

    private boolean hasTimestampFeature() {
        Feature feature = Feature.findFeature(features, BidibLibrary.FEATURE_BM_TIMESTAMP_ON);

        return (feature != null && feature.getValue() > 0);
    }

    private int decoderAddress = 10;

    private int getDecoderAddress() {
        decoderAddress++;

        if (decoderAddress > 15) {
            decoderAddress = 10;
        }

        return decoderAddress;
    }

    private int locationAddress = 120;

    private int getLocationAddress() {
        locationAddress++;

        if (locationAddress > 150) {
            locationAddress = 120;
        }

        return locationAddress;
    }

    protected void changeFeedbackPortStatus(int portNum) throws ProtocolException {

        FeedbackPort port = feedbackPorts.get(portNum);
        if (port != null) {
            BidibMessage response = null;

            BidibMessage responsePosition = null;

            switch (port.getStatus()) {
                case FREE:
                    port.setStatus(FeedbackPortStatus.OCCUPIED);
                    if (hasTimestampFeature()) {
                        response =
                            new FeedbackOccupiedResponse(getNodeAddress(), getNextSendNum(), portNum, getTimestamp());
                    }
                    else {
                        response = new FeedbackOccupiedResponse(getNodeAddress(), getNextSendNum(), portNum);
                    }

                    int locationType = ByteUtils.getInt(PositionLocationEnum.LOCATOR_BADGE.getType());
                    responsePosition =
                        new FeedbackPositionResponse(getNodeAddress(), getNextSendNum(), getDecoderAddress(),
                            locationType, getLocationAddress());
                    break;
                default:
                    port.setStatus(FeedbackPortStatus.FREE);

                    response = new FeedbackFreeResponse(getNodeAddress(), getNextSendNum(), portNum);
                    break;
            }

            sendSpontanousResponse(response.getContent());

            if (responsePosition != null) {
                sendSpontanousResponse(responsePosition.getContent());
            }

            publishFeedbackPortChange(port);
        }
        else {
            LOGGER.warn("The requested feedback port is not available: {}", portNum);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy