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

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

The newest version!
package org.bidib.wizard.simulation;

import java.io.File;
import java.io.FileInputStream;
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.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.bidib.jbidibc.core.schema.CvExchangeFactory;
import org.bidib.jbidibc.core.schema.cvexchange.CVSType;
import org.bidib.jbidibc.core.schema.cvexchange.SaveCV;
import org.bidib.jbidibc.messages.AddressData;
import org.bidib.jbidibc.messages.BidibLibrary;
import org.bidib.jbidibc.messages.BidibPort;
import org.bidib.jbidibc.messages.Feature;
import org.bidib.jbidibc.messages.FeedbackAddressData;
import org.bidib.jbidibc.messages.LcConfigX;
import org.bidib.jbidibc.messages.LcMacro;
import org.bidib.jbidibc.messages.enums.AddressTypeEnum;
import org.bidib.jbidibc.messages.enums.EnrailmentDirectionEnum;
import org.bidib.jbidibc.messages.enums.IoBehaviourSwitchEnum;
import org.bidib.jbidibc.messages.enums.LcMacroOperationCode;
import org.bidib.jbidibc.messages.enums.LcMacroState;
import org.bidib.jbidibc.messages.enums.LcOutputType;
import org.bidib.jbidibc.messages.enums.PortModelEnum;
import org.bidib.jbidibc.messages.enums.SoundPortEnum;
import org.bidib.jbidibc.messages.enums.SwitchPortEnum;
import org.bidib.jbidibc.messages.exception.ProtocolException;
import org.bidib.jbidibc.messages.helpers.Context;
import org.bidib.jbidibc.messages.message.AccessoryGetMessage;
import org.bidib.jbidibc.messages.message.AccessoryNotifyResponse;
import org.bidib.jbidibc.messages.message.AccessoryParaGetMessage;
import org.bidib.jbidibc.messages.message.AccessoryParaResponse;
import org.bidib.jbidibc.messages.message.AccessoryParaSetMessage;
import org.bidib.jbidibc.messages.message.AccessorySetMessage;
import org.bidib.jbidibc.messages.message.AccessoryStateResponse;
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.FeedbackAddressResponse;
import org.bidib.jbidibc.messages.message.FeedbackConfidenceResponse;
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.LcConfigXGetAllMessage;
import org.bidib.jbidibc.messages.message.LcConfigXGetMessage;
import org.bidib.jbidibc.messages.message.LcConfigXResponse;
import org.bidib.jbidibc.messages.message.LcConfigXSetMessage;
import org.bidib.jbidibc.messages.message.LcKeyMessage;
import org.bidib.jbidibc.messages.message.LcKeyResponse;
import org.bidib.jbidibc.messages.message.LcMacroGetMessage;
import org.bidib.jbidibc.messages.message.LcMacroHandleMessage;
import org.bidib.jbidibc.messages.message.LcMacroParaGetMessage;
import org.bidib.jbidibc.messages.message.LcMacroParaResponse;
import org.bidib.jbidibc.messages.message.LcMacroParaSetMessage;
import org.bidib.jbidibc.messages.message.LcMacroResponse;
import org.bidib.jbidibc.messages.message.LcMacroSetMessage;
import org.bidib.jbidibc.messages.message.LcMacroStateResponse;
import org.bidib.jbidibc.messages.message.LcNotAvailableResponse;
import org.bidib.jbidibc.messages.message.LcOutputMessage;
import org.bidib.jbidibc.messages.message.LcPortQueryAllMessage;
import org.bidib.jbidibc.messages.message.LcPortQueryMessage;
import org.bidib.jbidibc.messages.message.LcStatResponse;
import org.bidib.jbidibc.messages.message.VendorGetMessage;
import org.bidib.jbidibc.messages.message.VendorResponse;
import org.bidib.jbidibc.messages.port.BytePortConfigValue;
import org.bidib.jbidibc.messages.port.Int16PortConfigValue;
import org.bidib.jbidibc.messages.port.PortConfigUtils;
import org.bidib.jbidibc.messages.port.PortConfigValue;
import org.bidib.jbidibc.messages.port.RgbPortConfigValue;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.MacroUtils;
import org.bidib.jbidibc.messages.utils.NodeUtils;
import org.bidib.jbidibc.messages.utils.ThreadFactoryBuilder;
import org.bidib.jbidibc.simulation.SimulationBidibMessageProcessor;
import org.bidib.jbidibc.simulation.SwitchingFunctionsNode;
import org.bidib.jbidibc.simulation.annotation.BidibNodeSimulator;
import org.bidib.jbidibc.simulation.annotation.BidibNodeSimulators;
import org.bidib.jbidibc.simulation.nodes.DefaultNodeSimulator;
import org.bidib.jbidibc.simulation.nodes.FlatPortType;
import org.bidib.jbidibc.simulation.nodes.InputPortType;
import org.bidib.jbidibc.simulation.nodes.LightPortParamsType;
import org.bidib.jbidibc.simulation.nodes.LightPortType;
import org.bidib.jbidibc.simulation.nodes.MotorPortType;
import org.bidib.jbidibc.simulation.nodes.PortType;
import org.bidib.jbidibc.simulation.nodes.ServoPortType;
import org.bidib.jbidibc.simulation.nodes.SoundPortType;
import org.bidib.jbidibc.simulation.nodes.SwitchPortType;
import org.bidib.wizard.model.ports.FeedbackPort;
import org.bidib.wizard.model.ports.InputPort;
import org.bidib.wizard.model.ports.LightPort;
import org.bidib.wizard.model.ports.MotorPort;
import org.bidib.wizard.model.ports.Port;
import org.bidib.wizard.model.ports.ServoPort;
import org.bidib.wizard.model.ports.SoundPort;
import org.bidib.wizard.model.ports.SwitchPort;
import org.bidib.wizard.model.status.FeedbackPortStatus;
import org.bidib.wizard.model.status.InputPortStatus;
import org.bidib.wizard.model.status.LightPortStatus;
import org.bidib.wizard.model.status.ServoPortStatus;
import org.bidib.wizard.model.status.SoundPortStatus;
import org.bidib.wizard.model.status.SwitchPortStatus;
import org.bidib.wizard.model.stepcontrol.TurnTableType;
import org.bidib.wizard.simulation.events.EmergencyStopSetEvent;
import org.bidib.wizard.simulation.events.EmergencyStopStatusEvent;
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.bidib.wizard.simulation.events.InputPortStatusEvent;
import org.bidib.wizard.simulation.events.MotorPortStatusEvent;
import org.bidib.wizard.simulation.events.ServoPortStatusEvent;
import org.bidib.wizard.simulation.events.SoundPortStatusEvent;
import org.bidib.wizard.simulation.events.SwitchPortStatusEvent;
import org.bidib.wizard.simulation.macro.MacroContainer;
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 = "120"), @BidibNodeSimulator(vid = "251", pid = "202") })
public class OneStepControlSimulator extends DefaultNodeSimulator implements SwitchingFunctionsNode {
    private static final Logger LOGGER = LoggerFactory.getLogger(OneStepControlSimulator.class);

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

    private static final String CV_STEPPOS = "STEPPOS";

    protected final Map macros = new HashMap<>();

    private final Map feedbackPorts = new HashMap<>();

    protected final Map inputPorts = new HashMap<>();

    protected final Map lightPorts = new HashMap<>();

    protected final Map switchPorts = new HashMap<>();

    protected final Map soundPorts = new HashMap<>();

    protected final Map servoPorts = new HashMap<>();

    protected final Map motorPorts = new HashMap<>();

    private final AtomicBoolean statusFreeze = new AtomicBoolean();

    private final AtomicBoolean statusValid = new AtomicBoolean();

    private final AtomicBoolean statusSignal = new AtomicBoolean();

    private final AtomicBoolean emergencyStop = new AtomicBoolean();

    protected int inputPortCount;

    protected int inputPortOffset;

    protected int lightPortCount;

    protected int servoPortCount;

    protected int switchPortCount;

    protected int motorPortCount;

    protected int soundPortCount;

    protected Map aspectMap = new HashMap<>();

    protected Long totalSteps = 0L;

    private TurnTableType tableType = TurnTableType.round;

    private String simulationConfigSearchRoot;

    private final ScheduledExecutorService accessoryStateWorkers =
        Executors
            .newScheduledThreadPool(1,
                new ThreadFactoryBuilder().setNameFormat("simAccessoryStateWorkers-thread-%d").build());

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

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

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

        features.add(new Feature(BidibLibrary.FEATURE_CTRL_PORT_QUERY_AVAILABLE, 1));

        features.add(new Feature(BidibLibrary.FEATURE_CTRL_INPUT_NOTIFY, 1));

        features.add(new Feature(BidibLibrary.FEATURE_BM_SIZE, 4));
        // features.add(new Feature(BidibLibrary.FEATURE_BM_ADDR_DETECT_ON, 1));
        features.add(new Feature(BidibLibrary.FEATURE_BM_ON, 1));

        features.add(new Feature(BidibLibrary.FEATURE_ACCESSORY_MACROMAPPED, 7));
    }

    @Override
    public void init(final Context context) {
        LOGGER.info("Provided context: {}", context);

        this.simulationConfigSearchRoot = context.get("simulationConfigSearchRoot", String.class, null);

        super.init(context);
    }

    private Long getPositionValue(int currentCv) {

        int[] position = new int[4];
        position[0] = Integer.parseInt(configurationVariables.get(Integer.toString(currentCv)));
        position[1] = Integer.parseInt(configurationVariables.get(Integer.toString(currentCv + 1)));
        position[2] = Integer.parseInt(configurationVariables.get(Integer.toString(currentCv + 2)));
        position[3] = Integer.parseInt(configurationVariables.get(Integer.toString(currentCv + 3)));

        long longValue = 0;
        for (int byteIndex = 3; byteIndex > -1; byteIndex--) {
            longValue = (longValue << 8) | position[byteIndex];
        }
        Long positionValue = (longValue) & 0xffffffffL;

        return positionValue;
    }

    private void prepareAspectMap() {

        if (configurationVariables.get(CV_STEPPOS) == null) {
            configurationVariables.put(CV_STEPPOS, "0");
        }

        updateTableType();

        // get the total steps
        totalSteps = getPositionValue(116);

        // clear all configured aspects
        aspectMap.clear();

        // use correct aspect angles, iterate over CV values starting from 169
        int currentCV = 169;

        for (int index = 0; index < 48; index++) {

            Long positionValue = getPositionValue(currentCV + (index * 5) + 1);

            if (positionValue.intValue() == 0xFFFFFFFF) {
                LOGGER
                    .info("Found end of positions marker, stop adding aspects to aspectMap. Current index: {}", index);
                break;
            }

            if (index == 0) {
                configurationVariables.put(CV_STEPPOS, positionValue.toString());
                this.currentPositionValue = positionValue;
            }

            // if (index > 0 && positionValue.intValue() == 0) {
            // break;
            // }

            Float angleValue = positionValue.floatValue() / totalSteps.floatValue();

            LOGGER.info("Current aspect: {}, positionValue: {}, angleValue: {}", index, positionValue, angleValue);
            aspectMap.put(Integer.valueOf(index), positionValue);
        }
    }

    private void updateTableType() {
        final String cvTableType = configurationVariables.get(Integer.toString(114));
        if (cvTableType != null) {
            int valTableType = Integer.parseInt(cvTableType);
            this.tableType = TurnTableType.fromValue(ByteUtils.getLowByte(valTableType));
            LOGGER.info("Current tableType: {}", this.tableType);
        }
    }

    @Override
    public void postConstruct() {

        super.postConstruct();

        LOGGER.info("Add the features in postConstruct.");

        features.add(new Feature(BidibLibrary.FEATURE_CTRL_INPUT_COUNT, inputPortCount));
        features.add(new Feature(BidibLibrary.FEATURE_CTRL_LIGHT_COUNT, lightPortCount));
        features.add(new Feature(BidibLibrary.FEATURE_CTRL_SERVO_COUNT, servoPortCount));
        features.add(new Feature(BidibLibrary.FEATURE_CTRL_SWITCH_COUNT, switchPortCount));
        features.add(new Feature(BidibLibrary.FEATURE_CTRL_SOUND_COUNT, soundPortCount));
        features.add(new Feature(BidibLibrary.FEATURE_CTRL_MOTOR_COUNT, motorPortCount));

        if (MapUtils.isNotEmpty(this.configurationVariables)) {
            LOGGER
                .info(
                    "The configuration variables are set already. Prepare the aspectMap from the CV value in the simulation.xml.");

            prepareAspectMap();
            return;
        }

        String userHome = System.getProperty("user.home");
        File cvValuesFile = new File(userHome, ".bidib/data/simulation/stepcontrol-cv.xml");
        if (StringUtils.isNotBlank(this.simulationConfigSearchRoot)
            && new File(this.simulationConfigSearchRoot).isDirectory()) {
            LOGGER
                .info("Load the stepcontrol-cv.xml from folder based on simulationConfigSearchRoot: {}",
                    this.simulationConfigSearchRoot);
            cvValuesFile = new File(this.simulationConfigSearchRoot, "data/simulation/stepcontrol-cv.xml");
        }
        LOGGER.info("Prepared location to load CV values from cvValuesFile: {}", cvValuesFile.getPath());

        boolean cvValuesFromFileLoaded = false;
        if (cvValuesFile.exists()) {
            try (FileInputStream cvValues = new FileInputStream(cvValuesFile)) {

                SaveCV saveCV = new CvExchangeFactory().loadCV(cvValues);
                LOGGER.info("Loaded saveCV from stream.");

                configurationVariables.clear();

                if (saveCV != null && saveCV.getCVs() != null && CollectionUtils.isNotEmpty(saveCV.getCVs().getCv())) {
                    for (CVSType cv : saveCV.getCVs().getCv()) {

                        configurationVariables.put(cv.getNumber(), cv.getValue());
                    }
                }

                // configure bridge
                // configurationVariables.put("80", "1");

                // prepare the aspect map
                prepareAspectMap();

                cvValuesFromFileLoaded = true;
            }
            catch (Exception ex) {
                LOGGER.warn("Load CV values from file failed.", ex);
            }
        }

        if (!cvValuesFromFileLoaded) {

            configurationVariables.put("1", "1");
            configurationVariables.put("2", "13");
            configurationVariables.put("3", "120");
            configurationVariables.put("4", "0");
            // firmware
            configurationVariables.put("5", "0");
            configurationVariables.put("6", "0");
            configurationVariables.put("7", "21");

            configurationVariables.put("80", "0"); // buehne

            configurationVariables.put("113", "0"); // current aspect
            configurationVariables.put("114", "1"); // type: 0=linear, 1=round
            configurationVariables.put("115", "16"); // unit system

            configurationVariables.put("116", "0"); // number of steps
            configurationVariables.put("117", "50");
            configurationVariables.put("118", "0");
            configurationVariables.put("119", "0");

            configurationVariables.put("120", "112"); // current position
            configurationVariables.put("121", "11");
            configurationVariables.put("122", "0");
            configurationVariables.put("123", "0");

            configurationVariables.put(CV_STEPPOS, "0");

            // backlash
            // configurationVariables.put("124", "20");
            configurationVariables.put("124", "0");
            configurationVariables.put("125", "0");

            // speed
            configurationVariables.put("126", "10");
            configurationVariables.put("127", "0");

            // homing_ratio
            configurationVariables.put("128", "8");

            // accel
            configurationVariables.put("129", "50");
            configurationVariables.put("130", "0");

            // decel
            configurationVariables.put("131", "40");
            configurationVariables.put("132", "0");

            // stop
            configurationVariables.put("133", "100");
            configurationVariables.put("134", "0");

            // push interval
            configurationVariables.put("137", "50");

            // bitposition moving
            // configurationVariables.put("152", "0");

            //
            configurationVariables.put("153", "1");
            configurationVariables.put("154", "0");
            configurationVariables.put("156", "0");

            // motor steps
            configurationVariables.put("161", "144");
            configurationVariables.put("162", "1");
            // microsteps
            configurationVariables.put("163", "64");

            // nema
            configurationVariables.put("164", "13");
            // gear
            configurationVariables.put("165", "1");
            configurationVariables.put("166", "0");
            configurationVariables.put("167", "1");
            configurationVariables.put("168", "0");

            // aspectAngles = new int[] { 0, 20, 45, 60, 80, 130, 200, 320 };

            // exit 0
            configurationVariables.put("169", "0"); // polarity
            configurationVariables.put("170", "0");
            configurationVariables.put("171", "0");
            configurationVariables.put("172", "0");
            configurationVariables.put("173", "0");

            // exit 1
            configurationVariables.put("174", "0"); // polarity
            configurationVariables.put("175", "20");
            configurationVariables.put("176", "0");
            configurationVariables.put("177", "0");
            configurationVariables.put("178", "0");

            // exit 2
            configurationVariables.put("179", "1"); // polarity
            configurationVariables.put("180", "32");
            configurationVariables.put("181", "3");
            configurationVariables.put("182", "0");
            configurationVariables.put("183", "0");

            // exit 3
            configurationVariables.put("184", "1"); // polarity
            configurationVariables.put("185", "0");
            configurationVariables.put("186", "25");
            configurationVariables.put("187", "0");
            configurationVariables.put("188", "0");

            // exit 4
            configurationVariables.put("189", "1"); // polarity
            configurationVariables.put("190", "0");
            configurationVariables.put("191", "50");
            configurationVariables.put("192", "0");
            configurationVariables.put("193", "0");

            // exit 5
            configurationVariables.put("194", "1"); // polarity
            configurationVariables.put("195", "255");
            configurationVariables.put("196", "255");
            configurationVariables.put("197", "255");
            configurationVariables.put("198", "255");

            // exit 6
            configurationVariables.put("199", "1"); // polarity
            configurationVariables.put("200", "0");
            configurationVariables.put("201", "255");
            configurationVariables.put("202", "0");
            configurationVariables.put("203", "0");

            // exit 7
            configurationVariables.put("204", "1"); // polarity
            configurationVariables.put("205", "255");
            configurationVariables.put("206", "255");
            configurationVariables.put("207", "255");
            configurationVariables.put("208", "255");

            prepareAspectMap();
        }
    }

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

        AnnotationProcessor.process(this);

        // prepare the feedback ports
        setupFeedbackPorts();

        // prepare the input ports
        setupInputPorts();

        // prepare the light ports
        setupLightPorts();

        // prepare the switch ports
        setupSwitchPorts();

        // prepare the servo ports
        setupServoPorts();

        // prepare the motor ports
        setupMotorPorts();

        // prepare the sound ports
        setupSoundPorts();

        super.start();
    }

    @Override
    public void stop() {

        cancelSchedulePublishCurrentAngle();

        AnnotationProcessor.unprocess(this);
        super.stop();
    }

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

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

    protected void setupInputPorts() {
        if (CollectionUtils.isNotEmpty(inputPorts.values())) {
            return;
        }

        for (int id = 0; id < inputPortCount; id++) {
            InputPort port = new InputPort();

            port.setId(id);
            port.setStatus(id % 2 == 0 ? InputPortStatus.ON : InputPortStatus.OFF);
            inputPorts.put(id, port);
        }
    }

    protected void setupLightPorts() {

        if (CollectionUtils.isNotEmpty(lightPorts.values())) {
            return;
        }

        for (int id = 0; id < lightPortCount; id++) {
            LightPort port = new LightPort();

            port.setId(id);
            port.setStatus(id % 3 == 0 ? LightPortStatus.ON : LightPortStatus.OFF);

            lightPorts.put(id, port);
        }
    }

    protected void setupSwitchPorts() {
        if (CollectionUtils.isNotEmpty(switchPorts.values())) {
            return;
        }

        for (int id = 0; id < switchPortCount; id++) {
            SwitchPort port = new SwitchPort();

            port.setId(id);
            port.setStatus(SwitchPortStatus.OFF);

            // set some default values
            port.setOutputBehaviour(IoBehaviourSwitchEnum.HIGH);
            port.setSwitchOffTime(15);

            switchPorts.put(id, port);
        }
    }

    protected void setupSoundPorts() {
        if (CollectionUtils.isNotEmpty(soundPorts.values())) {
            return;
        }

        for (int id = 0; id < soundPortCount; id++) {
            SoundPort port = new SoundPort();

            port.setId(id);
            port.setStatus(SoundPortStatus.STOP);

            // set some default values
            port.setPulseTime(0x10);

            soundPorts.put(id, port);
        }
    }

    protected void setupServoPorts() {
        if (CollectionUtils.isNotEmpty(servoPorts.values())) {
            LOGGER.warn("The servo ports are already setup!");
            return;
        }

        LOGGER.info("Create servoPorts, count: {}", servoPortCount);
        for (int id = 0; id < servoPortCount; id++) {
            ServoPort port = new ServoPort();

            port.setId(id);
            port.setRelativeValue((id % 4) * 25);

            servoPorts.put(id, port);
        }
    }

    protected void setupMotorPorts() {
        if (CollectionUtils.isNotEmpty(motorPorts.values())) {
            LOGGER.warn("The motor ports are already setup!");
            return;
        }

        LOGGER.info("Create motorPorts, count: {}", motorPortCount);
        for (int id = 0; id < motorPortCount; id++) {
            MotorPort port = new MotorPort();

            port.setId(id);
            port.setValue((id % 4) * 25);

            motorPorts.put(id, port);
        }
    }

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

        byte[] response = null;
        switch (ByteUtils.getInt(bidibMessage.getType())) {

            case BidibLibrary.MSG_LC_OUTPUT:
                response = processLcOutputRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_LC_KEY_QUERY:
                response = processLcKeyQueryRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_LC_PORT_QUERY:
                response = processLcPortQueryRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_LC_CONFIGX_SET:
                response = processLcConfigXSetRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_LC_CONFIGX_GET:
                response = processLcConfigXGetRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_LC_CONFIGX_GET_ALL:
                processLcConfigXGetAllRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_LC_PORT_QUERY_ALL:
                processLcPortQueryAllRequest(bidibMessage);
                break;

            case BidibLibrary.MSG_LC_MACRO_HANDLE:
                response = processLcMacroHandleRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_LC_MACRO_PARA_GET:
                response = processLcMacroParaGetRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_LC_MACRO_PARA_SET:
                response = processLcMacroParaSetRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_LC_MACRO_GET:
                response = processLcMacroGetRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_LC_MACRO_SET:
                response = processLcMacroSetRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_ACCESSORY_SET:
                response = processAccessorySetRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_ACCESSORY_GET:
                response = processAccessoryGetRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_ACCESSORY_GETALL:
                response = processAccessoryGetAllRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_ACCESSORY_PARA_SET:
                response = processAccessoryParaSetRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_ACCESSORY_PARA_GET:
                response = processAccessoryParaGetRequest(bidibMessage);
                break;
            // occupancy
            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_ADDR_GET_RANGE:
                processBmAddrGetRangeRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_BM_GET_CONFIDENCE:
                response = processBmGetConfidenceRequest(bidibMessage);
                break;
            case BidibLibrary.MSG_VENDOR_GET:
                response = processVendorGetRequest(bidibMessage);
                break;
            default:
                response = super.prepareResponse(bidibMessage);
                break;
        }
        return response;
    }

    protected byte[] processLcConfigXSetRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the LcConfigXSet request: {}", bidibMessage);
        byte[] response = null;
        try {
            LcConfigXSetMessage lcConfigXSetMessage = (LcConfigXSetMessage) bidibMessage;
            LcOutputType outputType = lcConfigXSetMessage.getPortType(getPortModel());
            int outputNumber = lcConfigXSetMessage.getPortNumber(getPortModel());

            Port port = null;
            switch (outputType) {
                case LIGHTPORT:
                    LightPort lightPort = lightPorts.get(Integer.valueOf(outputNumber));
                    if (lightPort != null) {
                        lightPort.setPortConfigX(lcConfigXSetMessage.getLcConfigX(messageLogger).getPortConfig());
                        port = lightPort;
                    }
                    else {
                        LOGGER.warn("Lightport not available, outputNumber: {}", outputNumber);
                    }
                    break;
                case SWITCHPORT:
                    SwitchPort switchPort = switchPorts.get(Integer.valueOf(outputNumber));
                    if (switchPort != null) {
                        switchPort.setPortConfigX(lcConfigXSetMessage.getLcConfigX(messageLogger).getPortConfig());
                        port = switchPort;
                    }
                    else {
                        LOGGER.warn("Switchport not available, outputNumber: {}", outputNumber);
                    }
                    break;
                case SOUNDPORT:
                    SoundPort soundPort = soundPorts.get(Integer.valueOf(outputNumber));
                    if (soundPort != null) {
                        soundPort.setPortConfigX(lcConfigXSetMessage.getLcConfigX(messageLogger).getPortConfig());
                        port = soundPort;
                    }
                    else {
                        LOGGER.warn("Soundport not available, outputNumber: {}", outputNumber);
                    }
                    break;
                default:
                    LOGGER.warn("LcConfigSet request for unsupported port type detected: {}", outputType);
                    break;
            }

            BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, outputNumber);

            if (port == null) {
                LcConfigX lcConfigX =
                    new LcConfigX(bidibPort, lcConfigXSetMessage.getLcConfigX(messageLogger).getPortConfig());

                LcConfigXResponse lcConfigXResponse =
                    new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                        LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));

                response = lcConfigXResponse.getContent();
            }
            else {
                LOGGER.warn("No port assigned!");
                LcNotAvailableResponse magicResponse =
                    new LcNotAvailableResponse(bidibMessage.getAddr(), getNextSendNum(), bidibPort);
                response = magicResponse.getContent();
            }
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create LcConfigX response failed.", ex);
        }
        return response;
    }

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

        try {
            LcConfigXGetMessage lcConfigXGetMessage = (LcConfigXGetMessage) bidibMessage;
            int outputNumber = lcConfigXGetMessage.getPortNumber(getPortModel());

            LcOutputType lcOutputType = lcConfigXGetMessage.getPortType(getPortModel());

            Port port = null;
            // byte[] portConfig = null;
            Map> values = new LinkedHashMap<>();
            switch (lcOutputType) {
                case LIGHTPORT:
                    port = lightPorts.get(Integer.valueOf(outputNumber));
                    // portConfig = port.getPortConfig();
                    LightPort lightPort = (LightPort) port;
                    values
                        .put(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_ON,
                            new BytePortConfigValue(ByteUtils.getLowByte(lightPort.getPwmMax())));
                    values
                        .put(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_OFF,
                            new BytePortConfigValue(ByteUtils.getLowByte(lightPort.getPwmMin())));
                    values.put(BidibLibrary.BIDIB_PCFG_DIMM_UP_8_8, new Int16PortConfigValue(lightPort.getDimMax()));
                    values.put(BidibLibrary.BIDIB_PCFG_DIMM_DOWN_8_8, new Int16PortConfigValue(lightPort.getDimMin()));
                    // TODO RGB
                    if (lightPort.getRgbValue() != null) {
                        values.put(BidibLibrary.BIDIB_PCFG_RGB, new RgbPortConfigValue(lightPort.getRgbValue()));
                    }

                    // TODO remove this test
                    if (outputNumber == 2) {
                        values.put(BidibLibrary.BIDIB_PCFG_NONE, new BytePortConfigValue((byte) 12));
                    }

                    break;

                case SERVOPORT:
                    port = servoPorts.get(Integer.valueOf(outputNumber));
                    ServoPort servoPort = (ServoPort) port;

                    values.put(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_L, new BytePortConfigValue(Byte.valueOf((byte) 0x14)));

                    values.put(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_H, new BytePortConfigValue(Byte.valueOf((byte) 0xFA)));
                    values.put(BidibLibrary.BIDIB_PCFG_SERVO_SPEED, new BytePortConfigValue(Byte.valueOf((byte) 0x05)));

                    LOGGER.info("Return config of servo port: {}", servoPort);
                    break;

                case SWITCHPORT:
                    port = switchPorts.get(Integer.valueOf(outputNumber));
                    SwitchPort switchPort = (SwitchPort) port;

                    values
                        .put(BidibLibrary.BIDIB_PCFG_TICKS,
                            new BytePortConfigValue(ByteUtils.getLowByte(switchPort.getSwitchOffTime())));

                    LOGGER.info("Return config of switch port: {}", switchPort);
                    break;

                default:
                    LOGGER.warn("LcConfigGet request for unsupported port type detected: {}", lcOutputType);
                    break;
            }

            LOGGER.info("Return config of port: {}", port);
            BidibPort bidibPort = prepareBidibPort(getPortModel(), lcOutputType, outputNumber);
            LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

            LcConfigXResponse lcConfigXResponse =
                new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                    LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
            response = lcConfigXResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create LcConfigX response failed.", ex);
        }
        return response;
    }

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

        try {
            LcConfigXGetAllMessage lcConfigXGetAllMessage = (LcConfigXGetAllMessage) bidibMessage;
            LcOutputType outputType = lcConfigXGetAllMessage.getPortTypeFrom(getPortModel());

            // TODO evaluate port type/range to

            Map> values = new LinkedHashMap<>();

            if (outputType != null) {
                LOGGER.info("Get all ports for output type: {}", outputType);

                // TODO check the range

                switch (outputType) {
                    case SERVOPORT:
                        for (ServoPort servoPort : servoPorts.values()) {
                            values.clear();

                            values
                                .put(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_L,
                                    new BytePortConfigValue(Byte.valueOf((byte) 0x14)));

                            values
                                .put(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_H,
                                    new BytePortConfigValue(Byte.valueOf((byte) 0xFA)));
                            values
                                .put(BidibLibrary.BIDIB_PCFG_SERVO_SPEED,
                                    new BytePortConfigValue(Byte.valueOf((byte) 0x05)));

                            LOGGER.info("Return config of servo port: {}", servoPort);
                            BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, servoPort.getId());
                            LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

                            LcConfigXResponse lcConfigXResponse =
                                new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                                    LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
                            response = lcConfigXResponse.getContent();

                            LOGGER.info("Prepared lcConfigXResponse: {}", ByteUtils.bytesToHex(response));
                            sendSpontanousResponse(response);
                            response = null;
                        }
                        break;
                    case SWITCHPORT:
                        for (SwitchPort switchPort : switchPorts.values()) {
                            values.clear();

                            values
                                .put(BidibLibrary.BIDIB_PCFG_TICKS,
                                    new BytePortConfigValue(ByteUtils.getLowByte(switchPort.getSwitchOffTime())));

                            LOGGER.info("Return config of switch port: {}", switchPort);
                            BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, switchPort.getId());
                            LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

                            LcConfigXResponse lcConfigXResponse =
                                new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                                    LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
                            response = lcConfigXResponse.getContent();

                            LOGGER.info("Prepared lcConfigXResponse: {}", ByteUtils.bytesToHex(response));
                            sendSpontanousResponse(response);
                            response = null;
                        }
                        break;
                    case LIGHTPORT:
                        for (LightPort lightPort : lightPorts.values()) {
                            values.clear();

                            LOGGER.info("Return config of light port: {}", lightPort);
                            BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, lightPort.getId());
                            LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

                            LcConfigXResponse lcConfigXResponse =
                                new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                                    LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
                            response = lcConfigXResponse.getContent();

                            LOGGER.info("Prepared lcConfigXResponse: {}", ByteUtils.bytesToHex(response));
                            sendSpontanousResponse(response);
                            response = null;
                        }
                        break;
                    case INPUTPORT:
                        for (InputPort inputPort : inputPorts.values()) {
                            values.clear();

                            LOGGER.info("Return config of input port: {}", inputPort);
                            BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, inputPort.getId());
                            LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

                            LcConfigXResponse lcConfigXResponse =
                                new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                                    LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
                            response = lcConfigXResponse.getContent();

                            LOGGER.info("Prepared lcConfigXResponse: {}", ByteUtils.bytesToHex(response));
                            sendSpontanousResponse(response);
                            response = null;
                        }
                        break;
                    case MOTORPORT:
                        for (MotorPort motorPort : motorPorts.values()) {
                            values.clear();

                            LOGGER.info("Return config of motor port: {}", motorPort);
                            BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, motorPort.getId());
                            LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

                            LcConfigXResponse lcConfigXResponse =
                                new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                                    LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
                            response = lcConfigXResponse.getContent();

                            LOGGER.info("Prepared lcConfigXResponse: {}", ByteUtils.bytesToHex(response));
                            sendSpontanousResponse(response);
                            response = null;
                        }
                        break;
                    case SOUNDPORT:
                        for (SoundPort soundPort : soundPorts.values()) {
                            values.clear();

                            LOGGER.info("Return config of sound port: {}", soundPort);
                            BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, soundPort.getId());
                            LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

                            LcConfigXResponse lcConfigXResponse =
                                new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                                    LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
                            response = lcConfigXResponse.getContent();

                            LOGGER.info("Prepared lcConfigXResponse: {}", ByteUtils.bytesToHex(response));
                            sendSpontanousResponse(response);
                            response = null;
                        }
                        break;
                    default:
                        LOGGER.warn("Unsupported port type requested: {}", outputType);
                        break;
                }
            }
            else {
                // deliver servo ports
                for (ServoPort servoPort : servoPorts.values()) {
                    values.clear();

                    LOGGER.info("Return config of servo port: {}", servoPort);
                    BidibPort bidibPort = prepareBidibPort(getPortModel(), LcOutputType.SERVOPORT, servoPort.getId());
                    LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

                    LcConfigXResponse lcConfigXResponse =
                        new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                            LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
                    response = lcConfigXResponse.getContent();

                    LOGGER.info("Prepared lcConfigXResponse: {}", ByteUtils.bytesToHex(response));
                    sendSpontanousResponse(response);
                    response = null;
                }

                // deliver switch ports
                for (SwitchPort switchPort : switchPorts.values()) {
                    values.clear();

                    LOGGER.info("Return config of switch port: {}", switchPort);

                    values = switchPort.getPortConfigX();
                    BidibPort bidibPort = prepareBidibPort(getPortModel(), LcOutputType.SWITCHPORT, switchPort.getId());
                    LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

                    LcConfigXResponse lcConfigXResponse =
                        new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                            LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
                    response = lcConfigXResponse.getContent();

                    LOGGER.info("Prepared lcConfigXResponse: {}", ByteUtils.bytesToHex(response));
                    sendSpontanousResponse(response);
                    response = null;
                }

                // deliver sound ports
                for (SoundPort soundPort : soundPorts.values()) {
                    values.clear();

                    LOGGER.info("Return config of switch port: {}", soundPort);

                    values = soundPort.getPortConfigX();
                    BidibPort bidibPort = prepareBidibPort(getPortModel(), LcOutputType.SOUNDPORT, soundPort.getId());
                    LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

                    LcConfigXResponse lcConfigXResponse =
                        new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                            LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
                    response = lcConfigXResponse.getContent();

                    LOGGER.info("Prepared lcConfigXResponse: {}", ByteUtils.bytesToHex(response));
                    sendSpontanousResponse(response);
                    response = null;
                }

                // deliver input ports
                for (InputPort inputPort : inputPorts.values()) {
                    values.clear();

                    LOGGER.info("Return config of input port: {}", inputPort);
                    BidibPort bidibPort = prepareBidibPort(getPortModel(), LcOutputType.INPUTPORT, inputPort.getId());
                    LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

                    LcConfigXResponse lcConfigXResponse =
                        new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                            LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
                    response = lcConfigXResponse.getContent();

                    LOGGER.info("Prepared lcConfigXResponse: {}", ByteUtils.bytesToHex(response));
                    sendSpontanousResponse(response);
                    response = null;
                }

                // deliver light ports
                for (LightPort lightPort : lightPorts.values()) {
                    values.clear();

                    LOGGER.info("Return config of light port: {}", lightPort);
                    BidibPort bidibPort = prepareBidibPort(getPortModel(), LcOutputType.LIGHTPORT, lightPort.getId());
                    LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

                    LcConfigXResponse lcConfigXResponse =
                        new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                            LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
                    response = lcConfigXResponse.getContent();

                    LOGGER.info("Prepared lcConfigXResponse: {}", ByteUtils.bytesToHex(response));
                    sendSpontanousResponse(response);
                    response = null;
                }
            }
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create lcConfigXResponse response failed.", ex);
        }

    }

    protected byte[] processLcOutputRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the LcOutput request: {}", bidibMessage);
        byte[] response = null;
        try {
            LcOutputMessage lcOutputMessage = (LcOutputMessage) bidibMessage;
            LcOutputType outputType = lcOutputMessage.getOutputType(getPortModel());
            int outputNumber = lcOutputMessage.getOutputNumber(getPortModel());
            byte outputStatus = lcOutputMessage.getOutputStatus();

            Port port = null;
            switch (outputType) {
                case MOTORPORT:
                    MotorPort motorPort = motorPorts.get(Integer.valueOf(outputNumber));
                    Integer val = ByteUtils.getInt(outputStatus, 0x7F);
                    if ((outputStatus & 0x80) == 0x00) {
                        LOGGER.info("Negative speed: {}", val);
                    }
                    else {
                        LOGGER.info("Positive speed: {}", val);
                    }
                    // LOGGER.info("Current speed: {}", outputStatus);
                    motorPort.setValue(ByteUtils.getInteger(outputStatus));

                    port = motorPort;
                    break;
                case SWITCHPORT:
                    SwitchPort switchPort = switchPorts.get(Integer.valueOf(outputNumber));
                    switchPort.setStatus(SwitchPortStatus.valueOf(SwitchPortEnum.valueOf(outputStatus)));
                    port = switchPort;
                    break;
                case SOUNDPORT:
                    SoundPort soundPort = soundPorts.get(Integer.valueOf(outputNumber));
                    soundPort.setStatus(SoundPortStatus.valueOf(SoundPortEnum.valueOf(outputStatus)));
                    port = soundPort;
                    break;
                case SERVOPORT:
                    ServoPort servoPort = servoPorts.get(Integer.valueOf(outputNumber));
                    servoPort.setValue(ByteUtils.getInteger(outputStatus));
                    port = servoPort;
                    break;
                default:
                    LOGGER.warn("LcOutput request for unsupported port type detected: {}", outputType);
                    break;
            }

            BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, outputNumber);

            if (port != null) {
                LcStatResponse lcStatResponse =
                    new LcStatResponse(bidibMessage.getAddr(), getNextSendNum(), bidibPort, outputStatus);
                response = lcStatResponse.getContent();
            }
            else {
                LcNotAvailableResponse lcNotAvailableResponse =
                    new LcNotAvailableResponse(bidibMessage.getAddr(), getNextSendNum(), bidibPort);
                response = lcNotAvailableResponse.getContent();
            }

            if (port != null) {
                switch (outputType) {
                    case MOTORPORT:
                        publishMotorPortChange(port);
                        break;
                    case SWITCHPORT:
                        publishSwitchPortChange(port);
                        break;
                    case SOUNDPORT:
                        publishSoundPortChange(port);
                        break;
                    case SERVOPORT:
                        publishServoPortChange(port);
                        break;
                    default:
                        break;
                }
            }
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create LcStat response failed.", ex);
        }
        return response;
    }

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

        byte keyState = 0;
        Port port = null;
        try {
            LcKeyMessage lcKeyMessage = (LcKeyMessage) bidibMessage;
            int portNumber = lcKeyMessage.getBidibPort().getPortNumber(PortModelEnum.type);
            port = inputPorts.get(portNumber);
            keyState = port.getStatus().getType().getType();
            LcKeyResponse lcKeyResponse =
                new LcKeyResponse(bidibMessage.getAddr(), getNextSendNum(), ByteUtils.getLowByte(portNumber), keyState);
            response = lcKeyResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create LcKey response failed.", ex);
        }

        if (port != null) {
            publishInputPortChange(port);
        }

        return response;
    }

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

        byte portState = 0;

        try {
            final LcPortQueryMessage lcPortQueryMessage = (LcPortQueryMessage) bidibMessage;
            LcOutputType outputType = lcPortQueryMessage.getPortType(getPortModel());
            int portNumber = lcPortQueryMessage.getPortNumber(getPortModel());

            switch (outputType) {
                case INPUTPORT:
                    portState = inputPorts.get(portNumber).getStatus().getType().getType();
                    break;
                case LIGHTPORT:
                    portState = lightPorts.get(portNumber).getStatus().getType().getType();
                    break;
                case MOTORPORT:
                    portState = ByteUtils.getLowByte(motorPorts.get(portNumber).getValue());
                    break;
                case SWITCHPORT:
                    portState = switchPorts.get(portNumber).getStatus().getType().getType();
                    break;
                case SOUNDPORT:
                    portState = soundPorts.get(portNumber).getStatus().getType().getType();
                    break;
                case SERVOPORT:
                    portState = ByteUtils.getLowByte(servoPorts.get(portNumber).getValue());
                    break;
                default:
                    LOGGER.warn("LcOutputQuery for unsupported port type detected: {}", outputType);
                    break;
            }
            BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, portNumber);

            LcStatResponse lcStatResponse =
                new LcStatResponse(bidibMessage.getAddr(), getNextSendNum(), bidibPort, portState);
            response = lcStatResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create LcStat response failed.", ex);
        }
        return response;
    }

    protected byte[] processLcPortQueryAllRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the PortQueryAll request: {}", bidibMessage);
        byte[] response = null;
        try {
            LcPortQueryAllMessage portQueryAllMessage = (LcPortQueryAllMessage) bidibMessage;
            int portRangeFrom = portQueryAllMessage.getPortRangeFrom(getPortModel());
            int portRangeTo = portQueryAllMessage.getPortRangeTo(getPortModel());
            int portTypeMask = portQueryAllMessage.getPortTypeMask();

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

            if (PortConfigUtils.isSupportsInputPort(portTypeMask) && MapUtils.isNotEmpty(inputPorts)) {
                for (InputPort inputPort : inputPorts.values()) {

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

                            publishPortState(bidibMessage.getAddr(), LcOutputType.INPUTPORT, inputPort.getId(),
                                portStatus);
                        }
                        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) && MapUtils.isNotEmpty(lightPorts)) {
                for (LightPort lightPort : lightPorts.values()) {
                    if (lightPort.getId() >= portRangeFrom && lightPort.getId() < portRangeTo) {
                        try {
                            LightPortStatus lightPortStatus = lightPort.getStatus();
                            if (lightPortStatus == null) {
                                lightPort.setStatus(LightPortStatus.OFF);
                                lightPortStatus = lightPort.getStatus();
                            }
                            byte portStatus = lightPortStatus.getType().getType();

                            publishPortState(bidibMessage.getAddr(), LcOutputType.LIGHTPORT, lightPort.getId(),
                                portStatus);
                        }
                        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) && MapUtils.isNotEmpty(switchPorts)) {
                for (SwitchPort switchPort : switchPorts.values()) {
                    if (switchPort.getId() >= portRangeFrom && switchPort.getId() < portRangeTo) {
                        try {
                            SwitchPortStatus switchPortStatus = switchPort.getStatus();
                            if (switchPortStatus == null) {
                                switchPort.setStatus(SwitchPortStatus.OFF);
                                switchPortStatus = switchPort.getStatus();
                            }
                            byte portStatus = switchPortStatus.getType().getType();

                            publishPortState(bidibMessage.getAddr(), LcOutputType.SWITCHPORT, switchPort.getId(),
                                portStatus);
                        }
                        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.isSupportsServoPort(portTypeMask) && MapUtils.isNotEmpty(servoPorts)) {
                for (ServoPort servoPort : servoPorts.values()) {
                    if (servoPort.getId() >= portRangeFrom && servoPort.getId() < portRangeTo) {
                        try {
                            ServoPortStatus servoPortStatus = servoPort.getStatus();
                            if (servoPortStatus == null) {
                                servoPort.setStatus(ServoPortStatus.START);
                                servoPortStatus = servoPort.getStatus();
                            }
                            byte portStatus = servoPortStatus.getType().getType();

                            publishPortState(bidibMessage.getAddr(), LcOutputType.SERVOPORT, servoPort.getId(),
                                portStatus);
                        }
                        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);
                    }
                }
            }

            if (PortConfigUtils.isSupportsSoundPort(portTypeMask) && MapUtils.isNotEmpty(soundPorts)) {
                for (SoundPort soundPort : soundPorts.values()) {
                    if (soundPort.getId() >= portRangeFrom && soundPort.getId() < portRangeTo) {
                        try {
                            SoundPortStatus soundPortStatus = soundPort.getStatus();
                            if (soundPortStatus == null) {
                                soundPort.setStatus(SoundPortStatus.STOP);
                                soundPortStatus = soundPort.getStatus();
                            }
                            byte portStatus = soundPortStatus.getType().getType();

                            publishPortState(bidibMessage.getAddr(), LcOutputType.SOUNDPORT, soundPort.getId(),
                                portStatus);
                        }
                        catch (Exception ex) {
                            LOGGER.warn("Publish port state failed for port: {}", soundPort, ex);
                        }
                    }
                    else {
                        LOGGER.info("Skip sound port that is out of port range: {}", soundPort);
                    }
                }
            }

            LOGGER.info("Send the terminating LC_NA message.");
            publishLcNaResponse(bidibMessage.getAddr());
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create LcStat response failed.", ex);
        }
        return response;
    }

    protected void publishPortState(byte[] address, LcOutputType outputType, int outputNumber, byte portStatus)
        throws ProtocolException {

        BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, outputNumber);

        LcStatResponse lcStatResponse = new LcStatResponse(address, getNextSendNum(), bidibPort, portStatus);

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

        byte[] response = lcStatResponse.getContent();
        sendSpontanousResponse(response);
    }

    protected void publishLcNaResponse(byte[] address) throws ProtocolException {

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

        LcNotAvailableResponse lcNotAvailableResponse =
            new LcNotAvailableResponse(address, getNextSendNum(), bidibPort);

        LOGGER.info("Prepared LcNotAvailableResponse: {}", lcNotAvailableResponse);

        byte[] response = lcNotAvailableResponse.getContent();
        sendSpontanousResponse(response);
    }

    boolean positionValueChanged;

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

        byte[] result = super.processVendorDisableRequest(bidibMessage);

        if (positionValueChanged) {
            positionValueChanged = false;
            prepareAspectMap();
        }

        return result;
    }

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

        return super.processVendorSetRequest(bidibMessage, vd -> {
            // store the vendor data ...
            configurationVariables.put(vd.getName(), vd.getValue());

            switch (vd.getName()) {
                case "114":
                    // the table type was written
                    updateTableType();
                    break;
                default:
                    break;
            }

            if (!positionValueChanged) {
                try {
                    int cvNum = Integer.valueOf(vd.getName());
                    if (cvNum >= 169 && cvNum <= 408) {
                        positionValueChanged = true;
                    }
                }
                catch (NumberFormatException ex) {
                    LOGGER.warn("Parse CV number failed, message: {}", ex.getMessage());
                }
            }
        });
    }

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

        byte[] response = null;
        try {
            VendorGetMessage vendorGetMessage = (VendorGetMessage) bidibMessage;
            String vendorDataName = vendorGetMessage.getVendorDataName();
            LOGGER.info("Get the vendor data with name: {}", vendorDataName);

            if ("124".equals(vendorDataName)) {
                // special testing
                byte accessoryNum = 1;
                byte aspect = 1;
                byte[] value = new byte[] { 0x04, 0x00, 0x00 };
                AccessoryNotifyResponse accessoryNotifyResponse =
                    new AccessoryNotifyResponse(bidibMessage.getAddr(), getNextSendNum(), accessoryNum, aspect, value);
                LOGGER.info("Send the accessoryNotifyResponse fore testing purposes: {}", accessoryNotifyResponse);
                sendSpontanousResponse(accessoryNotifyResponse.getContent());
            }

            // fetch the value from the stored data ...
            String vendorDataValue = configurationVariables.get(vendorDataName);
            if (StringUtils.isBlank(vendorDataValue)) {
                vendorDataValue = "";
            }

            VendorResponse vendorResponse =
                new VendorResponse(bidibMessage.getAddr(), getNextSendNum(), vendorDataName, vendorDataValue);
            response = vendorResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create vendor response failed.", ex);
        }
        return response;
    }

    private void publishSwitchPortChange(Port port) {
        SwitchPort switchPort = (SwitchPort) port;
        SwitchPortStatus status = switchPort.getStatus();

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

    private void publishSoundPortChange(Port port) {
        SoundPort soundPort = (SoundPort) port;
        SoundPortStatus status = soundPort.getStatus();

        LOGGER.info("The soundport status has changed, notify the listeners, nodeAddress: {}", nodeAddress);
        EventBus.publish(new SoundPortStatusEvent(NodeUtils.formatAddress(nodeAddress), soundPort, status));
    }

    private void publishMotorPortChange(Port port) {
        MotorPort motorPort = (MotorPort) port;
        int value = motorPort.getValue();

        LOGGER.info("The motorport status has changed, notify the listeners, nodeAddress: {}", nodeAddress);
        EventBus.publish(new MotorPortStatusEvent(NodeUtils.formatAddress(nodeAddress), motorPort.getId(), value));
    }

    private void publishServoPortChange(Port port) {
        ServoPort servoPort = (ServoPort) port;
        Integer value = servoPort.getValue();

        LOGGER.info("The servoport status has changed, notify the listeners, nodeAddress: {}", nodeAddress);
        EventBus.publish(new ServoPortStatusEvent(NodeUtils.formatAddress(nodeAddress), servoPort.getId(), value));
    }

    private void publishInputPortChange(Port port) {
        InputPort inputPort = (InputPort) port;
        InputPortStatus status = inputPort.getStatus();

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

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

        try {
            LcMacroHandleMessage lcMacroHandleMessage = (LcMacroHandleMessage) bidibMessage;
            Integer macroNumber = lcMacroHandleMessage.getMacroNumber();
            LcMacroOperationCode lcMacroOperationCode = lcMacroHandleMessage.getMacroOperationCode();

            LOGGER
                .info("Handle macro request, macroNumber: {}, lcMacroOperationCode: {}", macroNumber,
                    lcMacroOperationCode);

            // TODO store the macro state value
            LcMacroState macroState = null;
            switch (lcMacroOperationCode) {
                case START:
                    macroState = LcMacroState.RUNNING;
                    break;
                case DELETE:
                    macroState = LcMacroState.DELETE;
                    LOGGER.info("Remove macro with number: {}", macroNumber);
                    macros.remove(macroNumber);
                    break;
                case OFF:
                    macroState = LcMacroState.OFF;
                    break;
                case RESTORE:
                    macroState = LcMacroState.RESTORE;
                    break;
                case SAVE:
                    macroState = LcMacroState.SAVE;
                    break;
                default:
                    macroState = LcMacroState.NOTEXIST;
                    break;
            }

            LcMacroStateResponse lcMacroStateResponse =
                new LcMacroStateResponse(bidibMessage.getAddr(), getNextSendNum(), ByteUtils.getLowByte(macroNumber),
                    macroState);
            response = lcMacroStateResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create LcMacroState response failed.", ex);
        }
        return response;
    }

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

        try {
            LcMacroParaGetMessage lcMacroParaGetMessage = (LcMacroParaGetMessage) bidibMessage;
            int macroNumber = lcMacroParaGetMessage.getMacroNumber();
            int paramId = lcMacroParaGetMessage.getParameterIndex();
            LOGGER.info("Process macroNumber: {}, paramId: {}", macroNumber, paramId);

            // return the stored parameter value
            MacroContainer container = macros.get(macroNumber);
            if (container == null) {
                LOGGER.info("Create new MacroContainer for macro number: {}", macroNumber);
                // initialize a new macro container
                container = new MacroContainer(macroNumber);
                macros.put(macroNumber, container);
            }
            byte[] parameter = container.getMacroParameter(paramId);

            LcMacroParaResponse lcMacroParaResponse =
                new LcMacroParaResponse(bidibMessage.getAddr(), getNextSendNum(), (byte) macroNumber, (byte) paramId,
                    parameter);
            response = lcMacroParaResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create LcMacroPara response failed.", ex);
        }
        return response;
    }

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

        try {
            LcMacroParaSetMessage lcMacroParaSetMessage = (LcMacroParaSetMessage) bidibMessage;
            int macroNumber = lcMacroParaSetMessage.getMacroNumber();
            int paramId = lcMacroParaSetMessage.getParameterIndex();
            byte[] parameter = lcMacroParaSetMessage.getValue();

            // store the parameter value
            MacroContainer container = macros.get(macroNumber);
            if (container == null) {
                // initialize a new macro container
                container = new MacroContainer(macroNumber);
                macros.put(macroNumber, container);
            }
            container.setMacroParameter(paramId, parameter);

            LcMacroParaResponse lcMacroParaResponse =
                new LcMacroParaResponse(bidibMessage.getAddr(), getNextSendNum(), (byte) macroNumber, (byte) paramId,
                    parameter);
            response = lcMacroParaResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create LcMacroPara response failed.", ex);
        }
        return response;
    }

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

        try {
            LcMacroGetMessage lcMacroGetMessage = (LcMacroGetMessage) bidibMessage;
            Integer macroNumber = lcMacroGetMessage.getMacroNumber();
            int stepNumber = lcMacroGetMessage.getStep();

            // fetch the macro content and test if the macro is available
            MacroContainer container = macros.get(macroNumber);
            if (container == null) {
                // initialize a new macro container
                container = new MacroContainer(macroNumber);
                macros.put(macroNumber, container);
            }
            LcMacro macroStep = container.getMacroStep(stepNumber);

            LcMacro value = macroStep;

            LcMacroResponse lcMacroResponse =
                new LcMacroResponse(bidibMessage.getAddr(), getNextSendNum(), ByteUtils.getLowByte(macroNumber),
                    ByteUtils.getLowByte(stepNumber), value);
            response = lcMacroResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create LcMacro response failed.", ex);
        }
        return response;
    }

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

        try {
            LcMacroSetMessage lcMacroSetMessage = (LcMacroSetMessage) bidibMessage;
            int macroNumber = lcMacroSetMessage.getMacroNumber();
            int stepNumber = lcMacroSetMessage.getStep();

            LcMacro macroStep = MacroUtils.getMacro(lcMacroSetMessage.getData());
            LOGGER.info("Current macroNumber: {}, stepNumber: {}, macroStep: {}", macroNumber, stepNumber, macroStep);

            // fetch the macro content and test if the macro container is available
            MacroContainer container = macros.get(macroNumber);
            if (container == null) {
                container = new MacroContainer(macroNumber);
                macros.put(macroNumber, container);
            }
            container.setMacroStep(stepNumber, macroStep);
            try {
                LcMacroResponse lcMacroResponse =
                    new LcMacroResponse(bidibMessage.getAddr(), getNextSendNum(), ByteUtils.getLowByte(macroNumber),
                        ByteUtils.getLowByte(stepNumber), macroStep);
                response = lcMacroResponse.getContent();
            }
            catch (NullPointerException npe) {
                LOGGER.warn("create response failed.", npe);
            }
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create LcMacro response failed.", ex);
        }
        return response;
    }

    private int currentAspectAccessoryControlling;

    private int currentAspectAccessoryOperating = 1;

    private int currentAngle;

    private boolean backwardsDirection;

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

        try {
            AccessorySetMessage accessorySetMessage = (AccessorySetMessage) bidibMessage;
            int accessoryNumber = accessorySetMessage.getAccessoryNumber();
            int aspect = accessorySetMessage.getAspect();

            // default response
            byte[] value = new byte[] { 0, 0, 0 };

            if (accessoryNumber == 0) { // controlling

                if (aspect < aspectMap.size()) {

                    int currentAngle = this.currentAngle;
                    Long positionValue = aspectMap.get(Integer.valueOf(aspect));

                    this.targetPositionValue.set(positionValue);

                    Float angleValue = (float) (positionValue.floatValue() / totalSteps.floatValue());

                    int targetAngle = (int) ((angleValue.floatValue() * 360) / 1.5f);

                    LOGGER
                        .info("Current aspect: {}, targetPositionValue: {}, targetAngle: {}, currentAngle: {}", aspect,
                            this.targetPositionValue.get(), targetAngle, currentAngle);

                    backwardsDirection = (targetAngle < currentAngle);

                    boolean finished = targetAngle == currentAngle;
                    // notify the current and the target angle
                    byte execute = (byte) (finished ? 0x00 : 0x01);
                    byte wait = (byte) (finished ? 0x00 : 0x48);

                    // notify the current and the target angle
                    value =
                        new byte[] { 0x30, execute, wait, BidibLibrary.BIDIB_ACC_DETAIL_CURR_ANGLE1DEG5,
                            ByteUtils.getLowByte(currentAngle), BidibLibrary.BIDIB_ACC_DETAIL_TARGET_ANGLE1DEG5,
                            ByteUtils.getLowByte(targetAngle) };

                    this.currentAngle = currentAngle;

                    currentAspectAccessoryControlling = aspect;

                    int currentStepPos = (int) (((currentAngle) * 1.5f * totalSteps.floatValue()) / 360);
                    configurationVariables.put(CV_STEPPOS, Integer.toString(currentStepPos));

                    schedulePublishCurrentAngle();
                }
                else {
                    LOGGER.warn("Ignore aspect out of bounds: {}", aspect);
                }
            }
            else if (accessoryNumber == 1) { // operating

                currentAspectAccessoryOperating = aspect;
            }

            AccessoryStateResponse accessoryStateResponse =
                new AccessoryStateResponse(bidibMessage.getAddr(), getNextSendNum(), (byte) accessoryNumber,
                    ByteUtils.getLowByte(aspect), value);
            response = accessoryStateResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create AccessoryState response failed.", ex);
        }
        return response;
    }

    private static final int STEP_WIDTH = 5;

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

        try {
            AccessoryGetMessage accessoryGetMessage = (AccessoryGetMessage) bidibMessage;
            int accessoryNumber = accessoryGetMessage.getAccessoryNumber();
            int aspect = 0;

            byte[] value = new byte[] { 0, 0, 0 };

            if (emergencyStop.get()) {
                LOGGER.info("Simulator is in emergency stop mode.");
                accessoryNumber = 0;
                aspect = ByteUtils.getLowByte(BidibLibrary.BIDIB_ACCESSORY_ASPECT_ESTOP);

                value = new byte[] { 0x03, (byte) 0x81, BidibLibrary.BIDIB_ACC_STATE_ERROR_NONE };
            }
            else if (accessoryNumber == 0) {
                boolean finished = false;

                aspect = currentAspectAccessoryControlling;

                Long positionValue = aspectMap.get(Integer.valueOf(aspect));
                Float angleValue = (float) (positionValue.floatValue() / totalSteps.floatValue());

                int targetAngle = (int) ((angleValue.floatValue() * 360) / 1.5f);
                LOGGER
                    .info("Current targetAngle: {}, currentAngle: {}, backwardsDirection: {}", targetAngle,
                        currentAngle, backwardsDirection);

                if (!backwardsDirection) {
                    if (targetAngle > currentAngle) {
                        currentAngle += STEP_WIDTH;
                    }
                    if (targetAngle <= currentAngle) {
                        currentAngle = targetAngle;
                        finished = true;
                    }
                }
                else {
                    if (targetAngle < currentAngle) {
                        currentAngle -= STEP_WIDTH;
                    }
                    if (targetAngle >= currentAngle) {
                        currentAngle = targetAngle;
                        finished = true;
                    }
                }

                // notify the current and the target angle
                byte execute = (byte) (finished ? 0x00 : 0x01);
                byte wait = (byte) (finished ? 0x00 : 0x48);

                LOGGER.info("finished: {}, execute: {}, wait: {}", finished, execute, wait);

                value =
                    new byte[] { 0x30, execute, wait, BidibLibrary.BIDIB_ACC_DETAIL_CURR_ANGLE1DEG5,
                        ByteUtils.getLowByte(currentAngle), BidibLibrary.BIDIB_ACC_DETAIL_TARGET_ANGLE1DEG5,
                        ByteUtils.getLowByte(targetAngle) };

                int currentStepPos = (int) (((currentAngle) * 1.5f * totalSteps.floatValue()) / 360);
                configurationVariables.put(CV_STEPPOS, Integer.toString(currentStepPos));

            }
            else {
                value = new byte[] { 4, 0, 0 };

                switch (accessoryNumber) {
                    case 1: // operating
                        value[0] = 4;
                        aspect = currentAspectAccessoryOperating;
                        break;
                    case 2: // homing
                        value[0] = 3;
                        break;
                    default:
                        value[0] = 7;
                        break;
                }

                LOGGER
                    .info("AccessoryGet, Return data for accessory: {}, aspect: {}, data: {}", accessoryNumber, aspect,
                        ByteUtils.bytesToHex(value));
            }

            AccessoryStateResponse accessoryStateResponse =
                new AccessoryStateResponse(bidibMessage.getAddr(), getNextSendNum(), (byte) accessoryNumber,
                    ByteUtils.getLowByte(aspect), value);
            response = accessoryStateResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create AccessoryState response failed.", ex);
        }
        return response;
    }

    private byte[] prepareMovingAspect(int accessoryNumber) {

        byte[] values = null;

        int aspect = 0;

        if (emergencyStop.get()) {
            LOGGER.info("Simulator is in emergency stop mode.");
            accessoryNumber = 0;
            aspect = ByteUtils.getLowByte(BidibLibrary.BIDIB_ACCESSORY_ASPECT_ESTOP);

            values = new byte[] { 0x03, (byte) 0x81, BidibLibrary.BIDIB_ACC_STATE_ERROR_NONE };
        }
        else if (accessoryNumber == 0) {
            boolean finished = false;

            Long positionValue = aspectMap.get(Integer.valueOf(aspect));
            if (positionValue == null) {
                positionValue = 0L;
            }
            Float angleValue = (float) (positionValue.floatValue() / totalSteps.floatValue());

            int targetAngle = (int) ((angleValue.floatValue() * 360) / 1.5f);
            LOGGER
                .info("Current targetAngle: {}, currentAngle: {}, backwardsDirection: {}", targetAngle, currentAngle,
                    backwardsDirection);

            if (!backwardsDirection) {
                if (targetAngle > currentAngle) {
                    currentAngle += STEP_WIDTH;
                }
                if (targetAngle <= currentAngle) {
                    currentAngle = targetAngle;
                    finished = true;
                }
            }
            else {
                if (targetAngle < currentAngle) {
                    currentAngle -= STEP_WIDTH;
                }
                if (targetAngle >= currentAngle) {
                    currentAngle = targetAngle;
                    finished = true;
                }
            }

            // notify the current and the target angle
            byte execute = (byte) (finished ? 0x00 : 0x01);
            byte wait = (byte) (finished ? 0x00 : 0x48);

            LOGGER.info("finished: {}, execute: {}, wait: {}", finished, execute, wait);

            values =
                new byte[] { 0x30, execute, wait, BidibLibrary.BIDIB_ACC_DETAIL_CURR_ANGLE1DEG5,
                    ByteUtils.getLowByte(currentAngle), BidibLibrary.BIDIB_ACC_DETAIL_TARGET_ANGLE1DEG5,
                    ByteUtils.getLowByte(targetAngle) };

            int currentStepPos = (int) (((currentAngle) * 1.5f * totalSteps.floatValue()) / 360);
            configurationVariables.put(CV_STEPPOS, Integer.toString(currentStepPos));

        }

        return values;
    }

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

        try {
            Feature feature = getFeature(BidibLibrary.FEATURE_ACCESSORY_COUNT);
            int totalAccessoryCount = feature.getValue();

            int aspect = 0;

            byte[] value = new byte[] { 0, 0, 0 };

            for (int accessoryNumber = 0; accessoryNumber < totalAccessoryCount; accessoryNumber++) {

                value = new byte[] { 4, 0, 0 };

                switch (accessoryNumber) {
                    case 0:
                        aspect = currentAspectAccessoryControlling;
                        value = prepareMovingAspect(accessoryNumber);
                        break;
                    case 1: // operating
                        value[0] = 4; // 4 aspects
                        aspect = currentAspectAccessoryOperating;
                        break;
                    case 2: // homing
                        value[0] = 3;
                        break;
                    default:
                        value[0] = 7;
                        break;
                }

                LOGGER
                    .info("AccessoryGetAll, Return data for accessory: {}, aspect: {}, data: {}", accessoryNumber,
                        aspect, ByteUtils.bytesToHex(value));

                AccessoryStateResponse accessoryStateResponse =
                    new AccessoryStateResponse(bidibMessage.getAddr(), getNextSendNum(), (byte) accessoryNumber,
                        ByteUtils.getLowByte(aspect), value);
                response = accessoryStateResponse.getContent();

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

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

        try {
            AccessoryParaSetMessage accessoryParaSetMessage = (AccessoryParaSetMessage) bidibMessage;
            int accessoryNumber = accessoryParaSetMessage.getAccessoryNumber();
            int paraNumber = accessoryParaSetMessage.getParaNumber();

            byte[] value = accessoryParaSetMessage.getValue();

            AccessoryParaResponse accessoryParaResponse =
                new AccessoryParaResponse(bidibMessage.getAddr(), getNextSendNum(),
                    ByteUtils.getLowByte(accessoryNumber), ByteUtils.getLowByte(paraNumber), value);
            response = accessoryParaResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create AccessoryPara response failed.", ex);
        }
        return response;
    }

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

        try {
            AccessoryParaGetMessage accessoryParaGetMessage = (AccessoryParaGetMessage) bidibMessage;
            int accessoryNumber = accessoryParaGetMessage.getAccessoryNumber();
            int paraNumber = accessoryParaGetMessage.getParaNumber();

            // TODO provide the correct data here ...
            byte[] value = new byte[] { 0, 0, 0, 0 };

            switch (accessoryNumber) {
                case 1:
                    if (paraNumber == BidibLibrary.BIDIB_ACCESSORY_PARA_MACROMAP) {
                        LOGGER.warn("Signal BIDIB_ACCESSORY_PARA_NOTEXIST for BIDIB_ACCESSORY_PARA_MACROMAP.");
                        value = new byte[] { ByteUtils.getLowByte(BidibLibrary.BIDIB_ACCESSORY_PARA_MACROMAP) };

                        paraNumber = BidibLibrary.BIDIB_ACCESSORY_PARA_NOTEXIST;
                    }
                    break;
                case 2:
                    value = new byte[] { 0, 0, 0 };
                    break;
                default:
                    break;
            }

            AccessoryParaResponse accessoryParaResponse =
                new AccessoryParaResponse(bidibMessage.getAddr(), getNextSendNum(),
                    ByteUtils.getLowByte(accessoryNumber), ByteUtils.getLowByte(paraNumber), value);
            response = accessoryParaResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create AccessoryPara 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 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), port.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);
        }
    }

    protected void changeFeedbackPortStatus(int portNum) throws ProtocolException {

        FeedbackPort port = feedbackPorts.get(portNum);
        if (port != null) {
            BidibMessage response = null;
            switch (port.getStatus()) {
                case FREE:
                    port.setStatus(FeedbackPortStatus.OCCUPIED);

                    response = new FeedbackOccupiedResponse(getNodeAddress(), getNextSendNum(), portNum);
                    break;
                default:
                    port.setStatus(FeedbackPortStatus.FREE);

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

            sendSpontanousResponse(response.getContent());

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

    @EventSubscriber(eventClass = EmergencyStopSetEvent.class)
    public void emergencyStopSetEvent(EmergencyStopSetEvent emergencyStopEvent) {
        String nodeAddress = emergencyStopEvent.getNodeAddr();
        LOGGER.info("The change of the emergency stop was requested, nodeAddress: {}", nodeAddress);

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

        emergencyStop.set(emergencyStopEvent.getEmergencyStop());

        byte aspect =
            ByteUtils
                .getLowByte((emergencyStop.get() ? BidibLibrary.BIDIB_ACCESSORY_ASPECT_ESTOP
                    : BidibLibrary.BIDIB_ACCESSORY_ASPECT_STOP));

        byte[] value =
            (emergencyStop.get() ? new byte[] { 0x03, (byte) 0x81, BidibLibrary.BIDIB_ACC_STATE_ERROR_NONE }
                : new byte[] { 0x03, 0x00, 0x00 });
        try {
            AccessoryNotifyResponse accessoryNotifyResponse =
                new AccessoryNotifyResponse(this.nodeAddress, getNextSendNum(), (byte) 0, aspect, value);
            LOGGER.info("Prepared accessoryNotifyResponse: {}", accessoryNotifyResponse);
            sendSpontanousResponse(accessoryNotifyResponse.getContent());
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Send accessoryNotifyResponse failed.", ex);
        }

        publishEmergencyStopStatusEvent(emergencyStop.get());
    }

    private void publishEmergencyStopStatusEvent(boolean emergencyStop) {

        LOGGER.info("The emergency Stop status has changed, notify the listeners, nodeAddress: {}", nodeAddress);
        EventBus.publish(new EmergencyStopStatusEvent(NodeUtils.formatAddress(nodeAddress), emergencyStop));
    }

    @Override
    public void setPortsConfig(FlatPortType portType) {
    }

    @Override
    public void setPortsConfig(PortType portType) {
        if (portType == null) {
            return;
        }

        if (portType instanceof LightPortType) {

            LightPortType lightPortType = (LightPortType) portType;
            lightPortCount = lightPortType.getCount();
            LOGGER.info("Total number of lightports: {}", lightPortCount);

            lightPorts.clear();

            // prepare initial lightports
            for (int portId = 0; portId < lightPortCount; portId++) {

                LightPort lightPort = new LightPort();
                lightPort.setId(portId);

                // add the unconfigured lightport
                lightPorts.put(portId, lightPort);
            }

            // overwrite configured ports
            if (CollectionUtils.isNotEmpty(lightPortType.getPort())) {
                final int cvBaseOffset = 215 /* offset */;
                // evaluate the configured ports
                for (LightPortParamsType portParams : lightPortType.getPort()) {
                    LightPort lightPort = new LightPort();
                    lightPort.setId(portParams.getPortId());
                    lightPort.setDimMin(portParams.getDimSlopeDown());
                    lightPort.setDimMax(portParams.getDimSlopeUp());
                    lightPort.setPwmMax(portParams.getIntensityOn());
                    lightPort.setPwmMin(portParams.getIntensityOff());
                    if (portParams.getRgbValue() != null) {
                        try {
                            Integer rgbValue = Integer.parseInt(portParams.getRgbValue(), 16);
                            lightPort.setRgbValue(rgbValue);
                        }
                        catch (Exception ex) {
                            LOGGER.warn("Parse RGB value failed: {}", portParams.getRgbValue(), ex);
                            lightPort.setRgbValue(null);
                        }
                    }

                    lightPort.setStatus(LightPortStatus.OFF);

                    LOGGER.info("Add configured port: {}", lightPort);

                    // add the configured port
                    lightPorts.put(lightPort.getId(), lightPort);

                    // prepare the CVs
                    int cvOffset = cvBaseOffset + portParams.getPortId() * 7 /* next */;

                    configurationVariables.put(String.valueOf(cvOffset), String.valueOf(-1));

                    configurationVariables.put(String.valueOf(cvOffset + 1), String.valueOf(lightPort.getPwmMin()));
                    configurationVariables.put(String.valueOf(cvOffset + 2), String.valueOf(lightPort.getPwmMax()));
                    configurationVariables
                        .put(String.valueOf(cvOffset + 3), String.valueOf(ByteUtils.getLowByte(lightPort.getDimMin())));
                    configurationVariables
                        .put(String.valueOf(cvOffset + 4),
                            String.valueOf(ByteUtils.getHighByte(lightPort.getDimMin())));
                    configurationVariables
                        .put(String.valueOf(cvOffset + 5), String.valueOf(ByteUtils.getLowByte(lightPort.getDimMax())));
                    configurationVariables
                        .put(String.valueOf(cvOffset + 6),
                            String.valueOf(ByteUtils.getHighByte(lightPort.getDimMax())));
                }

                // use the number of configured ports
                lightPortCount = lightPorts.size();
            }
        }
        else if (portType instanceof SwitchPortType) {
            switchPortCount = portType.getCount();
        }
        else if (portType instanceof InputPortType) {
            inputPortCount = portType.getCount();
            inputPortOffset = (portType.getOffset() != null ? portType.getOffset() : 0);
        }
        else if (portType instanceof ServoPortType) {
            servoPortCount = portType.getCount();
        }
        else if (portType instanceof SoundPortType) {
            soundPortCount = portType.getCount();
        }
        else if (portType instanceof MotorPortType) {
            motorPortCount = portType.getCount();
        }
    }

    private ScheduledFuture taskFuture;

    private void schedulePublishCurrentAngle() {

        if (taskFuture != null) {
            LOGGER.warn("The task to publish the current angle is running already.");
            cancelSchedulePublishCurrentAngle();
        }

        // add a task to the worker to check the accessory state
        taskFuture = accessoryStateWorkers.scheduleAtFixedRate(() -> {
            try {
                LOGGER.info("Publish the current angle.");

                if (!publishCurrentAngle()) {

                    cancelSchedulePublishCurrentAngle();

                    // release the task future
                    taskFuture = null;
                }
            }
            catch (Exception ex) {
                LOGGER.warn("Publish the current angle failed.", ex);
            }
        }, 250, 250, TimeUnit.MILLISECONDS);
    }

    private void cancelSchedulePublishCurrentAngle() {
        LOGGER.info("Cancel the publish current angle task.");
        // shutdown all tasks
        try {
            if (taskFuture != null) {
                taskFuture.cancel(true);

                taskFuture = null;
            }
        }
        catch (Exception ex) {
            LOGGER.warn("Stop scheduled task failed.", ex);
        }
    }

    private long currentPositionValue;

    private final AtomicLong targetPositionValue = new AtomicLong();

    private boolean publishCurrentAngle() {

        long totalSteps = this.totalSteps.longValue();

        if (backwardsDirection) {
            if (this.currentPositionValue - this.targetPositionValue.get() > 0) {
                this.currentPositionValue -= this.tableType == TurnTableType.round ? 100 : 500;
                if (this.currentPositionValue < 0) {
                    this.currentPositionValue = totalSteps;
                }
            }
            else {
                long delta = this.currentPositionValue - this.targetPositionValue.get();
                this.currentPositionValue -= delta;
            }
        }
        else {
            if (this.targetPositionValue.get() - this.currentPositionValue > 0) {
                this.currentPositionValue += this.tableType == TurnTableType.round ? 100 : 500;
                if (this.currentPositionValue > totalSteps) {
                    this.currentPositionValue = 0;
                }
            }
            else {
                long delta = this.targetPositionValue.get() - this.currentPositionValue;
                this.currentPositionValue += delta;
            }
        }

        int accessoryNumber = 0; // operating
        int aspect = this.currentAspectAccessoryControlling;

        Float currentAngleValue = (float) (this.currentPositionValue / this.totalSteps.floatValue());

        int currentAngle = (int) ((currentAngleValue.floatValue() * 360) / 1.5f);
        // int currentAngle = this.currentAngle + 10;

        // Long targetPositionValue = aspectMap.get(Integer.valueOf(aspect));
        Float angleValue = (float) (targetPositionValue.floatValue() / this.totalSteps.floatValue());

        int targetAngle = (int) ((angleValue.floatValue() * 360) / 1.5f);

        LOGGER
            .info(
                "Current aspect: {}, currentPositionValue: {}, targetPositionValue: {}, targetAngle: {}, currentAngle: {}",
                aspect, this.currentPositionValue, targetPositionValue, targetAngle, currentAngle);

        // backwardsDirection = (targetAngle < currentAngle);

        boolean finished = targetAngle == currentAngle;
        // notify the current and the target angle
        byte execute = (byte) (finished ? 0x00 : 0x01);
        byte wait = (byte) (finished ? 0x00 : 0x48);

        // notify the current and the target angle
        byte[] value =
            new byte[] { 0x30, execute, wait, BidibLibrary.BIDIB_ACC_DETAIL_CURR_ANGLE1DEG5,
                ByteUtils.getLowByte(currentAngle), BidibLibrary.BIDIB_ACC_DETAIL_TARGET_ANGLE1DEG5,
                ByteUtils.getLowByte(targetAngle) };

        this.currentAngle = currentAngle;

        AccessoryStateResponse accessoryStateResponse;
        try {
            accessoryStateResponse =
                new AccessoryStateResponse(getNodeAddress(), getNextSendNum(), (byte) accessoryNumber,
                    ByteUtils.getLowByte(aspect), value);
            byte[] response = accessoryStateResponse.getContent();
            publishResponse(response);
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Publish the current angle failed.", ex);
        }

        if (finished) {
            LOGGER.info("Target position reached. Stop publish current angle.");
            return false;
        }
        // continue
        return true;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy